fix: tighten pending oauth email routing and binding state

This commit is contained in:
IanShaw027 committed 2026-04-21 10:41:29 +08:00
1 parent dcd5c43da4
commit 7e89bca5e6
9 files changed
+685 -54

No files matched your search

@@ -8,6 +8,7 @@ const {
showErrorMock,
registerMock,
setTokenMock,
setPendingAuthSessionMock,
clearPendingAuthSessionMock,
getPublicSettingsMock,
sendVerifyCodeMock,
@@ -21,6 +22,7 @@ const {
showErrorMock: vi.fn(),
registerMock: vi.fn(),
setTokenMock: vi.fn(),
setPendingAuthSessionMock: vi.fn(),
clearPendingAuthSessionMock: vi.fn(),
getPublicSettingsMock: vi.fn(),
sendVerifyCodeMock: vi.fn(),
@@ -68,6 +70,7 @@ vi.mock('@/stores', () => ({
pendingAuthSession: authStoreState.pendingAuthSession,
register: (...args: any[]) => registerMock(...args),
setToken: (...args: any[]) => setTokenMock(...args),
setPendingAuthSession: (...args: any[]) => setPendingAuthSessionMock(...args),
clearPendingAuthSession: (...args: any[]) => clearPendingAuthSessionMock(...args),
}),
useAppStore: () => ({
@@ -100,6 +103,7 @@ describe('EmailVerifyView', () => {
showErrorMock.mockReset()
registerMock.mockReset()
setTokenMock.mockReset()
setPendingAuthSessionMock.mockReset()
clearPendingAuthSessionMock.mockReset()
getPublicSettingsMock.mockReset()
sendVerifyCodeMock.mockReset()
@@ -196,6 +200,97 @@ describe('EmailVerifyView', () => {
expect(showErrorMock).not.toHaveBeenCalled()
})
it('uses the pending oauth verify-code endpoint when auth store only carries the pending provider', async () => {
authStoreState.pendingAuthSession = {
token: '',
token_field: 'pending_oauth_token',
provider: 'oidc',
redirect: '/profile',
}
getPublicSettingsMock.mockResolvedValue({
turnstile_enabled: false,
turnstile_site_key: '',
site_name: 'Sub2API',
registration_email_suffix_whitelist: ['allowed.com'],
})
sessionStorage.setItem(
'register_data',
JSON.stringify({
email: 'fresh@example.com',
password: 'secret-123',
})
)
mount(EmailVerifyView, {
global: {
stubs: {
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
Icon: true,
TurnstileWidget: true,
transition: false,
},
},
})
await flushPromises()
expect(sendPendingOAuthVerifyCodeMock).toHaveBeenCalledWith({
email: 'fresh@example.com',
pending_oauth_token: undefined,
})
expect(sendVerifyCodeMock).not.toHaveBeenCalled()
expect(showErrorMock).not.toHaveBeenCalled()
})
it('returns to the oauth callback flow when pending send-code detects an existing account email', async () => {
authStoreState.pendingAuthSession = {
token: '',
token_field: 'pending_oauth_token',
provider: 'oidc',
redirect: '/profile/security',
}
getPublicSettingsMock.mockResolvedValue({
turnstile_enabled: false,
turnstile_site_key: '',
site_name: 'Sub2API',
registration_email_suffix_whitelist: ['allowed.com'],
})
sendPendingOAuthVerifyCodeMock.mockResolvedValue({
auth_result: 'pending_session',
provider: 'oidc',
redirect: '/profile/security',
})
sessionStorage.setItem(
'register_data',
JSON.stringify({
email: 'fresh@example.com',
password: 'secret-123',
})
)
mount(EmailVerifyView, {
global: {
stubs: {
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
Icon: true,
TurnstileWidget: true,
transition: false,
},
},
})
await flushPromises()
expect(setPendingAuthSessionMock).toHaveBeenCalledWith({
token: '',
token_field: 'pending_oauth_token',
provider: 'oidc',
redirect: '/profile/security',
})
expect(pushMock).toHaveBeenCalledWith('/auth/oidc/callback')
expect(showErrorMock).not.toHaveBeenCalled()
})
it('submits pending auth account creation when session storage has no pending metadata but auth store does', async () => {
authStoreState.pendingAuthSession = {
token: 'pending-token-1',
@@ -252,6 +347,70 @@ describe('EmailVerifyView', () => {
expect(registerMock).not.toHaveBeenCalled()
})
it('returns to the oauth callback flow when pending account creation becomes bind-login', async () => {
authStoreState.pendingAuthSession = {
token: '',
token_field: 'pending_oauth_token',
provider: 'oidc',
redirect: '/profile/security',
}
getPublicSettingsMock.mockResolvedValue({
turnstile_enabled: false,
turnstile_site_key: '',
site_name: 'Sub2API',
registration_email_suffix_whitelist: ['allowed.com'],
})
sessionStorage.setItem(
'register_data',
JSON.stringify({
email: 'fresh@example.com',
password: 'secret-123',
})
)
apiClientPostMock.mockResolvedValue({
data: {
auth_result: 'pending_session',
provider: 'oidc',
step: 'bind_login_required',
redirect: '/profile/security',
email: 'fresh@example.com',
},
})
const wrapper = mount(EmailVerifyView, {
global: {
stubs: {
AuthLayout: { template: '<div><slot /><slot name="footer" /></div>' },
Icon: true,
TurnstileWidget: true,
transition: false,
},
},
})
await flushPromises()
await wrapper.get('#code').setValue('123456')
await wrapper.get('form').trigger('submit.prevent')
await flushPromises()
expect(apiClientPostMock).toHaveBeenCalledWith('/auth/oauth/pending/create-account', {
email: 'fresh@example.com',
password: 'secret-123',
verify_code: '123456',
})
expect(setPendingAuthSessionMock).toHaveBeenCalledWith({
token: '',
token_field: 'pending_oauth_token',
provider: 'oidc',
redirect: '/profile/security',
})
expect(pushMock).toHaveBeenCalledWith('/auth/oidc/callback')
expect(setTokenMock).not.toHaveBeenCalled()
expect(persistOAuthTokenContextMock).not.toHaveBeenCalled()
expect(clearPendingAuthSessionMock).not.toHaveBeenCalled()
expect(showSuccessMock).not.toHaveBeenCalled()
})
it('keeps the normal email registration flow unchanged', async () => {
sessionStorage.setItem(
'register_data',