users: separate login and register pages + restyle

This commit is contained in:
Samantaz Fox 2022-10-13 18:26:27 +02:00
parent 542a60e599
commit 42fe574652
No known key found for this signature in database
GPG key ID: F42821059186176E
52 changed files with 648 additions and 455 deletions

View file

@ -9,6 +9,76 @@
* Licensed under AGPLv3
*/
/*
* login/Register pages
*/
.login-container,
.register-container {
width: max-content;
margin: 12vh auto;
text-align: end;
padding: 2em;
border: 1px solid;
}
.login-container label,
.register-container label {
width: max-content !important;
margin-right: 1.5em !important;
}
.login-container .login-submit-button,
.register-container .register-submit-button {
width: max-content;
margin: 1.75em auto 1em auto;
}
.login-container p,
.register-container p {
margin: 0 auto;
text-align: center;
}
.login-container .username-pass,
.register-container .username-pass {
width: max-content;
margin: auto;
padding: 0 2em;
}
.captcha {
margin: 2em 0.5em;
text-align: center;
padding: 1em;
}
/* Background color accent using transparency */
/* TODO: handle themes better, ffs */
.light-theme .captcha { background-color: #0002; }
.dark-theme .captcha { background-color: #fff2; }
@media (prefers-color-scheme: light) { .no-theme .captcha { background-color: #0002; } }
@media (prefers-color-scheme: dark) { .no-theme .captcha { background-color: #fff2; } }
.captcha div {
padding: 0.5em;
}
.captcha img {
width: 10.5em;
height: auto;
}
.captcha label {
width: auto !important;
margin: .4em 0 !important;;
}
.captcha a {
color: #335d7a;
}
/*
* User menu
*/

View file

@ -162,11 +162,8 @@
"Show replies": "عرض الردود",
"Incorrect password": "كلمة السر غير صحيحة",
"Wrong answer": "إجابة خاطئة",
"Erroneous CAPTCHA": "الكابتشا CAPTCHA غير صاحلة",
"CAPTCHA is a required field": "مكان الكابتشا CAPTCHA مطلوب",
"User ID is a required field": "مكان اسم المستخدم مطلوب",
"Password is a required field": "مكان كلمة السر مطلوب",
"Wrong username or password": "اسم المستخدم او كلمة السر غير صحيح",
"Password cannot be empty": "لا يمكن أن تكون كلمة السر فارغة",
"Password cannot be longer than 55 characters": "يجب أن لا تتعدى كلمة السر 55 حرفًا",
"Please log in": "الرجاء تسجيل الدخول",
@ -185,7 +182,6 @@
"Could not pull trending pages.": "لم يستطع عرض الصفحات الراجئة.",
"Hidden field \"challenge\" is a required field": "مكان مخفي \"تحدي\" مكان مطلوب",
"Hidden field \"token\" is a required field": "مكان مخفي \"رمز\" مكان مطلوب",
"Erroneous challenge": "تحدي غير صالح",
"Erroneous token": "رمز مميز خاطئ",
"No such user": "مستخدم غير صالح",
"Token is expired, please try again": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى",

View file

@ -419,7 +419,6 @@
"Playlist privacy": "Soukromí playlistu",
"Wrong answer": "Špatná odpověď",
"Could not pull trending pages.": "Nepodařilo se získat trendy stránky.",
"Erroneous CAPTCHA": "Chybná CAPTCHA",
"Password is a required field": "Heslo je vyžadované pole",
"preferences_automatic_instance_redirect_label": "Automatické přesměrování instance (fallback na redirect.invidious.io): ",
"Switch Invidious Instance": "Přepnout instanci Invidious",
@ -427,7 +426,7 @@
"footer_source_code": "Zdrojový kód",
"View YouTube comments": "Zobrazit YouTube komentáře",
"Blacklisted regions: ": "Oblasti na černé listině: ",
"Wrong username or password": "Nesprávné uživatelské jméno nebo heslo",
"error_invalid_username_or_password": "Nesprávné uživatelské jméno nebo heslo",
"Please sign in using 'Log in with Google'": "Přihlaste se prosím pomocí Googlu",
"Password cannot be empty": "Heslo nemůže být prázné",
"preferences_category_misc": "Různá nastavení",
@ -457,9 +456,7 @@
"Load more": "Načíst další",
"Not a playlist.": "Není playlist.",
"Playlist does not exist.": "Playlist neexistuje.",
"Erroneous challenge": "Chybná výzva",
"Premieres `x`": "Premiéra `x`",
"CAPTCHA is a required field": "CAPTCHA je vyžadované pole",
"`x` ago": "Před `x`",
"search_message_change_filters_or_query": "Zkuste rozšířit vyhledávaný dotaz a/nebo změnit filtry.",
"search_filters_date_option_none": "Jakékoli datum",

View file

@ -157,11 +157,9 @@
"Show replies": "Vis svar",
"Incorrect password": "Forkert adgangskode",
"Wrong answer": "Forkert svar",
"Erroneous CAPTCHA": "Fejlagtig CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA er et obligatorisk felt",
"User ID is a required field": "Bruger ID er et krævet felt",
"Password is a required field": "Adgangskode er et obligatorisk felt",
"Wrong username or password": "Forkert brugernavn eller adgangskode",
"error_invalid_username_or_password": "Forkert brugernavn eller adgangskode",
"Please sign in using 'Log in with Google'": "Log ind via 'Log ind med Google'",
"Password cannot be empty": "Adgangskoden må ikke være tom",
"Password cannot be longer than 55 characters": "Adgangskoden må ikke være længere end 55 tegn",
@ -306,7 +304,6 @@
"Marathi": "Marathi",
"Sindhi": "Sindhi",
"preferences_category_misc": "Diverse indstillinger",
"Erroneous challenge": "Fejlagtig udfordring",
"Hindi": "Hindi",
"Igbo": "Igbo",
"Javanese": "Javanesisk",

View file

@ -162,11 +162,9 @@
"Show replies": "Antworten anzeigen",
"Incorrect password": "Falsches Passwort",
"Wrong answer": "Ungültige Antwort",
"Erroneous CAPTCHA": "Ungültiges CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA ist eine erforderliche Eingabe",
"User ID is a required field": "Benutzer ID ist eine erforderliche Eingabe",
"Password is a required field": "Passwort ist eine erforderliche Eingabe",
"Wrong username or password": "Ungültiger Benutzername oder Passwort",
"error_invalid_username_or_password": "Ungültiger Benutzername oder Passwort",
"Password cannot be empty": "Passwort darf nicht leer sein",
"Password cannot be longer than 55 characters": "Passwort darf nicht länger als 55 Zeichen sein",
"Please log in": "Bitte anmelden",
@ -185,7 +183,6 @@
"Could not pull trending pages.": "Trendenz-Seiten konnten nicht geladen werden.",
"Hidden field \"challenge\" is a required field": "Verstecktes Feld „challenge“ ist eine erforderliche Eingabe",
"Hidden field \"token\" is a required field": "Verstecktes Feld „token“ ist eine erforderliche Eingabe",
"Erroneous challenge": "Ungültiger Test",
"Erroneous token": "Ungültiger Token",
"No such user": "Ungültiger Benutzer",
"Token is expired, please try again": "Token ist abgelaufen, bitte erneut versuchen",

View file

@ -153,11 +153,9 @@
"Show replies": "Προβολή απαντήσεων",
"Incorrect password": "Λανθασμένος κωδικός πρόσβασης",
"Wrong answer": "Λανθασμένη απάντηση",
"Erroneous CAPTCHA": "Λανθασμένο CAPTCHA",
"CAPTCHA is a required field": "Το CAPTCHA είναι απαιτούμενο πεδίο",
"User ID is a required field": "Η ταυτότητα χρήστη είναι απαιτούμενο πεδίο",
"Password is a required field": "Ο κωδικός πρόσβασης είναι απαιτούμενο πεδίο",
"Wrong username or password": "Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης",
"error_invalid_username_or_password": "Λανθασμένο όνομα χρήστη ή κωδικός πρόσβασης",
"Password cannot be empty": "Ο κωδικός πρόσβασης δεν γίνεται να είναι κενός",
"Password cannot be longer than 55 characters": "Ο κωδικός πρόσβασης δεν γίνεται να υπερβαίνει τους 55 χαρακτήρες",
"Please log in": "Συνδεθείτε",
@ -176,7 +174,6 @@
"Could not pull trending pages.": "Αδυναμία λήψης σελίδας τάσεων.",
"Hidden field \"challenge\" is a required field": "Το Κρυφό πεδίο \"δοκιμασία\" είναι απαραίτητο",
"Hidden field \"token\" is a required field": "Το κρυφό πεδίο \"αναγνωριστικό διασύνδεσης\" είναι απαραίτητο",
"Erroneous challenge": "Λανθασμένη δοκιμασία",
"Erroneous token": "Λανθασμένο αναγνωριστικό διασύνδεσης",
"No such user": "Μη υπαρκτός χρήστης",
"Token is expired, please try again": "Το αναγνωριστικό διασύνδεσης έχει λήξει, παρακαλώ ξαναπροσπαθήστε",

View file

@ -44,15 +44,6 @@
"An alternative front-end to YouTube": "An alternative front-end to YouTube",
"JavaScript license information": "JavaScript license information",
"source": "source",
"Log in": "Log in",
"Log in/register": "Log in/register",
"User ID": "User ID",
"Password": "Password",
"Time (h:mm:ss):": "Time (h:mm:ss):",
"Text CAPTCHA": "Text CAPTCHA",
"Image CAPTCHA": "Image CAPTCHA",
"Sign In": "Sign In",
"Register": "Register",
"E-mail": "E-mail",
"Preferences": "Preferences",
"preferences_category_player": "Player preferences",
@ -200,11 +191,6 @@
"Show replies": "Show replies",
"Incorrect password": "Incorrect password",
"Wrong answer": "Wrong answer",
"Erroneous CAPTCHA": "Erroneous CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA is a required field",
"User ID is a required field": "User ID is a required field",
"Password is a required field": "Password is a required field",
"Wrong username or password": "Wrong username or password",
"Password cannot be empty": "Password cannot be empty",
"Password cannot be longer than 55 characters": "Password cannot be longer than 55 characters",
"Please log in": "Please log in",
@ -225,9 +211,7 @@
"Not a playlist.": "Not a playlist.",
"Playlist does not exist.": "Playlist does not exist.",
"Could not pull trending pages.": "Could not pull trending pages.",
"Hidden field \"challenge\" is a required field": "Hidden field \"challenge\" is a required field",
"Hidden field \"token\" is a required field": "Hidden field \"token\" is a required field",
"Erroneous challenge": "Erroneous challenge",
"Erroneous token": "Erroneous token",
"No such user": "No such user",
"Token is expired, please try again": "Token is expired, please try again",
@ -464,5 +448,26 @@
"crash_page_read_the_faq": "read the <a href=\"`x`\">Frequently Asked Questions (FAQ)</a>",
"crash_page_search_issue": "searched for <a href=\"`x`\">existing issues on GitHub</a>",
"crash_page_report_issue": "If none of the above helped, please <a href=\"`x`\">open a new issue on GitHub</a> (preferably in English) and include the following text in your message (do NOT translate that text):",
"error_video_not_in_playlist": "The requested video doesn't exist in this playlist. <a href=\"`x`\">Click here for the playlist home page.</a>"
"error_video_not_in_playlist": "The requested video doesn't exist in this playlist. <a href=\"`x`\">Click here for the playlist home page.</a>",
"error_required_field_username": "Username is a required field",
"error_required_field_password": "Password is a required field",
"error_login_disabled": "Login has been disabled by the administrator",
"error_registration_disabled": "Registration has been disabled by the administrator",
"error_invalid_username_or_password": "Invalid username or password",
"error_invalid_captcha": "Invalid CAPTCHA",
"error_passwords_dont_match": "Passwords don't match",
"error_username_already_registered": "Username is already in use. Please try a different one.",
"error_database_unavailable": "Database unavailable, please try again later.<br/>\nIf the problem persist, please contact the instance admin.",
"login_page_title_login": "Sign in",
"login_page_title_register": "Register",
"login_page_login_button": "Sign in",
"login_page_register_button": "Register",
"login_page_username_label": "Username",
"login_page_password_label": "Password",
"login_page_confirm_label": "Confirm password",
"login_page_goto_register_prompt": "Don't have an account yet? <a href=\"`x`\">Register here!</a>",
"login_page_goto_login_prompt": "Already have an account? <a href=\"`x`\">Log in</a>",
"Time (h:mm:ss):": "Time (h:mm:ss):",
"login_page_request_text_captcha": "Text CAPTCHA",
"login_page_request_image_captcha": "Image CAPTCHA"
}

View file

@ -162,11 +162,9 @@
"Show replies": "Montri respondojn",
"Incorrect password": "Malbona pasvorto",
"Wrong answer": "Nevalida respondo",
"Erroneous CAPTCHA": "Nevalida CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA estas deviga kampo",
"User ID is a required field": "Uzula identigilo estas deviga kampo",
"Password is a required field": "Pasvorto estas deviga kampo",
"Wrong username or password": "Nevalida uzantnomo aŭ pasvorto",
"error_invalid_username_or_password": "Nevalida uzantnomo aŭ pasvorto",
"Please sign in using 'Log in with Google'": "Bonvolu ensaluti per 'Ensaluti per Google'",
"Password cannot be empty": "Pasvorto ne povas esti malplena",
"Password cannot be longer than 55 characters": "Pasvorto ne povas esti pli longa ol 55 signoj",
@ -186,7 +184,6 @@
"Could not pull trending pages.": "Ne povis venigi tendencajn paĝojn.",
"Hidden field \"challenge\" is a required field": "Kaŝita kampo \"challenge\" estas deviga kampo",
"Hidden field \"token\" is a required field": "Kaŝita kampo \"token\" estas deviga kampo",
"Erroneous challenge": "Nevalida defio",
"Erroneous token": "Nevalida ĵetono",
"No such user": "Nevalida uzanto",
"Token is expired, please try again": "Ĵetono senvalidiĝis, bonvolu provi denove",

View file

@ -162,11 +162,9 @@
"Show replies": "Mostrar las respuestas",
"Incorrect password": "Contraseña incorrecta",
"Wrong answer": "Respuesta no válida",
"Erroneous CAPTCHA": "CAPTCHA no válido",
"CAPTCHA is a required field": "El CAPTCHA es un campo obligatorio",
"User ID is a required field": "El nombre es un campo obligatorio",
"Password is a required field": "La contraseña es un campo obligatorio",
"Wrong username or password": "Nombre o contraseña incorrecto",
"error_invalid_username_or_password": "Nombre o contraseña incorrecto",
"Please sign in using 'Log in with Google'": "Inicie sesión con «Iniciar sesión con Google»",
"Password cannot be empty": "La contraseña no puede estar en blanco",
"Password cannot be longer than 55 characters": "La contraseña no puede tener más de 55 caracteres",
@ -186,7 +184,6 @@
"Could not pull trending pages.": "No se han podido obtener las páginas de tendencias.",
"Hidden field \"challenge\" is a required field": "El campo oculto «desafío» es un campo obligatorio",
"Hidden field \"token\" is a required field": "El campo oculto «símbolo» es un campo obligatorio",
"Erroneous challenge": "Desafío no válido",
"Erroneous token": "Símbolo no válido",
"No such user": "Usuario no existe",
"Token is expired, please try again": "El símbolo ha caducado, inténtelo de nuevo",

View file

@ -119,7 +119,7 @@
"Wrong answer": "Vale vastus",
"User ID is a required field": "Kasutaja ID on kohustuslik väli",
"Password is a required field": "Salasõna on kohustuslik väli",
"Wrong username or password": "Vale kasutajanimi või salasõna",
"error_invalid_username_or_password": "Vale kasutajanimi või salasõna",
"Please sign in using 'Log in with Google'": "Palun kasutage 'Logi sisse Google'iga'",
"Password cannot be longer than 55 characters": "Salasõna ei tohi olla pikem kui 55 tähemärki",
"Password cannot be empty": "Salasõna ei tohi olla tühi",
@ -293,7 +293,6 @@
"Lithuanian": "Leedu",
"Videos": "Videod",
"Community": "Kogukond",
"CAPTCHA is a required field": "CAPTCHA on kohustuslik väli",
"comments_points_count": "{{count}} punkt",
"comments_points_count_plural": "{{count}} punkti",
"Chinese": "Hiina",

View file

@ -164,7 +164,7 @@
"Premieres `x`": "'x' estrenaldiak",
"Wrong answer": "Erantzun ez zuzena",
"Password is a required field": "Pasahitza beharrezkoa da",
"Wrong username or password": "Pasahitza edo ezizena gaizki",
"error_invalid_username_or_password": "Pasahitza edo ezizena gaizki",
"Password cannot be longer than 55 characters": "Pasahitza 55 karaktere baino luzeagoa ezin da izan",
"This channel does not exist.": "Kanal hau ez dago.",
"`x` ago": "duela 'x'",
@ -191,12 +191,10 @@
"Danish": "Daniera",
"Dutch": "Alemaniera",
"Esperanto": "Esperanto",
"Erroneous challenge": "Erronka okerra",
"View all playlists": "Zerrenda guztiak ikusi",
"Show annotations": "Oharrak erakutsi",
"Empty playlist": "Zerrenda hutsik",
"Please log in": "Sartu, mesedez",
"CAPTCHA is a required field": "CAPTCHA beharrezko eremua da",
"preferences_category_data": "Dataren lehentasunak",
"preferences_default_home_label": "Homepage lehenetsia: ",
"preferences_automatic_instance_redirect_label": "berbideratze adibide automatikoa (atzera egin berbideratzeko: invidious.io) ",
@ -246,7 +244,6 @@
"revoke": "ukatu",
"preferences_continue_label": "Hurrengo lehenetsia jo: ",
"Whitelisted regions: ": "Zuri zerrendaren zonaldeak: ",
"Erroneous CAPTCHA": "CAPTCHA gaizki",
"Deleted or invalid channel": "Ezgai edota ezabatutako kanala",
"Could not create mix.": "Nahastea ezin sortu.",
"Not a playlist.": "Ez da zerrenda.",

View file

@ -169,11 +169,9 @@
"Show replies": "نمایش پاسخ ها",
"Incorrect password": "گذرواژه نا درست",
"Wrong answer": "پاسخ غلط",
"Erroneous CAPTCHA": "CAPTCHA نا درست",
"CAPTCHA is a required field": "CAPTCHA یک فیلد ضروری است",
"User ID is a required field": "شناسه کاربری یک فیلد ضروری است",
"Password is a required field": "گذرواژه یک فیلد ضروری است",
"Wrong username or password": "نام کاربری یا گذرواژه غلط است",
"error_invalid_username_or_password": "نام کاربری یا گذرواژه غلط است",
"Please sign in using 'Log in with Google'": "لطفا با استفاده از 'ورود توسط گوگل' وارد شوید",
"Password cannot be empty": "گذرواژه نمیتواند خالی باشد",
"Password cannot be longer than 55 characters": "گذر واژه نمیتواند از ۵۵ کاراکتر بیشتر باشد",
@ -195,7 +193,6 @@
"Could not pull trending pages.": "نمیتوان صفحه های پر طرفدار را بکشد.",
"Hidden field \"challenge\" is a required field": "فیلد مخفی \"چالش\" یک فیلد ضروری است",
"Hidden field \"token\" is a required field": "فیلد مخفی \"توکن\" یک فیلد ضروری است",
"Erroneous challenge": "چالش غلط",
"Erroneous token": "توکن غلط",
"No such user": "چنین کاربری وجود ندارد",
"Token is expired, please try again": "توکن ضروری است، لطفا دوباره تلاش کنید",

View file

@ -161,11 +161,9 @@
"Show replies": "Näytä vastaukset",
"Incorrect password": "Väärä salasana",
"Wrong answer": "Väärä vastaus",
"Erroneous CAPTCHA": "Virheellinen CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA-kenttä vaaditaan",
"User ID is a required field": "Käyttäjätunnus vaaditaan",
"Password is a required field": "Salasana vaaditaan",
"Wrong username or password": "Väärä käyttäjänimi tai salasana",
"error_invalid_username_or_password": "Väärä käyttäjänimi tai salasana",
"Password cannot be empty": "Salasana ei voi olla tyhjä",
"Password cannot be longer than 55 characters": "Salasana ei voi olla yli 55 merkkiä pitkä",
"Please log in": "Kirjaudu sisään, ole hyvä",
@ -184,7 +182,6 @@
"Could not pull trending pages.": "Nousussa olevien sivujen lataus epäonnistui.",
"Hidden field \"challenge\" is a required field": "Piilotettu kenttä \"challenge\" vaaditaan",
"Hidden field \"token\" is a required field": "Piilotettu kenttä \"tunnus\" vaaditaan",
"Erroneous challenge": "Virheellinen haaste",
"Erroneous token": "Virheellinen tunnus",
"No such user": "Käyttäjää ei ole olemassa",
"Token is expired, please try again": "Tunnus on vanhentunut, yritä uudestaan",

View file

@ -44,15 +44,6 @@
"An alternative front-end to YouTube": "Un front-end alternatif à YouTube",
"JavaScript license information": "Informations sur les licences JavaScript",
"source": "source",
"Log in": "Se connecter",
"Log in/register": "Se connecter/S'inscrire",
"User ID": "Identifiant utilisateur",
"Password": "Mot de passe",
"Time (h:mm:ss):": "Heure (h:mm:ss) :",
"Text CAPTCHA": "CAPTCHA textuel",
"Image CAPTCHA": "CAPTCHA graphique",
"Sign In": "Se connecter",
"Register": "S'inscrire",
"E-mail": "E-mail",
"Preferences": "Préférences",
"preferences_category_player": "Préférences du lecteur",
@ -177,11 +168,9 @@
"Show replies": "Afficher les réponses",
"Incorrect password": "Mot de passe incorrect",
"Wrong answer": "Réponse invalide",
"Erroneous CAPTCHA": "CAPTCHA invalide",
"CAPTCHA is a required field": "Veuillez entrer un CAPTCHA",
"User ID is a required field": "Veuillez entrer un Identifiant Utilisateur",
"Password is a required field": "Veuillez entrer un Mot de passe",
"Wrong username or password": "Nom d'utilisateur ou mot de passe invalide",
"error_invalid_username_or_password": "Nom d'utilisateur ou mot de passe invalide",
"Password cannot be empty": "Le mot de passe ne peut pas être vide",
"Password cannot be longer than 55 characters": "Le mot de passe ne doit pas comporter plus de 55 caractères",
"Please log in": "Veuillez vous connecter",
@ -204,7 +193,6 @@
"Could not pull trending pages.": "Impossible de charger les pages de tendances.",
"Hidden field \"challenge\" is a required field": "Le champ masqué \"challenge\" est un champ obligatoire",
"Hidden field \"token\" is a required field": "Le champ caché « token » est requis",
"Erroneous challenge": "Challenge invalide",
"Erroneous token": "Token invalide",
"No such user": "Cet utilisateur n'existe pas",
"Token is expired, please try again": "Le token est expiré, veuillez réessayer",
@ -464,5 +452,26 @@
"search_filters_date_label": "Date d'ajout",
"search_filters_features_option_vr180": "VR180",
"search_filters_duration_option_none": "Toutes les durées",
"error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>"
"error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. <a href=\"`x`\">Cliquez ici pour retourner à la liste de lecture.</a>",
"error_required_field_username": "Le champ \"nom d'utilisateur\" est requis",
"error_required_field_password": "Le champ \"mot de passe\" est requis",
"error_login_disabled": "L'administrateur a interdit l'utilisation des comptes utilisateur",
"error_registration_disabled": "L'administrateur a interdit la création de comptes utilisateur",
"error_invalid_username_or_password": "Mot de passe ou nom d'utilisateur invalide",
"error_invalid_captcha": "CAPTCHA invalide",
"error_passwords_dont_match": "Les mots de passe ne correspondent pas",
"error_username_already_registered": "Ce nom d'utilisateur n'est pas disponible. Merci d'en utiliser un autre.",
"error_database_unavailable": "Base de données indisponible, merci de réessayer plus tard.<br/>\nSi le problèm persiste, merci de contacter l'administrateur de cette instance.",
"login_page_title_login": "Connexion",
"login_page_title_register": "Créer un compte",
"login_page_login_button": "Connexion",
"login_page_register_button": "Créer un compte",
"login_page_username_label": "Nom d'utilisateur",
"login_page_password_label": "Mot de passe",
"login_page_confirm_label": "Confirmer le mot de passe",
"login_page_goto_register_prompt": "Pas encore de compte? <a href=\"`x`\">S'enregistrer</a>",
"login_page_goto_login_prompt": "Déjà un compte? <a href=\"`x`\">Se connecter</a>",
"Time (h:mm:ss):": "Time (h:mm:ss):",
"login_page_request_text_captcha": "Changer pour un CAPTCHA textuel",
"login_page_request_image_captcha": "Changer pour un CAPTCHA visuel"
}

View file

@ -130,10 +130,9 @@
"Show replies": "הצגת תגובות",
"Incorrect password": "סיסמה שגויה",
"Wrong answer": "תשובה שגויה",
"CAPTCHA is a required field": "שדה CAPTCHA הוא שדה חובה",
"User ID is a required field": "חובה למלא את שדה שם המשתמש",
"Password is a required field": "חובה למלא את שדה הסיסמה",
"Wrong username or password": "שם משתמש שגוי או סיסמה שגויה",
"error_invalid_username_or_password": "שם משתמש שגוי או סיסמה שגויה",
"Please sign in using 'Log in with Google'": "נא להתחבר בעזרת \"התחברות עם Google\"",
"Password cannot be longer than 55 characters": "על אורך הסיסמה להיות 55 תווים לכל היותר",
"Please log in": "נא להתחבר",

View file

@ -14,7 +14,6 @@
"License: ": "लाइसेंस: ",
"Wilson score: ": "Wilson स्कोर: ",
"Wrong answer": "गलत जवाब",
"Erroneous CAPTCHA": "गलत CAPTCHA",
"Please log in": "कृपया लॉग-इन करें",
"Bosnian": "बोस्नियाई",
"Bulgarian": "बुल्गारियाई",
@ -221,10 +220,9 @@
"Hide replies": "जवाब छिपाएँ",
"Show replies": "जवाब दिखाएँ",
"Incorrect password": "गलत पासवर्ड",
"CAPTCHA is a required field": "CAPTCHA एक ज़रूरी फ़ील्ड है",
"User ID is a required field": "सदस्य ID एक ज़रूरी फ़ील्ड है",
"Password is a required field": "पासवर्ड एक ज़रूरी फ़ील्ड है",
"Wrong username or password": "गलत सदस्यनाम या पासवर्ड",
"error_invalid_username_or_password": "गलत सदस्यनाम या पासवर्ड",
"Please sign in using 'Log in with Google'": "कृपया 'Google के साथ लॉग-इन करें' के साथ साइन-इन करें",
"Password cannot be empty": "पासवर्ड खाली नहीं हो सकता",
"Password cannot be longer than 55 characters": "पासवर्ड में अधिकतम 55 अक्षर हो सकते हैं",
@ -244,7 +242,6 @@
"Could not pull trending pages.": "रुझान के पृष्ठ प्राप्त न किए जा सके।",
"Hidden field \"challenge\" is a required field": "छिपाया गया फ़ील्ड \"चुनौती\" एक आवश्यक फ़ील्ड है",
"Hidden field \"token\" is a required field": "छिपाया गया फ़ील्ड \"टोकन\" एक आवश्यक फ़ील्ड है",
"Erroneous challenge": "त्रुटिपूर्ण चुनौती",
"Erroneous token": "त्रुटिपूर्ण टोकन",
"No such user": "यह सदस्य मौजूद नहीं हैं",
"Token is expired, please try again": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें",

View file

@ -162,11 +162,9 @@
"Show replies": "Prikaži odgovore",
"Incorrect password": "Neispravna lozinka",
"Wrong answer": "Krivi odgovor",
"Erroneous CAPTCHA": "Neispravan CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA je obavezno polje",
"User ID is a required field": "Korisnički ID je obavezno polje",
"Password is a required field": "Polje lozinke je obavezno polje",
"Wrong username or password": "Krivo korisničko ime ili lozinka",
"error_invalid_username_or_password": "Krivo korisničko ime ili lozinka",
"Password cannot be empty": "Polje lozinke ne smije ostati prazno",
"Password cannot be longer than 55 characters": "Lozinka ne može biti duža od 55 znakova",
"Please log in": "Prijavi se",
@ -185,7 +183,6 @@
"Could not pull trending pages.": "Neuspjelo preuzimanje stranica u trendu.",
"Hidden field \"challenge\" is a required field": "Skriveno polje „izazov” je obavezno polje",
"Hidden field \"token\" is a required field": "Skriveno polje „token” je obavezno polje",
"Erroneous challenge": "Neispravan izazov",
"Erroneous token": "Neispravan token",
"No such user": "Takav korisnik ne postoji",
"Token is expired, please try again": "Token je istekao, pokušaj ponovo",

View file

@ -171,11 +171,9 @@
"Show replies": "Válaszok mutatása",
"Incorrect password": "A jelszó nem megfelelő",
"Wrong answer": "Nem jól válaszoltál.",
"Erroneous CAPTCHA": "A CAPTCHA hibás.",
"CAPTCHA is a required field": "A CAPTCHA-mezőt ki kell tölteni.",
"User ID is a required field": "A felhasználói azonosítót meg kell adni.",
"Password is a required field": "Meg kell adni egy jelszót.",
"Wrong username or password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
"error_invalid_username_or_password": "Vagy a felhasználói név, vagy pedig a jelszó nem megfelelő.",
"Password cannot be empty": "A jelszót nem lehet kihagyni.",
"Password cannot be longer than 55 characters": "A jelszó nem lehet hosszabb 55 karakternél.",
"Please log in": "Kérjük, jelentkezz be.",
@ -198,7 +196,6 @@
"Could not pull trending pages.": "Nem lehetett betölteni a felkapott videók oldalát.",
"Hidden field \"challenge\" is a required field": "A rejtett „challenge” mezőt ki kell tölteni.",
"Hidden field \"token\" is a required field": "A rejtett „token” mezőt ki kell tölteni.",
"Erroneous challenge": "Hibás challenge",
"Erroneous token": "Hibás token",
"No such user": "Nincs ilyen felhasználó",
"Token is expired, please try again": "A token lejárt. Kérjük, próbáld meg újból.",

View file

@ -169,11 +169,9 @@
"Show replies": "Lihat balasan",
"Incorrect password": "Kata sandi salah",
"Wrong answer": "Jawaban salah",
"Erroneous CAPTCHA": "CAPTCHA salah",
"CAPTCHA is a required field": "CAPTCHA perlu diisi",
"User ID is a required field": "ID pengguna perlu diisi",
"Password is a required field": "Kata sandi perlu diisi",
"Wrong username or password": "Nama pengguna atau kata sandi salah",
"error_invalid_username_or_password": "Nama pengguna atau kata sandi salah",
"Password cannot be empty": "Kata sandi tidak boleh kosong",
"Password cannot be longer than 55 characters": "Kata sandi tidak boleh lebih dari 55 karakter",
"Please log in": "Harap masuk",
@ -194,7 +192,6 @@
"Could not pull trending pages.": "Tidak bisa mendapatkan laman tren.",
"Hidden field \"challenge\" is a required field": "Bidang \"tantangan\" tersembunyi wajib diisi",
"Hidden field \"token\" is a required field": "Bidang \"token\" tersembunyi wajib diisi",
"Erroneous challenge": "Tantangan salah",
"Erroneous token": "Token salah",
"No such user": "Tidak ada pengguna demikian",
"Token is expired, please try again": "Token kadaluwarsa, harap coba lagi",

View file

@ -153,11 +153,9 @@
"Show replies": "Sýna svör",
"Incorrect password": "Rangt lykilorð",
"Wrong answer": "Rangt svar",
"Erroneous CAPTCHA": "Rangt CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA er nauðsynlegur reitur",
"User ID is a required field": "Notandakenni er nauðsynlegur reitur",
"Password is a required field": "Lykilorð er nauðsynlegur reitur",
"Wrong username or password": "Rangt notandanafn eða lykilorð",
"error_invalid_username_or_password": "Rangt notandanafn eða lykilorð",
"Password cannot be empty": "Lykilorð má ekki vera autt",
"Password cannot be longer than 55 characters": "Lykilorð má ekki vera lengra en 55 stafir",
"Please log in": "Vinsamlegast skráðu þig inn",
@ -176,7 +174,6 @@
"Could not pull trending pages.": "Ekki tókst að draga vinsælar síður.",
"Hidden field \"challenge\" is a required field": "Falinn reitur \"áskorun\" er nauðsynlegur reitur",
"Hidden field \"token\" is a required field": "Falinn reitur \"tákn\" er nauðsynlegur reitur",
"Erroneous challenge": "Röng áskorun",
"Erroneous token": "Rangt tákn",
"No such user": "Enginn slíkur notandi",
"Token is expired, please try again": "Tákn er útrunnið, vinsamlegast reyndu aftur",

View file

@ -167,11 +167,9 @@
"Show replies": "Mostra le risposte",
"Incorrect password": "Password sbagliata",
"Wrong answer": "Risposta errata",
"Erroneous CAPTCHA": "CAPTCHA errato",
"CAPTCHA is a required field": "Il CAPTCHA è un campo obbligatorio",
"User ID is a required field": "L'ID utente è obbligatorio",
"Password is a required field": "La password è un campo obbligatorio",
"Wrong username or password": "Nome utente o password errati",
"error_invalid_username_or_password": "Nome utente o password errati",
"Password cannot be empty": "La password non può essere vuota",
"Password cannot be longer than 55 characters": "La password non può contenere più di 55 caratteri",
"Please log in": "Per favore, accedi",
@ -190,7 +188,6 @@
"Could not pull trending pages.": "Impossibile recuperare le tendenze.",
"Hidden field \"challenge\" is a required field": "Il campo nascosto \"challenge\" è obbligatorio",
"Hidden field \"token\" is a required field": "Il campo nascosto «token» è obbligatorio",
"Erroneous challenge": "Campo «challenge» non valido",
"Erroneous token": "Campo \"token\" non valido",
"No such user": "Utente non valido",
"Token is expired, please try again": "Gettone scaduto, riprova",

View file

@ -169,11 +169,9 @@
"Show replies": "返信を表示",
"Incorrect password": "パスワードが間違っています",
"Wrong answer": "回答が間違っています",
"Erroneous CAPTCHA": "CAPTCHA が間違っています",
"CAPTCHA is a required field": "CAPTCHA は必須項目です",
"User ID is a required field": "ユーザー ID は必須項目です",
"Password is a required field": "パスワードは必須項目です",
"Wrong username or password": "ユーザー名またはパスワードが間違っています",
"error_invalid_username_or_password": "ユーザー名またはパスワードが間違っています",
"Password cannot be empty": "パスワードを空にすることはできません",
"Password cannot be longer than 55 characters": "パスワードは55文字より長くできません",
"Please log in": "ログインをしてください",
@ -194,7 +192,6 @@
"Could not pull trending pages.": "急上昇ページを取得できませんでした。",
"Hidden field \"challenge\" is a required field": "非表示項目 \"challenge\" は必須項目です",
"Hidden field \"token\" is a required field": "非表示項目 \"token\" は必須項目です",
"Erroneous challenge": "チャレンジが間違っています",
"Erroneous token": "トークンが間違っています",
"No such user": "ユーザーが存在しません",
"Token is expired, please try again": "トークンが期限切れです。再度試してください",

View file

@ -234,7 +234,6 @@
"Hausa": "하우사어",
"No such user": "해당 사용자 없음",
"Erroneous token": "잘못된 token",
"Erroneous challenge": "잘못된 challenge",
"Hidden field \"token\" is a required field": "숨겨진 필드 \"token\"은 필수 필드입니다",
"Hidden field \"challenge\" is a required field": "숨겨진 필드 \"challenge\"는 필수 필드입니다",
"Could not pull trending pages.": "인기 급상승 페이지를 가져올 수 없습니다.",
@ -276,11 +275,9 @@
"Please log in": "로그인하세요",
"Password cannot be longer than 55 characters": "비밀번호는 55자 이하여야 합니다",
"Password cannot be empty": "비밀번호는 비워둘 수 없습니다",
"Wrong username or password": "잘못된 사용자 이름 또는 비밀번호",
"error_invalid_username_or_password": "잘못된 사용자 이름 또는 비밀번호",
"Password is a required field": "비밀번호는 필수 필드입니다",
"User ID is a required field": "사용자 ID는 필수 필드입니다",
"CAPTCHA is a required field": "CAPTCHA는 필수 필드입니다",
"Erroneous CAPTCHA": "잘못된 CAPTCHA",
"Blacklisted regions: ": "차단된 지역: ",
"Playlists": "재생목록",
"View as playlist": "재생목록으로 보기",

View file

@ -162,11 +162,9 @@
"Show replies": "Rodyti atsakymus",
"Incorrect password": "Slaptažodis neteisingas",
"Wrong answer": "Atsakymas neteisingas",
"Erroneous CAPTCHA": "Klaidinga CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA yra reikalinga šiam laukeliui",
"User ID is a required field": "Vartotojo ID yra reikalingas šiam laukeliui",
"Password is a required field": "Slaptažodis yra reikalingas šiam laukeliui",
"Wrong username or password": "Neteisingas vartotojo vardas arba slaptažodis",
"error_invalid_username_or_password": "Neteisingas vartotojo vardas arba slaptažodis",
"Password cannot be empty": "Slaptažodžio laukelis negali būti tuščias",
"Password cannot be longer than 55 characters": "Slaptažodis negali būti ilgesnis nei 55 simboliai",
"Please log in": "Prašome prisijungti",
@ -185,7 +183,6 @@
"Could not pull trending pages.": "Nepavyko ištraukti tendencijų puslapių.",
"Hidden field \"challenge\" is a required field": "Paslėptas laukas „iššūkis“ yra privalomas laukas",
"Hidden field \"token\" is a required field": "Paslėptas laukas „žetonas“ yra privalomas laukas",
"Erroneous challenge": "Klaidingas iššūkis",
"Erroneous token": "Klaidingas žetonas",
"No such user": "Nėra tokio vartotojo",
"Token is expired, please try again": "Žetonas pasibaigęs, prašome bandyti dar kartą",

View file

@ -162,11 +162,9 @@
"Show replies": "Vis svar",
"Incorrect password": "Feil passord",
"Wrong answer": "Ugyldig svar",
"Erroneous CAPTCHA": "Ugyldig CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA er et påkrevd felt",
"User ID is a required field": "Bruker-ID er et påkrevd felt",
"Password is a required field": "Passord er et påkrevd felt",
"Wrong username or password": "Ugyldig brukernavn eller passord",
"error_invalid_username_or_password": "Ugyldig brukernavn eller passord",
"Password cannot be empty": "Passordet kan ikke være tomt",
"Password cannot be longer than 55 characters": "Passordet kan ikke være lengre enn 55 tegn",
"Please log in": "Logg inn",
@ -185,7 +183,6 @@
"Could not pull trending pages.": "Kunne ikke hente trendsettende sider.",
"Hidden field \"challenge\" is a required field": "Skjult felt \"utfordring\" er et påkrevd felt",
"Hidden field \"token\" is a required field": "Skjult felt \"symbol\" er et påkrevd felt",
"Erroneous challenge": "Ugyldig utfordring",
"Erroneous token": "Ugyldig symbol",
"No such user": "Ugyldig bruker",
"Token is expired, please try again": "Symbol utløpt, prøv igjen",

View file

@ -157,11 +157,9 @@
"Show replies": "Antwoorden tonen",
"Incorrect password": "Wachtwoord is onjuist",
"Wrong answer": "Onjuist antwoord",
"Erroneous CAPTCHA": "Onjuiste CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA is vereist",
"User ID is a required field": "Gebruikers-id is vereist",
"Password is a required field": "Wachtwoord is vereist",
"Wrong username or password": "Onjuiste gebruikersnaam of wachtwoord",
"error_invalid_username_or_password": "Onjuiste gebruikersnaam of wachtwoord",
"Please sign in using 'Log in with Google'": "Log in via 'Inloggen met Google'",
"Password cannot be empty": "Het wachtwoordveld mag niet leeg zijn",
"Password cannot be longer than 55 characters": "Het wachtwoord mag niet langer dan 55 tekens zijn",
@ -181,7 +179,6 @@
"Could not pull trending pages.": "Kan uitgelichte pagina's niet ophalen.",
"Hidden field \"challenge\" is a required field": "Verborgen veld \"uitdaging\" is vereist",
"Hidden field \"token\" is a required field": "Verborgen veld \"toegangssleutel\" is vereist",
"Erroneous challenge": "Ongeldige uitdaging",
"Erroneous token": "Ongeldige toegangssleutel",
"No such user": "Gebruiker bestaat niet",
"Token is expired, please try again": "Toegangssleutel verlopen; probeer het opnieuw",

View file

@ -161,11 +161,9 @@
"Show replies": "Pokaż odpowiedzi",
"Incorrect password": "Niepoprawne hasło",
"Wrong answer": "Niepoprawna odpowiedź",
"Erroneous CAPTCHA": "CAPTCHA wykonane błędnie",
"CAPTCHA is a required field": "CAPTCHA jest polem wymaganym",
"User ID is a required field": "ID użytkownika jest polem wymaganym",
"Password is a required field": "Hasło jest polem wymaganym",
"Wrong username or password": "Niepoprawny login lub hasło",
"error_invalid_username_or_password": "Niepoprawny login lub hasło",
"Please sign in using 'Log in with Google'": "Zaloguj się używając \"Zaloguj się przez Google\"",
"Password cannot be empty": "Hasło nie może być puste",
"Password cannot be longer than 55 characters": "Hasło nie może być dłuższe niż 55 znaków",
@ -185,7 +183,6 @@
"Could not pull trending pages.": "Nie udało się pobrać strony na czasie.",
"Hidden field \"challenge\" is a required field": "Ukryte pole \"wyzwanie\" jest polem wymaganym",
"Hidden field \"token\" is a required field": "Ukryte pole \"token\" jest polem wymaganym",
"Erroneous challenge": "Niepoprawne wyzwanie",
"Erroneous token": "Niepoprawny token",
"No such user": "Niepoprawny użytkownik",
"Token is expired, please try again": "Token wygasł, spróbuj ponownie",

View file

@ -164,11 +164,9 @@
"Show replies": "Mostrar respostas",
"Incorrect password": "Senha incorreta",
"Wrong answer": "Resposta incorreta",
"Erroneous CAPTCHA": "CAPTCHA inválido",
"CAPTCHA is a required field": "O CAPTCHA é um campo obrigatório",
"User ID is a required field": "O nome de usuário é um campo obrigatório",
"Password is a required field": "A senha é um campo obrigatório",
"Wrong username or password": "Nome de usuário ou senha inválidos",
"error_invalid_username_or_password": "Nome de usuário ou senha inválidos",
"Please sign in using 'Log in with Google'": "Por favor, entre usando 'Entrar com conta Google'",
"Password cannot be empty": "A senha não pode ficar em branco",
"Password cannot be longer than 55 characters": "A senha não pode ter mais que 55 caracteres",
@ -188,7 +186,6 @@
"Could not pull trending pages.": "Não foi possível obter as páginas dos vídeos em alta.",
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é obrigatório",
"Erroneous challenge": "Desafio inválido",
"Erroneous token": "Token inválido",
"No such user": "Usuário inválido",
"Token is expired, please try again": "Token expirou, tente novamente",

View file

@ -164,11 +164,9 @@
"Show replies": "Mostrar respostas",
"Incorrect password": "Palavra-chave incorreta",
"Wrong answer": "Resposta errada",
"Erroneous CAPTCHA": "CAPTCHA inválido",
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
"Password is a required field": "Palavra-chave é um campo obrigatório",
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
"error_invalid_username_or_password": "Nome de utilizador ou palavra-chave incorreto",
"Please sign in using 'Log in with Google'": "Por favor, inicie sessão usando 'Iniciar sessão com o Google'",
"Password cannot be empty": "A palavra-chave não pode estar vazia",
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
@ -188,7 +186,6 @@
"Could not pull trending pages.": "Não foi possível obter as páginas de tendências.",
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
"Erroneous challenge": "Desafio inválido",
"Erroneous token": "Token inválido",
"No such user": "Utilizador inválido",
"Token is expired, please try again": "Token expirou, tente novamente",

View file

@ -106,7 +106,6 @@
"Token is expired, please try again": "Token expirou, tente novamente",
"No such user": "Utilizador inválido",
"Erroneous token": "Token inválido",
"Erroneous challenge": "Desafio inválido",
"Hidden field \"token\" is a required field": "O campo oculto \"token\" é um campo obrigatório",
"Hidden field \"challenge\" is a required field": "O campo oculto \"desafio\" é obrigatório",
"Playlist does not exist.": "A lista de reprodução não existe.",
@ -123,11 +122,9 @@
"Password cannot be longer than 55 characters": "A palavra-chave não pode ser superior a 55 caracteres",
"Password cannot be empty": "A palavra-chave não pode estar vazia",
"Please sign in using 'Log in with Google'": "Por favor, inicie sessão usando 'Iniciar sessão com o Google'",
"Wrong username or password": "Nome de utilizador ou palavra-chave incorreto",
"error_invalid_username_or_password": "Nome de utilizador ou palavra-chave incorreto",
"Password is a required field": "Palavra-chave é um campo obrigatório",
"User ID is a required field": "O nome de utilizador é um campo obrigatório",
"CAPTCHA is a required field": "CAPTCHA é um campo obrigatório",
"Erroneous CAPTCHA": "CAPTCHA inválido",
"Wrong answer": "Resposta errada",
"Incorrect password": "Palavra-chave incorreta",
"Show replies": "Mostrar respostas",

View file

@ -153,11 +153,9 @@
"Show replies": "Afișați replicile",
"Incorrect password": "Parolă incorectă",
"Wrong answer": "Răspuns invalid",
"Erroneous CAPTCHA": "CAPTCHA invalid",
"CAPTCHA is a required field": "Câmpul CAPTCHA este obligatoriu",
"User ID is a required field": "Câmpul ID Utilizator este obligatoriu",
"Password is a required field": "Câmpul Parolă este obligatoriu",
"Wrong username or password": "Nume de utilizator sau parolă invalidă",
"error_invalid_username_or_password": "Nume de utilizator sau parolă invalidă",
"Please sign in using 'Log in with Google'": "Vă rog conectați-vă folosind \"Conectați-vă cu Google\"",
"Password cannot be empty": "Parola nu poate fi goală",
"Password cannot be longer than 55 characters": "Parola nu poate să conțină mai mult de 55 de caractere",
@ -177,7 +175,6 @@
"Could not pull trending pages.": "Încărcarea paginilor de tendințe a eșuat.",
"Hidden field \"challenge\" is a required field": "Câmpul ascuns \"challenge\" este un câmp obligatoriu",
"Hidden field \"token\" is a required field": "Câmpul ascuns \"token\" este un câmp obligatoriu",
"Erroneous challenge": "Challenge invalid",
"Erroneous token": "Token invalid",
"No such user": "Acest utilizator nu există",
"Token is expired, please try again": "Jetonul a expirat, vă rugăm să încercați din nou",

View file

@ -162,11 +162,9 @@
"Show replies": "Показать ответы",
"Incorrect password": "Неправильный пароль",
"Wrong answer": "Неправильный ответ",
"Erroneous CAPTCHA": "Неправильная капча",
"CAPTCHA is a required field": "Необходимо решить капчу",
"User ID is a required field": "Необходимо ввести ID пользователя",
"Password is a required field": "Необходимо ввести пароль",
"Wrong username or password": "Неправильный логин или пароль",
"error_invalid_username_or_password": "Неправильный логин или пароль",
"Please sign in using 'Log in with Google'": "Пожалуйста, нажмите «Войти через Google»",
"Password cannot be empty": "Пароль не может быть пустым",
"Password cannot be longer than 55 characters": "Пароль не может быть длиннее 55 символов",
@ -186,7 +184,6 @@
"Could not pull trending pages.": "Не удаётся загрузить страницы «в тренде».",
"Hidden field \"challenge\" is a required field": "Необходимо заполнить скрытое поле «challenge»",
"Hidden field \"token\" is a required field": "Необходимо заполнить скрытое поле «токен»",
"Erroneous challenge": "Неправильный ответ в «challenge»",
"Erroneous token": "Неправильный токен",
"No such user": "Пользователь не найден",
"Token is expired, please try again": "Срок действия токена истёк, попробуйте позже",

View file

@ -126,7 +126,6 @@
"comments_points_count_2": "{{count}} točke",
"comments_points_count_3": "{{count}} točk",
"Hidden field \"token\" is a required field": "Skrito polje »žeton« je zahtevano polje",
"Erroneous challenge": "Napačen izziv",
"English": "angleščina",
"English (United States)": "angleščina (Združene države)",
"Albanian": "albanščina",
@ -329,10 +328,9 @@
"Premieres in `x`": "Premiere v `x`",
"Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.": "Živjo! Izgleda, da imaš izklopljene JavaScripte . Klikni tukaj, če si želiš ogledati komentarje, vendar vedi, da bo lahko nalaganje trajajo nekoliko dlje.",
"Show replies": "Pokaži odgovore",
"Erroneous CAPTCHA": "Napačna CAPTCHA",
"User ID is a required field": "ID uporabnika je obvezno polje",
"Password is a required field": "Geslo je obvezno polje",
"Wrong username or password": "Napačno uporabniško ime ali geslo",
"error_invalid_username_or_password": "Napačno uporabniško ime ali geslo",
"Password cannot be longer than 55 characters": "Geslo ne sme biti daljše od 55 znakov",
"channel:`x`": "kanal: `x`",
"Could not fetch comments": "Ni bilo mogoče pridobiti komentarjev",
@ -350,7 +348,6 @@
"Token is expired, please try again": "Žeton je potekel, poskusi znova",
"English (United Kingdom)": "angleščina (Združeno kraljestvo)",
"Wrong answer": "Napačen odgovor",
"CAPTCHA is a required field": "CAPTCHA je obvezno polje",
"Could not get channel info.": "Ni bilo mogoče dobiti informacij o kanalu.",
"comments_view_x_replies_0": "Poglej {{count}} odgovor",
"comments_view_x_replies_1": "Poglej {{count}} odgovora",

View file

@ -157,11 +157,9 @@
"Blacklisted regions: ": "Rajone të palejuara: ",
"Premieres in `x`": "Premiera në `x`",
"Wrong answer": "Përgjigje e gabuar",
"Erroneous CAPTCHA": "CAPTCHA e gabuar",
"CAPTCHA is a required field": "CAPTCHA është fushë e domosdoshme",
"User ID is a required field": "ID-ja e përdoruesit është fushë e domosdoshme",
"Password is a required field": "Fusha e fjalëkalimit është e domosdoshme",
"Wrong username or password": "Emër përdoruesi ose fjalëkalim i gabuar",
"error_invalid_username_or_password": "Emër përdoruesi ose fjalëkalim i gabuar",
"Please sign in using 'Log in with Google'": "Ju lutemi, bëni hyrjen duke përdorur “Bëni hyrjen me Google”",
"Password cannot be empty": "Fjalëkalimi smund të jetë i zbrazët",
"Password cannot be longer than 55 characters": "Fjalëkalimi smund të jetë më i gjatë se 55 shenja",

View file

@ -56,7 +56,6 @@
"Editing playlist `x`": "Izmena plej liste `x`",
"Please sign in using 'Log in with Google'": "Molimo Vas da se prijavite pomoću 'Log in with Google'",
"Playlist does not exist.": "Nepostojeća plej lista.",
"Erroneous challenge": "Pogrešan izazov",
"Maltese": "Malteški",
"Download": "Preuzmi",
"Download as: ": "Preuzmi kao: ",
@ -77,7 +76,7 @@
"Switch Invidious Instance": "Promeni Invidious instancu",
"Hide annotations": "Sakrij napomene",
"User ID is a required field": "Korisnički ID je obavezno polje",
"Wrong username or password": "Pogrešno korisničko ime ili lozinka",
"error_invalid_username_or_password": "Pogrešno korisničko ime ili lozinka",
"Please log in": "Molimo vas da se prijavite",
"channel:`x`": "kanal:`x`",
"Could not fetch comments": "Uzimanje komentara nije uspelo",
@ -178,7 +177,6 @@
"": "Prikaži `x` komentara"
},
"View Reddit comments": "Prikaži Reddit komentare",
"CAPTCHA is a required field": "CAPTCHA je obavezno polje",
"Croatian": "Hrvatski",
"Estonian": "Estonski",
"Filipino": "Filipino",
@ -278,7 +276,6 @@
"Wrong answer": "Pogrešan odgovor",
"preferences_quality_label": "Preferirani video kvalitet: ",
"Hide replies": "Sakrij odgovore",
"Erroneous CAPTCHA": "Pogrešna CAPTCHA",
"Erroneous token": "Pogrešan žeton",
"Czech": "Češki",
"Latin": "Latinski",

View file

@ -147,7 +147,6 @@
"Burmese": "Бурмански",
"preferences_quality_dash_label": "Преферирани квалитет DASH видео формата: ",
"Erroneous token": "Погрешан жетон",
"CAPTCHA is a required field": "CAPTCHA је обавезно поље",
"No such user": "Непостојећи корисник",
"Chinese (Traditional)": "Кинески (Традиционални)",
"adminprefs_modified_source_code_url_label": "УРЛ веза до складишта са Измењеном Изворном Кодом",
@ -191,7 +190,7 @@
"preferences_category_misc": "Остала подешавања",
"User ID is a required field": "Кориснички ИД је обавезно поље",
"Password is a required field": "Лозинка је обавезно поље",
"Wrong username or password": "Погрешно корисничко име или лозинка",
"error_invalid_username_or_password": "Погрешно корисничко име или лозинка",
"Please sign in using 'Log in with Google'": "Молимо Вас да се пријавите помоћу 'Log in with Google'",
"Password cannot be empty": "Лозинка не може бити празна",
"Password cannot be longer than 55 characters": "Лозинка не може бити дужа од 55 карактера",
@ -267,7 +266,6 @@
"Shared `x`": "Подељено `x`",
"Playlists": "Плеј листе",
"Yoruba": "Јоруба",
"Erroneous challenge": "Погрешан изазов",
"Danish": "Дански",
"Could not get channel info.": "Узимање података о каналу није успело.",
"search_filters_features_option_hd": "HD",
@ -352,7 +350,6 @@
"footer_source_code": "Изворна Кода",
"search_filters_features_option_three_d": "3D",
"search_filters_features_option_four_k": "4K",
"Erroneous CAPTCHA": "Погрешна CAPTCHA",
"`x` ago": "пре `x`",
"Arabic": "Арапски",
"Bulgarian": "Бугарски",

View file

@ -160,11 +160,9 @@
"Show replies": "Visa svar",
"Incorrect password": "Fel lösenord",
"Wrong answer": "Fel svar",
"Erroneous CAPTCHA": "Ogiltig CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA är ett obligatoriskt fält",
"User ID is a required field": "Användar-ID är ett obligatoriskt fält",
"Password is a required field": "Lösenord är ett obligatoriskt fält",
"Wrong username or password": "Ogiltigt användarnamn eller lösenord",
"error_invalid_username_or_password": "Ogiltigt användarnamn eller lösenord",
"Please sign in using 'Log in with Google'": "Logga in genom \"Google-inloggning\"",
"Password cannot be empty": "Lösenordet kan inte vara tomt",
"Password cannot be longer than 55 characters": "Lösenordet kan inte vara längre än 55 tecken",
@ -184,7 +182,6 @@
"Could not pull trending pages.": "Kunde inte hämta trendande sidor.",
"Hidden field \"challenge\" is a required field": "Dolt fält \"challenge\" är ett obligatoriskt fält",
"Hidden field \"token\" is a required field": "Dolt fält \"token\" är ett obligatoriskt fält",
"Erroneous challenge": "Felaktig challenge",
"Erroneous token": "Felaktig token",
"No such user": "Ogiltig användare",
"Token is expired, please try again": "Token föråldrad, försök igen",

View file

@ -162,11 +162,9 @@
"Show replies": "Cevapları göster",
"Incorrect password": "Yanlış parola",
"Wrong answer": "Yanlış cevap",
"Erroneous CAPTCHA": "Hatalı CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA zorunlu bir alandır",
"User ID is a required field": "Kullanıcı kimliği zorunlu bir alandır",
"Password is a required field": "Parola zorunlu bir alandır",
"Wrong username or password": "Yanlış kullanıcı adı ya da parola",
"error_invalid_username_or_password": "Yanlış kullanıcı adı ya da parola",
"Please sign in using 'Log in with Google'": "Lütfen 'Google ile giriş yap' seçeneğini kullanarak oturum açın",
"Password cannot be empty": "Parola boş olamaz",
"Password cannot be longer than 55 characters": "Parola 55 karakterden uzun olamaz",
@ -186,7 +184,6 @@
"Could not pull trending pages.": "Trend sayfaları alınamıyor.",
"Hidden field \"challenge\" is a required field": "Gizli alan \"challenge\" zorunlu bir alandır",
"Hidden field \"token\" is a required field": "\"belirteç\" gizli alanı zorunlu bir alandır",
"Erroneous challenge": "Hatalı challenge",
"Erroneous token": "Hatalı belirteç",
"No such user": "Böyle bir kullanıcı yok",
"Token is expired, please try again": "Belirtecin süresi doldu, lütfen tekrar deneyin",

View file

@ -153,11 +153,9 @@
"Show replies": "Показати відповіді",
"Incorrect password": "Неправильний пароль",
"Wrong answer": "Неправильна відповідь",
"Erroneous CAPTCHA": "Неправильна капча",
"CAPTCHA is a required field": "Необхідно пройти CAPTCHA",
"User ID is a required field": "Необхідно ввести ID користувача",
"Password is a required field": "Необхідно ввести пароль",
"Wrong username or password": "Неправильний логін чи пароль",
"error_invalid_username_or_password": "Неправильний логін чи пароль",
"Please sign in using 'Log in with Google'": "Будь ласка, натисніть «Увійти через Google»",
"Password cannot be empty": "Пароль не може бути порожнім",
"Password cannot be longer than 55 characters": "Пароль не може бути довшим за 55 знаків",
@ -177,7 +175,6 @@
"Could not pull trending pages.": "Не вдається завантажити сторінки «у тренді».",
"Hidden field \"challenge\" is a required field": "Необхідно заповнити приховане поле «challenge»",
"Hidden field \"token\" is a required field": "Необхідно заповнити приховане поле «token»",
"Erroneous challenge": "Неправильна відповідь у «challenge»",
"Erroneous token": "Недійсний токен",
"No such user": "Недопустиме ім’я користувача",
"Token is expired, please try again": "Термін дії токена закінчився, спробуйте пізніше",

View file

@ -150,11 +150,9 @@
"Show replies": "Hiển thị câu trả lời",
"Incorrect password": "Mật khẩu không đúng",
"Wrong answer": "Câu trả lời sai",
"Erroneous CAPTCHA": "CAPTCHA bị lỗi",
"CAPTCHA is a required field": "CAPTCHA là trường bắt buộc",
"User ID is a required field": "User ID là trường bắt buộc",
"Password is a required field": "Mật khẩu là trường bắt buộc",
"Wrong username or password": "Tên người dùng hoặc mật khẩu sai",
"error_invalid_username_or_password": "Tên người dùng hoặc mật khẩu sai",
"Password cannot be empty": "Mật khẩu không được để trống",
"Password cannot be longer than 55 characters": "Mật khẩu không được dài hơn 55 ký tự",
"Please log in": "Xin vui lòng đăng nhập",
@ -171,7 +169,6 @@
"Could not pull trending pages.": "Không thể kéo các trang thịnh hành.",
"Hidden field \"challenge\" is a required field": "Trường ẩn \"challenge\" là trường bắt buộc",
"Hidden field \"token\" is a required field": "Trường ẩn \"token\" là trường bắt buộc",
"Erroneous challenge": "Thử thách sai",
"Erroneous token": "Mã thông báo bị lỗi",
"No such user": "Không có người dùng như vậy",
"Token is expired, please try again": "Token đã hết hạn, vui lòng thử lại",

View file

@ -169,11 +169,9 @@
"Show replies": "显示回复",
"Incorrect password": "密码错误",
"Wrong answer": "错误的回复",
"Erroneous CAPTCHA": "验证码错误",
"CAPTCHA is a required field": "验证码必填",
"User ID is a required field": "用户名必填",
"Password is a required field": "密码必填",
"Wrong username or password": "用户名或密码错误",
"error_invalid_username_or_password": "用户名或密码错误",
"Password cannot be empty": "密码不能为空",
"Password cannot be longer than 55 characters": "密码长度不能大于 55",
"Please log in": "请登录",
@ -194,7 +192,6 @@
"Could not pull trending pages.": "无法获取“时下流行”页面。",
"Hidden field \"challenge\" is a required field": "隐藏表单项 \"challenge\" 为必填",
"Hidden field \"token\" is a required field": "隐藏表单项 \"token\" 为必填",
"Erroneous challenge": "错误的验证回复(challenge)",
"Erroneous token": "错误的令牌",
"No such user": "用户不存在",
"Token is expired, please try again": "令牌过期,请重试",

View file

@ -169,11 +169,9 @@
"Show replies": "顯示回覆",
"Incorrect password": "不正確的密碼",
"Wrong answer": "錯誤的答案",
"Erroneous CAPTCHA": "錯誤的 CAPTCHA",
"CAPTCHA is a required field": "CAPTCHA 為必填欄位",
"User ID is a required field": "使用者 ID 為必填欄位",
"Password is a required field": "密碼為必填欄位",
"Wrong username or password": "錯誤的使用者名稱或密碼",
"error_invalid_username_or_password": "錯誤的使用者名稱或密碼",
"Password cannot be empty": "密碼不能為空",
"Password cannot be longer than 55 characters": "密碼不能長於55個字元",
"Please log in": "請登入",
@ -194,7 +192,6 @@
"Could not pull trending pages.": "無法拉取趨勢頁面。",
"Hidden field \"challenge\" is a required field": "隱藏的欄位 \"challenge\" 是必填欄位",
"Hidden field \"token\" is a required field": "隱藏的欄位 \"token\" 是必填欄位",
"Erroneous challenge": "錯誤的 challenge",
"Erroneous token": "錯誤的 token",
"No such user": "無此使用者",
"Token is expired, please try again": "Token 已過期,請再試一次",

View file

@ -0,0 +1,135 @@
module Invidious::Frontend::LoginRegister
extend self
# HTML form input template
#
# `id` defines the CSS `id` attribute, as well as the localized label text.
# `type` defines the type of input (text, password, etc...)
private macro text_input(id, type)
str << %(\t\t\t<div class="pure-control-group">\n)
str << "\t\t\t\t<label for='{{id}}'>"
str << translate(locale, "login_page_{{id}}_label")
str << "</label><input type='{{type}}' id='{{id}}'>\n"
str << "\t\t\t</div>\n"
end
# Submit button template
#
# `variant` provided defines the CSS class name, as well
# as the associated localized text string.
private macro submit_button(variant)
str << %(\t<div class="pure-controls {{variant}}-submit-button">\n)
str << %(\t\t<button type='submit' class="pure-button pure-button-primary">)
str << translate(locale, "login_page_{{variant}}_button")
str << "</button>\n"
str << "\t</div>\n"
end
# Generate the log-in form's HTML
def gen_login_form(
env : HTTP::Server::Context,
account_type : String = "invidious",
captcha : User::Captcha? = nil
) : String
locale = env.get("preferences").as(Preferences).locale
# Create the parameters for the form URL
params = HTTP::Params.new
params["type"] = account_type
if referer = env.get?("referer").try &.as(String)
params["referer"] = referer
end
url = URI.new(path: "/login", query: params)
return String.build(1200) do |str|
# Begin log-in form
str << %(\t<form class="pure-form" method='post' action=") << url << %(">)
# Form content
case account_type
when "invidious"
# Text inputs
str << %(\t\t<div class="username-pass-fields">)
str << %(<fieldset class="pure-form-aligned">\n)
text_input(username, text)
text_input(password, password)
str << "\t\t</fieldset></div>\n"
# Captcha, if required
if !captcha.nil? && !captcha.type.none?
str << rendered "components/captcha"
end
end
# End of log-in form
str << "\t</form>\n"
submit_button(login)
# Prompt for the register page (we reuse the form's
# uri object for simplicity sake)
url.path = "/register"
str << "\t<p>"
str << translate(locale, "login_page_goto_register_prompt", url.to_s)
str << "</p>\n"
end
end
# Generate the registration form's HTML
def gen_register_form(
env : HTTP::Server::Context,
captcha : User::Captcha?
) : String
locale = env.get("preferences").as(Preferences).locale
# Create the parameters for the form URL
params = HTTP::Params.new
if referer = env.get?("referer").try &.as(String)
params["referer"] = referer
end
url = URI.new(path: "/register", query: params)
return String.build(1200) do |str|
# Begin registration form
str << %(\t<form class="pure-form" method='post' action=") << url << %(">)
# Text inputs
str << %(\t\t<div class="username-pass-fields">)
str << %(<fieldset class="pure-form-aligned">\n)
text_input(username, text)
str << "<br/>"
text_input(password, password)
text_input(confirm, password)
str << "\t\t</fieldset></div>\n"
# Captcha, if required
if !captcha.nil? && !captcha.type.none?
str << rendered "components/captcha"
end
# End of registration form
str << "\t</form>\n"
submit_button(register)
# Prompt for the login page (we reuse the form's
# uri object for simplicity sake)
url.path = "/login"
str << "\t<p>"
str << translate(locale, "login_page_goto_login_prompt", url.to_s)
str << "</p>\n"
str << "</div>"
end
end
end

View file

@ -48,9 +48,9 @@ module Invidious::Routes::Account
return error_template(400, ex)
end
password = env.params.body["password"]?
if !password
return error_template(401, "Password is a required field")
old_password = env.params.body["password"]?
if old_password.nil? || old_password.empty?
return error_template(401, "error_required_field_password")
end
new_passwords = env.params.body.select { |k, v| k.match(/^new_password\[\d+\]$/) }.map { |k, v| v }
@ -68,7 +68,7 @@ module Invidious::Routes::Account
return error_template(400, "Password cannot be longer than 55 characters")
end
if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
if !user.validate_password(old_password)
return error_template(401, "Incorrect password")
end

View file

@ -3,187 +3,160 @@
module Invidious::Routes::Login
def self.login_page(env)
locale = env.get("preferences").as(Preferences).locale
user = env.get? "user"
return env.redirect "/feed/subscriptions" if user
if !CONFIG.login_enabled
return error_template(400, "Login has been disabled by administrator.")
end
referer = get_referer(env, "/feed/subscriptions")
email = nil
password = nil
captcha = nil
return env.redirect referer if env.get? "user"
account_type = env.params.query["type"]?
account_type ||= "invidious"
if !CONFIG.login_enabled
return error_template(403, "error_login_disabled")
end
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
account_type = env.params.query["type"]? || "invidious"
tfa = env.params.query["tfa"]?
prompt = nil
captcha_type = User::Captcha.parse_type(env.params.query)
captcha = User::Captcha.generate(captcha_type)
templated "user/login"
return templated "user/login"
end
def self.login(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
if !CONFIG.login_enabled
return error_template(403, "Login has been disabled by administrator.")
return error_template(403, "error_login_disabled")
end
# https://stackoverflow.com/a/574698
email = env.params.body["email"]?.try &.downcase.byte_slice(0, 254)
password = env.params.body["password"]?
# Verify captcha
if CONFIG.captcha_enabled
begin
captcha_verified = User::Captcha.verify(env)
raise InfoException.new("error_invalid_captcha") if !captcha_verified
rescue ex
return error_template(400, ex)
end
end
account_type = env.params.query["type"]?
account_type ||= "invidious"
account_type = env.params.query["type"]? || "invidious"
case account_type
when "invidious"
if !email
return error_template(401, "User ID is a required field")
# https://stackoverflow.com/a/574698
username = env.params.body["username"]?.try &.downcase.byte_slice(0, 254)
password = env.params.body["password"]?
if username.nil? || username.empty? || password.nil? || password.empty?
return error_template(403, "error_invalid_username_or_password")
end
if !password
return error_template(401, "Password is a required field")
end
user = Invidious::Database::Users.select(email: email)
if user
if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
Invidious::Database::SessionIDs.insert(sid, email)
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
else
return error_template(401, "Wrong username or password")
end
# Since this user has already registered, we don't want to overwrite their preferences
if env.request.cookies["PREFS"]?
cookie = env.request.cookies["PREFS"]
cookie.expires = Time.utc(1990, 1, 1)
env.response.cookies << cookie
end
else
if !CONFIG.registration_enabled
return error_template(400, "Registration has been disabled by administrator.")
end
if password.empty?
return error_template(401, "Password cannot be empty")
end
# See https://security.stackexchange.com/a/39851
if password.bytesize > 55
return error_template(400, "Password cannot be longer than 55 characters")
end
password = password.byte_slice(0, 55)
if CONFIG.captcha_enabled
captcha_type = env.params.body["captcha_type"]?
answer = env.params.body["answer"]?
change_type = env.params.body["change_type"]?
if !captcha_type || change_type
if change_type
captcha_type = change_type
end
captcha_type ||= "image"
account_type = "invidious"
tfa = false
prompt = ""
if captcha_type == "image"
captcha = Invidious::User::Captcha.generate_image(HMAC_KEY)
else
captcha = Invidious::User::Captcha.generate_text(HMAC_KEY)
end
return templated "user/login"
end
tokens = env.params.body.select { |k, _| k.match(/^token\[\d+\]$/) }.map { |_, v| v }
answer ||= ""
captcha_type ||= "image"
case captcha_type
when "image"
answer = answer.lstrip('0')
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
begin
validate_request(tokens[0], answer, env.request, HMAC_KEY, locale)
rescue ex
return error_template(400, ex)
end
else # "text"
answer = Digest::MD5.hexdigest(answer.downcase.strip)
if tokens.empty?
return error_template(500, "Erroneous CAPTCHA")
end
found_valid_captcha = false
error_exception = Exception.new
tokens.each do |tok|
begin
validate_request(tok, answer, env.request, HMAC_KEY, locale)
found_valid_captcha = true
rescue ex
error_exception = ex
end
end
if !found_valid_captcha
return error_template(500, error_exception)
end
end
end
user = Database::Users.select(email: username)
if !user.nil? && user.validate_password(password)
# Generate session ID and store it.
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user, sid = create_user(sid, email, password)
if language_header = env.request.headers["Accept-Language"]?
if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
user.preferences.locale = language.header
end
begin
Database::SessionIDs.insert(sid, username)
rescue ex
return error_template(500, "error_database_unavailable")
end
Invidious::Database::Users.insert(user)
Invidious::Database::SessionIDs.insert(sid, email)
view_name = "subscriptions_#{sha256(user.email)}"
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
if env.request.cookies["PREFS"]?
user.preferences = env.get("preferences").as(Preferences)
Invidious::Database::Users.update_preferences(user)
cookie = env.request.cookies["PREFS"]
cookie.expires = Time.utc(1990, 1, 1)
env.response.cookies << cookie
end
# Generate cookies
env.response.cookies["SID"] = User::Cookies.sid(CONFIG.domain, sid)
env.response.cookies["PREFS"] = User::Cookies.prefs(CONFIG.domain, user.preferences)
else
return error_template(403, "error_invalid_username_or_password")
end
env.redirect referer
else
env.redirect referer
end
return env.redirect referer
end
def self.register_page(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
return env.redirect referer if env.get? "user"
if !CONFIG.registration_enabled
return error_template(403, "error_registration_disabled")
end
captcha_type = User::Captcha.parse_type(env.params.query)
captcha = User::Captcha.generate(captcha_type)
return templated "user/register"
end
def self.register(env)
locale = env.get("preferences").as(Preferences).locale
referer = get_referer(env, "/feed/subscriptions")
if !CONFIG.registration_enabled
return error_template(403, "error_registration_disabled")
end
# https://stackoverflow.com/a/574698
username = env.params.body["username"]?.try &.downcase.byte_slice(0, 254)
password = env.params.body["password"]?
confirm = env.params.body["confirm"]?
if username.nil? || username.empty?
return error_template(400, "error_required_field_username")
end
if password.nil? || password.empty? || confirm.nil? || confirm.empty?
return error_template(400, "error_required_field_password")
end
if password != confirm
return error_template(400, "error_passwords_dont_match")
end
# TODO: find a way to allow longer passwords
# See https://security.stackexchange.com/a/39851
if password.bytesize > 55
return error_template(400, "Password cannot be longer than 55 characters")
end
# Verify captcha
if CONFIG.captcha_enabled
begin
captcha_verified = User::Captcha.verify(env)
raise InfoException.new("error_invalid_captcha") if !captcha_verified
rescue ex
return error_template(400, ex)
end
end
# Make sure that user doesn't exist!!
user_check = Database::Users.select(email: username)
if !user_check.nil?
return error_template(400, "error_username_already_registered")
end
# Generate session ID
sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = User.create(sid, username, password)
# Use the preferences from user cookie (pre-registration)
# and save them into the account. Otherwise, make a new one.
if env.request.cookies["PREFS"]?
user.preferences = env.get("preferences").as(Preferences)
end
# Create the proper DN
# TODO: use DB transaction here to avoid corrupted states
Database::Users.insert(user)
Database::SessionIDs.insert(sid, username)
view_name = "subscriptions_#{sha256(user.email)}"
PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
# Generate cookies
env.response.cookies["SID"] = User::Cookies.sid(CONFIG.domain, sid)
env.response.cookies["PREFS"] = User::Cookies.prefs(CONFIG.domain, user.preferences)
return env.redirect referer
end
def self.signout(env)
@ -214,6 +187,6 @@ module Invidious::Routes::Login
env.response.cookies << cookie
end
env.redirect referer
return env.redirect referer
end
end

View file

@ -51,9 +51,11 @@ module Invidious::Routing
# -------------------
def register_user_routes
# User login/out
# User login/out and registration
get "/login", Routes::Login, :login_page
post "/login", Routes::Login, :login
get "/register", Routes::Login, :register_page
post "/register", Routes::Login, :register
post "/signout", Routes::Login, :signout
# User preferences

View file

@ -1,12 +1,57 @@
require "openssl/hmac"
struct Invidious::User
module Captcha
extend self
struct Captcha
private TEXTCAPTCHA_URL = URI.parse("https://textcaptcha.com")
def generate_image(key)
# Structure that holds the type, the question string and the
# cryptographically signed response(s).
getter type : Type
getter question : String
getter tokens : Array(String)
def initialize(@type, @question, @tokens)
end
# -------------------
# Type parsing
# -------------------
enum Type
None
Text
Image
end
def self.parse_type(params : HTTP::Params) : Type
if CONFIG.captcha_enabled
type_text = params["captcha"]? || "image"
type = Type.parse?(type_text) || Type::Image
# You opened the dev tools, didn't you? :P
type = Type::Image if type.none?
else
type = Type::None
end
return type
end
# -------------------
# Generators
# -------------------
# High-level method that calls the captcha generator for the given type.
def self.generate(type : Type) : Captcha?
case type
when .image? then return gen_image_captcha(HMAC_KEY)
when .text? then return gen_text_captcha(HMAC_KEY)
else
return nil
end
end
private def self.gen_image_captcha(key) : Captcha
second = Random::Secure.rand(12)
second_angle = second * 30
second = second * 5
@ -17,9 +62,6 @@ struct Invidious::User
hour = Random::Secure.rand(12)
hour_angle = hour * 30 + minute_angle.to_f / 12
if hour == 0
hour = 12
end
clock_svg = <<-END_SVG
<svg viewBox="0 0 100 100" width="200px" height="200px">
@ -52,16 +94,43 @@ struct Invidious::User
Base64.strict_encode(proc.output.gets_to_end)
end
answer = "#{hour}:#{minute.to_s.rjust(2, '0')}:#{second.to_s.rjust(2, '0')}"
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer)
answer_raw = self.format_time(hour, minute, second, validate: false)
answer = OpenSSL::HMAC.hexdigest(:sha256, key, answer_raw)
return {
LOGGER.trace("Captcha: image question is #{answer_raw} (anwser digest: #{answer})")
return Captcha.new(
type: Type::Image,
question: image,
tokens: {generate_response(answer, {":login"}, key, use_nonce: true)},
}
tokens: [generate_response(answer, {":login"}, key, use_nonce: true)],
)
end
def generate_text(key)
private def self.format_time(hours : Int, minutes : Int, seconds : Int, *, validate : Bool)
# Check for incorrect answers
if validate
raise Exception.new if !(0..23).includes?(hours)
raise Exception.new if !(0..59).includes?(minutes)
raise Exception.new if !(0..59).includes?(seconds)
end
# Normalize hours
case hours
when .zero? then hours = 12
when .> 12 then hours -= 12
end
# Craft answer string
return String.build(8) do |answer|
answer << hours.to_s(precision: 2)
answer << ':'
answer << minutes.to_s(precision: 2)
answer << ':'
answer << seconds.to_s(precision: 2)
end
end
private def self.gen_text_captcha(key) : Captcha
response = make_client(TEXTCAPTCHA_URL, &.get("/github.com/iv.org/invidious.json").body)
response = JSON.parse(response)
@ -69,10 +138,70 @@ struct Invidious::User
generate_response(answer.as_s, {":login"}, key, use_nonce: true)
end
return {
question: response["q"].as_s,
tokens: tokens,
}
question = response["q"].as_s
LOGGER.trace("Captcha: text question is #{question}: (answers digests: #{tokens})")
return Captcha.new(
type: Type::Text,
question: question,
tokens: tokens,
)
end
# -------------------
# Validation
# -------------------
# Return true if the captcha was succesfully validated
# Otherwise, raise the appropriate Exception
def self.verify(env) : Bool
captcha_type = self.parse_type(env.params.body)
answer = env.params.body["answer"]? || ""
tokens = env.params.body.fetch_all("token")
if answer.empty? || tokens.empty?
LOGGER.debug("Captcha: validate: got error_invalid_captcha, answer or token is empty")
raise InfoException.new("error_invalid_captcha")
end
case captcha_type
when .image?
begin
hours, minutes, seconds = answer.split(':').map &.to_i
answer = self.format_time(hours, minutes, seconds, validate: true)
rescue ex
LOGGER.debug("Captcha: validate: got error_invalid_captcha, answer to image captcha failed to parse")
raise InfoException.new("error_invalid_captcha")
end
answer = OpenSSL::HMAC.hexdigest(:sha256, HMAC_KEY, answer)
# Raises on error
validate_request(tokens[0], answer, env.request, HMAC_KEY)
return true
when .text?
answer = Digest::MD5.hexdigest(answer.downcase.strip)
error_exception = InfoException.new
tokens.each do |tok|
begin
# Raises on error
validate_request(tok, answer, env.request, HMAC_KEY)
return true
rescue ex
error_exception = ex
end
end
LOGGER.debug("Captcha: validate: bad answer to text captcha")
raise error_exception
end
# Just to be safe
return false
end
end
end

View file

@ -4,16 +4,36 @@ struct Invidious::User
include DB::Serializable
property updated : Time
property notifications : Array(String)
property subscriptions : Array(String)
property notifications : Array(String) = [] of String
property subscriptions : Array(String) = [] of String
property email : String
@[DB::Field(converter: Invidious::User::PreferencesConverter)]
property preferences : Preferences
property password : String?
property token : String
property watched : Array(String)
property feed_needs_update : Bool?
property watched : Array(String) = [] of String
property feed_needs_update : Bool? = true
def initialize(*, @email, @token, @password)
@updated = Time.utc
@preferences = Preferences.new(CONFIG.default_user_preferences.to_tuple)
end
def self.create(sid, email, password) : User
hashed_pwd = Crypto::Bcrypt::Password.create(password, cost: 10)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
return User.new(email: email, token: token, password: hashed_pwd.to_s)
end
def validate_password(password : String) : Bool
# Damned Google accounts were stored with a nil password
stored_password = @password
return false if stored_password.nil?
return Crypto::Bcrypt::Password.new(stored_password).verify(password)
end
module PreferencesConverter
def self.from_rs(rs)

View file

@ -3,25 +3,6 @@ require "crypto/bcrypt/password"
# Materialized views may not be defined using bound parameters (`$1` as used elsewhere)
MATERIALIZED_VIEW_SQL = ->(email : String) { "SELECT cv.* FROM channel_videos cv WHERE EXISTS (SELECT subscriptions FROM users u WHERE cv.ucid = ANY (u.subscriptions) AND u.email = E'#{email.gsub({'\'' => "\\'", '\\' => "\\\\"})}') ORDER BY published DESC" }
def create_user(sid, email, password)
password = Crypto::Bcrypt::Password.create(password, cost: 10)
token = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
user = Invidious::User.new({
updated: Time.utc,
notifications: [] of String,
subscriptions: [] of String,
email: email,
preferences: Preferences.new(CONFIG.default_user_preferences.to_tuple),
password: password.to_s,
token: token,
watched: [] of String,
feed_needs_update: true,
})
return user, sid
end
def get_subscription_feed(user, max_results = 40, page = 1)
limit = max_results.clamp(0, MAX_ITEMS_PER_PAGE)
offset = (page - 1) * limit

View file

@ -0,0 +1,39 @@
<div class="captcha flexible">
<input type='hidden' name='captcha_type' value="<%= captcha.type %>">
<%- captcha.tokens.each do |tok| -%>
<input type='hidden' name='tokens' value="<%= HTML.escape(tok) %>">
<%- end -%>
<%- if captcha.type.image? %>
<div class="left">
<img src="<%= captcha.question %>"/>
</div>
<div class="right">
<fieldset class="pure-form-stacked">
<div class="pure-control-group">
<label for='answer'><%= translate(locale, "Time (h:mm:ss):") %></label><input
type='text' name='answer' type='text' placeholder="hh:mm:ss">
</div>
</fieldset>
<a src="<%= url %>&captcha_type=text"><%= translate(locale, "login_page_request_text_captcha") %></a>
</div>
<%- else # type.text? %>
<fieldset class="pure-form-stacked">
<div class="pure-control-group">
<label for='answer' lang="en"><%= captcha.question %></label><input
type='text' name='answer' type='text' placeholder="<%= translate(locale, "Answer") %>">
</div>
</fieldset>
<a src=") << url << %(&captcha_type=image"><%= translate(locale, "login_page_request_image_captcha") %></a>
<%- end %>
</div>

View file

@ -1,77 +1,10 @@
<% content_for "header" do %>
<title><%= translate(locale, "Log in") %> - Invidious</title>
<title><%= translate(locale, "login_page_title_login") %> - Invidious</title>
<link rel="stylesheet" href="/css/user.css?v=<%= ASSET_COMMIT %>">
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<%- case account_type when -%>
<%- when "invidious" -%>
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.encode_www_form(referer) %>&type=invidious" method="post">
<fieldset>
<% if email %>
<input name="email" type="hidden" value="<%= HTML.escape(email) %>">
<% else %>
<label for="email"><%= translate(locale, "User ID") %> :</label>
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
<% end %>
<% if password %>
<input name="password" type="hidden" value="<%= HTML.escape(password) %>">
<% else %>
<label for="password"><%= translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
<% end %>
<% if captcha %>
<% case captcha_type when %>
<% when "image" %>
<% captcha = captcha.not_nil! %>
<img style="width:50%" src='<%= captcha[:question] %>'/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="image">
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
<% else # "text" %>
<% captcha = captcha.not_nil! %>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= HTML.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="text">
<label for="answer"><%= captcha[:question] %></label>
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
<% end %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Register") %>
</button>
<% case captcha_type when %>
<% when "image" %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
<%= translate(locale, "Text CAPTCHA") %>
</button>
</label>
<% else # "text" %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
<%= translate(locale, "Image CAPTCHA") %>
</button>
</label>
<% end %>
<% else %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
</button>
<% end %>
</fieldset>
</form>
<%- end -%>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="h-box">
<div class="login-container">
<%= Invidious::Frontend::LoginRegister.gen_login_form(env, account_type, captcha) %>
</div>
</div>

View file

@ -0,0 +1,10 @@
<% content_for "header" do %>
<title><%= translate(locale, "login_page_title_register") %> - Invidious</title>
<link rel="stylesheet" href="/css/user.css?v=<%= ASSET_COMMIT %>">
<% end %>
<div class="h-box">
<div class="register-container">
<%= Invidious::Frontend::LoginRegister.gen_register_form(env, captcha) %>
</div>
</div>