feat(auth): aggregate OAuth 支持 auxx.cn 自定义规格(按 endpoint 自动分派)\n\n- oauth-strategies.ts: endpoint 命中 auxx.cn 时走新的 auxxStrategy\n - getAuthorizeUrl: GET {endpoint}/connect.php?act=login 拿 {url} 后转发(附 state)\n - exchangeToken: 直接 GET {endpoint}/connect.php?act=callback 拿 social_uid/access_token/nickname/faceimg,10 分钟内存缓存供 getUserInfo 读取\n - getUserInfo: 命中缓存返回 OAuthUserInfo(id=social_uid, username=nickname, avatar=faceimg)\n- 标准 OAuth2 /oauth/... 规格仍由 standardAggregateStrategy 保留,非 auxx 域名不受影响
This commit is contained in:
@@ -312,7 +312,98 @@ function aggregateSign(params: Record<string, string>, appKey: string): string {
|
||||
return createHmac('sha256', appKey).update(sorted).digest('hex')
|
||||
}
|
||||
|
||||
const aggregateStrategy: OAuthStrategy = {
|
||||
const auxxUserInfoCache = new Map<string, any>()
|
||||
|
||||
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 => {
|
||||
|
||||
Reference in New Issue
Block a user