Browse Source

initial commit and v1?

root
bleh 6 months ago
parent
commit
92aadbf613
36 changed files with 2977 additions and 0 deletions
  1. +2
    -0
      .gitignore
  2. +23
    -0
      Cargo.toml
  3. +141
    -0
      adj-list.txt
  4. +26
    -0
      config.toml.sample
  5. +0
    -0
      db/.gitkeep
  6. +0
    -0
      db/db.sqlite
  7. +322
    -0
      lang.json
  8. +1
    -0
      migrations/20200809180000_create_form/down.sql
  9. +8
    -0
      migrations/20200809180000_create_form/up.sql
  10. +880
    -0
      name-list.txt
  11. +243
    -0
      src/account.rs
  12. +71
    -0
      src/config.rs
  13. +50
    -0
      src/database/methods.rs
  14. +3
    -0
      src/database/mod.rs
  15. +10
    -0
      src/database/schema.rs
  16. +16
    -0
      src/database/structs.rs
  17. +45
    -0
      src/errors.rs
  18. +265
    -0
      src/forward.rs
  19. +92
    -0
      src/main.rs
  20. +86
    -0
      src/sniff.rs
  21. +62
    -0
      src/templates.rs
  22. BIN
      templates/assets/Ubuntu-R.ttf
  23. +140
    -0
      templates/assets/cloud.css
  24. BIN
      templates/assets/favicon.ico
  25. BIN
      templates/assets/index-background.png
  26. +230
    -0
      templates/assets/index.css
  27. +51
    -0
      templates/assets/logo.svg
  28. BIN
      templates/assets/screen-fields.png
  29. BIN
      templates/assets/screen-formslist.png
  30. BIN
      templates/assets/screen-params.png
  31. BIN
      templates/assets/screen-question.png
  32. BIN
      templates/assets/screen-responses-export.png
  33. BIN
      templates/assets/screen-responses.png
  34. +28
    -0
      templates/error.html
  35. +118
    -0
      templates/index.html
  36. +64
    -0
      templates/link.html

+ 2
- 0
.gitignore View File

@ -10,3 +10,5 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
/config.toml
/db/sncf.sqlite

+ 23
- 0
Cargo.toml View File

@ -0,0 +1,23 @@
[package]
name = "sncf"
version = "1.0.0"
authors = ["Association 42l <contact@noreply.example.org>"]
edition = "2018"
[dependencies]
actix-rt = "1.0.0"
actix-web = "3.0.0-beta.3"
actix-files = "0.3.0-beta.1"
diesel = { version = "1.4", features = ["sqlite", "r2d2", "chrono"] }
diesel_migrations = "1.4"
url = "2.0"
toml = "0.5"
lazy_static = "1.4"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
chrono = { version = "0.4", features = ["serde"] }
regex = "1.3"
base64 = "0.12"
rand = "0.7"
askama = "0.10"

+ 141
- 0
adj-list.txt View File

@ -0,0 +1,141 @@
Adorable
Adventurous
Aggressive
Agreeable
Ambitious
Amused
Annoying
Ashy
Attractive
Beautiful
Better
Black
Blue
Blushing
Brave
Bright
Busy
Calm
Careful
Cautious
Charming
Cheerful
Clever
Clumsy
Colorful
Combative
Confident
Cooperative
Courageous
Crazy
Creative
Creepy
Cruel
Curious
Cute
Dangerous
Dark
Dazzling
Delightful
Determined
Different
Distinct
Dizzy
Eager
Elegant
Embarrassed
Enchanting
Encouraging
Energetic
Enthusiastic
Evil
Excited
Fair
Faithful
Famous
Fancy
Fantastic
Fierce
Fine
Friendly
Funny
Gentle
Gifted
Glad
Glamorous
Gleaming
Glorious
Good
Gorgeous
Graceful
Gray
Green
Grumpy
Handsome
Happy
Hardy
Healthy
Helpful
Hilarious
Homely
Hungry
Icy
Important
Innocent
Inquisitive
Jolly
Joyful
Joyous
Kind
Lazy
Light
Lively
Lovely
Loving
Lucky
Magnificent
Marvelous
Misty
Modern
Muscular
Mysterious
Nice
Obedient
Optimistic
Orange
Outstanding
Perfect
Pleasant
Polite
Powerful
Precious
Proud
Purple
Red
Scary
Shiny
Shy
Silly
Skinny
Sleepy
Smiling
Sparkling
Splendid
Spotless
Stormy
Strange
Super
Talented
Tenacious
Thankful
Thoughtful
Tired
Tough
Unusual
Victorious
Vivacious
White
Wild
Witty
Wonderful
Yellow

+ 26
- 0
config.toml.sample View File

@ -0,0 +1,26 @@
# The address and port sncf will listen
listening_address = "0.0.0.0"
listening_port = 8000
# Public-facing domain for sncf.
# includes protocol, FQDN and port, without the trailing slash.
sncf_url = "http://localhost:8000"
# path to the SQLite DB
database_path = "./db/sncf.sqlite"
# IP address of the Nextcloud instance, including protocol and port
nextcloud_url = "http://10.0.0.0"
# Nextcloud admin account
admin_username = "adminusername"
admin_password = "adminverylongandsecurepassword"
# How many days of inactivity for an admin token before deleting NC accounts
prune_days = 150
# Displays route names and a lot of information
debug_mode = true
# Don't touch this unless you know what you're doing
config_version = 1

+ 0
- 0
db/.gitkeep View File


+ 0
- 0
db/db.sqlite View File


+ 322
- 0
lang.json View File

@ -0,0 +1,322 @@
{
"lang_code": {
"en": "en",
"fr": "fr"
},
"lang_full": {
"en": "English",
"fr": "Français"
},
"meta_description": {
"en": "42l Forms : create forms for free, without registration while protecting your privacy",
"fr": "42l Formulaires (Forms) : créez des formulaires ou questionnaires gratuitement, sans inscription et dans le respect de votre vie privée"
},
"index_title": {
"en": "42l Forms",
"fr": "42l Formulaires"
},
"index_description": {
"en": "Create forms without registration",
"fr": "Créez des questionnaires sans inscription"
},
"index_beta_tag": {
"en": "BETA",
"fr": "BETA"
},
"index_createform_button": {
"en": "Create a form",
"fr": "Créer un formulaire"
},
"index_beta_banner_title": {
"en": "Warning: Service in beta.",
"fr": "Attention : Service en bêta."
},
"index_beta_banner_desc1": {
"en": "This service is currently under development and might behave in an unexpected way.",
"fr": "Ce service est en cours de développement et pourrait se comporter de manière inattendue."
},
"index_beta_banner_desc2": {
"en": "Feel free to send feedbacks on our ",
"fr": "Vous pouvez nous envoyer vos retours sur "
},
"index_beta_banner_desc_link": {
"en": "our contact page",
"fr": "notre page de contact"
},
"index_disclaimer1": {
"en": "This service is maintained for free, without subscription nor advertising nor tracking or selling of your personal data, on a server hosted in France.",
"fr": "Ce service vous est fourni gratuitement, sans inscription, sans publicités, sans pistage ou revente de vos données personnelles, sur un serveur hébergé en France."
},
"index_disclaimer2": {
"en": "If you appreciate our work, please consider donating to ",
"fr": "Si vous appréciez notre travail, merci d'envisager de faire un don à "
},
"index_disclaimer2_link_org": {
"en": "the 42l association",
"fr": "l'association 42l"
},
"index_disclaimer2_or": {
"en": " or ",
"fr": " ou à "
},
"index_disclaimer2_nc": {
"en": "Nextcloud",
"fr": "Nextcloud"
},
"index_panel1_title": {
"en": "Responsive and intuitive interface",
"fr": "Interface intuitive et compatible mobile"
},
"index_panel1_desc1": {
"en": "Are you searching for a privacy-friendly alternative to Google Forms while keeping its ease of use?",
"fr": "Cherchez-vous une alternative éthique à Google Forms qui reste simple d'utilisation ?"
},
"index_panel1_desc2": {
"en": "You've just found it.",
"fr": "Vous venez de la trouver."
},
"index_panel2_title": {
"en": "Choose and order your fields",
"fr": "Choisissez et ordonnez vos champs"
},
"index_panel2_desc1": {
"en": "The software currently supports five field types.",
"fr": "Pour le moment, le logiciel supporte cinq types de champs."
},
"index_panel2_desc2": {
"en": "New field types are ",
"fr": "De nouveaux types de champs sont "
},
"index_panel2_desc2_link": {
"en": "currently in the works",
"fr": "en cours d'élaboration"
},
"index_panel3_title": {
"en": "Analyze the answers",
"fr": "Analysez les réponses"
},
"index_panel3_desc1": {
"en": "See detailed graphs of the answers to your form.",
"fr": "Visualisez les réponses à votre formulaire avec un graphique."
},
"index_panel4_title": {
"en": "Export the answers",
"fr": "Exportez les réponses"
},
"index_panel4_desc1": {
"en": "Export the raw data of your form in CSV format to integrate the answers in other software (e.g. LibreOffice Calc or Microsoft Excel).",
"fr": "Exportez les données brutes de votre formulaire en format CSV pour intégrer les réponses dans d'autres logiciels (ex. LibreOffice Calc ou Microsoft Excel)."
},
"index_panel5_title": {
"en": "Edit your form's settings",
"fr": "Paramétrez vos formulaires"
},
"index_panel5_desc1": {
"en": "Use the share link to send your form to other people.",
"fr": "Utilisez le lien de partage pour envoyer votre formulaire à d'autres personnes."
},
"index_panel5_desc2": {
"en": "You can also define an expiration date for your form.",
"fr": "Vous pouvez également définir une date d'expiration pour votre formulaire."
},
"index_panel6_title": {
"en": "All your forms in one place",
"fr": "Tous vos formulaires au même endroit"
},
"index_panel6_desc1": {
"en": "Find all your forms in the same panel.",
"fr": "Retrouvez tous vos formulaires sur un même panel."
},
"index_bottom_docs": {
"en": "Documentation",
"fr": "Documentation"
},
"index_bottom_source": {
"en": "Source code",
"fr": "Code source"
},
"index_bottom_lic": {
"en": "License",
"fr": "Licence"
},
"index_credits_title": {
"en": "Credits",
"fr": "Crédits"
},
"index_credits_desc1": {
"en": "The Nextcloud software suite and the Nextcloud Forms application has been developed by ",
"fr": "La suite logicielle Nextcloud et l'application Nextcloud Forms a été développée par "
},
"index_credits_desc1_link": {
"en": "the Nextcloud team",
"fr": "l'équipe Nextcloud"
},
"index_credits_desc1_a": {
"en": " and its contributors.",
"fr": " et ses contributeur·ices."
},
"index_credits_desc2": {
"en": "The Simple Nextcloud Forms software, which simplifies the form creation process, has been developed by ",
"fr": "Le logiciel Simple Nextcloud Forms, qui simplifie la création de formulaires, a été développé par "
},
"index_credits_desc2_for": {
"en": " for ",
"fr": " pour "
},
"index_credits_desc2_org": {
"en": "the 42l association",
"fr": "l'association 42l"
},
"index_credits_desc3": {
"en": "(sources available soon)",
"fr": "(sources bientôt disponibles)"
},
"link_title": {
"en": "Link created",
"fr": "Lien créé"
},
"link_desc1": {
"en": "Here's an <b>administration link</b>, which will allow you to access all your forms and check your answers.",
"fr": "Voici un <b>lien d'administration</b>, qui vous permettra d'accéder à tous vos formulaires et de consulter vos réponses."
},
"link_desc2": {
"en": "<b>Keep it</b> carefully and don't give it away (it'd be the same as giving out your password!).",
"fr": "<b>Conservez-le</B> bien précieusement et ne le donnez pas (cela reviendrait à donner un mot de passe !)."
},
"link_desc3": {
"en": "Once your link copied, click on the button below to start editing your forms.",
"fr": "Une fois votre lien copié, cliquez sur le bouton ci-dessous pour commencer à éditer vos formulaires."
},
"link_access_btn": {
"en": "Access the forms",
"fr": "Accéder aux formulaires"
},
"link_note": {
"en": "Note: If you don't use your administration link during more than ",
"fr": "Note : Si vous n'utilisez pas votre lien d'administration pendant plus de "
},
"link_note2": {
"en": " days, your forms will be automatically deleted.",
"fr": " jours, vos formulaires seront automatiquement supprimés."
},
"link_copy": {
"en": "Copy link",
"fr": "Copier le lien"
},
"link_copied": {
"en": "Link copied!",
"fr": "Lien copié !"
},
"error_title": {
"en": "Oops!...",
"fr": "Oups !..."
},
"error_description": {
"en": "The application encountered a problem:",
"fr": "L'application a rencontré un problème :"
},
"error_back": {
"en": "Back to the main page",
"fr": "Retour à la page principale"
},
"error_note1": {
"en": "We are (probably) aware of this bug, but feel free to contact us if you need assistance.",
"fr": "Nous sommes (probablement) au courant, mais n'hésitez pas à nous contacter si vous avez besoin d'aide."
},
"error_note2": {
"en": "Sorry for the inconvenience.",
"fr": "Désolés pour les désagréments occasionnés."
},
"error_forward_req": {
"en": "Error while connecting to the Nextcloud instance.",
"fr": "Erreur lors de la connexion à l'instance Nextcloud."
},
"error_forward_resp": {
"en": "Error while reading Nextcloud instance's response.",
"fr": "Erreur lors de la lecture de la réponse de l'instance Nextcloud."
},
"error_forward_isanon": {
"en": "Couldn't set the form's isAnonymous value.",
"fr": "Échec lors de la définition de la valeur isAnonymous du formulaire."
},
"error_forwardlogin_db": {
"en": "Couldn't connect to the local database.",
"fr": "Échec lors de la connexion à la base de données locale."
},
"error_forwardlogin_db_get": {
"en": "Error during information retrieval from the local database.",
"fr": "Erreur lors de la récupération des informations dans la base de données locale."
},
"error_login_get": {
"en": "The account creation request (GET) to Nextcloud has failed.",
"fr": "La requête de création de compte (GET) vers l'instance Nextcloud a échoué."
},
"error_login_get_body": {
"en": "Reading response from the account creation request to Nextcloud has failed.",
"fr": "La lecture de la réponse à la requête de création de compte vers l'instance Nextcloud a échoué."
},
"error_login_post": {
"en": "The account creation request (POST) to Nextcloud has failed.",
"fr": "La requête de création de compte (POST) vers l'instance Nextcloud a échoué."
},
"error_login_redir": {
"en": "Redirection to Nextcloud accout failed.",
"fr": "La redirection vers le compte Nextcloud a échoué."
},
"error_createaccount_post": {
"en": "Account creation: connection to the Nextcloud API failed.",
"fr": "Création de compte : la connexion à l'API Nextcloud a échoué."
},
"error_createaccount_post_body": {
"en": "Account creation: reading the answer from the Nextcloud API failed.",
"fr": "Création de compte : le traitement de la réponse de l'API Nextcloud a échoué."
},
"error_createaccount_status": {
"en": "The Nextcloud instance responded with an unexpected status code.",
"fr": "L'instance Nextcloud a répondu avec un code de statut inattendu."
},
"error_createaccount_ncstatus": {
"en": "The Nextcloud API responded with an unexpected status code.",
"fr": "L'API Nextcloud a répondu avec un code de statut inattendu."
},
"error_createaccount_ncstatus_parse": {
"en": "Error parsing Nextcloud API's status code.",
"fr": "Erreur lors de la lecture du code de statut de l'API Nextcloud."
},
"error_forwardregister_pool": {
"en": "Error while connecting to the local database.",
"fr": "Erreur lors de la connexion à la base de données locale."
},
"error_forwardregister_db": {
"en": "Failed adding the Nextcloud account in the local database.",
"fr": "L'ajout du compte Nextcloud dans la base de données locale a échoué."
},
"error_login_cookiepair": {
"en": "Couldn't read cookies.",
"fr": "Échec lors de la lecture de cookies."
},
"error_login_regex": {
"en": "Couldn't read the CSRF token.",
"fr": "Échec lors de la lecture du token CSRF."
},
"error_login_setcookie": {
"en": "Error during cookies transfer.",
"fr": "Erreur lors du transfert de cookies."
},
"error_form_insert": {
"en": "The local database couldn't be reached",
"fr": "Échec de la connexion avec la base de données locale"
},
"error_createaccount": {
"en": "The Nextcloud API returned an unexpected result",
"fr": "L'API de Nextcloud a retourné un résultat inattendu"
},
"error_dirtyhacker": {
"en": "Attempt to access an unauthorized resource.",
"fr": "Tentative d'accès à une ressource non autorisée."
},
"error_tplrender": {
"en": "Template rendering failed.",
"fr": "Le rendu du template a échoué."
}
}

+ 1
- 0
migrations/20200809180000_create_form/down.sql View File

@ -0,0 +1 @@
DELETE TABLE form;

+ 8
- 0
migrations/20200809180000_create_form/up.sql View File

@ -0,0 +1,8 @@
CREATE TABLE form (
id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL,
created_at TIMESTAMP NOT NULL,
lastvisit_at TIMESTAMP NOT NULL,
token VARCHAR UNIQUE NOT NULL,
nc_username VARCHAR UNIQUE NOT NULL,
nc_password VARCHAR NOT NULL
);

+ 880
- 0
name-list.txt View File

@ -0,0 +1,880 @@
Abomasnow
Abra
Absol
Accelgor
Aegislash
Aerodactyl
Aggron
Aipom
Alakazam
Alcremie
Alomomola
Altaria
Amaura
Ambipom
Amoonguss
Ampharos
Anorith
Appletun
Applin
Araquanid
Arbok
Arcanine
Arceus
Archen
Archeops
Arctovish
Arctozolt
Ariados
Armaldo
Aromatisse
Aron
Arrokuda
Articuno
Audino
Aurorus
Avalugg
Axew
Azelf
Azumarill
Azurill
Bagon
Baltoy
Banette
Barbaracle
Barboach
Barraskewda
Basculin
Bastiodon
Bayleef
Beartic
Beautifly
Beedrill
Beheeyem
Beldum
Bellossom
Bellsprout
Bergmite
Bewear
Bibarel
Bidoof
Binacle
Bisharp
Blacephalon
Blastoise
Blaziken
Blipbug
Blissey
Blitzle
Boldore
Boltund
Bonsly
Bouffalant
Bounsweet
Braixen
Braviary
Breloom
Brionne
Bronzong
Bronzor
Bruxish
Budew
Buizel
Bulbasaur
Buneary
Bunnelby
Burmy
Butterfree
Buzzwole
Cacnea
Cacturne
Calyrex
Camerupt
Carbink
Carkol
Carnivine
Carracosta
Carvanha
Cascoon
Castform
Caterpie
Celebi
Celesteela
Centiskorch
Chandelure
Chansey
Charizard
Charjabug
Charmander
Charmeleon
Chatot
Cherrim
Cherubi
Chesnaught
Chespin
Chewtle
Chikorita
Chimchar
Chimecho
Chinchou
Chingling
Cinccino
Cinderace
Clamperl
Clauncher
Clawitzer
Claydol
Clefable
Clefairy
Cleffa
Clobbopus
Cloyster
Coalossal
Cobalion
Cofagrigus
Combee
Combusken
Comfey
Conkeldurr
Copperajah
Corphish
Corsola
Corviknight
Corvisquire
Cosmoem
Cosmog
Cottonee
Crabominable
Crabrawler
Cradily
Cramorant
Cranidos
Crawdaunt
Cresselia
Croagunk
Crobat
Croconaw
Crustle
Cryogonal
Cubchoo
Cubone
Cufant
Cursola
Cutiefly
Cyndaquil
Darkrai
Darmanitan
Dartrix
Darumaka
Decidueye
Dedenne
Deerling
Deino
Delcatty
Delibird
Delphox
Deoxys
Dewgong
Dewott
Dewpider
Dhelmise
Dialga
Diancie
Diggersby
Diglett
Ditto
Dodrio
Doduo
Donphan
Dottler
Doublade
Dracovish
Dracozolt
Dragalge
Dragapult
Dragonair
Dragonite
Drakloak
Drampa
Drapion
Dratini
Drednaw
Dreepy
Drifblim
Drifloon
Drilbur
Drizzile
Drowzee
Druddigon
Dubwool
Ducklett
Dugtrio
Dunsparce
Duosion
Duraludon
Durant
Dusclops
Dusknoir
Duskull
Dustox
Dwebble
Eelektrik
Eelektross
Eevee
Eiscue
Ekans
Eldegoss
Electabuzz
Electivire
Electrike
Electrode
Elekid
Elgyem
Emboar
Emolga
Empoleon
Entei
Escavalier
Espeon
Espurr
Eternatus
Excadrill
Exeggcute
Exeggutor
Exploud
Falinks
Farfetch’d
Fearow
Feebas
Fennekin
Feraligatr
Ferroseed
Ferrothorn
Finneon
Flaaffy
Flabebe
Flapple
Flareon
Fletchinder
Fletchling
Floatzel
Floette
Florges
Flygon
Fomantis
Foongus
Forretress
Fraxure
Frillish
Froakie
Frogadier
Froslass
Frosmoth
Furfrou
Furret
Gabite
Gallade
Galvantula
Garbodor
Garchomp
Gardevoir
Gastly
Gastrodon
Genesect
Gengar
Geodude
Gible
Gigalith
Girafarig
Giratina
Glaceon
Glalie
Glameow
Gligar
Gliscor
Gloom
Gogoat
Golbat
Goldeen
Golduck
Golem
Golett
Golisopod
Golurk
Goodra
Goomy
Gorebyss
Gossifleur
Gothita
Gothitelle
Gothorita
Gourgeist
Granbull
Grapploct
Graveler
Greedent
Greninja
Grimer
Grimmsnarl
Grookey
Grotle
Groudon
Grovyle
Growlithe
Grubbin
Grumpig
Gulpin
Gumshoos
Gurdurr
Guzzlord
Gyarados
Happiny
Hariyama
Hatenna
Hatterene
Hattrem
Haunter
Hawlucha
Haxorus
Heatmor
Heatran
Heliolisk
Helioptile
Heracross
Herdier
Hippopotas
Hippowdon
Hitmonchan
Hitmonlee
Hitmontop
Honchkrow
Honedge
Hoopa
Hoothoot
Hoppip
Horsea
Houndoom
Houndour
Huntail
Hydreigon
Hypno
Igglybuff
Illumise
Impidimp
Incineroar
Indeedee
Infernape
Inkay
Inteleon
Ivysaur
Jellicent
Jigglypuff
Jirachi
Jolteon
Joltik
Jumpluff
Jynx
Kabuto
Kabutops
Kadabra
Kakuna
Kangaskhan
Karrablast
Kartana
Kecleon
Keldeo
Kingdra
Kingler
Kirlia
Klang
Klefki
Klink
Klinklang
Koffing
Komala
Krabby
Kricketot
Kricketune
Krokorok
Krookodile
Kubfu
Kyogre
Kyurem
Lairon
Lampent
Landorus
Lanturn
Lapras
Larvesta
Larvitar
Latias
Latios
Leafeon
Leavanny
Ledian
Ledyba
Lickilicky
Lickitung
Liepard
Lileep
Lilligant
Lillipup
Linoone
Litleo
Litten
Litwick
Lombre
Lopunny
Lotad
Loudred
Lucario
Ludicolo
Lugia
Lumineon
Lunala
Lunatone
Lurantis
Luvdisc
Luxio
Luxray
Lycanroc
Machamp
Machoke
Machop
Magby
Magcargo
Magearna
Magikarp
Magmar
Magmortar
Magnemite
Magneton
Magnezone
Makuhita
Malamar
Mamoswine
Manaphy
Mandibuzz
Manectric
Mankey
Mantine
Mantyke
Maractus
Mareanie
Mareep
Marill
Marowak
Marshadow
Marshtomp
Masquerain
Mawile
Medicham
Meditite
Meganium
Melmetal
Meloetta
Meltan
Meowstic
Meowth
Mesprit
Metagross
Metang
Metapod
Mew
Mewtwo
Mienfoo
Mienshao
Mightyena
Milcery
Milotic
Miltank
Mimikyu
Minccino
Minior
Minun
Misdreavus
Mismagius
Moltres
Monferno
Morelull
Morgrem
Morpeko
Mothim
Mudbray
Mudkip
Mudsdale
Muk
Munchlax
Munna
Murkrow
Musharna
Naganadel
Natu
Necrozma
Nickit
Nidoking
Nidoqueen
Nidoran
Nidorina
Nidorino
Nihilego
Nincada
Ninetales
Ninjask
Noctowl
Noibat
Noivern
Nosepass
Numel
Nuzleaf
Obstagoon
Octillery
Oddish
Omanyte
Omastar
Onix
Oranguru
Orbeetle
Oricorio
Oshawott
Pachirisu
Palkia
Palossand
Palpitoad
Pancham
Pangoro
Panpour
Pansage
Pansear
Paras
Parasect
Passimian
Patrat
Pawniard
Pelipper
Perrserker
Persian
Petilil
Phanpy
Phantump
Pheromosa
Phione
Pichu
Pidgeot
Pidgeotto
Pidgey
Pidove
Pignite
Pikachu
Pikipek
Piloswine
Pincurchin
Pineco
Pinsir
Piplup
Plusle
Poipole
Politoed
Poliwag
Poliwhirl
Poliwrath
Polteageist
Ponyta
Poochyena
Popplio
Porygon
Primarina
Primeape
Prinplup
Probopass
Psyduck
Pumpkaboo
Pupitar
Purrloin
Purugly
Pyroar
Pyukumuku
Quagsire
Quilava
Quilladin
Qwilfish
Raboot
Raichu
Raikou
Ralts
Rampardos
Rapidash
Raticate
Rattata
Rayquaza
Regice
Regidrago
Regieleki
Regigigas
Regirock
Registeel
Relicanth
Remoraid
Reshiram
Reuniclus
Rhydon
Rhyhorn
Rhyperior
Ribombee
Rillaboom
Riolu
Rockruff
Roggenrola
Rolycoly
Rookidee
Roselia
Roserade
Rotom
Rowlet
Rufflet
Runerigus
Sableye
Salamence
Salandit
Salazzle
Samurott
Sandaconda
Sandile
Sandshrew
Sandslash
Sandygast
Sawk
Sawsbuck
Scatterbug
Sceptile
Scizor
Scolipede
Scorbunny
Scrafty
Scraggy
Scyther
Seadra
Seaking
Sealeo
Seedot
Seel
Seismitoad
Sentret
Serperior
Servine
Seviper
Sewaddle
Sharpedo
Shaymin
Shedinja
Shelgon
Shellder
Shellos
Shelmet
Shieldon
Shiftry
Shiinotic
Shinx
Shroomish
Shuckle
Shuppet
Sigilyph
Silcoon
Silicobra
Silvally
Simipour
Simisage
Simisear
Sinistea
Sizzlipede
Skarmory
Skiddo
Skiploom
Skitty
Skorupi
Skrelp
Skuntank
Skwovet
Slaking
Slakoth
Sliggoo
Slowbro
Slowking
Slowpoke
Slugma
Slurpuff
Smeargle
Smoochum
Sneasel
Snivy
Snom
Snorlax
Snorunt
Snover
Snubbull
Sobble
Solgaleo
Solosis
Solrock
Spearow
Spewpa
Spheal
Spinarak
Spinda
Spiritomb
Spoink
Spritzee
Squirtle
Stakataka
Stantler
Staraptor
Staravia
Starly
Starmie
Staryu
Steelix
Steenee
Stonjourner
Stoutland
Stufful
Stunfisk
Stunky
Sudowoodo
Suicune
Sunflora
Sunkern
Surskit
Swablu
Swadloon
Swalot
Swampert
Swanna
Swellow
Swinub
Swirlix
Swoobat
Sylveon
Taillow
Talonflame
Tangela
Tangrowth
Tauros
Teddiursa
Tentacool
Tentacruel
Tepig
Terrakion
Thievul
Throh
Thundurus
Thwackey
Timburr
Tirtouga
Togedemaru
Togekiss
Togepi
Togetic
Torchic
Torkoal
Tornadus
Torracat
Torterra
Totodile
Toucannon
Toxapex
Toxel
Toxicroak
Toxtricity
Tranquill
Trapinch
Treecko
Trevenant
Tropius
Trubbish
Trumbeak
Tsareena
Turtonator
Turtwig
Tympole
Tynamo
Typhlosion
Tyranitar
Tyrantrum
Tyrogue
Tyrunt
Umbreon
Unfezant
Unown
Ursaring
Urshifu
Uxie
Vanillish
Vanillite
Vanilluxe
Vaporeon
Venipede
Venomoth
Venonat
Venusaur
Vespiquen
Vibrava
Victini
Victreebel
Vigoroth
Vikavolt
Vileplume
Virizion
Vivillon
Volbeat
Volcanion
Volcarona
Voltorb
Vullaby
Vulpix
Wailmer
Wailord
Walrein
Wartortle
Watchog
Weavile
Weedle
Weepinbell
Weezing
Whimsicott
Whirlipede
Whiscash
Whismur
Wigglytuff
Wimpod
Wingull
Wishiwashi
Wobbuffet
Woobat
Wooloo
Wooper
Wormadam
Wurmple
Wynaut
Xatu
Xerneas
Xurkitree
Yamask
Yampur
Yanma
Yanmega
Yungoos
Yveltal
Zacian
Zamazenta
Zangoose
Zapdos
Zarude
Zebstrika
Zekrom
Zeraora
Zigzagoon
Zoroark
Zorua
Zubat
Zweilous
Zygarde

+ 243
- 0
src/account.rs View File

@ -0,0 +1,243 @@
use actix_web::client::Client;
use actix_web::{http, web, HttpRequest, HttpResponse};
use base64::URL_SAFE_NO_PAD;
use rand::rngs::OsRng;
use rand::Rng;
use rand::RngCore;
use regex::Regex;
use crate::templates::get_lang;
use crate::config::{ADJ_LIST, NAME_LIST};
use crate::errors::{crash, TrainCrash};
use crate::debug;
use crate::CONFIG;
#[derive(Serialize)]
struct NCLoginForm<'a> {
pub user: &'a str,
pub password: &'a str,
pub timezone: &'a str,
pub timezone_offset: &'a str,
pub requesttoken: &'a str,
}
// check if the user is connected to Nextcloud
// returns Some(cookie_raw_value) if connected
// returns None if disconnected
pub fn is_logged_in(req: &HttpRequest) -> Option<&str> {
let c = req.headers().get("Cookie")?.to_str().ok()?;
if c.contains("nc_username") {
Some(c)
} else {
None
}
}
pub fn has_admintoken(req: &HttpRequest) -> Option<&str> {
let c = req.headers().get("Cookie")?.to_str().ok()?;
if c.contains("sncf_admin_token") {
Some(c)
} else {
None
}
}
// attempts to create the account from Nextcloud's API
// returns the newly created username.
// if it fails (bad return code), returns None.
pub async fn create_account(
client: &web::Data<Client>,
user: &str,
password: &str,
lang: String,
) -> Result<String, TrainCrash> {
let mut register_query = client
.post(format!(
"{}/{}",
CONFIG.nextcloud_url, "ocs/v1.php/cloud/users"
))
.basic_auth(&CONFIG.admin_username, Some(&CONFIG.admin_password))
.header(
http::header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
)
.header("OCS-APIRequest", "true")
.send_form(&NCCreateAccountForm {
userid: user,
password,
quota: "0B",
language: &lang,
})
.await
.map_err(|e| {
eprintln!("error_createaccount_post: {}", e);
crash(lang.clone(), "error_createaccount_post")
})?;
// only 200 http status code is allowed
if register_query.status() != 200 {
eprintln!("error_createaccount_status: {}", register_query.status());
return Err(crash(lang.clone(), "error_createaccount_status"));
}
// extract response body
let response_body = register_query.body().await.map_err(|e| {
eprintln!("error_createaccount_post_body: {}", e);
crash(lang.clone(), "error_createaccount_post_body")
})?;
let response_body = String::from_utf8_lossy(&response_body);
// grasp NC status code
let status_start = response_body.find("<statuscode>").ok_or_else(|| {
eprintln!("error_createaccount_ncstatus_parse: start missing");
crash(lang.clone(), "error_createaccount_ncstatus_parse")
})? + 12;
let status_end = response_body.find("</statuscode>").ok_or_else(|| {
eprintln!("error_createaccount_ncstatus_parse: end missing");
crash(lang.clone(), "error_createaccount_ncstatus_parse")
})?;
let code = &response_body[status_start..status_end];
match code.parse::<u16>() {
Ok(100) => Ok(String::from(user)), // success
Ok(r) => {
eprintln!("error_createaccount_ncstatus: {}", r);
Err(crash(lang.clone(), "error_createaccount_ncstatus"))
}
Err(e) => {
eprintln!("error_createaccount_ncstatus_parse: {}", e);
Err(crash(lang.clone(), "error_createaccount_ncstatus_parse"))
}
}
}
#[derive(Serialize)]
struct NCCreateAccountForm<'a> {
pub userid: &'a str,
pub password: &'a str,
pub quota: &'a str,
pub language: &'a str,
}
pub async fn login(
client: &web::Data<Client>,
req: &HttpRequest,
user: &str,
password: &str,
) -> Result<HttpResponse, TrainCrash> {
debug(&format!("Sending forged login for user {}", user));
// 1. GET /login
let mut login_get = client
.get(format!("{}/{}", CONFIG.nextcloud_url, "login"))
.header("User-Agent", "Actix-web")
.send()
.await.map_err(|e| {
eprintln!("error_login_get: {}", e);
crash(get_lang(&req), "error_login_get")
})?;
// rewrite cookie headers from GET to POST
let mut str_cookiepair = String::new();
for h_value in login_get.headers().get_all("set-cookie") {
str_cookiepair = format!("{}; {}", str_cookiepair, h_value.clone().to_str().map_err(|e| {
eprintln!("error_login_cookiepair: {}", e);
crash(get_lang(&req), "error_login_cookiepair")
})?);
}
// load requesttoken regex
lazy_static! {
static ref RE: Regex = Regex::new(r#"requesttoken="(?P<token>.*)""#).expect("Error while parsing the requesttoken regex");
}
let post_body = login_get.body().await.map_err(|e| {
eprintln!("error_login_get_body: {}", e);
crash(get_lang(&req), "error_login_get_body")
})?;
let post_body_str = String::from_utf8_lossy(&post_body);
// save requesttoken (CSRF) for POST
let requesttoken = RE
.captures(&post_body_str)
.ok_or_else(|| {
eprintln!("error_login_regex (no capture)");
crash(get_lang(&req), "error_login_regex")
})?
.name("token")
.ok_or_else(|| {
eprintln!("error_login_regex (no capture named token)");
crash(get_lang(&req), "error_login_regex")
})?
.as_str();
// 2. POST /login
let mut login_post = client
.post(format!("{}/{}", CONFIG.nextcloud_url, "login"))
.header("User-Agent", "Actix-web");
// include all NC cookies in one cookie (cookie pair)
login_post = login_post.header("Cookie", str_cookiepair);
// send the same POST data as you'd log in from a web browser
let response_post = login_post
.send_form(&NCLoginForm {
user,
password,
timezone: "UTC",
timezone_offset: "2",
requesttoken,
})
.await.map_err(|e| {
eprintln!("error_login_post: {}", e);
crash(get_lang(&req), "error_login_post")
})?;
// 3. set the same cookies in the user's browser
let mut user_response = HttpResponse::SeeOther();
for item in response_post.headers().clone().get_all("set-cookie") {
user_response.header("Set-Cookie", item.to_str().map_err(|e| {
eprintln!("error_login_setcookie: {}", e);
crash(get_lang(&req), "error_login_setcookie")
})?);
}
// redirect to forms!
Ok(user_response
.header(http::header::LOCATION, "/apps/forms")
.finish()
.await.map_err(|e| {
eprintln!("error_login_redir: {}", e);
crash(get_lang(&req), "error_login_redir")
})?)
}
// checks if the token seems valid before asking the db.
// The token must be 45 bytes long and base64-encoded.
// returns true if the token is valid
pub fn check_token(token: &str) -> bool {
let token_dec = base64::decode_config(token, URL_SAFE_NO_PAD);
if let Ok(token_bytes) = token_dec {
token_bytes.len() == 45
} else {
false
}
}
// generates a new token
pub fn gen_token() -> String {
// Using /dev/random to generate random bytes
let mut r = OsRng;
let mut my_secure_bytes = vec![0u8; 45];
r.fill_bytes(&mut my_secure_bytes);
base64::encode_config(my_secure_bytes, URL_SAFE_NO_PAD)
}
pub fn gen_name() -> String {
format!("{}{}", list_rand(&ADJ_LIST), list_rand(&NAME_LIST))
}
pub fn list_rand(list: &Vec<String>) -> &String {
let mut rng = rand::thread_rng();
let roll = rng.gen_range(0, list.len() - 1);
&list[roll]
}

+ 71
- 0
src/config.rs View File

@ -0,0 +1,71 @@
use std::fs::File;
use std::io::Read;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use serde_json::Value;
// payload limit set to 5MiB
pub const PAYLOAD_LIMIT: usize = 50_000_000;
pub const CONFIG_FILE: &str = "./config.toml";
pub const CONFIG_VERSION: u8 = 1;
pub const ADJ_LIST_FILE: &str = "./adj-list.txt";
pub const NAME_LIST_FILE: &str = "./name-list.txt";
pub const LOC_FILE: &str = "./lang.json";
lazy_static! {
pub static ref CONFIG: Config = Config::init();
pub static ref ADJ_LIST: Vec<String> =
lines_from_file(ADJ_LIST_FILE).expect("Failed to load adjectives list");
pub static ref NAME_LIST: Vec<String> =
lines_from_file(NAME_LIST_FILE).expect("Failed to load names list");
pub static ref LOC: Value = init_lang();
}
fn init_lang() -> Value {
let mut file = File::open(LOC_FILE).expect("init_lang: Can't open translations file");
let mut data = String::new();
file.read_to_string(&mut data)
.expect("init_lang: Can't read translations file");
serde_json::from_str(&data).expect("init_lang(): Can't parse translations file")
}
fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>> {
BufReader::new(File::open(filename)?).lines().collect()
}
#[derive(Deserialize)]
pub struct Config {
pub listening_address: String,
pub listening_port: u16,
pub sncf_url: String,
pub database_path: String,
pub nextcloud_url: String,
pub admin_username: String,
pub admin_password: String,
pub prune_days: u16,
pub debug_mode: bool,
pub config_version: u8,
}
// totally not copypasted from rs-short
impl Config {
pub fn init() -> Self {
let mut conffile = File::open(CONFIG_FILE).expect(
r#"Config file config.toml not found.
Please create it using config.toml.sample."#,
);
let mut confstr = String::new();
conffile
.read_to_string(&mut confstr)
.expect("Couldn't read config to string");
toml::from_str(&confstr).expect("Couldn't deserialize the config")
}
pub fn check_version(&self) {
if self.config_version != CONFIG_VERSION {
eprintln!("Your configuration file is obsolete! Please update it with config.toml.sample and update its version to {}.", CONFIG_VERSION);
panic!();
}
}
}

+ 50
- 0
src/database/methods.rs View File

@ -0,0 +1,50 @@
use chrono::NaiveDateTime;
use chrono::Utc;
use diesel::prelude::*;
use crate::database::schema::form::dsl::*;
use crate::database::structs::Form;
use crate::database::schema::form;
use crate::SqliteConnection;
#[table_name = "form"]
#[derive(Serialize, Insertable)]
pub struct InsertableForm<'b> {
pub created_at: NaiveDateTime,
pub lastvisit_at: NaiveDateTime,
pub token: &'b str,
pub nc_username: &'b str,
pub nc_password: &'b str,
}
impl Form {
// gets a Form from a token.
// also updates lastvisit_at.
pub fn get_from_token(
i_token: &str,
conn: &SqliteConnection,
) -> Result<Option<Form>, diesel::result::Error> {
if let Some(formdata) = form.filter(token.eq(i_token)).first::<Form>(conn).optional()? {
match formdata.update_lastvisit(conn) {
Ok(_) => Ok(Some(formdata)),
Err(e) => Err(e),
}
} else {
Ok(None)
}
}
pub fn update_lastvisit(&self, conn: &SqliteConnection) -> Result<usize, diesel::result::Error> {
diesel::update(form.find(self.id))
.set(lastvisit_at.eq(Utc::now().naive_utc()))
.execute(conn)
}
pub fn insert<'b>(i_form: InsertableForm<'b>, conn: &SqliteConnection
) -> Result<InsertableForm<'b>, diesel::result::Error> {
match diesel::insert_into(form).values(&i_form).execute(conn) {
Ok(_) => Ok(i_form),
Err(e) => Err(e),
}
}
}

+ 3
- 0
src/database/mod.rs View File

@ -0,0 +1,3 @@
pub mod schema;
pub mod structs;
pub mod methods;

+ 10
- 0
src/database/schema.rs View File

@ -0,0 +1,10 @@
table! {
form (id) {
id -> Integer,
created_at -> Timestamp,
lastvisit_at -> Timestamp,
token -> Text,
nc_username -> Text,
nc_password -> Text,
}
}

+ 16
- 0
src/database/structs.rs View File

@ -0,0 +1,16 @@
//use diesel::{self, prelude::*};
use chrono::NaiveDateTime;
use crate::database::schema::form;
//use crate::config::CONFIG;
#[table_name = "form"]
#[derive(Serialize, Queryable, Insertable, Debug, Clone)]
pub struct Form {
pub id: i32,
pub created_at: NaiveDateTime,
pub lastvisit_at: NaiveDateTime,
pub token: String,
pub nc_username: String,
pub nc_password: String,
}

+ 45
- 0
src/errors.rs View File

@ -0,0 +1,45 @@
use crate::templates::TplError;
use std::fmt;
use actix_web::dev::HttpResponseBuilder;
use actix_web::{error, http::header, http::StatusCode, HttpResponse};
use askama::Template;
pub fn crash(lang: String, error_msg: &'static str) -> TrainCrash {
TrainCrash { lang, error_msg }
}
#[derive(Debug)]
pub struct TrainCrash {
pub error_msg: &'static str,
pub lang: String,
}
// gonna avoid using failure crate
// by implementing display
impl fmt::Display for TrainCrash {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self.error_msg)
}
}
impl error::ResponseError for TrainCrash {
fn error_response(&self) -> HttpResponse {