diff --git a/locales/ar.json b/locales/ar.json index 5a981578..e371daac 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -423,5 +423,15 @@ "Current version: ": "الإصدار الحالي: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/bn_BD.json b/locales/bn_BD.json index 5f91c67e..bc92b927 100644 --- a/locales/bn_BD.json +++ b/locales/bn_BD.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/cs.json b/locales/cs.json index abb2d503..f759f167 100644 --- a/locales/cs.json +++ b/locales/cs.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/da.json b/locales/da.json index fca62ecf..e20b3ac5 100644 --- a/locales/da.json +++ b/locales/da.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/de.json b/locales/de.json index 9406f2eb..0758e3f8 100644 --- a/locales/de.json +++ b/locales/de.json @@ -423,5 +423,15 @@ "Current version: ": "Aktuelle Version: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/el.json b/locales/el.json index 830fb0fe..b951e3f4 100644 --- a/locales/el.json +++ b/locales/el.json @@ -423,5 +423,15 @@ "Current version: ": "Τρέχουσα έκδοση: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/en-US.json b/locales/en-US.json index 88daa4eb..eb838fc1 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -423,5 +423,15 @@ "Current version: ": "Current version: ", "next_steps_error_message": "After which you should try to: ", "next_steps_error_message_refresh": "Refresh", - "next_steps_error_message_go_to_youtube": "Go to Youtube" + "next_steps_error_message_go_to_youtube": "Go to Youtube", + "setup-totp-form-header": "Setup two factor authenticiation (TOTP)", + "setup-totp-instructions-download-auth": "Install an authenticator app (or anything that supports totp) on your device", + "setup-totp-instructions-enter-code": "Enter the following secret code:", + "setup-totp-instructions-validate-code": "Enter the 6 digit number on your screen. Be sure to do it under thirty seconds!", + "setup-totp-submit-button": "Setup TOTP", + "general-totp-empty-field": "The TOTP code is a required field", + "general-totp-invalid-code": "The TOTP code entered is invalid", + "general-totp-enter-code-field": "6 digit number", + "general-totp-enter-code-header": "Two-factor authentication", + "general-totp-verify-button": "Verifiy" } diff --git a/locales/eo.json b/locales/eo.json index e3970159..ed7e2284 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -423,5 +423,15 @@ "Current version: ": "Nuna versio: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/es.json b/locales/es.json index ee93298c..37c9cc6b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -423,5 +423,15 @@ "Current version: ": "Versión actual: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/eu.json b/locales/eu.json index 87d0b902..f29e8892 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/fa.json b/locales/fa.json index d449948a..15cff988 100644 --- a/locales/fa.json +++ b/locales/fa.json @@ -423,5 +423,15 @@ "Current version: ": "نسخه فعلی: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/fi.json b/locales/fi.json index 60c2aed6..86d7093e 100644 --- a/locales/fi.json +++ b/locales/fi.json @@ -423,5 +423,15 @@ "Current version: ": "Tämänhetkinen versio: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/fr.json b/locales/fr.json index 9c6bd8eb..1568748f 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -423,5 +423,15 @@ "Current version: ": "Version actuelle : ", "next_steps_error_message": "Vous pouvez essayer de : ", "next_steps_error_message_refresh": "Rafraîchir la page", - "next_steps_error_message_go_to_youtube": "Aller sur Youtube" + "next_steps_error_message_go_to_youtube": "Aller sur Youtube", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/he.json b/locales/he.json index 5d7f85c6..990ab51f 100644 --- a/locales/he.json +++ b/locales/he.json @@ -423,5 +423,15 @@ "Current version: ": "הגרסה הנוכחית: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/hr.json b/locales/hr.json index fd978edb..cc3c4dd2 100644 --- a/locales/hr.json +++ b/locales/hr.json @@ -423,5 +423,15 @@ "Current version: ": "Trenutačna verzija: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/hu-HU.json b/locales/hu-HU.json index 1c00bdbe..ae9fcad8 100644 --- a/locales/hu-HU.json +++ b/locales/hu-HU.json @@ -423,5 +423,15 @@ "Current version: ": "Jelenlegi verzió: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/id.json b/locales/id.json index d3558396..59729272 100644 --- a/locales/id.json +++ b/locales/id.json @@ -423,5 +423,15 @@ "Current version: ": "Versi saat ini: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/is.json b/locales/is.json index 73665c49..d059bb37 100644 --- a/locales/is.json +++ b/locales/is.json @@ -423,5 +423,15 @@ "Current version: ": "Núverandi útgáfa: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/it.json b/locales/it.json index 676bd650..f871b27b 100644 --- a/locales/it.json +++ b/locales/it.json @@ -423,5 +423,15 @@ "Current version: ": "Versione attuale: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/ja.json b/locales/ja.json index 4f3f155a..b20175e8 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -423,5 +423,15 @@ "Current version: ": "現在のバージョン: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/lt.json b/locales/lt.json index 68d7437a..657db3ec 100644 --- a/locales/lt.json +++ b/locales/lt.json @@ -423,5 +423,15 @@ "Current version: ": "Dabartinė versija: ", "next_steps_error_message": "Po to turėtumėte pabandyti: ", "next_steps_error_message_refresh": "Atnaujinti", - "next_steps_error_message_go_to_youtube": "Eiti į Youtube" + "next_steps_error_message_go_to_youtube": "Eiti į Youtube", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/nb-NO.json b/locales/nb-NO.json index 10723bfa..c839241a 100644 --- a/locales/nb-NO.json +++ b/locales/nb-NO.json @@ -423,5 +423,15 @@ "Current version: ": "Gjeldende versjon: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/nl.json b/locales/nl.json index c4948fd1..e32e964a 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -423,5 +423,15 @@ "Current version: ": "Huidige versie: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/pl.json b/locales/pl.json index 10cb0cc2..61a0f3bf 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -423,5 +423,15 @@ "Current version: ": "Aktualna wersja: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/pt-BR.json b/locales/pt-BR.json index 478847f2..27c37aca 100644 --- a/locales/pt-BR.json +++ b/locales/pt-BR.json @@ -423,5 +423,15 @@ "Current version: ": "Versão atual: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/pt-PT.json b/locales/pt-PT.json index 1384648c..fa047259 100644 --- a/locales/pt-PT.json +++ b/locales/pt-PT.json @@ -423,5 +423,15 @@ "Current version: ": "Versão atual: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/ro.json b/locales/ro.json index ce961c39..5b44dbbf 100644 --- a/locales/ro.json +++ b/locales/ro.json @@ -423,5 +423,15 @@ "Current version: ": "Versiunea actuală: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/ru.json b/locales/ru.json index bd2bf360..f5d8ba6f 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -423,5 +423,15 @@ "Current version: ": "Текущая версия: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/si.json b/locales/si.json index f59629d0..ba6f84f6 100644 --- a/locales/si.json +++ b/locales/si.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/sk.json b/locales/sk.json index 32df0569..3f5a9b2d 100644 --- a/locales/sk.json +++ b/locales/sk.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/sr.json b/locales/sr.json index 83cc12c1..a21142d5 100644 --- a/locales/sr.json +++ b/locales/sr.json @@ -423,5 +423,15 @@ "Current version: ": "", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/sr_Cyrl.json b/locales/sr_Cyrl.json index 92cfd103..f76b1219 100644 --- a/locales/sr_Cyrl.json +++ b/locales/sr_Cyrl.json @@ -423,5 +423,15 @@ "Current version: ": "Тренутна верзија: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/sv-SE.json b/locales/sv-SE.json index 7aaaab7b..fb138b8e 100644 --- a/locales/sv-SE.json +++ b/locales/sv-SE.json @@ -423,5 +423,15 @@ "Current version: ": "Nuvarande version: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/tr.json b/locales/tr.json index 5246ab6f..f4c01d68 100644 --- a/locales/tr.json +++ b/locales/tr.json @@ -423,5 +423,15 @@ "Current version: ": "Şu anki sürüm: ", "next_steps_error_message": "Bundan sonra şunları denemelisiniz: ", "next_steps_error_message_refresh": "Yenile", - "next_steps_error_message_go_to_youtube": "Youtube'a git" + "next_steps_error_message_go_to_youtube": "Youtube'a git", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/uk.json b/locales/uk.json index e51aa5ba..a3cbc55a 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -423,5 +423,15 @@ "Current version: ": "Поточна версія: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/vi.json b/locales/vi.json index a3614ce9..1783acb2 100644 --- a/locales/vi.json +++ b/locales/vi.json @@ -423,5 +423,15 @@ "Current version: ": "Phiên bản hiện tại: ", "next_steps_error_message": "", "next_steps_error_message_refresh": "", - "next_steps_error_message_go_to_youtube": "" + "next_steps_error_message_go_to_youtube": "", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 93631b3f..68ffa289 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -423,5 +423,15 @@ "Current version: ": "当前版本: ", "next_steps_error_message": "在此之后你应尝试: ", "next_steps_error_message_refresh": "刷新", - "next_steps_error_message_go_to_youtube": "转到 Youtube" + "next_steps_error_message_go_to_youtube": "转到 Youtube", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index e554b23a..c647cf24 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -423,5 +423,15 @@ "Current version: ": "目前版本: ", "next_steps_error_message": "之後您應該嘗試: ", "next_steps_error_message_refresh": "重新整理", - "next_steps_error_message_go_to_youtube": "到 YouTube" + "next_steps_error_message_go_to_youtube": "到 YouTube", + "setup-totp-form-header": "", + "setup-totp-instructions-download-auth": "", + "setup-totp-instructions-enter-code": "", + "setup-totp-instructions-validate-code": "", + "setup-totp-submit-button": "", + "general-totp-empty-field": "", + "general-totp-invalid-code": "", + "general-totp-enter-code-field": "", + "general-totp-enter-code-header": "", + "general-totp-verify-button": "" } diff --git a/shard.lock b/shard.lock index 35d1aefd..5b18ce40 100644 --- a/shard.lock +++ b/shard.lock @@ -1,5 +1,13 @@ version: 2.0 shards: + base32: + git: https://github.com/philnash/base32.git + version: 0.1.1+git.commit.0a21c1d90731fdefcb3f0db4913f49d3d25350ac + + crotp: + git: https://github.com/philnash/crotp.git + version: 1.0.0 + db: git: https://github.com/crystal-lang/crystal-db.git version: 0.10.1 diff --git a/shard.yml b/shard.yml index fca1ce02..ad783f05 100644 --- a/shard.yml +++ b/shard.yml @@ -25,6 +25,8 @@ dependencies: lsquic: github: iv-org/lsquic.cr version: ~> 2.18.1-2 + crotp: + github: philnash/crotp crystal: 1.0.0 diff --git a/src/invidious.cr b/src/invidious.cr index ba818a3a..259392f6 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -345,6 +345,10 @@ Invidious::Routing.get "/preferences", Invidious::Routes::PreferencesRoute, :sho Invidious::Routing.post "/preferences", Invidious::Routes::PreferencesRoute, :update Invidious::Routing.get "/toggle_theme", Invidious::Routes::PreferencesRoute, :toggle_theme +Invidious::Routing.get "/setup_2fa", Invidious::Routes::Accounts, :setup_2fa_page +Invidious::Routing.post "/setup_2fa", Invidious::Routes::Accounts, :setup_2fa +Invidious::Routing.post "/validate_2fa", Invidious::Routes::Accounts, :validate_2fa + # Users post "/watch_ajax" do |env| diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 6ee07d7a..9b667b1e 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -471,3 +471,78 @@ def fetch_random_instance return filtered_instance_list.sample(1)[0] end + +def totp_validator(env) + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + referer = get_referer(env) + + email = env.params.body["email"]?.try &.downcase.byte_slice(0, 254) + password = env.params.body["password"]? + totp_code = env.params.body["totp_code"]? + user = PG_DB.query_one?("SELECT * FROM users WHERE email = $1", email, as: User) + + if !totp_code + return error_template(401, translate(locale, "general-totp-empty-field")) + end + + # Verify if possible + if token = env.params.body["csrf_token"]? + begin + validate_request(token, sid, env.request, HMAC_KEY, PG_DB, locale) + rescue ex + return error_template(400, ex) + end + end + + totp_instance = CrOTP::TOTP.new(user.totp_secret) + if !totp_instance.verify(totp_code) + return error_template(401, translate(locale, "general-totp-invalid-code")) + end + + if Kemal.config.ssl || CONFIG.https_only + secure = true + else + secure = false + end + + # There are two routes we can go here. + # 1. Where the user is already logged in and is + # confirming an dangerous task. + # 2. The user is logging in. + # + # This can be detected by the hidden email and password parameter + + # https://stackoverflow.com/a/574698 + if email && password + # The rest of the login code. + if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55)) + sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) + PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.utc) + + if CONFIG.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: "#{CONFIG.domain}", value: sid, expires: Time.utc + 2.years, + secure: secure, http_only: true) + else + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.utc + 2.years, + secure: secure, http_only: true) + end + 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 + + env.redirect referer + else + if CONFIG.domain + env.response.cookies["2faVerified"] = HTTP::Cookie.new(name: "2faVerified", domain: "#{CONFIG.domain}", value: true, expires: Time.utc + 1.hours, secure: secure, http_only: true) + else + env.response.cookies["2faVerified"] = HTTP::Cookie.new(name: "2faVerified", value: true, expires: Time.utc + 1.hours, secure: secure, http_only: true) + end + end +end diff --git a/src/invidious/routes/accounts.cr b/src/invidious/routes/accounts.cr new file mode 100644 index 00000000..a78c9e84 --- /dev/null +++ b/src/invidious/routes/accounts.cr @@ -0,0 +1,135 @@ +require "crotp" +require "./base_route" + +# Different routes relating to existing accounts and the control of their data. +class Invidious::Routes::Accounts < Invidious::Routes::BaseRoute + def setup_2fa_page(env) + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env) + + user = user.as(User) + sid = sid.as(String) + csrf_token = generate_response(sid, {":setup_2fa"}, HMAC_KEY, PG_DB) + + db_secret = Random::Secure.random_bytes(16).hexstring + totp = CrOTP::TOTP.new(db_secret) + user_secret = totp.base32_secret + + return templated "account/setup_2fa" + end + + # Setup TOTP (post) request. + def setup_2fa(env) + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + + user = env.get? "user" + sid = env.get? "sid" + referer = get_referer(env) + + if !user + return env.redirect referer + end + + user = user.as(User) + sid = sid.as(String) + token = env.params.body["csrf_token"]? + + begin + validate_request(token, sid, env.request, HMAC_KEY, PG_DB, locale) + rescue ex + return error_template(400, ex) + end + + totp_code = env.params.body["totp_code"]? + db_secret = env.params.body["db_secret"] # Must exist + if !totp_code + return error_template(401, translate(locale, "general-totp-empty-field")) + end + + totp_instance = CrOTP::TOTP.new(db_secret) + if !totp_instance.verify(totp_code) + return error_template(401, translate(locale, "general-totp-invalid-code")) + end + + PG_DB.exec("UPDATE users SET totp_secret = $1 WHERE email = $2", db_secret.to_s, user.email) + end + + # Validate 2fa code endpoint + def validate_2fa(env) + locale = LOCALES[env.get("preferences").as(Preferences).locale]? + referer = get_referer(env) + + email = env.params.body["email"]?.try &.downcase.byte_slice(0, 254) + password = env.params.body["password"]? + totp_code = env.params.body["totp_code"]? + # This endpoint is only called when the user has a totp_secret. + user = PG_DB.query_one?("SELECT * FROM users WHERE email = $1", email, as: User).not_nil! + + if !totp_code + return error_template(401, translate(locale, "general-totp-empty-field")) + end + + totp_instance = CrOTP::TOTP.new(user.totp_secret.not_nil!) + if !totp_instance.verify(totp_code) + return error_template(401, translate(locale, "general-totp-invalid-code")) + end + + if Kemal.config.ssl || CONFIG.https_only + secure = true + else + secure = false + end + + # There are two routes we can go here. + # 1. Where the user is already logged in and is + # confirming an dangerous task. + # 2. The user is logging in. + # + # This can be detected by the hidden email and password parameter + + # https://stackoverflow.com/a/574698 + if email && password + # The rest of the login code. + if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55)) + sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) + PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.utc) + + if CONFIG.domain + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", domain: "#{CONFIG.domain}", value: sid, expires: Time.utc + 2.years, + secure: secure, http_only: true) + else + env.response.cookies["SID"] = HTTP::Cookie.new(name: "SID", value: sid, expires: Time.utc + 2.years, + secure: secure, http_only: true) + end + 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 + + env.redirect referer + else + token = env.params.body["csrf_token"] + + begin + validate_request(token, env.get?("sid").as(String), env.request, HMAC_KEY, PG_DB, locale) + rescue ex + return error_template(400, ex) + end + + if CONFIG.domain + env.response.cookies["2faVerified"] = HTTP::Cookie.new(name: "2faVerified", domain: "#{CONFIG.domain}", value: "1", expires: Time.utc + 1.hours, secure: secure, http_only: true) + else + env.response.cookies["2faVerified"] = HTTP::Cookie.new(name: "2faVerified", value: "1", expires: Time.utc + 1.hours, secure: secure, http_only: true) + end + end + end +end diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr index f9e6ea6c..5e6096bf 100644 --- a/src/invidious/routes/login.cr +++ b/src/invidious/routes/login.cr @@ -324,6 +324,9 @@ class Invidious::Routes::Login < Invidious::Routes::BaseRoute if user if !user.password return error_template(400, "Please sign in using 'Log in with Google'") + elsif user.totp_secret + csrf_token = nil # setting this to false for compatibility reasons. + return templated "account/validate_2fa" end if Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55)) diff --git a/src/invidious/views/account/setup_2fa.ecr b/src/invidious/views/account/setup_2fa.ecr new file mode 100644 index 00000000..a22e5f0b --- /dev/null +++ b/src/invidious/views/account/setup_2fa.ecr @@ -0,0 +1,36 @@ +<% content_for "header" do %> +