diff --git a/assets/css/user.css b/assets/css/user.css index 4ed00a59..7f2cb2b2 100644 --- a/assets/css/user.css +++ b/assets/css/user.css @@ -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 */ diff --git a/locales/ar.json b/locales/ar.json index 24d6cb55..4d15e042 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -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": "الرمز منتهى الصلاحية، الرجاء المحاولة مرة اخرى", diff --git a/locales/cs.json b/locales/cs.json index 843e6739..e22d04fd 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -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", diff --git a/locales/da.json b/locales/da.json index b7915460..2dd02298 100644 --- a/locales/da.json +++ b/locales/da.json @@ -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", diff --git a/locales/de.json b/locales/de.json index 4158e83c..eef8467c 100644 --- a/locales/de.json +++ b/locales/de.json @@ -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", diff --git a/locales/el.json b/locales/el.json index a386f17d..3f450570 100644 --- a/locales/el.json +++ b/locales/el.json @@ -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": "Το αναγνωριστικό διασύνδεσης έχει λήξει, παρακαλώ ξαναπροσπαθήστε", diff --git a/locales/en-US.json b/locales/en-US.json index aa6a71fc..834c26f1 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -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 Frequently Asked Questions (FAQ)", "crash_page_search_issue": "searched for existing issues on GitHub", "crash_page_report_issue": "If none of the above helped, please open a new issue on GitHub (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. Click here for the playlist home page." + "error_video_not_in_playlist": "The requested video doesn't exist in this playlist. Click here for the playlist home page.", + "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.
\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? Register here!", + "login_page_goto_login_prompt": "Already have an account? Log in", + "Time (h:mm:ss):": "Time (h:mm:ss):", + "login_page_request_text_captcha": "Text CAPTCHA", + "login_page_request_image_captcha": "Image CAPTCHA" } diff --git a/locales/eo.json b/locales/eo.json index a7544214..4827b54d 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -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", diff --git a/locales/es.json b/locales/es.json index 317fc853..1ad718ef 100644 --- a/locales/es.json +++ b/locales/es.json @@ -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", diff --git a/locales/et.json b/locales/et.json index 1a3e3f27..a74a9145 100644 --- a/locales/et.json +++ b/locales/et.json @@ -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", diff --git a/locales/eu.json b/locales/eu.json index 928a7757..eede0af6 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -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.", diff --git a/locales/fa.json b/locales/fa.json index 086e5fe0..de826b6f 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -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": "توکن ضروری است، لطفا دوباره تلاش کنید", diff --git a/locales/fi.json b/locales/fi.json index 20448d2f..f9c68694 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -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", diff --git a/locales/fr.json b/locales/fr.json index 23d1806b..163b234d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -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. Cliquez ici pour retourner à la liste de lecture." + "error_video_not_in_playlist": "La vidéo demandée n'existe pas dans cette liste de lecture. Cliquez ici pour retourner à la liste de lecture.", + "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.
\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? S'enregistrer", + "login_page_goto_login_prompt": "Déjà un compte? Se connecter", + "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" } diff --git a/locales/he.json b/locales/he.json index c19ba31d..f4a5093e 100644 --- a/locales/he.json +++ b/locales/he.json @@ -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": "נא להתחבר", diff --git a/locales/hi.json b/locales/hi.json index 82cbd703..f7f656bf 100644 --- a/locales/hi.json +++ b/locales/hi.json @@ -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": "टोकन की समय-सीमा समाप्त हो चुकी है, कृपया दोबारा कोशिश करें", diff --git a/locales/hr.json b/locales/hr.json index 501e709d..d6e833aa 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -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", diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 7cc79ebb..b9a5b4ec 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -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.", diff --git a/locales/id.json b/locales/id.json index 5c9e5511..33ed00b5 100644 --- a/locales/id.json +++ b/locales/id.json @@ -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", diff --git a/locales/is.json b/locales/is.json index e578332d..b24d6626 100644 --- a/locales/is.json +++ b/locales/is.json @@ -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", diff --git a/locales/it.json b/locales/it.json index 80b7f72f..e369260d 100644 --- a/locales/it.json +++ b/locales/it.json @@ -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", diff --git a/locales/ja.json b/locales/ja.json index 8df1c096..53f57dad 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -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": "トークンが期限切れです。再度試してください", diff --git a/locales/ko.json b/locales/ko.json index 32e073b5..544f9397 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -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": "재생목록으로 보기", diff --git a/locales/lt.json b/locales/lt.json index 812072b2..50e99951 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -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ą", diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 1bcf6cc0..678823da 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -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", diff --git a/locales/nl.json b/locales/nl.json index 5d2de3a8..8b6354aa 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -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", diff --git a/locales/pl.json b/locales/pl.json index f6720ca2..a35425bd 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -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", diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 1719050d..d5422fda 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -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", diff --git a/locales/pt-PT.json b/locales/pt-PT.json index 27f2cdf1..75cca317 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -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", diff --git a/locales/pt.json b/locales/pt.json index 25611860..e90ca528 100644 --- a/locales/pt.json +++ b/locales/pt.json @@ -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", diff --git a/locales/ro.json b/locales/ro.json index 958efd4b..050ec11c 100644 --- a/locales/ro.json +++ b/locales/ro.json @@ -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", diff --git a/locales/ru.json b/locales/ru.json index 04b3430c..a493ad2b 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -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": "Срок действия токена истёк, попробуйте позже", diff --git a/locales/sl.json b/locales/sl.json index a9838fd3..d80d9afb 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -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", diff --git a/locales/sq.json b/locales/sq.json index cc890abd..0041cb54 100644 --- a/locales/sq.json +++ b/locales/sq.json @@ -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 s’mund të jetë i zbrazët", "Password cannot be longer than 55 characters": "Fjalëkalimi s’mund të jetë më i gjatë se 55 shenja", diff --git a/locales/sr.json b/locales/sr.json index 89adf231..230c9d14 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -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", diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index ebe61bb3..92af5fda 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -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": "Бугарски", diff --git a/locales/sv-SE.json b/locales/sv-SE.json index af8b868a..1619363b 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -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", diff --git a/locales/tr.json b/locales/tr.json index e18dfbd1..15f7a0d3 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -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", diff --git a/locales/uk.json b/locales/uk.json index ef2591f7..9e7cc88e 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -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": "Термін дії токена закінчився, спробуйте пізніше", diff --git a/locales/vi.json b/locales/vi.json index d781a6e5..2a6110d3 100644 --- a/locales/vi.json +++ b/locales/vi.json @@ -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", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index b8fd84ad..abde4841 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -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": "令牌过期,请重试", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index c847fcda..27c8238c 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -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 已過期,請再試一次", diff --git a/src/invidious/frontend/login_register.cr b/src/invidious/frontend/login_register.cr new file mode 100644 index 00000000..cebf07b1 --- /dev/null +++ b/src/invidious/frontend/login_register.cr @@ -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
\n) + str << "\t\t\t\t\n" + str << "\t\t\t
\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
\n) + str << %(\t\t\n" + str << "\t
\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 content + case account_type + when "invidious" + # Text inputs + str << %(\t\t
) + str << %(
\n) + + text_input(username, text) + text_input(password, password) + + str << "\t\t
\n" + + # Captcha, if required + if !captcha.nil? && !captcha.type.none? + str << rendered "components/captcha" + end + end + + # End of log-in form + str << "\t
\n" + + submit_button(login) + + # Prompt for the register page (we reuse the form's + # uri object for simplicity sake) + url.path = "/register" + + str << "\t

" + str << translate(locale, "login_page_goto_register_prompt", url.to_s) + str << "

\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
) + + # Text inputs + str << %(\t\t
) + str << %(
\n) + + text_input(username, text) + str << "
" + + text_input(password, password) + text_input(confirm, password) + + str << "\t\t
\n" + + # Captcha, if required + if !captcha.nil? && !captcha.type.none? + str << rendered "components/captcha" + end + + # End of registration form + str << "\t
\n" + + submit_button(register) + + # Prompt for the login page (we reuse the form's + # uri object for simplicity sake) + url.path = "/login" + + str << "\t

" + str << translate(locale, "login_page_goto_login_prompt", url.to_s) + str << "

\n" + + str << "" + end + end +end diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr index d3030fc0..378b34fd 100644 --- a/src/invidious/routes/account.cr +++ b/src/invidious/routes/account.cr @@ -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 diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index c9351630..fb658962 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -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 diff --git a/src/invidious/routing.cr b/src/invidious/routing.cr index b854d537..dfd5d9e6 100644 --- a/src/invidious/routing.cr +++ b/src/invidious/routing.cr @@ -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 diff --git a/src/invidious/user/captcha.cr b/src/invidious/user/captcha.cr index 8a0f67e5..3dee409b 100644 --- a/src/invidious/user/captcha.cr +++ b/src/invidious/user/captcha.cr @@ -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 @@ -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 diff --git a/src/invidious/user/user.cr b/src/invidious/user/user.cr index a6d05fd1..1c5efc99 100644 --- a/src/invidious/user/user.cr +++ b/src/invidious/user/user.cr @@ -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) diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 65566d20..84478683 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -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 diff --git a/src/invidious/views/components/captcha.ecr b/src/invidious/views/components/captcha.ecr new file mode 100644 index 00000000..cd60cc25 --- /dev/null +++ b/src/invidious/views/components/captcha.ecr @@ -0,0 +1,39 @@ +
+ + + +<%- captcha.tokens.each do |tok| -%> + +<%- end -%> + +<%- if captcha.type.image? %> + +
+ +
+ +
+
+
+ +
+
+ + <%= translate(locale, "login_page_request_text_captcha") %> +
+ +<%- else # type.text? %> + +
+
+ "> +
+
+ + <%= translate(locale, "login_page_request_image_captcha") %> + +<%- end %> + +
diff --git a/src/invidious/views/user/login.ecr b/src/invidious/views/user/login.ecr index 9cfa3ae1..5d6153ae 100644 --- a/src/invidious/views/user/login.ecr +++ b/src/invidious/views/user/login.ecr @@ -1,77 +1,10 @@ <% content_for "header" do %> -<%= translate(locale, "Log in") %> - Invidious +<%= translate(locale, "login_page_title_login") %> - Invidious + <% end %> -
-
-
-
- <%- case account_type when -%> - <%- when "invidious" -%> -
-
- <% if email %> - - <% else %> - - "> - <% end %> - - <% if password %> - - <% else %> - - "> - <% end %> - - <% if captcha %> - <% case captcha_type when %> - <% when "image" %> - <% captcha = captcha.not_nil! %> - - <% captcha[:tokens].each_with_index do |token, i| %> - - <% end %> - - - - <% else # "text" %> - <% captcha = captcha.not_nil! %> - <% captcha[:tokens].each_with_index do |token, i| %> - - <% end %> - - - "> - <% end %> - - - - <% case captcha_type when %> - <% when "image" %> - - <% else # "text" %> - - <% end %> - <% else %> - - <% end %> -
-
- <%- end -%> -
-
-
+
+
diff --git a/src/invidious/views/user/register.ecr b/src/invidious/views/user/register.ecr new file mode 100644 index 00000000..8a3b3941 --- /dev/null +++ b/src/invidious/views/user/register.ecr @@ -0,0 +1,10 @@ +<% content_for "header" do %> +<%= translate(locale, "login_page_title_register") %> - Invidious + +<% end %> + +
+
+ <%= Invidious::Frontend::LoginRegister.gen_register_form(env, captcha) %> +
+