diff --git a/.github/workflows/deploy-cloudflare-auth-ui.yml b/.github/workflows/deploy-cloudflare-auth-ui.yml index 8a0648243..00a85e1dc 100644 --- a/.github/workflows/deploy-cloudflare-auth-ui.yml +++ b/.github/workflows/deploy-cloudflare-auth-ui.yml @@ -21,7 +21,7 @@ jobs: environment: name: Auth Production - url: https://auth.airi.build/ui/ + url: https://accounts.airi.build/ui/ steps: - uses: actions/checkout@v6 diff --git a/apps/server/README.md b/apps/server/README.md index ff6466b4e..1e86ba0d0 100644 --- a/apps/server/README.md +++ b/apps/server/README.md @@ -32,7 +32,7 @@ docker compose -f apps/server/docker-compose.otel.yml up -d Default: -`AUTH_UI_URL=https://auth.airi.build/ui` +`AUTH_UI_URL=https://accounts.airi.build/ui` Set this when previewing or deploying auth UI to a different Cloudflare URL. diff --git a/apps/server/docs/ai-context/email-auth-resend.md b/apps/server/docs/ai-context/email-auth-resend.md index 1e7e32220..f0a4f863d 100644 --- a/apps/server/docs/ai-context/email-auth-resend.md +++ b/apps/server/docs/ai-context/email-auth-resend.md @@ -46,7 +46,7 @@ Out: - **Resend SDK**:使用官方 `resend` npm 包。错误处理走 `errorMessageFrom`(`@moeru/std`);失败时抛 `ApiError(502, 'email/send_failed', ...)` 让 Better Auth 把错传回前端。 - **触发邮件的位置**:Better Auth 的 hook 是 server 内部回调,不是 HTTP 路由——跨实例时只有处理该次 sign-in/up 的实例会触发,不会重复。 -- **Verify / reset 链接 URL**:链接落地页不放 `apps/server`,而是放独立部署的 `apps/ui-server-auth`。`API_SERVER_URL` 是 server 自身(如 `https://api.airi.build`),ui-server-auth 是同站点的另一域(如 `https://auth.airi.build/ui`);两者通过 trustedOrigins 互信。链接组装规则: +- **Verify / reset 链接 URL**:链接落地页不放 `apps/server`,而是放独立部署的 `apps/ui-server-auth`。`API_SERVER_URL` 是 server 自身(如 `https://api.airi.build`),ui-server-auth 是同站点的另一域(如 `https://accounts.airi.build/ui`);两者通过 trustedOrigins 互信。链接组装规则: - Verify email:`/verify-email?token=` - Reset password:`/reset-password?token=` - 由 `getAuthTrustedOrigins(request)` 第一个匹配的 origin 决定 ``,避免硬编码。 diff --git a/apps/server/src/libs/env.ts b/apps/server/src/libs/env.ts index c6578044a..bf11d7627 100644 --- a/apps/server/src/libs/env.ts +++ b/apps/server/src/libs/env.ts @@ -83,7 +83,7 @@ const EnvSchema = object({ // Standalone auth UI base URL. The server keeps `/auth/*` as the historical // entrypoint and redirects those requests here after ui-server-auth moved out // of the server image. - AUTH_UI_URL: optional(string(), 'https://auth.airi.build/ui'), + AUTH_UI_URL: optional(string(), 'https://accounts.airi.build/ui'), // Standalone admin UI base URL. The server keeps `/admin/*` as the historical // entrypoint and redirects those requests here after ui-admin moved out of diff --git a/apps/server/src/libs/tests/auth.test.ts b/apps/server/src/libs/tests/auth.test.ts index 5e1b0d392..41d557990 100644 --- a/apps/server/src/libs/tests/auth.test.ts +++ b/apps/server/src/libs/tests/auth.test.ts @@ -1,3 +1,6 @@ +import type { Database } from '../db' +import type { Env } from '../env' + import { describe, expect, it, vi } from 'vitest' import { createAuth, ensureDynamicFirstPartyRedirectUri, seedTrustedClients } from '../auth' @@ -31,7 +34,7 @@ function createMockDb(existingRowsByCall: unknown[][] = []) { describe('createAuth', () => { it('allows signed-in users to link OAuth accounts that use a different email', () => { - const auth = createAuth({} as any, { + 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', @@ -39,7 +42,7 @@ describe('createAuth', () => { AUTH_GITHUB_CLIENT_SECRET: 'github-secret', BETTER_AUTH_SECRET: 'test-secret-test-secret-test-secret', ADDITIONAL_TRUSTED_ORIGINS: [], - } as any) + } as unknown as Env) expect(auth.options.account?.accountLinking?.allowDifferentEmails).toBe(true) }) diff --git a/apps/server/src/libs/tests/env.test.ts b/apps/server/src/libs/tests/env.test.ts index 14b8b2924..4bf4a0252 100644 --- a/apps/server/src/libs/tests/env.test.ts +++ b/apps/server/src/libs/tests/env.test.ts @@ -41,7 +41,7 @@ describe('parseEnv', () => { expect(env.DATABASE_URL).toBe('postgres://example') expect(env.REDIS_URL).toBe('redis://example') - expect(env.AUTH_UI_URL).toBe('https://auth.airi.build/ui') + expect(env.AUTH_UI_URL).toBe('https://accounts.airi.build/ui') expect(env.ADMIN_UI_URL).toBe('https://admin.airi.build') expect(env.ADDITIONAL_TRUSTED_ORIGINS).toEqual([]) }) diff --git a/apps/server/src/routes/auth/oidc-userinfo-ban.test.ts b/apps/server/src/routes/auth/oidc-userinfo-ban.test.ts index a326cec88..aaf02a267 100644 --- a/apps/server/src/routes/auth/oidc-userinfo-ban.test.ts +++ b/apps/server/src/routes/auth/oidc-userinfo-ban.test.ts @@ -43,7 +43,7 @@ async function buildRoutes(currentUser: SessionUser) { db: {} as any, // userinfo path never queries the DB env: { API_SERVER_URL: 'http://localhost:3000', - AUTH_UI_URL: 'https://auth.airi.build/ui', + AUTH_UI_URL: 'https://accounts.airi.build/ui', ADDITIONAL_TRUSTED_ORIGINS: [], } as any, configKV: createConfigKV(), @@ -103,7 +103,7 @@ describe('auth UI routes', () => { expect(res.status).toBe(302) const location = res.headers.get('location') - expect(location).toBe('https://auth.airi.build/ui/sign-in?provider=github&client_id=stage-web&prompt=login&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback&api_server_url=http%3A%2F%2Flocalhost%3A3000') + expect(location).toBe('https://accounts.airi.build/ui/sign-in?provider=github&client_id=stage-web&prompt=login&redirect_uri=http%3A%2F%2Flocalhost%3A5173%2Fauth%2Fcallback&api_server_url=http%3A%2F%2Flocalhost%3A3000') }) it('redirects Electron OIDC callback queries to the standalone auth UI relay', async () => { @@ -112,6 +112,6 @@ describe('auth UI routes', () => { const res = await routes.request('/api/auth/oidc/electron-callback?code=sample-code&state=43123%3Aopaque-state') expect(res.status).toBe(302) - expect(res.headers.get('location')).toBe('https://auth.airi.build/ui/api/auth/oidc/electron-callback?code=sample-code&state=43123%3Aopaque-state') + expect(res.headers.get('location')).toBe('https://accounts.airi.build/ui/api/auth/oidc/electron-callback?code=sample-code&state=43123%3Aopaque-state') }) }) diff --git a/apps/server/src/utils/auth-ui.ts b/apps/server/src/utils/auth-ui.ts index 5026c0694..344e07296 100644 --- a/apps/server/src/utils/auth-ui.ts +++ b/apps/server/src/utils/auth-ui.ts @@ -1,6 +1,6 @@ export const SERVER_AUTH_UI_BASE_PATH = '/auth' export const AUTH_UI_API_SERVER_URL_QUERY_PARAM = 'api_server_url' -export const DEFAULT_AUTH_UI_URL = 'https://auth.airi.build/ui' +export const DEFAULT_AUTH_UI_URL = 'https://accounts.airi.build/ui' export const SERVER_DEV_API_SERVER_URL = 'https://airi-server-dev.up.railway.app' export const SERVER_DEV_AUTH_UI_URL = 'https://server-dev.airi-server-auth.pages.dev/ui' diff --git a/apps/server/src/utils/origin.ts b/apps/server/src/utils/origin.ts index 9540b8601..453e3c4a6 100644 --- a/apps/server/src/utils/origin.ts +++ b/apps/server/src/utils/origin.ts @@ -13,7 +13,7 @@ const TRUSTED_EXACT_ORIGINS = [ 'capacitor://localhost', // Capacitor mobile (iOS) 'ai.moeru.airi-pocket://links', // Android deep link 'https://airi.moeru.ai', // Production - 'https://auth.airi.build', // Standalone auth UI + 'https://accounts.airi.build', // Standalone auth UI 'https://server-dev.airi-server-auth.pages.dev', // Server-dev standalone auth UI 'https://admin.airi.build', // Standalone admin UI 'https://server-dev.airi-server-admin.pages.dev', // Server-dev standalone admin UI diff --git a/apps/server/src/utils/tests/auth-ui.test.ts b/apps/server/src/utils/tests/auth-ui.test.ts index 0caf7cedb..bf597b27f 100644 --- a/apps/server/src/utils/tests/auth-ui.test.ts +++ b/apps/server/src/utils/tests/auth-ui.test.ts @@ -4,16 +4,16 @@ import { buildAuthUiRedirectUrl, buildAuthUiUrl, resolveAuthUiUrl } from '../aut describe('auth UI URL helpers', () => { it('builds auth UI URLs under the configured auth base path', () => { - expect(buildAuthUiUrl('https://auth.airi.build/ui', '/sign-in', '?client_id=web')).toBe( - 'https://auth.airi.build/ui/sign-in?client_id=web', + expect(buildAuthUiUrl('https://accounts.airi.build/ui', '/sign-in', '?client_id=web')).toBe( + 'https://accounts.airi.build/ui/sign-in?client_id=web', ) }) it('maps server /auth requests to the standalone auth UI while preserving queries', () => { expect(buildAuthUiRedirectUrl( - 'https://auth.airi.build/ui/', + 'https://accounts.airi.build/ui/', 'https://api.airi.build/auth/verify-email?verified=true', - )).toBe('https://auth.airi.build/ui/verify-email?verified=true') + )).toBe('https://accounts.airi.build/ui/verify-email?verified=true') }) it('adds the API server origin for standalone auth UI cross-environment redirects', () => { @@ -28,7 +28,7 @@ describe('auth UI URL helpers', () => { it('routes server-dev default auth UI redirects to the matching Pages branch', () => { expect(buildAuthUiRedirectUrl( - 'https://auth.airi.build/ui', + 'https://accounts.airi.build/ui', 'https://airi-server-dev.up.railway.app/auth/sign-in?client_id=airi-stage-web', 'https://airi-server-dev.up.railway.app', )).toBe( diff --git a/apps/server/src/utils/tests/origin.test.ts b/apps/server/src/utils/tests/origin.test.ts index 031c2ab74..d912f34ca 100644 --- a/apps/server/src/utils/tests/origin.test.ts +++ b/apps/server/src/utils/tests/origin.test.ts @@ -13,7 +13,7 @@ describe('origin utils', () => { }) it('allows the standalone auth and admin UI origins', () => { - expect(getTrustedOrigin('https://auth.airi.build')).toBe('https://auth.airi.build') + expect(getTrustedOrigin('https://accounts.airi.build')).toBe('https://accounts.airi.build') expect(getTrustedOrigin('https://server-dev.airi-server-auth.pages.dev')).toBe('https://server-dev.airi-server-auth.pages.dev') expect(getTrustedOrigin('https://admin.airi.build')).toBe('https://admin.airi.build') expect(getTrustedOrigin('https://server-dev.airi-server-admin.pages.dev')).toBe('https://server-dev.airi-server-admin.pages.dev') diff --git a/apps/ui-server-auth/README.md b/apps/ui-server-auth/README.md index 30273f88d..01381e89e 100644 --- a/apps/ui-server-auth/README.md +++ b/apps/ui-server-auth/README.md @@ -40,6 +40,6 @@ Production expects: VITE_SERVER_URL=https://api.airi.build ``` -The server redirects historical `/auth/*` URLs to `AUTH_UI_URL`, which defaults to `https://auth.airi.build/ui`. +The server redirects historical `/auth/*` URLs to `AUTH_UI_URL`, which defaults to `https://accounts.airi.build/ui`. The `server-dev` workflow deploys a Cloudflare Pages branch build at `https://server-dev.moeru-ai-airi-auth.pages.dev/ui/` with `VITE_SERVER_URL=https://airi-server-dev.up.railway.app`. Set the server-dev API environment variable `AUTH_UI_URL=https://server-dev.moeru-ai-airi-auth.pages.dev/ui` when the full dev auth redirect chain should stay on server-dev. diff --git a/apps/ui-server-auth/src/modules/server-auth-context.test.ts b/apps/ui-server-auth/src/modules/server-auth-context.test.ts index e006e96ba..e52daad5a 100644 --- a/apps/ui-server-auth/src/modules/server-auth-context.test.ts +++ b/apps/ui-server-auth/src/modules/server-auth-context.test.ts @@ -7,17 +7,17 @@ import { getServerAuthBootstrapContext, resolveStandaloneServerAuthContext } fro describe('ui-server-auth bootstrap context', () => { it('uses the trusted API server origin carried by standalone server redirects', () => { expect(resolveStandaloneServerAuthContext( - 'https://auth.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fauth&client_id=airi-stage-web', + 'https://accounts.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fauth&client_id=airi-stage-web', 'https://api.airi.build', )).toEqual({ apiServerUrl: 'https://airi-server-dev.up.railway.app', - currentUrl: 'https://auth.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fauth&client_id=airi-stage-web', + currentUrl: 'https://accounts.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fairi-server-dev.up.railway.app%2Fapi%2Fauth&client_id=airi-stage-web', }) }) it('ignores untrusted API server origins from crafted standalone auth URLs', () => { expect(resolveStandaloneServerAuthContext( - 'https://auth.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fevil.example&client_id=airi-stage-web', + 'https://accounts.airi.build/ui/sign-in?api_server_url=https%3A%2F%2Fevil.example&client_id=airi-stage-web', 'https://api.airi.build', )).toBeNull() }) diff --git a/apps/ui-server-auth/src/modules/server-auth-context.ts b/apps/ui-server-auth/src/modules/server-auth-context.ts index 957e35afd..eab0d8b73 100644 --- a/apps/ui-server-auth/src/modules/server-auth-context.ts +++ b/apps/ui-server-auth/src/modules/server-auth-context.ts @@ -58,7 +58,7 @@ export function getServerAuthBootstrapContext(): ServerAuthBootstrapContext | nu * * Use when: * - The standalone auth UI serves more than one AIRI environment from the same - * Pages deployment, such as production `auth.airi.build` handling server-dev + * Pages deployment, such as production `accounts.airi.build` handling server-dev * OIDC redirects. * * Expects: diff --git a/packages/stage-ui/src/composables/use-linked-accounts.test.ts b/packages/stage-ui/src/composables/use-linked-accounts.test.ts index 0728eb9e9..9f8c17dde 100644 --- a/packages/stage-ui/src/composables/use-linked-accounts.test.ts +++ b/packages/stage-ui/src/composables/use-linked-accounts.test.ts @@ -24,7 +24,7 @@ describe('useLinkedAccounts', () => { }, isAuthenticated: ref(false), describeError: () => '', - buildCallbackURL: () => 'https://auth.airi.build/ui/profile', + buildCallbackURL: () => 'https://accounts.airi.build/ui/profile', messages: { listFailed: 'list failed', unlinkFailed: 'unlink failed', @@ -48,8 +48,8 @@ describe('useLinkedAccounts', () => { expect(linkSocial).toHaveBeenCalledWith({ provider: 'github', - callbackURL: 'https://auth.airi.build/ui/profile', - errorCallbackURL: 'https://auth.airi.build/ui/profile', + callbackURL: 'https://accounts.airi.build/ui/profile', + errorCallbackURL: 'https://accounts.airi.build/ui/profile', }) }) })