feat(auth): update AUTH_UI_URL to point to accounts.airi.build

This commit is contained in:
RainbowBird
2026-06-09 18:01:34 +08:00
parent 7fa7f8a072
commit a331d3a42a
15 changed files with 29 additions and 26 deletions
@@ -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
+1 -1
View File
@@ -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.
@@ -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`<UI_BASE>/verify-email?token=<token>`
- Reset password`<UI_BASE>/reset-password?token=<token>`
-`getAuthTrustedOrigins(request)` 第一个匹配的 origin 决定 `<UI_BASE>`,避免硬编码。
+1 -1
View File
@@ -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
+5 -2
View File
@@ -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)
})
+1 -1
View File
@@ -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([])
})
@@ -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')
})
})
+1 -1
View File
@@ -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'
+1 -1
View File
@@ -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
+5 -5
View File
@@ -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(
+1 -1
View File
@@ -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')
+1 -1
View File
@@ -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.
@@ -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()
})
@@ -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:
@@ -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',
})
})
})