1
0
Fork 0
mirror of https://git.42l.fr/neil/sncf.git synced 2024-04-25 11:00:33 +02:00

initial commit and v1?

This commit is contained in:
bleh 2020-08-19 01:21:42 +02:00
parent 3b86d5ddd4
commit 92aadbf613
36 changed files with 2977 additions and 0 deletions

2
.gitignore vendored
View file

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

23
Cargo.toml Normal file
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
adj-list.txt Normal file
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
config.toml.sample Normal file
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
db/.gitkeep Normal file
View file

0
db/db.sqlite Normal file
View file

322
lang.json Normal file
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é."
}
}

View file

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

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
name-list.txt Normal file
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
Farfetchd
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
src/account.rs Normal file
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
src/config.rs Normal file
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
src/database/methods.rs Normal file
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
src/database/mod.rs Normal file
View file

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

10
src/database/schema.rs Normal file
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
src/database/structs.rs Normal file
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
src/errors.rs Normal file
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 {
eprintln!("Error reached: {}", self.error_msg);
HttpResponseBuilder::new(self.status_code())
.set_header(header::CONTENT_TYPE, "text/html; charset=utf-8")
.body(
TplError {
lang: &self.lang,
error_msg: self.error_msg,
}
.render()
.expect("error_tplrender (TplError). Empty page sent to client."))
}
fn status_code(&self) -> StatusCode {
match *self {
_ => StatusCode::INTERNAL_SERVER_ERROR,
}
}
}

265
src/forward.rs Normal file
View file

@ -0,0 +1,265 @@
use actix_web::client::{Client, ClientRequest};
use actix_web::{http, web, HttpRequest, HttpResponse};
use askama::Template;
use chrono::Utc;
use url::Url;
use crate::account::*;
use crate::config::PAYLOAD_LIMIT;
use crate::database::methods::InsertableForm;
use crate::database::structs::Form;
use crate::debug;
use crate::sniff::*;
use crate::templates::*;
use crate::DbPool;
use crate::CONFIG;
use crate::errors::{TrainCrash, crash};
pub async fn forward(
req: HttpRequest,
body: web::Bytes,
url: web::Data<Url>,
client: web::Data<Client>,
) -> Result<HttpResponse, TrainCrash> {
let route = req.uri().path();
// if check_route returns true,
// the user supposedly tried to access a restricted page.
// They get redirected to the main page.
if check_route(route) {
debug(&format!("Restricted route blocked: {}", route));
return Ok(web_redir("/"));
}
let forwarded_req = forge_from(route, &req, &url, &client);
// check the request before sending it
// (prevents the user from sending some specific POST requests)
if check_request(route, &body) {
debug(&format!(
"Restricted request: {}",
String::from_utf8_lossy(&body)
));
return Err(crash(get_lang(&req), "error_dirtyhacker"));
}
// send the request to the Nextcloud instance
let mut res = forwarded_req.send_body(body).await.map_err(|e| {
eprintln!("error_forward_resp: {}", e);
crash(get_lang(&req), "error_forward_req")
})?;
let mut client_resp = HttpResponse::build(res.status());
// remove connection as per the spec
// and content-encoding since we have to decompress the traffic to edit it
for (header_name, header_value) in res
.headers()
.iter()
.filter(|(h, _)| *h != "connection" && *h != "content-encoding")
{
client_resp.header(header_name.clone(), header_value.clone());
}
// retreive the body from the request result
let response_body = res.body().limit(PAYLOAD_LIMIT).await.map_err(|e| {
eprintln!("error_forward_resp: {}", e);
crash(get_lang(&req), "error_forward_resp")
})?;
// if a new form is created, automatically set some fields.
// this is very hackish but it works! for now.
let form_id = check_new_form(route, &response_body);
if form_id > 0 {
debug(&format!(
"New form. Forging request to set isAnonymous for id {}",
form_id
));
let forged_body = format!(
r#"{{"id":{},"keyValuePairs":{{"isAnonymous":true}}}}"#,
form_id
);
let update_req = forge_from("/apps/forms/api/v1/form/update", &req, &url, &client)
.set_header("content-length", forged_body.len())
.set_header("content-type", "application/json;charset=utf-8");
let res = update_req
.send_body(forged_body)
.await
.map_err(|e| {
eprintln!("error_forward_isanon: {}", e);
crash(get_lang(&req), "error_forward_isanon")
})?;
debug(&format!("(new_form) Request returned {}", res.status()));
}
// check the response before returning it
if check_response(route, &response_body) {
return Ok(web_redir("/"));
}
Ok(client_resp.body(response_body))
}
#[derive(Deserialize)]
pub struct LoginToken {
pub token: String,
}
pub async fn forward_login(
req: HttpRequest,
params: web::Path<LoginToken>,
client: web::Data<Client>,
dbpool: web::Data<DbPool>,
) -> Result<HttpResponse, TrainCrash> {
// if the user is already logged in, redirect to the Forms app
if is_logged_in(&req).is_some() {
return Ok(web_redir("/apps/forms/"));
}
// check if the provided token seems valid. If not, early return.
if !check_token(&params.token) {
debug("Incorrect admin token given.");
return Err(crash(get_lang(&req), "error_dirtyhacker"));
}
let conn = dbpool
.get()
.map_err(|e| {
eprintln!("error_forwardlogin_db: {}", e);
crash(get_lang(&req), "error_forwardlogin_db")
})?;
// check if the link exists in DB. if it does, update lastvisit_at.
let formdata = Form::get_from_token(&params.token, &conn).map_err(|e| {
eprintln!("error_forwardlogin_db_get (diesel error): {}", e);
crash(get_lang(&req), "error_forwardlogin_db_get")
})?.ok_or_else(|| {
eprintln!("error_forwardlogin_db_get (none error)");
crash(get_lang(&req), "error_forwardlogin_db_get")
})?;
// else, try to log the user in with DB data, then redirect.
login(&client, &req, &formdata.nc_username, &formdata.nc_password).await
}
// creates a NC account using a random name and password.
// the account gets associated with a token in sqlite DB.
pub async fn forward_register(
req: HttpRequest,
client: web::Data<Client>,
dbpool: web::Data<DbPool>,
) -> Result<HttpResponse, TrainCrash> {
let lang = get_lang(&req);
// if the user is already logged in, redirect to the Forms app
if is_logged_in(&req).is_some() {
return Ok(web_redir("/apps/forms/"));
}
// if the user has already generated an admin token, redirect too
if let Some(token) = has_admintoken(&req) {
let admin_token =
token.splitn(2, ';').collect::<Vec<&str>>()[0].replace("sncf_admin_token=", "");
// sanitize the token beforehand, cookies are unsafe
if check_token(&admin_token) {
return Ok(web_redir(&format!(
"{}/admin/{}",
CONFIG.sncf_url, &admin_token
)));
} else {
debug("Incorrect admin token given.");
return Err(crash(lang, "error_dirtyhacker"));
}
}
let nc_username = gen_name();
let nc_password = gen_token();
// attempts to create the account
create_account(&client, &nc_username, &nc_password, lang.clone()).await?;
debug(&format!("Created user {}", nc_username));
let conn = dbpool
.get()
.map_err(|e| {
eprintln!("error_forwardregister_pool: {}", e);
crash(lang.clone(), "error_forwardregister_pool")
})?;
let token = gen_token();
// store the result in DB
let form_result = Form::insert(
InsertableForm {
created_at: Utc::now().naive_utc(),
lastvisit_at: Utc::now().naive_utc(),
token: &token,
nc_username: &nc_username,
nc_password: &nc_password,
},
&conn,
);
if form_result.is_err() {
return Err(crash(lang, "error_forwardregister_db"))
}
Ok(HttpResponse::Ok()
.content_type("text/html")
.set_header(
"Set-Cookie",
format!("sncf_admin_token={}; HttpOnly; SameSite=Strict", &token),
)
.body(
TplLink {
lang: &lang,
admin_token: &token,
config: &CONFIG,
}
.render()
.map_err(|e| {
eprintln!("error_tplrender (TplLink): {}", e);
crash(lang, "error_tplrender")
})?,
))
}
// create a new query destined to the nextcloud instance
// needed to forward any query
fn forge_from(
route: &str,
req: &HttpRequest,
url: &web::Data<Url>,
client: &web::Data<Client>,
) -> ClientRequest {
let mut new_url = url.get_ref().clone();
new_url.set_path(route);
new_url.set_query(req.uri().query());
// insert forwarded header if we can
let forwarded_req = client.request_from(new_url.as_str(), req.head());
if let Some(addr) = req.head().peer_addr {
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
} else {
forwarded_req
}
}
fn web_redir(location: &str) -> HttpResponse {
HttpResponse::SeeOther()
.header(http::header::LOCATION, location)
.finish()
}
pub async fn index(req: HttpRequest) -> Result<HttpResponse, TrainCrash> {
Ok(HttpResponse::Ok().content_type("text/html").body(
TplIndex {
lang: &get_lang(&req),
}
.render()
.map_err(|e| {
eprintln!("error_tplrender (TplIndex): {}", e);
crash(get_lang(&req), "error_tplrender")
})?,
))
}

92
src/main.rs Normal file
View file

@ -0,0 +1,92 @@
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
use actix_web::client::Client;
use actix_web::{middleware, web, App, FromRequest, HttpServer};
use actix_files::Files;
use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
use url::Url;
use crate::config::CONFIG;
use crate::config::PAYLOAD_LIMIT;
use crate::forward::*;
mod config;
mod database;
mod forward;
mod sniff;
mod account;
mod templates;
mod errors;
type DbPool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
embed_migrations!();
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
/* std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();*/
println!("ta ta tala ~ SNCF init");
println!("Checking configuration file...");
CONFIG.check_version();
println!("Opening database {}", CONFIG.database_path);
let manager = ConnectionManager::<SqliteConnection>::new(&CONFIG.database_path);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("ERROR: main: Failed to create the database pool.");
let conn = pool.get().expect("ERROR: main: DB connection failed");
println!("Running migrations...");
embedded_migrations::run(&*conn).expect("ERROR: main: Failed to run database migrations");
let forward_url = Url::parse(&CONFIG.nextcloud_url).expect("Couldn't parse the forward url from config");
println!(
"Now listening at {}:{}",
CONFIG.listening_address, CONFIG.listening_port
);
// starting the http server
HttpServer::new(move || {
App::new()
.data(pool.clone())
.data(Client::new())
.data(forward_url.clone())
/*.route("/mimolette", web::get().to(login))*/
/*.route("/login", web::post().to(forward))*/
.wrap(middleware::Compress::default())
.service(Files::new("/assets/", "./templates/assets/").index_file("index.html"))
.route("/", web::get().to(index))
.route("/link", web::get().to(forward_register))
.route("/admin/{token}", web::get().to(forward_login))
.default_service(web::route().to(forward)).data(String::configure(|cfg| {
cfg.limit(PAYLOAD_LIMIT)
}))
.app_data(actix_web::web::Bytes::configure(|cfg| {
cfg.limit(PAYLOAD_LIMIT)
}))
})
.bind((CONFIG.listening_address.as_str(), CONFIG.listening_port))?
.system_exit()
.run()
.await
}
pub fn debug(text: &str) {
if CONFIG.debug_mode {
println!("{}", text);
}
}

86
src/sniff.rs Normal file
View file

@ -0,0 +1,86 @@
use actix_web::web;
use serde_json::Value;
use crate::debug;
// checks to be done on user requests
// if it returns true, cancels the request
pub fn check_request(route: &str, body: &web::Bytes) -> bool {
match route {
"/apps/forms/api/v1/form/update" => rq_form_update(body),
_ => false,
}
}
// prevents the user from doing anything other than link sharing.
fn rq_form_update(body: &web::Bytes) -> bool {
let req = String::from_utf8_lossy(body);
// try to serialize the body.
// If the parsing fails, drop the request
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
eprintln!("check_request: failed to parse JSON: {}", e);
Value::Null
});
// if the type or isAnonymous is set (isn't null),
// drop the request.
// Also drop if v is null because of parsing fail.
v == Value::Null
|| v["keyValuePairs"]["isAnonymous"] != Value::Null
|| v["keyValuePairs"]["access"]["type"] != Value::Null
}
// checks to be done on responses from the Nextcloud instance
// if it returns true, cancels the request
pub fn check_response(_route: &str, _body: &web::Bytes) -> bool {
false
}
// checks if a form has been created.
// if it's the case, sets some parameters.
// this part may need code quality improvements
pub fn check_new_form(route: &str, body: &web::Bytes) -> u64 {
let req = String::from_utf8_lossy(body);
let new_form_route = "/apps/forms/api/v1/form";
if route != new_form_route {
return 0;
}
// finds the form ID
let v: Value = serde_json::from_str(&req).unwrap_or_else(|e| {
eprintln!("check_new_form: failed to parse JSON: {}", e);
Value::Null
});
if v != Value::Null && v["id"] != Value::Null && v["isAnonymous"] == Value::Null {
v["id"].as_u64().unwrap_or_else(|| {
eprintln!("check_new_form: failed to parse formid: {}", v);
0
})
} else {
0
}
}
const BLOCKED_ROUTES: &'static [&'static str] = &[
"/apps/settings",
"/login",
"/settings",
"/ocs/v",
"/remote.php",
];
// checks if the accessed route is allowed for the user.
// if it returns true, redirects elsewhere
pub fn check_route(route: &str) -> bool {
debug(route);
for r in BLOCKED_ROUTES {
if route.starts_with(r) {
return true;
}
}
false
}

62
src/templates.rs Normal file
View file

@ -0,0 +1,62 @@
use actix_web::HttpRequest;
use askama::Template;
use crate::config::Config;
#[derive(Template)]
#[template(path = "index.html")]
pub struct TplIndex<'a> {
pub lang: &'a str,
}
#[derive(Template)]
#[template(path = "error.html")]
pub struct TplError<'a> {
pub lang: &'a str,
pub error_msg: &'a str,
}
#[derive(Template)]
#[template(path = "link.html")]
pub struct TplLink<'a> {
pub lang: &'a str,
pub admin_token: &'a str,
pub config: &'a Config,
}
pub fn get_lang(req: &HttpRequest) -> String {
// getting language from client header
// taking the two first characters of the Accept-Language header,
// in lowercase, then parsing it.
// if it fails, returns "en"
if let Some(l) = req.headers().get("Accept-Language") {
if let Ok(s) = l.to_str() {
return s.to_lowercase()[..2].to_string();
}
}
String::from("en")
}
mod filters {
use crate::config::LOC;
pub fn tr(key: &str, lang: &str) -> askama::Result<String> {
Ok(String::from(
LOC.get(key)
.ok_or_else(|| {
eprintln!("tr filter: couldn't find the key {}", key);
askama::Error::from(std::fmt::Error)
})?
.get(lang)
.ok_or_else(|| {
eprintln!("tr filter: couldn't find the lang {} in key {}", lang, key);
askama::Error::from(std::fmt::Error)
})?
.as_str()
.ok_or_else(|| {
eprintln!("tr filter: lang {} in key {} is not str", lang, key);
askama::Error::from(std::fmt::Error)
})?,
))
}
}

Binary file not shown.

140
templates/assets/cloud.css Normal file
View file

@ -0,0 +1,140 @@
.has-text-centered > * {
text-align: center;
}
.c-subelem, .c-fullwidth > * {
color: #2c2c2c;
}
.c-blue {
}
.c-blue > a {
color: white;
background: #1f58c6;
}
.c-flex {
display: flex;
flex-wrap: wrap;
justify-content: space-evenly;
}
@media screen and (min-width:1280px) {
.c-flex.c-flex-reverse {
flex-direction: row-reverse;
}
.c-jumbo {
padding: 1.5rem 0;
}
.c-subelem {
padding: 0;
max-width: 40vw;
margin: auto 0;
}
}
.c-jumbo.c-jumbo-big {
min-height: 25rem;
padding: 1rem;
}
.c-jumbo.c-jumbo-medium {
min-height: 18rem;
padding: 1rem;
}
.c-jumbo.c-jumbo-small {
min-height: 10rem;
padding: 1rem;
}
.c-button {
display: block;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
border-radius: 2px;
text-align: center;
transition: all .2s ease-in-out;
white-space: nowrap;
cursor: pointer;
text-decoration: none;
padding: 1rem;
width: max-content;
margin: 0.5rem;
}
.c-button:only-child {
margin: auto;
}
.c-button.c-big {
font-size: x-large;
}
.c-subelem {
margin: auto 2rem;
padding: 1rem 0;
width: 100%;
}
.c-img-shadow {
height: auto;
max-width: 100%;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
border-radius: 2px;
}
.c-img-center {
display: block;
margin: auto;
}
.c-fullwidth {
width: 100%;
margin: auto 2rem;
}
@media screen and (max-width:1279px) {
.c-no-margin-mobile {
margin: 0 !important;
}
}
.c-jumbo {
padding: .5rem 0;
width: 100%;
}
.c-fade-left {
opacity: 0;
transform: translateX(-100px);
animation: fadeInLeft 2s ease-in-out both;
}
.c-fade-right {
opacity: 0;
transform: translateX(100px);
animation: fadeInRight 2s ease-in-out both;
}
@keyframes fadeInLeft {
0% {
opacity: 0;
transform: translateX(-100px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes fadeInRight {
0% {
opacity: 0;
transform: translateX(100px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

230
templates/assets/index.css Normal file
View file

@ -0,0 +1,230 @@
@font-face {
font-family: 'Ubuntu-R';
src: url('/assets/Ubuntu-R.ttf');
font-weight: normal;
font-style: normal;
}
* {
font-family: Ubuntu,"Ubuntu-R",sans-serif;
}
a {
text-decoration: none;
color: #2359fb;
}
.flex {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.fullheight {
min-height: 100vh;
}
.fullheight-nav {
min-height: calc(100vh - 50px);
}
.fullwidth {
width: 100%;
text-align: center;
}
.title {
color: white;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
}
h1 {
font-size: 4vw;
}
h2 {
font-size: 2.25vw;
}
h3 {
font-size: 1.75vw;
}
p {
font-size: 1.25vw;
line-height: 1.6;
}
.beta-tag {
background: #f47606;
color: white;
border-radius: 5px;
font-size: 0.9rem;
padding: 0.3rem;
margin-left: 0.5rem;
}
.beta-banner a {
color: #ffeb7f;
}
.beta-banner {
background: repeating-linear-gradient( 45deg, #d56009, #d56009 10px, #c44c05 10px, #c44c05 20px );
color: white;
padding: 1rem;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
}
.logo {
width: 10vw;
margin-right: 2vw;
}
.page-heading {
background-image: url("/assets/index-background.png"), linear-gradient(0deg, #1f58c6 0%, #1c66f2 100%);
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
}
.page-heading-text {
width: auto;
margin: auto;
}
.page-heading > p {
color: white;
}
.page-heading > p > a {
color: #c3cce8;
}
.page-heading.error {
background: url("/assets/index-background.png"), linear-gradient(0deg, #790000 0%, #a40000 100%)
}
.ncstyle-button.error {
background: #ee4040;
}
.navbar {
height: 50px;
}
body, html {
margin: 0;
padding: 0;
}
.ncstyle-button {
color: #FFF;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
border-radius: 1vw;
text-decoration: none;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.18),0 5px 5px rgba(0, 0, 0, 0.18);
white-space: nowrap;
height: max-content;
line-height: 2.25rem;
padding: 2vh 3vw;
background: #2a87ff;
font-size: 1.5vw;
min-width: 18vw;
display: block;
transition: all .25s ease-in-out;
}
.margin-bottom {
margin-bottom: 1rem;
}
.ncstyle-button:hover {
background: #2478e3;
}
.ncstyle-input {
margin: auto;
padding: 7px 6px;
font-size: 16px;
background-color: white;
color: #454545;
border: 1px solid #dbdbdb;
outline: none;
border-radius: 3px;
cursor: text;
width: 50vw;
}
#script-copy {
display: none;
}
@media only screen and (max-width: 1080px) {
h1 {
font-size: 48px;
}
h2 {
font-size: 32px;
}
h3 {
font-size: 24px;
}
p {
font-size: 16px;
}
.title {
text-align: center;
}
.logo {
width: 20vw;
margin: 0;
}
.ncstyle-button {
font-size: 24px;
}
}
@media only screen and (max-width: 1080px), screen and (max-height: 600px) {
.scroll-down-arrow {
display: none;
}
}
.scroll-down-arrow {
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2hldnJvbl90aGluX2Rvd24iIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAgMjAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDIwIDIwIiBmaWxsPSJ3aGl0ZSIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PHBhdGggZD0iTTE3LjQxOCw2LjEwOWMwLjI3Mi0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwYzAuMjcsMC4yNjgsMC4yNzEsMC43MDEsMCwwLjk2OWwtNy45MDgsNy44M2MtMC4yNywwLjI2OC0wLjcwNywwLjI2OC0wLjk3OSwwbC03LjkwOC03LjgzYy0wLjI3LTAuMjY4LTAuMjctMC43MDEsMC0wLjk2OWMwLjI3MS0wLjI2OCwwLjcwOS0wLjI2OCwwLjk3OSwwTDEwLDEzLjI1TDE3LjQxOCw2LjEwOXoiLz48L3N2Zz4=);
background-size: contain;
background-repeat: no-repeat;
}
.scroll-down-link {
cursor:pointer;
height: 60px;
width: 80px;
margin: 0px 0 0 -40px;
line-height: 60px;
position: absolute;
left: 50%;
bottom: 10px;
color: #FFF;
text-align: center;
font-size: 70px;
z-index: 100;
text-decoration: none;
text-shadow: 0px 0px 3px rgba(0, 0, 0, 0.4);
animation: fade_move_down 2s ease-in-out infinite;
}
/*animated scroll arrow animation*/
@keyframes fade_move_down {
0% { transform:translate(0,-20px); opacity: 0; }
50% { opacity: 1; }
100% { transform:translate(0,20px); opacity: 0; }
}

51
templates/assets/logo.svg Normal file
View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 1866.187 1632.163" space="preserve" version="1.1" viewBox="0 0 1661.3 1444.5" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(-154.86 -156.52)">
<polygon points="1565.4 562.79 1588.7 361.49 1588.8 361.49 1813.4 554.74" fill="#e4b50a"/>
<polygon points="1565.4 562.79 1532.6 845.46 1258.8 580.05 1588.7 360.86 1588.7 361.49" fill="#e5de00"/>
<polygon points="932.04 759.04 1532.6 845.46 1011.8 1051.4 1011.4 1051.5 1011.3 1051.3 932.6 759.59" fill="#6dc9ee"/>
<polygon points="931.31 758.41 850.93 184.55 1258.8 580.05 1532.6 845.46 932.04 759.04" fill="#80be7b"/>
<polygon points="520.6 1596.9 520.11 1596.4 831.9 961.52 931.31 758.96 931.39 758.96 932.04 759.04 932.6 759.59 1011.3 1051.3" fill="#eb76a7"/>
<polygon points="931.31 758.41 932.04 759.04 931.39 758.96"/>
<polygon points="931.31 758.96 831.9 961.52 158.98 411.87 554.38 411.87 555.11 411.16 931.31 758.41 931.39 758.96" fill="#8d5197"/>
<polygon points="555.11 411.16 284.68 161.53 850.93 184.55 931.31 758.41" fill="#0476b8"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m933.04 758.02 76.812 288.6-2.885 0.773s-70.529-140.11-100.98-88.575l-384.7 634.52z" fill="#e9609b"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m931.15 757.63-96.492 198.39s45.029-163.52 16.082-182.82c0 0-141.52-176.9-344.15-294.3 0 0-61.111-53.07-348.46-67.544h396.71z" fill="#764382"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m1533.7 843.44 0.129 0.257-522.21 206.75-78.287-291.15c75.328 158.63 104.6 191.44 114.12 198.19 0.515 0.965 1.351 1.801 2.316 2.316l2.251 2.123c21.679 24.381 481.69-118.49 481.69-118.49z" fill="#00a3e2"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m1526.1 843.06-594.9-82.725-78.737-565.44c49.082 160.05 123.64 462.58 158.7 492.75 2.38 3.345 5.404 6.176 9.135 8.363 48.31 37.76 344.47 102.22 505.81 147.05z" fill="#00a57a"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m851.09 184.45 78.851 572.66s-157.19-452.29-192.97-481.05c0 0 0-0.064-0.064-0.064-20.662-43.471-453.86-114.75-453.86-114.75z" fill="#1c63a9"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m1813.5 553.97-247.66 8.555 22.515-200.51s2.637 147.82 24.444 156.51c14.731 19.363 200.7 35.445 200.7 35.445z" fill="#d79617"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m1587.3 360.98-53.585 484.58-274.36-264.77c5.854 3.795 200.19 129.49 228.11 119.14h0.643c0.45 0 0.836-0.128 1.158-0.257 0.772-0.128 1.608-0.321 2.444-0.708 28.175-3.024 95.591-337.98 95.591-337.98z" fill="#d7bb00"/>
</g>
<g transform="translate(-154.86 -156.52)">
<path d="m1263.1 582.98c29.089-19.338 58.178-38.677 87.267-58.015l159.88-106.29c26.636-17.708 53.272-35.415 79.908-53.123 4.006-2.663 0.255-9.162-3.785-6.476l-247.15 164.3c-26.636 17.708-53.272 35.415-79.908 53.122-4.007 2.665-0.256 9.164 3.785 6.478z" fill="#fff"/>
<path d="m1585.8 365.01c31.895 27.341 63.79 54.683 95.684 82.024 40.752 34.935 81.505 69.87 122.26 104.8 1.973 1.691 3.945 3.382 5.918 5.073 3.651 3.129 8.981-2.151 5.304-5.304-31.895-27.341-63.79-54.683-95.684-82.024-40.752-34.935-81.505-69.87-122.26-104.8-1.973-1.691-3.945-3.382-5.918-5.073-3.651-3.129-8.982 2.151-5.304 5.304z" fill="#fff"/>
<path d="m1812.3 551.17c-46.01 1.282-92.019 2.564-138.03 3.846-36.051 1.004-72.102 2.009-108.15 3.014-4.826 0.134-4.839 7.635 0 7.5 46.01-1.282 92.019-2.564 138.03-3.846 36.051-1.004 72.102-2.009 108.15-3.014 4.826-0.134 4.839-7.634 0-7.5z" fill="#fff"/>
<path d="m1584.2 363.15-10.359 89.256c-7.257 62.528-14.515 125.06-21.772 187.58-6.408 55.211-12.816 110.42-19.225 165.63-1.506 12.98-3.013 25.96-4.52 38.941-0.557 4.799 6.949 4.746 7.5 0l10.359-89.256c7.257-62.528 14.515-125.06 21.772-187.58 6.408-55.211 12.816-110.42 19.225-165.63 1.506-12.98 3.013-25.96 4.52-38.941 0.557-4.799-6.95-4.745-7.5 0z" fill="#fff"/>
<path d="m1534.1 841.72-36.633-35.523c-31.571-30.616-63.143-61.231-94.714-91.847-43.339-42.027-86.679-84.054-130.02-126.08-47.515-46.077-95.03-92.153-142.54-138.23-44.099-42.764-88.197-85.527-132.3-128.29-33.09-32.088-66.179-64.176-99.269-96.264-14.489-14.05-28.977-28.1-43.466-42.15-0.133-0.129-0.267-0.259-0.401-0.389-3.472-3.367-8.78 1.932-5.303 5.304 12.211 11.841 24.422 23.682 36.632 35.523 31.571 30.616 63.143 61.231 94.714 91.846 43.339 42.027 86.679 84.055 130.02 126.08 47.515 46.076 95.03 92.153 142.54 138.23 44.099 42.764 88.197 85.527 132.3 128.29 33.09 32.088 66.179 64.176 99.269 96.264 14.489 14.05 28.977 28.1 43.466 42.15 0.134 0.129 0.267 0.259 0.4 0.389 3.473 3.367 8.782-1.931 5.305-5.303z" fill="#fff"/>
<path d="m852.12 181.84c-26.327-1.174-52.654-2.349-78.981-3.523-59.471-2.653-118.94-5.306-178.41-7.959-63.349-2.826-126.7-5.652-190.05-8.479-37.96-1.693-75.92-3.387-113.88-5.08-2.101-0.094-4.201-0.188-6.302-0.281-4.837-0.216-4.816 7.285 0 7.5 26.327 1.174 52.654 2.349 78.981 3.523 59.471 2.653 118.94 5.306 178.41 7.959 63.349 2.826 126.7 5.652 190.05 8.479 37.96 1.693 75.92 3.387 113.88 5.08 2.101 0.094 4.201 0.188 6.302 0.281 4.838 0.216 4.817-7.285 0-7.5z" fill="#fff"/>
<path d="m281.84 162.92c13.316 12.318 26.632 24.637 39.948 36.956 33.969 31.424 67.938 62.848 101.91 94.272 45.652 42.232 91.304 84.463 136.96 126.7l145.1 134.23c42.109 38.954 84.217 77.908 126.33 116.86 26.882 24.869 53.765 49.738 80.648 74.606 5.09 4.709 10.181 9.418 15.271 14.127 3.544 3.278 8.861-2.013 5.304-5.304-13.316-12.318-26.633-24.637-39.949-36.956-33.969-31.424-67.938-62.848-101.91-94.272-45.652-42.232-91.303-84.463-136.96-126.7l-145.1-134.23c-42.108-38.954-84.217-77.908-126.33-116.86-26.883-24.869-53.766-49.738-80.648-74.606-5.09-4.709-10.181-9.418-15.271-14.127-3.544-3.278-8.861 2.013-5.303 5.304z" fill="#fff"/>
<path d="m935.59 757.54c-3.589-25.746-7.178-51.491-10.766-77.237-8.161-58.546-16.322-117.09-24.482-175.64-8.825-63.313-17.65-126.62-26.476-189.94-5.582-40.044-11.164-80.087-16.745-120.13-0.465-3.333-0.929-6.665-1.394-9.998-0.666-4.773-7.891-2.734-7.232 1.994 3.589 25.746 7.178 51.491 10.766 77.236 8.161 58.547 16.322 117.09 24.483 175.64 8.825 63.313 17.65 126.62 26.475 189.94 5.582 40.043 11.164 80.087 16.745 120.13 0.465 3.333 0.929 6.665 1.394 9.998 0.665 4.774 7.891 2.735 7.232-1.993z" fill="#fff"/>
<path d="m930.75 762.13c24.773 3.578 49.547 7.157 74.321 10.735 57.265 8.271 114.53 16.543 171.79 24.815 64.172 9.27 128.34 18.539 192.52 27.809 45.496 6.572 90.992 13.144 136.49 19.715l23.388 3.378c4.729 0.683 6.766-6.543 1.994-7.232-24.773-3.578-49.547-7.157-74.32-10.735-57.265-8.271-114.53-16.543-171.79-24.815-64.172-9.27-128.34-18.539-192.52-27.809-45.496-6.572-90.992-13.144-136.49-19.715l-23.388-3.378c-4.73-0.683-6.767 6.543-1.995 7.232z" fill="#fff"/>
<path d="m929.24 756.82c-7.599 15.446-15.198 30.892-22.797 46.337l-58.8 119.52c-26.806 54.484-53.611 108.97-80.417 163.45-29.215 59.382-58.43 118.76-87.646 178.15-26.83 54.533-53.659 109.07-80.488 163.6-19.648 39.936-39.296 79.873-58.944 119.81l-23.013 46.776h-1e-3c-2.126 4.322 4.341 8.125 6.477 3.785 7.599-15.445 15.198-30.892 22.797-46.337l58.8-119.52c26.806-54.485 53.611-108.97 80.417-163.45 29.215-59.382 58.43-118.76 87.646-178.15 26.83-54.533 53.659-109.07 80.488-163.6 19.648-39.937 39.296-79.873 58.944-119.81l23.013-46.776s0-1e-3 1e-3 -1e-3c2.126-4.322-4.342-8.125-6.477-3.785z" fill="#fff"/>
<path d="m523.83 1599.8c14.242-15.838 28.484-31.677 42.726-47.516 34.868-38.777 69.736-77.554 104.6-116.33 43.708-48.608 87.416-97.216 131.12-145.82 40.761-45.331 81.522-90.662 122.28-135.99 26.028-28.946 52.057-57.893 78.085-86.839 3.806-4.232 7.612-8.465 11.418-12.698 3.224-3.585-2.063-8.907-5.304-5.304-14.242 15.839-28.483 31.677-42.726 47.516-34.868 38.777-69.736 77.554-104.6 116.33-43.708 48.608-87.416 97.216-131.12 145.82-40.761 45.331-81.522 90.663-122.28 135.99-26.028 28.946-52.057 57.893-78.085 86.839-3.806 4.232-7.612 8.465-11.418 12.698-3.223 3.585 2.064 8.906 5.304 5.302z" fill="#fff"/>
<path d="m1014.8 1051.5c-10.611-39.859-21.221-79.719-31.832-119.58-14.401-54.101-28.803-108.2-43.204-162.3-1.153-4.33-2.305-8.66-3.458-12.989-1.243-4.669-8.478-2.686-7.232 1.994 10.611 39.859 21.221 79.719 31.832 119.58 14.401 54.101 28.803 108.2 43.204 162.3 1.153 4.33 2.305 8.66 3.458 12.989 1.243 4.669 8.478 2.685 7.232-1.994z" fill="#fff"/>
<path d="m1013.9 1055.5c24.812-9.907 49.625-19.813 74.438-29.72 55.685-22.232 111.37-44.464 167.06-66.697 58.428-23.327 116.86-46.654 175.28-69.981 33.041-13.192 66.082-26.383 99.122-39.575 1.087-0.434 2.174-0.868 3.261-1.302 4.435-1.771 2.502-9.027-1.994-7.232-24.812 9.907-49.625 19.813-74.438 29.72-55.685 22.232-111.37 44.464-167.06 66.697-58.428 23.327-116.86 46.654-175.28 69.981-33.041 13.192-66.082 26.383-99.122 39.575-1.087 0.434-2.174 0.868-3.261 1.302-4.434 1.771-2.502 9.027 1.994 7.232z" fill="#fff"/>
<path d="m553.86 406.73h-395.09c-4.836 0-4.836 7.5 0 7.5h395.09c4.837 0 4.837-7.5 0-7.5z" fill="#fff"/>
<path d="m156.13 413.13c14.181 11.624 28.363 23.248 42.544 34.872 36.089 29.581 72.178 59.162 108.27 88.744 48.314 39.601 96.628 79.203 144.94 118.8 50.856 41.685 101.71 83.369 152.57 125.05 43.715 35.831 87.43 71.663 131.14 107.49 26.89 22.041 53.781 44.082 80.671 66.124l12.36 10.131c3.707 3.039 9.046-2.235 5.303-5.304-14.181-11.624-28.363-23.248-42.544-34.872-36.089-29.581-72.178-59.163-108.27-88.744-48.314-39.601-96.628-79.203-144.94-118.8-50.856-41.685-101.71-83.37-152.57-125.06-43.715-35.831-87.43-71.663-131.14-107.49-26.89-22.041-53.781-44.082-80.671-66.124l-12.36-10.131c-3.707-3.037-9.047 2.236-5.303 5.305z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

28
templates/error.html Normal file
View file

@ -0,0 +1,28 @@
<!doctype html>
<html lang="{{ lang }}">
<head>
<title>{{ "error_title"|tr(lang) }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
<body>
<div class="flex page-heading error fullheight">
<div class="flex page-heading-text">
<div>
<h1 class="title">{{ "error_title"|tr(lang) }}</h1>
<h2 class="title">{{ "error_description"|tr(lang) }}</h2>
<h3 class="title">{{ error_msg|tr(lang) }}</h3>
<p class="title">{{ "error_note1"|tr(lang) }}</h3>
<p class="title">{{ "error_note2"|tr(lang) }}</h3>
</div>
</div>
<div class="fullwidth flex">
<a class="ncstyle-button error margin-bottom" href="/">{{ "error_back"|tr(lang) }}</a>
</div>
</div>
</body>
</html>

118
templates/index.html Normal file
View file

@ -0,0 +1,118 @@
<!doctype html>
<html lang="{{ lang }}">
<head>
<title>{{ "index_title"|tr(lang) }} {{ "index_description"|tr(lang) }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
<body>
<div class="flex page-heading fullheight">
<div class="flex page-heading-text">
<div class="flex">
<a class="flex" href="https://42l.fr"><img class="logo" src="/assets/logo.svg" /></a>
</div>
<div>
<h1 class="title">{{ "index_title"|tr(lang) }}<sup class="beta-tag">{{ "index_beta_tag"|tr(lang) }}</sup></h1>
<h2 class="title">{{ "index_description"|tr(lang) }}</h2>
</div>
</div>
<div class="fullwidth flex">
<a class="ncstyle-button margin-bottom" href="/link">{{ "index_createform_button"|tr(lang) }}</a>
</div>
<a class="scroll-down-link scroll-down-arrow"></a>
</div>
<div class="has-text-centered beta-banner">
<h3>{{ "index_beta_banner_desc1"|tr(lang) }}</h3>
<p>{{ "index_beta_banner_desc1"|tr(lang) }}</p>
<p>{{ "index_beta_banner_desc2"|tr(lang) }}<a href="https://42l.fr/Contact">{{ "index_beta_banner_desc_link"|tr(lang) }}</a>.</p>
</div>
<div>
<div class="c-flex c-jumbo c-color-inverted c-color-mailred">
<div class="c-fullwidth">
<div class="has-text-centered">
<br />
<br />
<p>{{ "index_disclaimer1"|tr(lang) }}</p>
<p>{{ "index_disclaimer2"|tr(lang) }}<a href="https://42l.fr/Faire-un-don">{{ "index_disclaimer2_link_org"|tr(lang) }}</a>{{ "index_disclaimer2_or"|tr(lang) }}<a href="https://www.bountysource.com/teams/nextcloud">{{ "index_disclaimer2_nc"|tr(lang) }}</a>.</p>
</div>
</div>
</div>
<br />
<div class="c-flex c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-question.png"><img class="c-img-shadow" alt="" src="/assets/screen-question.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel1_title"|tr(lang) }}</h3>
<p>{{ "index_panel1_desc1"|tr(lang) }}</p>
<p>{{ "index_panel1_desc2"|tr(lang) }}</p>
</div>
</div>
<div class="c-flex c-flex-reverse c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-fields.png"><img class="c-img-shadow" alt="" src="/assets/screen-fields.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel2_title"|tr(lang) }}</h3>
<p>{{ "index_panel2_desc1"|tr(lang) }}</p>
<p>{{ "index_panel2_desc2"|tr(lang) }}<a href="https://github.com/nextcloud/forms/issues?q=is%3Aissue+is%3Aopen+label%3A%22feature%3A+%E2%9D%93+question+types%22">{{ "index_panel2_desc2_link"|tr(lang) }}</a>.</p>
</div>
</div>
<div class="c-flex c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-responses.png"><img class="c-img-shadow" alt="" src="/assets/screen-responses.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel3_title"|tr(lang) }}</h3>
<p>{{ "index_panel3_desc1"|tr(lang) }}</p>
</div>
</div>
<div class="c-flex c-flex-reverse c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-responses-export.png"><img class="c-img-shadow" alt="" src="/assets/screen-responses-export.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel4_title"|tr(lang) }}</h3>
<p>{{ "index_panel4_desc1"|tr(lang) }}</p>
</div>
</div>
<div class="c-flex c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-params.png"><img class="c-img-shadow" alt="" src="/assets/screen-params.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel5_title"|tr(lang) }}</h3>
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
<p>{{ "index_panel5_desc2"|tr(lang) }}</p>
</div>
</div>
<div class="c-flex c-flex-reverse c-jumbo">
<div class="c-subelem">
<a target="_blank" href="/assets/screen-formslist.png"><img class="c-img-shadow" alt="" src="/assets/screen-formslist.png" /></a>
</div>
<div class="c-subelem">
<h3>{{ "index_panel6_title"|tr(lang) }}</h3>
<p>{{ "index_panel5_desc1"|tr(lang) }}</p>
</div>
</div>
</div>
<br />
<div class="c-flex c-jumbo c-blue">
<a href="https://42l.fr/Rapport-technique" class="c-button" target="_blank">{{ "index_bottom_docs"|tr(lang) }}</a>
<a href="https://github.com/nextcloud/forms" class="c-button" target="_blank">{{ "index_bottom_source"|tr(lang) }}</a>
<a href="https://github.com/nextcloud/forms/blob/master/LICENSE" class="c-button" target="_blank">{{ "index_bottom_lic"|tr(lang) }}</a>
</div>
<br />
<div class="has-text-centered page-heading">
<br />
<h3 class="title">Crédits</h3>
<p>{{ "index_credits_desc1"|tr(lang) }}<a href="https://nextcloud.com/">{{ "index_credits_desc1_link"|tr(lang) }}</a>{{ "index_credits_desc1_a"|tr(lang) }}</p>
<p>{{ "index_credits_desc2"|tr(lang) }}<a href="https://shelter.moe/@Neil">Neil</a>{{ "index_credits_desc2_for"|tr(lang) }}<a href="https://42l.fr">{{ "index_credits_desc2_org"|tr(lang) }}</a> {{"index_credits_desc3"|tr(lang) }}.</p>
<br />
</div>
</body>
</html>

64
templates/link.html Normal file
View file

@ -0,0 +1,64 @@
<!doctype html>
<html lang="{{ lang }}">
<head>
<title>{{ "link_title"|tr(lang) }} {{ "index_title"|tr(lang) }}</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{ "meta_description"|tr(lang) }}" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="icon" type="image/png" sizes="48x48" href="/assets/favicon.ico" />
<link rel="stylesheet" href="/assets/index.css?v=1.0" />
<link rel="stylesheet" href="/assets/cloud.css?v=1.0" />
<script type="text/javascript">
window.onload = function () {
// show link copy button if javascript is enabled
document.getElementById("script-copy").style.display = "unset";
let btn = document.getElementById("script-copy-btn");
btn.style.cursor = "pointer";
btn.addEventListener('click', function() {
var copyText = document.getElementById("link");
/* Select the text field */
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
btn.innerHTML = '{{ "link_copied"|tr(lang) }}';
});
}
</script>
<body>
<div class="navbar has-text-centered page-heading"></div>
<div class="fullheight-nav">
<div class="c-flex c-jumbo">
<div class="c-fullwidth">
<div class="has-text-centered">
<br />
<h2>{{ "link_title"|tr(lang) }}</h2>
<p>{{ "link_desc1"|tr(lang)|safe }}</p>
<p>{{ "link_desc2"|tr(lang)|safe }}</p>
<br />
<div class="c-flex">
<input id="link" class="ncstyle-input" type="text" readonly value="{{ config.sncf_url }}/admin/{{ admin_token }}" />
</div>
<br />
<div id="script-copy">
<br />
<div class="c-flex">
<a id="script-copy-btn" class="ncstyle-button margin-bottom">{{ "link_copy"|tr(lang) }}</a>
</div>
<br />
</div>
<p>{{ "link_desc3"|tr(lang) }}</p>
<br />
<div class="c-flex">
<a id="forms-btn" class="ncstyle-button margin-bottom" href="{{ config.sncf_url }}/admin/{{ admin_token }}">{{ "link_access_btn"|tr(lang) }}</a>
</div>
<br />
<p>{{ "link_note"|tr(lang) }}{{ config.prune_days }}{{ "link_note2"|tr(lang) }}</p>
</div>
</div>
</div>
</div>
</body>
</html>