mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-05-06 06:00:44 +08:00
fix frontend wechat oauth capability recovery
This commit is contained in:
@@ -292,12 +292,13 @@ import {
|
||||
completeWeChatOAuthRegistration,
|
||||
exchangePendingOAuthCompletion,
|
||||
getAuthToken,
|
||||
hasExplicitWeChatOAuthCapabilities,
|
||||
getOAuthCompletionKind,
|
||||
isOAuthLoginCompletion,
|
||||
login2FA,
|
||||
prepareOAuthBindAccessTokenCookie,
|
||||
persistOAuthTokenContext,
|
||||
resolveWeChatOAuthStart,
|
||||
resolveWeChatOAuthStartStrict,
|
||||
type OAuthAdoptionDecision,
|
||||
type PendingOAuthExchangeResponse
|
||||
} from '@/api/auth'
|
||||
@@ -368,41 +369,32 @@ function sanitizeRedirectPath(path: string | null | undefined): string {
|
||||
return path
|
||||
}
|
||||
|
||||
function resolveWeChatOAuthMode(): 'open' | 'mp' {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'open'
|
||||
}
|
||||
return /MicroMessenger/i.test(navigator.userAgent) ? 'mp' : 'open'
|
||||
}
|
||||
|
||||
function normalizeWeChatOAuthMode(value: unknown): 'open' | 'mp' | null {
|
||||
return value === 'open' || value === 'mp' ? value : null
|
||||
}
|
||||
|
||||
async function ensurePublicSettingsLoaded(): Promise<void> {
|
||||
if (appStore.cachedPublicSettings || appStore.publicSettingsLoaded) {
|
||||
if (hasExplicitWeChatOAuthCapabilities(appStore.cachedPublicSettings)) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await appStore.fetchPublicSettings()
|
||||
} catch {
|
||||
// Fall back to legacy mode selection when public settings are unavailable.
|
||||
if (appStore.publicSettingsLoaded) {
|
||||
return
|
||||
}
|
||||
|
||||
await appStore.fetchPublicSettings()
|
||||
}
|
||||
|
||||
function resolveConfiguredWeChatOAuthMode(): 'open' | 'mp' | null {
|
||||
if (!appStore.cachedPublicSettings && !appStore.publicSettingsLoaded) {
|
||||
if (!hasExplicitWeChatOAuthCapabilities(appStore.cachedPublicSettings)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return resolveWeChatOAuthStart(appStore.cachedPublicSettings).mode
|
||||
return resolveWeChatOAuthStartStrict(appStore.cachedPublicSettings).mode
|
||||
}
|
||||
|
||||
function resolveWeChatOAuthUnavailableMessage(): string {
|
||||
const resolved = resolveWeChatOAuthStart(appStore.cachedPublicSettings)
|
||||
const resolved = resolveWeChatOAuthStartStrict(appStore.cachedPublicSettings)
|
||||
|
||||
switch (resolved.unavailableReason) {
|
||||
case 'capability_unknown':
|
||||
return 'WeChat sign-in availability could not be confirmed. Refresh and retry.'
|
||||
case 'external_browser_required':
|
||||
return 'This WeChat sign-in flow is only available in your system browser.'
|
||||
case 'wechat_browser_required':
|
||||
@@ -414,6 +406,17 @@ function resolveWeChatOAuthUnavailableMessage(): string {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRuntimeWeChatOAuthMode(): 'open' | 'mp' {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'open'
|
||||
}
|
||||
return /MicroMessenger/i.test(navigator.userAgent) ? 'mp' : 'open'
|
||||
}
|
||||
|
||||
function normalizeWeChatOAuthMode(value: unknown): 'open' | 'mp' | null {
|
||||
return value === 'open' || value === 'mp' ? value : null
|
||||
}
|
||||
|
||||
function resolveRequestedWeChatOAuthMode(): 'open' | 'mp' | null {
|
||||
const configuredMode = resolveConfiguredWeChatOAuthMode()
|
||||
if (configuredMode) {
|
||||
@@ -421,7 +424,11 @@ function resolveRequestedWeChatOAuthMode(): 'open' | 'mp' | null {
|
||||
}
|
||||
|
||||
const queryMode = normalizeWeChatOAuthMode(route.query.mode)
|
||||
return queryMode || resolveWeChatOAuthMode()
|
||||
if (queryMode) {
|
||||
return queryMode
|
||||
}
|
||||
|
||||
return resolveRuntimeWeChatOAuthMode()
|
||||
}
|
||||
|
||||
function resolveRedirectTarget(): string {
|
||||
@@ -786,7 +793,11 @@ async function handleSubmitTotpChallenge() {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await ensurePublicSettingsLoaded()
|
||||
try {
|
||||
await ensurePublicSettingsLoaded()
|
||||
} catch {
|
||||
// Binding recovery requires confirmed capability flags. Use the strict guard below.
|
||||
}
|
||||
|
||||
if (typeof route.query.email === 'string') {
|
||||
existingAccountEmail.value = route.query.email
|
||||
|
||||
@@ -201,6 +201,61 @@ describe('WechatCallbackView', () => {
|
||||
expect(locationState.current.href).not.toContain('mode=mp')
|
||||
})
|
||||
|
||||
it('falls back to the query mode when capability settings cannot be confirmed', async () => {
|
||||
routeState.query = {
|
||||
wechat_bind_existing: '1',
|
||||
mode: 'mp',
|
||||
redirect: '/profile',
|
||||
}
|
||||
fetchPublicSettingsMock.mockResolvedValue(null)
|
||||
getAuthTokenMock.mockReturnValue('current-auth-token')
|
||||
|
||||
mount(WechatCallbackView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /></div>' },
|
||||
Icon: true,
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(prepareOAuthBindAccessTokenCookieMock).toHaveBeenCalledTimes(1)
|
||||
expect(locationState.current.href).toContain('mode=mp')
|
||||
})
|
||||
|
||||
it('ignores legacy aggregate wechat settings and reuses the query mode during bind recovery', async () => {
|
||||
routeState.query = {
|
||||
wechat_bind_existing: '1',
|
||||
mode: 'open',
|
||||
redirect: '/profile',
|
||||
}
|
||||
appStoreState.cachedPublicSettings = {
|
||||
wechat_oauth_enabled: true,
|
||||
}
|
||||
appStoreState.publicSettingsLoaded = true
|
||||
getAuthTokenMock.mockReturnValue('current-auth-token')
|
||||
|
||||
mount(WechatCallbackView, {
|
||||
global: {
|
||||
stubs: {
|
||||
AuthLayout: { template: '<div><slot /></div>' },
|
||||
Icon: true,
|
||||
RouterLink: { template: '<a><slot /></a>' },
|
||||
transition: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await flushPromises()
|
||||
|
||||
expect(prepareOAuthBindAccessTokenCookieMock).toHaveBeenCalledTimes(1)
|
||||
expect(locationState.current.href).toContain('mode=open')
|
||||
})
|
||||
|
||||
it('does not send adoption decisions during the initial exchange', async () => {
|
||||
exchangePendingOAuthCompletionMock.mockResolvedValue({
|
||||
access_token: 'access-token',
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
:oidc-enabled="oidcOAuthEnabled"
|
||||
:oidc-provider-name="oidcOAuthProviderName"
|
||||
:wechat-enabled="wechatOAuthEnabled"
|
||||
:wechat-open-enabled="wechatOAuthOpenEnabled"
|
||||
:wechat-mp-enabled="wechatOAuthMPEnabled"
|
||||
/>
|
||||
|
||||
<div
|
||||
@@ -89,6 +91,8 @@ const balanceLowNotifyEnabled = ref(false)
|
||||
const systemDefaultThreshold = ref(0)
|
||||
const linuxdoOAuthEnabled = ref(false)
|
||||
const wechatOAuthEnabled = ref(false)
|
||||
const wechatOAuthOpenEnabled = ref<boolean | undefined>(undefined)
|
||||
const wechatOAuthMPEnabled = ref<boolean | undefined>(undefined)
|
||||
const oidcOAuthEnabled = ref(false)
|
||||
const oidcOAuthProviderName = ref('OIDC')
|
||||
|
||||
@@ -132,6 +136,12 @@ onMounted(async () => {
|
||||
systemDefaultThreshold.value = settings.balance_low_notify_threshold ?? 0
|
||||
linuxdoOAuthEnabled.value = settings.linuxdo_oauth_enabled ?? false
|
||||
wechatOAuthEnabled.value = settings.wechat_oauth_enabled ?? false
|
||||
wechatOAuthOpenEnabled.value = typeof settings.wechat_oauth_open_enabled === 'boolean'
|
||||
? settings.wechat_oauth_open_enabled
|
||||
: undefined
|
||||
wechatOAuthMPEnabled.value = typeof settings.wechat_oauth_mp_enabled === 'boolean'
|
||||
? settings.wechat_oauth_mp_enabled
|
||||
: undefined
|
||||
oidcOAuthEnabled.value = settings.oidc_oauth_enabled ?? false
|
||||
oidcOAuthProviderName.value = settings.oidc_oauth_provider_name || 'OIDC'
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user