fix: preserve wechat bind resume state
This commit is contained in:
@@ -388,6 +388,15 @@ function resolveWeChatOAuthMode(): 'open' | 'mp' {
|
|||||||
return /MicroMessenger/i.test(navigator.userAgent) ? 'mp' : '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' {
|
||||||
|
const queryMode = normalizeWeChatOAuthMode(route.query.mode)
|
||||||
|
return queryMode || resolveWeChatOAuthMode()
|
||||||
|
}
|
||||||
|
|
||||||
function resolveRedirectTarget(): string {
|
function resolveRedirectTarget(): string {
|
||||||
return sanitizeRedirectPath(
|
return sanitizeRedirectPath(
|
||||||
(route.query.redirect as string | undefined) || redirectTo.value || '/dashboard'
|
(route.query.redirect as string | undefined) || redirectTo.value || '/dashboard'
|
||||||
@@ -398,7 +407,7 @@ function resolveWeChatStartURL(intent: 'bind_current_user' | 'adopt_existing_use
|
|||||||
const apiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined) || '/api/v1'
|
const apiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined) || '/api/v1'
|
||||||
const normalized = apiBase.replace(/\/$/, '')
|
const normalized = apiBase.replace(/\/$/, '')
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
mode: resolveWeChatOAuthMode(),
|
mode: resolveRequestedWeChatOAuthMode(),
|
||||||
redirect: resolveRedirectTarget(),
|
redirect: resolveRedirectTarget(),
|
||||||
intent,
|
intent,
|
||||||
})
|
})
|
||||||
@@ -415,6 +424,7 @@ function buildExistingAccountResumePath(): string {
|
|||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
wechat_bind_existing: '1',
|
wechat_bind_existing: '1',
|
||||||
redirect: resolveRedirectTarget(),
|
redirect: resolveRedirectTarget(),
|
||||||
|
mode: resolveRequestedWeChatOAuthMode(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const email = existingAccountEmail.value.trim()
|
const email = existingAccountEmail.value.trim()
|
||||||
@@ -727,9 +737,21 @@ onMounted(async () => {
|
|||||||
existingAccountEmail.value = route.query.email
|
existingAccountEmail.value = route.query.email
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.query.wechat_bind_existing === '1' && getAuthToken()) {
|
if (route.query.wechat_bind_existing === '1') {
|
||||||
prepareOAuthBindAccessTokenCookie()
|
if (getAuthToken()) {
|
||||||
window.location.href = resolveWeChatStartURL('bind_current_user')
|
prepareOAuthBindAccessTokenCookie()
|
||||||
|
window.location.href = resolveWeChatStartURL('bind_current_user')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
redirect: buildExistingAccountResumePath(),
|
||||||
|
})
|
||||||
|
const email = existingAccountEmail.value.trim()
|
||||||
|
if (email) {
|
||||||
|
params.set('email', email)
|
||||||
|
}
|
||||||
|
await router.replace(`/login?${params.toString()}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ describe('WechatCallbackView', () => {
|
|||||||
expect(replaceMock.mock.calls[0]?.[0]).toContain('/login?')
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('/login?')
|
||||||
expect(replaceMock.mock.calls[0]?.[0]).toContain('wechat_bind_existing%3D1')
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('wechat_bind_existing%3D1')
|
||||||
expect(replaceMock.mock.calls[0]?.[0]).toContain('email=user%40example.com')
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('email=user%40example.com')
|
||||||
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('mode%3Dopen')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('collects email for pending oauth account creation and submits adoption decisions', async () => {
|
it('collects email for pending oauth account creation and submits adoption decisions', async () => {
|
||||||
@@ -592,7 +593,8 @@ describe('WechatCallbackView', () => {
|
|||||||
it('restarts the current-user bind flow after returning from login', async () => {
|
it('restarts the current-user bind flow after returning from login', async () => {
|
||||||
routeState.query = {
|
routeState.query = {
|
||||||
wechat_bind_existing: '1',
|
wechat_bind_existing: '1',
|
||||||
redirect: '/profile'
|
redirect: '/profile',
|
||||||
|
mode: 'mp',
|
||||||
}
|
}
|
||||||
getAuthTokenMock.mockReturnValue('existing-auth-token')
|
getAuthTokenMock.mockReturnValue('existing-auth-token')
|
||||||
|
|
||||||
@@ -612,7 +614,38 @@ describe('WechatCallbackView', () => {
|
|||||||
expect(exchangePendingOAuthCompletionMock).not.toHaveBeenCalled()
|
expect(exchangePendingOAuthCompletionMock).not.toHaveBeenCalled()
|
||||||
expect(prepareOAuthBindAccessTokenCookieMock).toHaveBeenCalledTimes(1)
|
expect(prepareOAuthBindAccessTokenCookieMock).toHaveBeenCalledTimes(1)
|
||||||
expect(locationState.current.href).toContain('/api/v1/auth/oauth/wechat/start?')
|
expect(locationState.current.href).toContain('/api/v1/auth/oauth/wechat/start?')
|
||||||
|
expect(locationState.current.href).toContain('mode=mp')
|
||||||
expect(locationState.current.href).toContain('intent=bind_current_user')
|
expect(locationState.current.href).toContain('intent=bind_current_user')
|
||||||
expect(locationState.current.href).toContain('redirect=%2Fprofile')
|
expect(locationState.current.href).toContain('redirect=%2Fprofile')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('redirects back to login instead of falling through when bind-existing resume has no auth token', async () => {
|
||||||
|
routeState.query = {
|
||||||
|
wechat_bind_existing: '1',
|
||||||
|
redirect: '/profile',
|
||||||
|
mode: 'mp',
|
||||||
|
email: 'resume@example.com',
|
||||||
|
}
|
||||||
|
getAuthTokenMock.mockReturnValue(null)
|
||||||
|
|
||||||
|
mount(WechatCallbackView, {
|
||||||
|
global: {
|
||||||
|
stubs: {
|
||||||
|
AuthLayout: { template: '<div><slot /></div>' },
|
||||||
|
Icon: true,
|
||||||
|
RouterLink: { template: '<a><slot /></a>' },
|
||||||
|
transition: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
expect(exchangePendingOAuthCompletionMock).not.toHaveBeenCalled()
|
||||||
|
expect(replaceMock).toHaveBeenCalledTimes(1)
|
||||||
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('/login?')
|
||||||
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('wechat_bind_existing%3D1')
|
||||||
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('mode%3Dmp')
|
||||||
|
expect(replaceMock.mock.calls[0]?.[0]).toContain('email=resume%40example.com')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user