From 715dbf7077855dc0d9a9c2e5aff1dab54c0f61b3 Mon Sep 17 00:00:00 2001 From: Innei Date: Wed, 12 Nov 2025 17:56:22 +0800 Subject: [PATCH] refactor(oauth): remove redirect URI settings from system configuration - Removed oauthGoogleRedirectUri and oauthGithubRedirectUri from SYSTEM_SETTING_DEFINITIONS and related service logic. - Updated SystemSettingService to eliminate handling of redirect URIs for Google and GitHub. - Adjusted UI schema and types to reflect the removal of redirect URI fields. - Cleaned up associated validation and parsing logic to streamline OAuth configuration. Signed-off-by: Innei --- .../system-setting.constants.ts | 12 ---- .../system-setting/system-setting.service.ts | 64 ------------------- .../system-setting/system-setting.types.ts | 2 - .../system-setting.ui-schema.ts | 26 +------- .../src/modules/platform/auth/auth.config.ts | 1 - .../modules/platform/auth/auth.controller.ts | 6 +- .../modules/platform/auth/auth.provider.ts | 57 +++++++++++------ .../platform/super-admin/super-admin.dto.ts | 9 --- photos | 1 + 9 files changed, 43 insertions(+), 135 deletions(-) create mode 160000 photos diff --git a/be/apps/core/src/modules/configuration/system-setting/system-setting.constants.ts b/be/apps/core/src/modules/configuration/system-setting/system-setting.constants.ts index c369e67b..450aa9aa 100644 --- a/be/apps/core/src/modules/configuration/system-setting/system-setting.constants.ts +++ b/be/apps/core/src/modules/configuration/system-setting/system-setting.constants.ts @@ -58,12 +58,6 @@ export const SYSTEM_SETTING_DEFINITIONS = { defaultValue: null as string | null, isSensitive: true, }, - oauthGoogleRedirectUri: { - key: 'system.auth.oauth.google.redirectUri', - schema: nullableUrl, - defaultValue: null as string | null, - isSensitive: false, - }, oauthGithubClientId: { key: 'system.auth.oauth.github.clientId', schema: nullableNonEmptyString, @@ -76,12 +70,6 @@ export const SYSTEM_SETTING_DEFINITIONS = { defaultValue: null as string | null, isSensitive: true, }, - oauthGithubRedirectUri: { - key: 'system.auth.oauth.github.redirectUri', - schema: nullableUrl, - defaultValue: null as string | null, - isSensitive: false, - }, } as const export type SystemSettingField = keyof typeof SYSTEM_SETTING_DEFINITIONS diff --git a/be/apps/core/src/modules/configuration/system-setting/system-setting.service.ts b/be/apps/core/src/modules/configuration/system-setting/system-setting.service.ts index b049a51b..dd9c1382 100644 --- a/be/apps/core/src/modules/configuration/system-setting/system-setting.service.ts +++ b/be/apps/core/src/modules/configuration/system-setting/system-setting.service.ts @@ -71,14 +71,6 @@ export class SystemSettingService { SYSTEM_SETTING_DEFINITIONS.oauthGoogleClientSecret.schema, SYSTEM_SETTING_DEFINITIONS.oauthGoogleClientSecret.defaultValue, ) - const oauthGoogleRedirectUri = this.normalizeRedirectPath( - this.parseSetting( - rawValues[SYSTEM_SETTING_DEFINITIONS.oauthGoogleRedirectUri.key], - SYSTEM_SETTING_DEFINITIONS.oauthGoogleRedirectUri.schema, - SYSTEM_SETTING_DEFINITIONS.oauthGoogleRedirectUri.defaultValue, - ), - ) - const oauthGithubClientId = this.parseSetting( rawValues[SYSTEM_SETTING_DEFINITIONS.oauthGithubClientId.key], SYSTEM_SETTING_DEFINITIONS.oauthGithubClientId.schema, @@ -89,14 +81,6 @@ export class SystemSettingService { SYSTEM_SETTING_DEFINITIONS.oauthGithubClientSecret.schema, SYSTEM_SETTING_DEFINITIONS.oauthGithubClientSecret.defaultValue, ) - const oauthGithubRedirectUri = this.normalizeRedirectPath( - this.parseSetting( - rawValues[SYSTEM_SETTING_DEFINITIONS.oauthGithubRedirectUri.key], - SYSTEM_SETTING_DEFINITIONS.oauthGithubRedirectUri.schema, - SYSTEM_SETTING_DEFINITIONS.oauthGithubRedirectUri.defaultValue, - ), - ) - return { allowRegistration, maxRegistrableUsers, @@ -105,10 +89,8 @@ export class SystemSettingService { oauthGatewayUrl, oauthGoogleClientId, oauthGoogleClientSecret, - oauthGoogleRedirectUri, oauthGithubClientId, oauthGithubClientSecret, - oauthGithubRedirectUri, } } @@ -195,13 +177,6 @@ export class SystemSettingService { } } - if (patch.oauthGoogleRedirectUri !== undefined) { - const sanitized = this.normalizeRedirectPath(patch.oauthGoogleRedirectUri) - if (sanitized !== current.oauthGoogleRedirectUri) { - enqueueUpdate('oauthGoogleRedirectUri', sanitized) - } - } - if (patch.oauthGithubClientId !== undefined) { const sanitized = this.normalizeNullableString(patch.oauthGithubClientId) if (sanitized !== current.oauthGithubClientId) { @@ -216,13 +191,6 @@ export class SystemSettingService { } } - if (patch.oauthGithubRedirectUri !== undefined) { - const sanitized = this.normalizeRedirectPath(patch.oauthGithubRedirectUri) - if (sanitized !== current.oauthGithubRedirectUri) { - enqueueUpdate('oauthGithubRedirectUri', sanitized) - } - } - if (updates.length === 0) { return current } @@ -291,7 +259,6 @@ export class SystemSettingService { providers.google = { clientId: settings.oauthGoogleClientId, clientSecret: settings.oauthGoogleClientSecret, - redirectPath: settings.oauthGoogleRedirectUri, } } @@ -299,7 +266,6 @@ export class SystemSettingService { providers.github = { clientId: settings.oauthGithubClientId, clientSecret: settings.oauthGithubClientSecret, - redirectPath: settings.oauthGithubRedirectUri, } } @@ -337,36 +303,6 @@ export class SystemSettingService { } } - private normalizeRedirectPath(value: string | null | undefined): string | null { - if (value === undefined || value === null) { - return null - } - - const trimmed = value.trim() - if (trimmed.length === 0) { - return null - } - - const ensureLeadingSlash = (input: string): string | null => { - if (!input.startsWith('/')) { - return null - } - return input - } - - try { - if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) { - const url = new URL(trimmed) - const pathWithQuery = `${url.pathname}${url.search ?? ''}` - return ensureLeadingSlash(pathWithQuery) ?? null - } - } catch { - // fall through to path handling - } - - return ensureLeadingSlash(trimmed) - } - private buildStats(settings: SystemSettings, totalUsers: number): SystemSettingStats { const remaining = settings.maxRegistrableUsers === null ? null : Math.max(settings.maxRegistrableUsers - totalUsers, 0) diff --git a/be/apps/core/src/modules/configuration/system-setting/system-setting.types.ts b/be/apps/core/src/modules/configuration/system-setting/system-setting.types.ts index 2197a1bc..d6d3ff7b 100644 --- a/be/apps/core/src/modules/configuration/system-setting/system-setting.types.ts +++ b/be/apps/core/src/modules/configuration/system-setting/system-setting.types.ts @@ -10,10 +10,8 @@ export interface SystemSettings { oauthGatewayUrl: string | null oauthGoogleClientId: string | null oauthGoogleClientSecret: string | null - oauthGoogleRedirectUri: string | null oauthGithubClientId: string | null oauthGithubClientSecret: string | null - oauthGithubRedirectUri: string | null } export type SystemSettingValueMap = { diff --git a/be/apps/core/src/modules/configuration/system-setting/system-setting.ui-schema.ts b/be/apps/core/src/modules/configuration/system-setting/system-setting.ui-schema.ts index b4707674..38547efd 100644 --- a/be/apps/core/src/modules/configuration/system-setting/system-setting.ui-schema.ts +++ b/be/apps/core/src/modules/configuration/system-setting/system-setting.ui-schema.ts @@ -2,7 +2,7 @@ import type { UiNode, UiSchema } from 'core/modules/ui/ui-schema/ui-schema.type' import type { SystemSettingField } from './system-setting.constants' -export const SYSTEM_SETTING_UI_SCHEMA_VERSION = '1.2.0' +export const SYSTEM_SETTING_UI_SCHEMA_VERSION = '1.3.0' export const SYSTEM_SETTING_UI_SCHEMA: UiSchema = { version: SYSTEM_SETTING_UI_SCHEMA_VERSION, @@ -74,7 +74,7 @@ export const SYSTEM_SETTING_UI_SCHEMA: UiSchema = { type: 'field', id: 'oauth-gateway-url', title: 'OAuth 网关地址', - description: '统一的 OAuth 回调入口,例如 https://auth.afilmory.art。留空则直接回调到租户域名。', + description: '所有第三方登录统一走该回调入口(例如 https://auth.afilmory.art)。留空则回退到租户域名。', helperText: '必须包含 http/https 协议,结尾无需斜杠。', key: 'oauthGatewayUrl', component: { @@ -113,17 +113,6 @@ export const SYSTEM_SETTING_UI_SCHEMA: UiSchema = { autoComplete: 'off', }, }, - { - type: 'field', - id: 'oauth-google-redirect-uri', - title: 'Redirect URI', - description: 'OAuth 回调路径,域名会自动使用当前租户如 slug.主域名。', - key: 'oauthGoogleRedirectUri', - component: { - type: 'text', - placeholder: '/api/auth/callback/google', - }, - }, ], }, { @@ -157,17 +146,6 @@ export const SYSTEM_SETTING_UI_SCHEMA: UiSchema = { autoComplete: 'off', }, }, - { - type: 'field', - id: 'oauth-github-redirect-uri', - title: 'Redirect URI', - description: 'GitHub 回调路径,域名会自动使用租户的 subdomain。', - key: 'oauthGithubRedirectUri', - component: { - type: 'text', - placeholder: '/api/auth/callback/github', - }, - }, ], }, ], diff --git a/be/apps/core/src/modules/platform/auth/auth.config.ts b/be/apps/core/src/modules/platform/auth/auth.config.ts index c44d4a77..bbef22bc 100644 --- a/be/apps/core/src/modules/platform/auth/auth.config.ts +++ b/be/apps/core/src/modules/platform/auth/auth.config.ts @@ -4,7 +4,6 @@ import { injectable } from 'tsyringe' export interface SocialProviderOptions { clientId: string clientSecret: string - redirectPath?: string | null } export interface SocialProvidersConfig { diff --git a/be/apps/core/src/modules/platform/auth/auth.controller.ts b/be/apps/core/src/modules/platform/auth/auth.controller.ts index fa4e87d3..1c414b00 100644 --- a/be/apps/core/src/modules/platform/auth/auth.controller.ts +++ b/be/apps/core/src/modules/platform/auth/auth.controller.ts @@ -43,13 +43,13 @@ function resolveSocialProviderMetadata(id: string): { name: string; icon: string function buildProviderResponse(socialProviders: SocialProvidersConfig) { return Object.entries(socialProviders) .filter(([, config]) => Boolean(config)) - .map(([id, config]) => { + .map(([id]) => { const metadata = resolveSocialProviderMetadata(id) return { id, name: metadata.name, icon: metadata.icon, - callbackPath: config?.redirectPath ?? null, + callbackPath: `/api/auth/callback/${id}`, } }) } @@ -120,7 +120,7 @@ export class AuthController { } if (!tenantContext || isPlaceholderTenantContext(tenantContext)) { - const {tenantId} = (authContext.user as { tenantId?: string | null }) + const { tenantId } = authContext.user as { tenantId?: string | null } if (tenantId) { try { const aggregate = await this.tenantService.getById(tenantId) diff --git a/be/apps/core/src/modules/platform/auth/auth.provider.ts b/be/apps/core/src/modules/platform/auth/auth.provider.ts index 4908018c..30344fbb 100644 --- a/be/apps/core/src/modules/platform/auth/auth.provider.ts +++ b/be/apps/core/src/modules/platform/auth/auth.provider.ts @@ -1,3 +1,5 @@ +import { createHash } from 'node:crypto' + import { authAccounts, authSessions, authUsers, authVerifications, generateId } from '@afilmory/db' import type { OnModuleInit } from '@afilmory/framework' import { createLogger, HttpContext } from '@afilmory/framework' @@ -21,7 +23,6 @@ const logger = createLogger('Auth') @injectable() export class AuthProvider implements OnModuleInit { - private moduleOptionsPromise?: Promise private instances = new Map>() private placeholderTenantId: string | null = null @@ -33,7 +34,7 @@ export class AuthProvider implements OnModuleInit { ) {} async onModuleInit(): Promise { - await this.getAuth() + await this.config.getOptions() } private resolveTenantIdFromContext(): string | null { @@ -71,13 +72,6 @@ export class AuthProvider implements OnModuleInit { return sanitizedSlug ? `better-auth-${sanitizedSlug}` : 'better-auth' } - private async getModuleOptions(): Promise { - if (!this.moduleOptionsPromise) { - this.moduleOptionsPromise = this.config.getOptions() - } - return this.moduleOptionsPromise - } - private async resolveFallbackTenantId(): Promise { if (this.placeholderTenantId) { return this.placeholderTenantId @@ -152,7 +146,7 @@ export class AuthProvider implements OnModuleInit { return entries.reduce>( (acc, [key, value]) => { - const redirectUri = this.buildRedirectUri(tenantSlug, key, value, oauthGatewayUrl) + const redirectUri = this.buildRedirectUri(tenantSlug, key, oauthGatewayUrl) acc[key] = { clientId: value.clientId, clientSecret: value.clientSecret, @@ -167,13 +161,10 @@ export class AuthProvider implements OnModuleInit { private buildRedirectUri( tenantSlug: string | null, provider: keyof SocialProvidersConfig, - options: SocialProviderOptions, oauthGatewayUrl: string | null, ): string | null { - const basePath = options.redirectPath ?? `/api/auth/callback/${provider}` - if (!basePath.startsWith('/')) { - return null - } + const basePath = `/api/auth/callback/${provider}` + if (oauthGatewayUrl) { return this.buildGatewayRedirectUri(oauthGatewayUrl, basePath, tenantSlug) } @@ -193,8 +184,10 @@ export class AuthProvider implements OnModuleInit { return `${normalizedBase}${basePath}${query ? `?${query}` : ''}` } - private async createAuthForEndpoint(tenantSlug: string | null): Promise { - const options = await this.getModuleOptions() + private async createAuthForEndpoint( + tenantSlug: string | null, + options: AuthModuleOptions, + ): Promise { const db = this.drizzleProvider.getDb() const socialProviders = this.buildBetterAuthProvidersForHost( tenantSlug, @@ -323,7 +316,7 @@ export class AuthProvider implements OnModuleInit { } async getAuth(): Promise { - const options = await this.getModuleOptions() + const options = await this.config.getOptions() const endpoint = this.resolveRequestEndpoint() const fallbackHost = options.baseDomain.trim().toLowerCase() const requestedHost = (endpoint.host ?? fallbackHost).trim().toLowerCase() @@ -331,10 +324,11 @@ export class AuthProvider implements OnModuleInit { const host = this.applyTenantSlugToHost(requestedHost || fallbackHost, fallbackHost, tenantSlug) const protocol = this.determineProtocol(host, endpoint.protocol) const slugKey = tenantSlug ?? 'global' - const cacheKey = `${protocol}://${host}::${slugKey}` + const optionSignature = this.computeOptionsSignature(options) + const cacheKey = `${protocol}://${host}::${slugKey}::${optionSignature}` if (!this.instances.has(cacheKey)) { - const instancePromise = this.createAuthForEndpoint(tenantSlug).then((instance) => { + const instancePromise = this.createAuthForEndpoint(tenantSlug, options).then((instance) => { logger.info(`Better Auth initialized for ${cacheKey}`) return instance }) @@ -344,6 +338,29 @@ export class AuthProvider implements OnModuleInit { return await this.instances.get(cacheKey)! } + private computeOptionsSignature(options: AuthModuleOptions): string { + const hash = createHash('sha256') + hash.update(options.baseDomain) + hash.update('|gateway=') + hash.update(options.oauthGatewayUrl ?? 'null') + + const providerEntries = Object.entries(options.socialProviders) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([provider, config]) => { + const secretHash = config?.clientSecret + ? createHash('sha256').update(config.clientSecret).digest('hex') + : 'null' + return { + provider, + clientId: config?.clientId ?? '', + secretHash, + } + }) + + hash.update(JSON.stringify(providerEntries)) + return hash.digest('hex') + } + async handler(context: Context): Promise { const auth = await this.getAuth() return auth.handler(context.req.raw) diff --git a/be/apps/core/src/modules/platform/super-admin/super-admin.dto.ts b/be/apps/core/src/modules/platform/super-admin/super-admin.dto.ts index 68ca75b8..edb4305e 100644 --- a/be/apps/core/src/modules/platform/super-admin/super-admin.dto.ts +++ b/be/apps/core/src/modules/platform/super-admin/super-admin.dto.ts @@ -1,13 +1,6 @@ import { createZodDto } from '@afilmory/framework' import { z } from 'zod' -const redirectPathInputSchema = z - .string() - .trim() - .refine((value) => value.length === 0 || value.startsWith('/'), { - message: '路径必须以 / 开头', - }) - const updateSuperAdminSettingsSchema = z .object({ allowRegistration: z.boolean().optional(), @@ -30,10 +23,8 @@ const updateSuperAdminSettingsSchema = z .optional(), oauthGoogleClientId: z.string().trim().min(1).nullable().optional(), oauthGoogleClientSecret: z.string().trim().min(1).nullable().optional(), - oauthGoogleRedirectUri: redirectPathInputSchema.nullable().optional(), oauthGithubClientId: z.string().trim().min(1).nullable().optional(), oauthGithubClientSecret: z.string().trim().min(1).nullable().optional(), - oauthGithubRedirectUri: redirectPathInputSchema.nullable().optional(), }) .refine((value) => Object.values(value).some((entry) => entry !== undefined), { message: '至少需要更新一项设置', diff --git a/photos b/photos new file mode 160000 index 00000000..906be11f --- /dev/null +++ b/photos @@ -0,0 +1 @@ +Subproject commit 906be11f8ee865bfc89b46f23514de293bec9745