"frontend/src/vscode:/vscode.git/clone" did not exist on "a161f9d045d463515f2363efed96611bbab9b041"
Commit 7fdede57 authored by IanShaw027's avatar IanShaw027
Browse files

fix: preserve wechat bind resume state

parent 4d10ba42
...@@ -388,6 +388,15 @@ function resolveWeChatOAuthMode(): 'open' | 'mp' { ...@@ -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 ...@@ -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 { ...@@ -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,12 +737,24 @@ onMounted(async () => { ...@@ -727,12 +737,24 @@ 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') {
if (getAuthToken()) {
prepareOAuthBindAccessTokenCookie() prepareOAuthBindAccessTokenCookie()
window.location.href = resolveWeChatStartURL('bind_current_user') window.location.href = resolveWeChatStartURL('bind_current_user')
return 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
}
const params = parseFragmentParams() const params = parseFragmentParams()
const error = params.get('error') const error = params.get('error')
const errorDesc = params.get('error_description') || params.get('error_message') || '' const errorDesc = params.get('error_description') || params.get('error_message') || ''
......
...@@ -342,6 +342,7 @@ describe('WechatCallbackView', () => { ...@@ -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', () => { ...@@ -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', () => { ...@@ -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')
})
}) })
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment