diff --git a/apps/server/src/libs/auth.ts b/apps/server/src/libs/auth.ts index de10e72ef..50c899397 100644 --- a/apps/server/src/libs/auth.ts +++ b/apps/server/src/libs/auth.ts @@ -586,6 +586,13 @@ export function createAuth( google: { clientId: env.AUTH_GOOGLE_CLIENT_ID, clientSecret: env.AUTH_GOOGLE_CLIENT_SECRET, + // Force the provider's authorization page to let users choose an + // identity before linking. Without this, an existing provider session + // can silently reuse the previously authorized account and immediately + // hit account_already_linked_to_different_user. + // Source: @better-auth/core/src/oauth2/create-authorization-url.ts + // forwards provider `prompt` to the OAuth authorization URL. + prompt: 'select_account', // NOTICE: // Why: better-auth's google provider already maps email_verified // through, but a stale Google profile that omits the claim falls @@ -602,6 +609,13 @@ export function createAuth( github: { clientId: env.AUTH_GITHUB_CLIENT_ID, clientSecret: env.AUTH_GITHUB_CLIENT_SECRET, + // Force GitHub's authorization page to let users choose an identity + // before linking. Without this, an existing github.com session can + // silently reuse the previously authorized account and immediately hit + // account_already_linked_to_different_user. + // Source: @better-auth/core/src/oauth2/create-authorization-url.ts + // forwards provider `prompt` to the OAuth authorization URL. + prompt: 'select_account', // NOTICE: // Why: better-auth derives emailVerified from the GitHub /user/emails // response, but `emails.find(e => e.email === profile.email)?.verified` diff --git a/apps/server/src/libs/tests/auth.test.ts b/apps/server/src/libs/tests/auth.test.ts index 41d557990..16859b8a7 100644 --- a/apps/server/src/libs/tests/auth.test.ts +++ b/apps/server/src/libs/tests/auth.test.ts @@ -46,6 +46,21 @@ describe('createAuth', () => { expect(auth.options.account?.accountLinking?.allowDifferentEmails).toBe(true) }) + + it('asks social providers to show the account picker during OAuth authorization', () => { + const auth = createAuth({} as unknown as Database, { + API_SERVER_URL: 'http://localhost:3000', + AUTH_GOOGLE_CLIENT_ID: 'google-client', + AUTH_GOOGLE_CLIENT_SECRET: 'google-secret', + AUTH_GITHUB_CLIENT_ID: 'github-client', + AUTH_GITHUB_CLIENT_SECRET: 'github-secret', + BETTER_AUTH_SECRET: 'test-secret-test-secret-test-secret', + ADDITIONAL_TRUSTED_ORIGINS: [], + } as unknown as Env) + + expect(auth.options.socialProviders?.google?.prompt).toBe('select_account') + expect(auth.options.socialProviders?.github?.prompt).toBe('select_account') + }) }) describe('seedTrustedClients', () => { diff --git a/packages/i18n/src/locales/en/settings.yaml b/packages/i18n/src/locales/en/settings.yaml index 0de80a96e..75c3c4733 100644 --- a/packages/i18n/src/locales/en/settings.yaml +++ b/packages/i18n/src/locales/en/settings.yaml @@ -262,6 +262,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: This OAuth account is already linked to another AIRI account. Choose a different provider account, or unlink it from the other AIRI account first. + oauthCallbackFailed: We could not connect this OAuth account. Try again or choose another provider account. lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Security diff --git a/packages/i18n/src/locales/es/settings.yaml b/packages/i18n/src/locales/es/settings.yaml index c28fe0c22..4219b6db8 100644 --- a/packages/i18n/src/locales/es/settings.yaml +++ b/packages/i18n/src/locales/es/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: No se pudo cargar las cuentas conectadas. unlinkFailed: No se pudo desvincular esta cuenta. linkFailed: No se pudo iniciar el flujo de vinculación. + accountAlreadyLinkedToDifferentUser: Esta cuenta OAuth ya está vinculada a otra cuenta de AIRI. Elige otra cuenta del proveedor o desvincúlala primero de la otra cuenta de AIRI. + oauthCallbackFailed: No pudimos conectar esta cuenta OAuth. Inténtalo de nuevo o elige otra cuenta del proveedor. lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Seguridad diff --git a/packages/i18n/src/locales/fr/settings.yaml b/packages/i18n/src/locales/fr/settings.yaml index 1a65eceb2..d206c1a61 100644 --- a/packages/i18n/src/locales/fr/settings.yaml +++ b/packages/i18n/src/locales/fr/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: Ce compte OAuth est déjà lié à un autre compte AIRI. Choisissez un autre compte fournisseur ou dissociez-le d'abord de l'autre compte AIRI. + oauthCallbackFailed: Impossible de connecter ce compte OAuth. Réessayez ou choisissez un autre compte fournisseur. lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Security diff --git a/packages/i18n/src/locales/ja/settings.yaml b/packages/i18n/src/locales/ja/settings.yaml index 396d73f42..9f735c871 100644 --- a/packages/i18n/src/locales/ja/settings.yaml +++ b/packages/i18n/src/locales/ja/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: この OAuth アカウントは別の AIRI アカウントにすでに連携されています。別のプロバイダーアカウントを選ぶか、先に別の AIRI アカウントから連携を解除してください。 + oauthCallbackFailed: この OAuth アカウントを連携できませんでした。もう一度試すか、別のプロバイダーアカウントを選んでください。 lastAccount: "これはあなたの唯一のサインイン方法です。\nリンクを解除する前にパスワードを設定してください。" security: tab: セキュリティ diff --git a/packages/i18n/src/locales/ko/settings.yaml b/packages/i18n/src/locales/ko/settings.yaml index 266a2140f..5d9353136 100644 --- a/packages/i18n/src/locales/ko/settings.yaml +++ b/packages/i18n/src/locales/ko/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: 이 OAuth 계정은 이미 다른 AIRI 계정에 연결되어 있습니다. 다른 제공자 계정을 선택하거나, 먼저 다른 AIRI 계정에서 연결을 해제하세요. + oauthCallbackFailed: 이 OAuth 계정을 연결할 수 없습니다. 다시 시도하거나 다른 제공자 계정을 선택하세요. lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Security diff --git a/packages/i18n/src/locales/ru/settings.yaml b/packages/i18n/src/locales/ru/settings.yaml index 49543d201..c853e2f82 100644 --- a/packages/i18n/src/locales/ru/settings.yaml +++ b/packages/i18n/src/locales/ru/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Не удалось загрузить подключенные учетные записи. unlinkFailed: Не удалось отвязать эту учетную запись. linkFailed: Не удалось запустить поток ссылок. + accountAlreadyLinkedToDifferentUser: Этот OAuth-аккаунт уже привязан к другому аккаунту AIRI. Выберите другой аккаунт провайдера или сначала отвяжите его от другого аккаунта AIRI. + oauthCallbackFailed: Не удалось подключить этот OAuth-аккаунт. Попробуйте еще раз или выберите другой аккаунт провайдера. lastAccount: Это единственный способ входа в систему. Установите пароль перед отсоединением. security: tab: Безопасность diff --git a/packages/i18n/src/locales/vi/settings.yaml b/packages/i18n/src/locales/vi/settings.yaml index 24e8826c5..1beae3ff5 100644 --- a/packages/i18n/src/locales/vi/settings.yaml +++ b/packages/i18n/src/locales/vi/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: Tài khoản OAuth này đã được liên kết với một tài khoản AIRI khác. Hãy chọn tài khoản nhà cung cấp khác hoặc hủy liên kết tài khoản đó khỏi tài khoản AIRI kia trước. + oauthCallbackFailed: Không thể kết nối tài khoản OAuth này. Hãy thử lại hoặc chọn tài khoản nhà cung cấp khác. lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Security diff --git a/packages/i18n/src/locales/zh-Hans/settings.yaml b/packages/i18n/src/locales/zh-Hans/settings.yaml index c65228b3f..c7a94a54a 100644 --- a/packages/i18n/src/locales/zh-Hans/settings.yaml +++ b/packages/i18n/src/locales/zh-Hans/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: 暂时无法加载已关联的帐户 unlinkFailed: 无法解除此帐户的关联 linkFailed: 暂时无法开始关联的流程,要之后再试试吗? + accountAlreadyLinkedToDifferentUser: 这个 OAuth 帐户已经关联到另一个 AIRI 帐户了。请选择另一个第三方帐户,或者先在另一个 AIRI 帐户中解除关联。 + oauthCallbackFailed: 暂时无法关联这个 OAuth 帐户。请重试,或选择另一个第三方帐户。 lastAccount: 这是你当前唯一的登录方式,请务必先设置密码再解除关联哦 security: tab: 安全 diff --git a/packages/i18n/src/locales/zh-Hant/settings.yaml b/packages/i18n/src/locales/zh-Hant/settings.yaml index ed91a4d6b..32b022d79 100644 --- a/packages/i18n/src/locales/zh-Hant/settings.yaml +++ b/packages/i18n/src/locales/zh-Hant/settings.yaml @@ -248,6 +248,8 @@ pages: listFailed: Could not load connected accounts. unlinkFailed: Could not unlink this account. linkFailed: Could not start the link flow. + accountAlreadyLinkedToDifferentUser: 這個 OAuth 帳戶已經連結到另一個 AIRI 帳戶了。請選擇另一個第三方帳戶,或先在另一個 AIRI 帳戶中解除連結。 + oauthCallbackFailed: 暫時無法連結這個 OAuth 帳戶。請重試,或選擇另一個第三方帳戶。 lastAccount: This is your only sign-in method. Set a password first before unlinking it. security: tab: Security diff --git a/packages/stage-pages/src/pages/settings/account/account-settings-page.vue b/packages/stage-pages/src/pages/settings/account/account-settings-page.vue index af02a7971..8ad728221 100644 --- a/packages/stage-pages/src/pages/settings/account/account-settings-page.vue +++ b/packages/stage-pages/src/pages/settings/account/account-settings-page.vue @@ -1,7 +1,7 @@