diff --git a/server/utils/oauth-strategies.ts b/server/utils/oauth-strategies.ts index f69e010..cf3b7d7 100644 --- a/server/utils/oauth-strategies.ts +++ b/server/utils/oauth-strategies.ts @@ -312,7 +312,98 @@ function aggregateSign(params: Record, appKey: string): string { return createHmac('sha256', appKey).update(sorted).digest('hex') } -const aggregateStrategy: OAuthStrategy = { +const auxxUserInfoCache = new Map() + +function isAuxxEndpoint(endpoint: string): boolean { + return /(^|\.)auxx\.cn(\/|$)/i.test(endpoint) +} + +const auxxStrategy: OAuthStrategy = { + async getAuthorizeUrl(redirectUri: string, state: string) { + const settings = await loadAggregateSettings() + if (!settings?.aggregateOAuthEnabled || !settings.aggregateOAuthAppId || !settings.aggregateOAuthAppKey || !settings.aggregateOAuthEndpoint) { + throw createError({ statusCode: 500, message: '聚合登录未配置' }) + } + if (!isSafeAggregateOAuthUrl(settings.aggregateOAuthEndpoint)) { + throw createError({ statusCode: 500, message: '聚合登录端点不安全或格式错误' }) + } + const loginTypes = getAggregateOAuthLoginTypesOrDefault(settings.aggregateOAuthLoginType) + const loginType = loginTypes[0] || 'qq' + const endpoint = normalizeEndpoint(settings.aggregateOAuthEndpoint) + const loginQuery = new URLSearchParams({ + act: 'login', + appid: settings.aggregateOAuthAppId, + appkey: settings.aggregateOAuthAppKey, + type: loginType, + redirect_uri: redirectUri + }).toString() + try { + const resp: any = await $fetch(`${endpoint}/connect.php?${loginQuery}`, { + headers: { Accept: 'application/json' } + }) + if (resp.code !== 0 || !resp.url) { + throw new Error(resp.msg || '获取登录跳转地址失败') + } + const sep = resp.url.includes('?') ? '&' : '?' + return `${resp.url}${sep}state=${encodeURIComponent(state)}` + } catch (e: any) { + console.error('Auxx OAuth authorize URL failed', e) + throw new Error(e.message || '获取登录跳转地址失败') + } + }, + async exchangeToken(code: string, redirectUri: string) { + const settings = await loadAggregateSettings() + if (!settings?.aggregateOAuthEnabled || !settings.aggregateOAuthAppId || !settings.aggregateOAuthAppKey || !settings.aggregateOAuthEndpoint) { + throw createError({ statusCode: 500, message: '聚合登录未配置' }) + } + const loginTypes = getAggregateOAuthLoginTypesOrDefault(settings.aggregateOAuthLoginType) + const loginType = loginTypes[0] || 'qq' + const endpoint = normalizeEndpoint(settings.aggregateOAuthEndpoint) + const cbQuery = new URLSearchParams({ + act: 'callback', + appid: settings.aggregateOAuthAppId, + appkey: settings.aggregateOAuthAppKey, + type: loginType, + code + }).toString() + try { + const resp: any = await $fetch(`${endpoint}/connect.php?${cbQuery}`, { + headers: { Accept: 'application/json' } + }) + if (resp.code !== 0 || !resp.access_token || !resp.social_uid) { + throw new Error(resp.msg || '回调换取用户信息失败') + } + const cacheKey = `${settings.aggregateOAuthAppId}:${resp.access_token}` + auxxUserInfoCache.set(cacheKey, resp) + setTimeout(() => auxxUserInfoCache.delete(cacheKey), 10 * 60 * 1000) + return String(resp.access_token) + } catch (e: any) { + console.error('Auxx OAuth token exchange failed', e) + throw new Error(e.message || '回调换取用户信息失败') + } + }, + async getUserInfo(accessToken: string) { + const settings = await loadAggregateSettings() + const appId = settings?.aggregateOAuthAppId || '' + const cacheKey = `${appId}:${accessToken}` + let resp = auxxUserInfoCache.get(cacheKey) + if (!resp) { + resp = Array.from(auxxUserInfoCache.values()).find((r: any) => r.access_token === accessToken) + } + if (!resp || resp.code !== 0 || !resp.social_uid) { + throw new Error('获取用户信息失败') + } + return { + id: String(resp.social_uid), + username: resp.nickname || `wx_${String(resp.social_uid).slice(0, 8)}`, + name: resp.nickname || undefined, + email: undefined, + avatar: resp.faceimg || undefined + } + } +} + +const standardAggregateStrategy: OAuthStrategy = { async getAuthorizeUrl(redirectUri: string, state: string) { const settings = await loadAggregateSettings() if (!settings?.aggregateOAuthEnabled || !settings.aggregateOAuthAppId || !settings.aggregateOAuthAppKey || !settings.aggregateOAuthEndpoint) { @@ -415,6 +506,30 @@ const aggregateStrategy: OAuthStrategy = { } } +const aggregateStrategy: OAuthStrategy = { + async getAuthorizeUrl(redirectUri: string, state: string) { + const settings = await loadAggregateSettings() + const endpoint = settings?.aggregateOAuthEndpoint || '' + return isAuxxEndpoint(endpoint) + ? auxxStrategy.getAuthorizeUrl(redirectUri, state) + : standardAggregateStrategy.getAuthorizeUrl(redirectUri, state) + }, + async exchangeToken(code: string, redirectUri: string) { + const settings = await loadAggregateSettings() + const endpoint = settings?.aggregateOAuthEndpoint || '' + return isAuxxEndpoint(endpoint) + ? auxxStrategy.exchangeToken(code, redirectUri) + : standardAggregateStrategy.exchangeToken(code, redirectUri) + }, + async getUserInfo(accessToken: string) { + const settings = await loadAggregateSettings() + const endpoint = settings?.aggregateOAuthEndpoint || '' + return isAuxxEndpoint(endpoint) + ? auxxStrategy.getUserInfo(accessToken) + : standardAggregateStrategy.getUserInfo(accessToken) + } +} + strategies.aggregate = aggregateStrategy export const getOAuthStrategy = (provider: string): OAuthStrategy => {