From 2b941ae64821ac379e69337922265fe885fb7b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A1=E3=82=83=E3=81=A8=E3=83=BC?= =?UTF-8?q?=E3=81=AB=E3=82=85?= <17376330+u1-liquid@users.noreply.github.com> Date: Sun, 30 Jul 2023 03:35:42 +0900 Subject: [PATCH] =?UTF-8?q?feat:=202FA=E3=81=AE=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=20(#121)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ar-SA.yml | 2 +- locales/bn-BD.yml | 2 +- locales/de-DE.yml | 2 +- locales/en-US.yml | 6 ++++-- locales/es-ES.yml | 2 +- locales/fr-FR.yml | 2 +- locales/id-ID.yml | 2 +- locales/index.d.ts | 2 ++ locales/it-IT.yml | 2 +- locales/ja-JP.yml | 6 ++++-- locales/ja-KS.yml | 4 ++-- locales/ko-KR.yml | 6 ++++-- locales/pl-PL.yml | 2 +- locales/ru-RU.yml | 2 +- locales/sk-SK.yml | 2 +- locales/th-TH.yml | 2 +- locales/uk-UA.yml | 2 +- locales/vi-VN.yml | 2 +- locales/zh-CN.yml | 2 +- locales/zh-TW.yml | 2 +- .../migration/1690569881926-user-2fa-backup-codes.js | 11 +++++++++++ .../backend/src/core/entities/UserEntityService.ts | 1 + packages/backend/src/models/entities/UserProfile.ts | 5 +++++ packages/backend/src/models/json-schema/user.ts | 5 +++++ packages/backend/src/server/api/SigninApiService.ts | 7 +++++++ .../backend/src/server/api/endpoints/i/2fa/done.ts | 9 +++++++++ .../src/server/api/endpoints/i/2fa/unregister.ts | 1 + packages/backend/test/e2e/2fa.ts | 12 ++++++------ packages/backend/test/e2e/users.ts | 2 ++ packages/frontend/.storybook/fakes.ts | 1 + packages/frontend/src/components/MkSignin.vue | 2 +- packages/frontend/src/pages/settings/2fa.vue | 11 +++++++++-- packages/misskey-js/etc/misskey-js.api.md | 1 + packages/misskey-js/src/entities.ts | 1 + 34 files changed, 91 insertions(+), 32 deletions(-) create mode 100644 packages/backend/migration/1690569881926-user-2fa-backup-codes.js diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 116973a4e6..d7df0233f0 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1255,7 +1255,7 @@ _2fa: step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})." step2: "امسح رمز الاستجابة السريعة الموجد على الشاشة." step3: "أدخل الرمز الموجود في تطبيقك لإكمال التثبيت." - step4: "من هذه اللحظة أثناء ولوجك سيُطلب منك الرمز." + step4: "من هذه اللحظة أثناء ولوجك سيُطلب منك الرمز.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" renewTOTPCancel: "ليس اﻵن" _permissions: "read:account": "اعرض معلومات حسابك" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 78dbd77eb2..de9502acf4 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -1042,7 +1042,7 @@ _2fa: step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।" step2Url: "ডেস্কটপ অ্যাপে, নিম্নলিখিত URL লিখুন:" step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।" - step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।" + step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷" _permissions: "read:account": "অ্যাকাউন্টের তথ্য দেখুন" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 040e8836ef..22c6f5f642 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:" step3Title: "Authentifizierungsscode eingeben" step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird." - step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen." + step4: "Alle folgenden Anmeldeversuche werden ab sofort die Eingabe eines solchen Tokens benötigen.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "Dein Browser unterstützt keine Security-Tokens." registerTOTPBeforeKey: "Um einen Security-Token oder einen Passkey zu registrieren, musst du zuerst eine Authentifizierungs-App registrieren." securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten." diff --git a/locales/en-US.yml b/locales/en-US.yml index 03b37f1356..4ae4094773 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1686,7 +1686,9 @@ _2fa: step2Url: "You can also enter this URL if you're using a desktop program:" step3Title: "Enter an authentication code" step3: "Enter the token provided by your app to finish setup." - step4: "From now on, any future login attempts will ask for such a login token." + step4: "From now on, any future login attempts will ask for such a login token.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" + twoFactorBackupSecretWarning: "You have used some of your backup codes. If your authentication app is no longer available, please reconfigure your authentication app as soon as possible." + twoFactorBackupSecretExhausted: "You have exhausted all your backup codes. If your authentication app is no longer available, you will not be able to access your account anymore. please reconfigure your authentication app as soon as possible." securityKeyNotSupported: "Your browser does not support security keys." registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key." securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account." @@ -1698,7 +1700,7 @@ _2fa: removeKeyConfirm: "Really delete the {name} key?" whyTOTPOnlyRenew: "The authenticator app cannot be removed as long as a security key is registered." renewTOTP: "Reconfigure authenticator app" - renewTOTPConfirm: "This will cause verification codes from your previous app to stop working" + renewTOTPConfirm: "This will cause verification codes from your previous app and backup codes to stop working" renewTOTPOk: "Reconfigure" renewTOTPCancel: "Cancel" _permissions: diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 99a0eea4e5..a6687ff5e3 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "En una aplicación de escritorio se puede ingresar la siguiente URL:" step3Title: "Ingresa un código de autenticación" step3: "Para terminar, ingrese el token mostrado en la aplicación." - step4: "Ahora cuando inicie sesión, ingrese el mismo token" + step4: "Ahora cuando inicie sesión, ingrese el mismo token\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "Tu navegador no soporta claves de autenticación." registerTOTPBeforeKey: "Please set up an authenticator app to register a security or pass key.\npor favor. configura una aplicación de autenticación para registrar una llave de seguridad." securityKeyInfo: "Se puede configurar el inicio de sesión usando una clave de seguridad de hardware que soporte FIDO2 o con un certificado de huella digital o con un PIN" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 526e80b2d3..f53613948e 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1251,7 +1251,7 @@ _2fa: step2: "Ensuite, scannez le code QR affiché sur l’écran." step2Url: "Vous pouvez également saisir cette URL si vous utilisez un programme de bureau :" step3: "Entrez le jeton affiché sur votre application pour compléter la configuration." - step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos connexions." + step4: "À partir de maintenant, ce même jeton vous sera demandé à chacune de vos connexions.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyInfo: "Vous pouvez configurer l'authentification WebAuthN pour sécuriser davantage le processus de connexion grâce à une clé de sécurité matérielle qui prend en charge FIDO2, ou bien en configurant l'authentification par empreinte digitale ou par code PIN sur votre appareil." removeKeyConfirm: "Voulez-vous supprimer {name} ?" renewTOTPCancel: "Pas maintenant" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index c6ed07ebc9..6d3b6bdcbc 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -1656,7 +1656,7 @@ _2fa: step2Url: "Di aplikasi desktop, masukkan URL berikut:" step3Title: "Masukkan kode autentikasi" step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan." - step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu." + step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "Peramban kamu tidak mendukung security key." registerTOTPBeforeKey: "Mohon atur aplikasi autentikator untuk mendaftarkan security key atau passkey." securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu." diff --git a/locales/index.d.ts b/locales/index.d.ts index c4ef573a80..7fc21a1ccc 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1804,6 +1804,8 @@ export interface Locale { "step3Title": string; "step3": string; "step4": string; + "twoFactorBackupSecretWarning": string; + "twoFactorBackupSecretExhausted": string; "securityKeyNotSupported": string; "registerTOTPBeforeKey": string; "securityKeyInfo": string; diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 864918117e..03d76ac807 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "Nell'applicazione desktop inserire il seguente URL: " step3Title: "Inserisci il codice di verifica" step3: "Inserite il token visualizzato nell'app e il gioco è fatto." - step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo." + step4: "D'ora in poi, quando si accede, si inserisce il token nello stesso modo.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "Il tuo browser non supporta le chiavi di sicurezza." registerTOTPBeforeKey: "Ti occorre un'app di autenticazione con OTP, prima di registrare la chiave di sicurezza." securityKeyInfo: "È possibile impostare il dispositivo per accedere utilizzando una chiave di sicurezza hardware che supporta FIDO2 o un'impronta digitale o un PIN sul dispositivo." diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 07ba3cecc2..d5c50b59d2 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1721,7 +1721,9 @@ _2fa: step2Url: "デスクトップアプリでは次のURIを入力します:" step3Title: "確認コードを入力" step3: "アプリに表示されている確認コード(トークン)を入力して完了です。" - step4: "これからログインするときも、同じように確認コードを入力します。" + step4: "これからログインするときも、同じように確認コードを入力します。\n\n認証アプリが使えなくなった時、アカウントへの緊急アクセスのためのバックアップコードを作成しました。\n紛失防止のため、必ず安全な場所に保管してください。\nこれらのコードはそれぞれ1回だけ使用することができます。\nもし全てのコードを使い切ってしまうとアカウントにアクセスすることができなくなってしまうので、出来るだけ早く認証アプリを再設定してください。\n\nバックアップコード:\n\n{codes}" + twoFactorBackupSecretWarning: "バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。" + twoFactorBackupSecretExhausted: "バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。できるだけ早く認証アプリを再登録してください。" securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。" registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。" @@ -1733,7 +1735,7 @@ _2fa: removeKeyConfirm: "{name}を削除しますか?" whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。" renewTOTP: "認証アプリを再設定" - renewTOTPConfirm: "今までの認証アプリの確認コードは使用できなくなります" + renewTOTPConfirm: "今までの認証アプリの確認コードとバックアップコードは使用できなくなります" renewTOTPOk: "再設定する" renewTOTPCancel: "やめておく" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 80c810d26b..de8ba8c98d 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "デスクトップアプリやったら次のURLを入力してや:" step3Title: "確認コードを入れてーや" step3: "アプリに表示されているトークンを入力して終わりや。" - step4: "これからログインするときも、同じようにトークンを入力するんやで" + step4: "これからログインするときも、同じようにトークンを入力するんやで\n\n認証アプリが使えなくなった時、アカウントへの緊急アクセスのためのバックアップコードを作成しました。\n紛失防止のため、必ず安全な場所に保管してください。\nこれらのコードはそれぞれ1回だけ使用することができます。\nもし全てのコードを使い切ってしまうとアカウントにアクセスすることができなくなってしまうので、出来るだけ早く認証アプリを再設定してください。\n\nバックアップコード:\n\n{codes}" securityKeyNotSupported: "今使とるブラウザはセキュリティキーに対応してへんのやってさ。" registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するんやったら、まず認証アプリを設定してーな。" securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーか端末の指紋認証やPINを使ってログインするように設定できるで。" @@ -1694,7 +1694,7 @@ _2fa: removeKeyConfirm: "{name}を消すん?" whyTOTPOnlyRenew: "セキュリティキーが登録されとったら、認証アプリの設定は解除できへんで。" renewTOTP: "認証アプリをもっかい設定" - renewTOTPConfirm: "今までの人称アプリの確認コードは使えんくなるけどええか?" + renewTOTPConfirm: "今までの人称アプリの確認コードとバックアップコードは使えんくなるけどええか?" renewTOTPOk: "もっかい設定する" renewTOTPCancel: "やめとく" _permissions: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 42a829c125..7b6a97e364 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1679,7 +1679,9 @@ _2fa: step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:" step3Title: "인증 코드 입력" step3: "앱에 표시된 토큰을 입력하시면 완료됩니다." - step4: "다음 로그인부터는 토큰을 입력해야 합니다." + step4: "다음 로그인부터는 토큰을 입력해야 합니다.\n\n인증 앱이 사용할 수 없게 되었을 때, 계정에 응급 접근할 수 있는 백업 코드를 생성했습니다.\n코드를 잃어버리지 않도록 안전한 곳에 보관해 두세요.\n코드는 각각 1번씩만 사용할 수 있습니다.\n만일 모든 코드를 사용해버렸다면 더이상 계정에 접근할 수 없게 되므로, 가능한 한 빨리 인증 앱을 다시 등록해 주세요.\n\n백업 코드:\n\n{code}" + twoFactorBackupSecretWarning: "백업 코드가 사용되었습니다. 인증 앱을 사용할 수 없는 경우, 가능한 한 빨리 인증 앱을 다시 등록해 주세요." + twoFactorBackupSecretExhausted: "백업 코드가 모두 사용되었습니다. 인증 앱을 사용할 수 없는 경우, 더이상 계정에 접근할 수 없게 됩니다. 가능한 한 빨리 인증 앱을 다시 등록해 주세요." securityKeyNotSupported: "이 브라우저는 보안 키를 지원하지 않습니다." registerTOTPBeforeKey: "보안 키 또는 패스키를 등록하려면 인증 앱을 등록하십시오." securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다." @@ -1691,7 +1693,7 @@ _2fa: removeKeyConfirm: "{name} 을(를) 삭제하시겠습니까?" whyTOTPOnlyRenew: "보안 키가 등록되어 있는 경우 인증 앱을 해제할 수 없습니다." renewTOTP: "인증 앱 재설정" - renewTOTPConfirm: "기존에 등록되어 있던 인증 키는 사용하지 못하게 됩니다." + renewTOTPConfirm: "기존에 등록되어 있던 인증 키와 백업 코드는 사용하지 못하게 됩니다." renewTOTPOk: "재설정" renewTOTPCancel: "취소" _permissions: diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 8b6b4be7d0..d902d609be 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1087,7 +1087,7 @@ _2fa: step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b}) na swoim urządzeniu." step2: "Następnie, zeskanuje kod QR z ekranu." step3: "Wprowadź token podany w aplikacji, aby ukończyć konfigurację." - step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania." + step4: "Od teraz, przy każdej próbie logowania otrzymasz prośbę o token logowania.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" removeKeyConfirm: "Usunąć kopię zapasową {name}?" renewTOTPCancel: "Nie teraz" _permissions: diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index f83df32886..a13b2e151d 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1572,7 +1572,7 @@ _2fa: step2Url: "Если пользуетесь приложением на компьютере, можете ввести в него эту строку (URL):" step3Title: "Введите проверочный код" step3: "И наконец, введите код, который покажет приложение." - step4: "Теперь при каждом входе на сайт вам нужно будет вводить код из приложения аналогичным образом." + step4: "Теперь при каждом входе на сайт вам нужно будет вводить код из приложения аналогичным образом.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "Ваш браузер не поддерживает ключи безопасности." registerTOTPBeforeKey: "Чтобы зарегистрировать ключ безопасности и пароль, сначала настройте приложение аутентификации." securityKeyInfo: "Вы можете настроить вход с помощью аппаратного ключа безопасности, поддерживающего FIDO2, или отпечатка пальца или PIN-кода на устройстве." diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index f4c598ac83..2da7e4255e 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -1149,7 +1149,7 @@ _2fa: step2: "Potom, naskenujte QR kód zobrazený na obrazovke." step2Url: "Do aplikácie zadajte nasledujúcu URL adresu:" step3: "Nastavenie dokončíte zadaním tokenu z vašej aplikácie." - step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token." + step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyInfo: "Okrem odtlačku prsta alebo PIN autentifikácie si môžete nastaviť autentifikáciu cez hardvérový bezpečnostný kľúč podporujúci FIDO2 a tak ešte viac zabezpečiť svoj účet." removeKeyConfirm: "Naozaj chcete odstrániť \"{name}\"?" renewTOTPCancel: "Nie, ďakujem" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index ddbafbe79a..e74c699f36 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "คุณยังสามารถป้อนบน URL นี้หากคุณใช้โปรแกรมเดสก์ท็อป:" step3Title: "ป้อนรหัสยืนยัน" step3: "ป้อนโทเค็นที่แอปของคุณให้มาเพื่อเสร็จสิ้นการตั้งค่า" - step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว" + step4: "นับจากนี้เป็นต้นไปการพยายามเข้าสู่ระบบในอนาคตนั้น อาจจะต้องขอโทเค็นในการเข้าสู่ระบบดังกล่าว\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "เบราว์เซอร์ของคุณไม่รองรับคีย์ความปลอดภัยนะ" registerTOTPBeforeKey: "กรุณาตั้งค่าแอปยืนยันตัวตนเพื่อลงทะเบียนรหัสความปลอดภัยหรือรหัสผ่าน" securityKeyInfo: "นอกจากนี้การตรวจสอบความถูกต้องด้วยลายนิ้วมือหรือ PIN แล้ว คุณยังสามารถตั้งค่าการตรวจสอบสิทธิ์ผ่านคีย์ความปลอดภัยของฮาร์ดแวร์ที่รองรับ FIDO2 เพื่อเพิ่มความปลอดภัยให้กับบัญชีของคุณ" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 1ac07ff9b2..a57a463689 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -1333,7 +1333,7 @@ _2fa: step2: "Потім відскануйте QR-код, який відображається на цьому екрані." step2Url: "Ви також можете ввести цю URL-адресу, якщо використовуєте програму для ПК:" step3: "Щоб завершити налаштування, введіть токен, наданий вашою програмою." - step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена." + step4: "Відтепер будь-які майбутні спроби входу вимагатимуть такого токена.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" renewTOTPCancel: "Не зараз" _permissions: "read:account": "Переглядати дані профілю" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 898c478bf5..35081148a2 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1353,7 +1353,7 @@ _2fa: step2: "Sau đó, quét mã QR hiển thị trên màn hình này." step2Url: "Bạn cũng có thể nhập URL này nếu sử dụng một chương trình máy tính:" step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập." - step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó." + step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó.\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình." removeKey: "Xóa mã bảo mật" removeKeyConfirm: "Xóa bản sao lưu {name}?" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index eff2df9849..e3924b878b 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "在桌面应用程序中输入以下 URL:" step3Title: "输入验证码" step3: "输入您的应用提供的动态口令以完成设置。" - step4: "从现在开始,任何登录操作都将要求您提供动态口令。" + step4: "从现在开始,任何登录操作都将要求您提供动态口令。\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "您的浏览器不支持安全密钥。" registerTOTPBeforeKey: "要注册安全密钥或 Passkey,请先设置验证器应用程序。" securityKeyInfo: "注册兼容 WebAuthn 的密钥,例如支持 FIDO2 的硬件安全密钥、设备上的生物识别功能、PIN 码以及 Passkey 等。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index df4122ef3d..5fa6acbea6 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1682,7 +1682,7 @@ _2fa: step2Url: "請在桌面版應用程式中輸入以下的 URL:" step3Title: "輸入驗證碼" step3: "輸入應用程式所提供的權杖以完成設定。" - step4: "從現在開始,任何登入操作都將要求您提供權杖。" + step4: "從現在開始,任何登入操作都將要求您提供權杖。\n\nWe've generated backup codes for emergency access to your account in case your authentication app becomes unavailable.\nPlease ensure these are stored in a safe place to prevent loss.\nEach of these codes can only be used once.\nIf you exhaust all the codes, you will not be able to access your account anymore, so please reconfigure your authentication app as soon as possible.\n\nBackup Codes:\n\n{codes}" securityKeyNotSupported: "您的瀏覽器不支援安全金鑰。" registerTOTPBeforeKey: "如要註冊安全金鑰或 Passkey,請先設定驗證應用程式。" securityKeyInfo: "您可以設定使用支援 FIDO2 的硬體安全鎖、終端設備的指紋認證,或者 PIN 碼來登入。" diff --git a/packages/backend/migration/1690569881926-user-2fa-backup-codes.js b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js new file mode 100644 index 0000000000..2049df8ea2 --- /dev/null +++ b/packages/backend/migration/1690569881926-user-2fa-backup-codes.js @@ -0,0 +1,11 @@ +export class User2faBackupCodes1690569881926 { + name = 'User2faBackupCodes1690569881926' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" ADD "twoFactorBackupSecret" character varying array`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "twoFactorBackupSecret"`); + } +} diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 5987eeb81f..e86eef0042 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -455,6 +455,7 @@ export class UserEntityService implements OnModuleInit { preventAiLearning: profile!.preventAiLearning, isExplorable: user.isExplorable, isDeleted: user.isDeleted, + twoFactorBackupCodes: profile?.twoFactorBackupSecret?.length === 20 ? 'full' : (profile?.twoFactorBackupSecret?.length ?? 0) > 0 ? 'partial' : 'none', hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: this.noteUnreadsRepository.count({ where: { userId: user.id, isSpecified: true }, diff --git a/packages/backend/src/models/entities/UserProfile.ts b/packages/backend/src/models/entities/UserProfile.ts index c4ed9db9bb..1ae6533127 100644 --- a/packages/backend/src/models/entities/UserProfile.ts +++ b/packages/backend/src/models/entities/UserProfile.ts @@ -91,6 +91,11 @@ export class UserProfile { }) public twoFactorTempSecret: string | null; + @Column('varchar', { + nullable: true, array: true, + }) + public twoFactorBackupSecret: string[] | null; + @Column('varchar', { length: 128, nullable: true, }) diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 311e2db9c0..4fb71828ed 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -320,6 +320,11 @@ export const packedMeDetailedOnlySchema = { type: 'boolean', nullable: false, optional: false, }, + twoFactorBackupCodes: { + type: 'string', + enum: ['full', 'partial', 'none'], + nullable: false, optional: false, + }, hideOnlineStatus: { type: 'boolean', nullable: false, optional: false, diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index bd3d8a28da..43344b1835 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -155,6 +155,13 @@ export class SigninApiService { }); } + if (profile.twoFactorBackupSecret?.includes(token)) { + await this.userProfilesRepository.update({ userId: profile.userId }, { + twoFactorBackupSecret: profile.twoFactorBackupSecret.filter((secret) => secret !== token), + }); + return this.signinService.signin(request, reply, user); + } + const delta = OTPAuth.TOTP.validate({ secret: OTPAuth.Secret.fromBase32(profile.twoFactorSecret!), digits: 6, diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts index 6c31075e05..d0bd4cd783 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -54,8 +54,13 @@ export default class extends Endpoint { throw new Error('not verified'); } + const backupCodes = Array.from({ length: 20 }, () => { + return new OTPAuth.Secret().base32; + }); + await this.userProfilesRepository.update(me.id, { twoFactorSecret: profile.twoFactorTempSecret, + twoFactorBackupSecret: backupCodes, twoFactorEnabled: true, }); @@ -64,6 +69,10 @@ export default class extends Endpoint { detail: true, includeSecrets: true, })); + + return { + backupCodes: backupCodes, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts index e0e7ba6658..c081a544c1 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -42,6 +42,7 @@ export default class extends Endpoint { await this.userProfilesRepository.update(me.id, { twoFactorSecret: null, + twoFactorBackupSecret: null, twoFactorEnabled: false, usePasswordLessLogin: false, }); diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 04be97ad9d..14f9c3da39 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -186,7 +186,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const usersShowResponse = await api('/users/show', { username, @@ -211,7 +211,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { password, @@ -267,7 +267,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { password, @@ -324,7 +324,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { password, @@ -366,7 +366,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const registerKeyResponse = await api('/i/2fa/register-key', { password, @@ -418,7 +418,7 @@ describe('2要素認証', () => { const doneResponse = await api('/i/2fa/done', { token: otpToken(registerResponse.body.secret), }, alice); - assert.strictEqual(doneResponse.status, 204); + assert.strictEqual(doneResponse.status, 200); const usersShowResponse = await api('/users/show', { username, diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 622d25a802..46d482daf2 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -148,6 +148,7 @@ describe('ユーザー', () => { preventAiLearning: user.preventAiLearning, isExplorable: user.isExplorable, isDeleted: user.isDeleted, + twoFactorBackupCodes: user.twoFactorBackupCodes, hideOnlineStatus: user.hideOnlineStatus, hasUnreadSpecifiedNotes: user.hasUnreadSpecifiedNotes, hasUnreadMentions: user.hasUnreadMentions, @@ -394,6 +395,7 @@ describe('ユーザー', () => { assert.strictEqual(response.preventAiLearning, true); assert.strictEqual(response.isExplorable, true); assert.strictEqual(response.isDeleted, false); + assert.strictEqual(response.twoFactorBackupCodes, 'none'); assert.strictEqual(response.hideOnlineStatus, false); assert.strictEqual(response.hasUnreadSpecifiedNotes, false); assert.strictEqual(response.hasUnreadMentions, false); diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 7a8ab8044e..4ad5debeca 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -111,6 +111,7 @@ export function userDetailed(id = 'someuserid', username = 'miskist', host = 'mi publicReactions: false, securityKeys: false, twoFactorEnabled: false, + twoFactorBackupCodes: 'none', updatedAt: null, uri: null, url: null, diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index b1a509b9e6..b4b9a84e32 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -32,7 +32,7 @@ - + diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index aff7765ed5..92da74a2f6 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -3,6 +3,13 @@
+ + {{ i18n.ts._2fa.twoFactorBackupSecretWarning }} + + + {{ i18n.ts._2fa.twoFactorBackupSecretExhausted }} + + @@ -114,13 +121,13 @@ async function registerTOTP() { }); if (token.canceled) return; - await os.apiWithDialog('i/2fa/done', { + const { backupCodes } = await os.apiWithDialog('i/2fa/done', { token: token.result.toString(), }); await os.alert({ type: 'success', - text: i18n.ts._2fa.step4, + text: i18n.t('_2fa.step4', { codes: backupCodes.join('\n') }), }); } diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 8dba89d523..62152384fc 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -2455,6 +2455,7 @@ type MeDetailed = UserDetailed & { hasUnreadMessagingMessage: boolean; hasUnreadNotification: boolean; hasUnreadSpecifiedNotes: boolean; + twoFactorBackupCodes: 'full' | 'partial' | 'none'; hideOnlineStatus: boolean; injectFeaturedNote: boolean; integrations: Record; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 01a0206154..9cf0045288 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -95,6 +95,7 @@ export type MeDetailed = UserDetailed & { hasUnreadMessagingMessage: boolean; hasUnreadNotification: boolean; hasUnreadSpecifiedNotes: boolean; + twoFactorBackupCodes: 'full' | 'partial' | 'none'; hideOnlineStatus: boolean; injectFeaturedNote: boolean; integrations: Record;