fix: 修复邮箱快捷登录前端回调兜底
This commit is contained in:
parent
480fe27b31
commit
7f185422a5
@ -34,6 +34,7 @@ import GoogleMark from './GoogleMark.vue'
|
||||
import { resolveAffiliateReferralCode, storeOAuthAffiliateCode } from '@/utils/oauthAffiliate'
|
||||
|
||||
type EmailOAuthProvider = 'github' | 'google'
|
||||
const EMAIL_OAUTH_PENDING_PROVIDER_KEY = 'email_oauth_pending_provider'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
disabled?: boolean
|
||||
@ -73,6 +74,7 @@ function startLogin(provider: EmailOAuthProvider): void {
|
||||
const redirectTo = (route.query.redirect as string) || '/dashboard'
|
||||
const affiliateCode = resolveAffiliateReferralCode(props.affCode, route.query.aff, route.query.aff_code)
|
||||
storeOAuthAffiliateCode(affiliateCode)
|
||||
window.sessionStorage.setItem(EMAIL_OAUTH_PENDING_PROVIDER_KEY, provider)
|
||||
const apiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined) || '/api/v1'
|
||||
const normalized = apiBase.replace(/\/$/, '')
|
||||
const params = new URLSearchParams({ redirect: redirectTo })
|
||||
|
||||
@ -57,6 +57,7 @@ describe('EmailOAuthButtons', () => {
|
||||
'/api/v1/auth/oauth/github/start?redirect=%2Fbilling%3Fplan%3Dpro&aff_code=AFF123'
|
||||
)
|
||||
expect(window.sessionStorage.getItem('oauth_aff_code')).toBe('AFF123')
|
||||
expect(window.sessionStorage.getItem('email_oauth_pending_provider')).toBe('github')
|
||||
})
|
||||
|
||||
it('uses a full-width descriptive button when only GitHub is enabled', () => {
|
||||
|
||||
@ -140,6 +140,7 @@ const invitationError = ref('')
|
||||
const pendingProvider = ref<'github' | 'google'>('github')
|
||||
const redirectTo = ref('/dashboard')
|
||||
const invalidCallback = ref(false)
|
||||
const EMAIL_OAUTH_PENDING_PROVIDER_KEY = 'email_oauth_pending_provider'
|
||||
|
||||
type EmailOAuthPendingCompletion = Partial<OAuthTokenResponse> & {
|
||||
error?: string
|
||||
@ -190,9 +191,37 @@ function sanitizeRedirectPath(path: string | null | undefined): string {
|
||||
return path
|
||||
}
|
||||
|
||||
function readPendingEmailOAuthProvider(): 'github' | 'google' | null {
|
||||
if (typeof window === 'undefined') return null
|
||||
const provider = window.sessionStorage.getItem(EMAIL_OAUTH_PENDING_PROVIDER_KEY)
|
||||
if (provider === 'github' || provider === 'google') return provider
|
||||
return null
|
||||
}
|
||||
|
||||
function redirectProviderCallbackToBackend(provider: 'github' | 'google'): void {
|
||||
if (typeof window === 'undefined') return
|
||||
const apiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined) || '/api/v1'
|
||||
const normalized = apiBase.replace(/\/$/, '')
|
||||
const params = new URLSearchParams()
|
||||
for (const [key, value] of Object.entries(route.query)) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item) => {
|
||||
if (item != null) params.append(key, String(item))
|
||||
})
|
||||
} else if (value != null) {
|
||||
params.set(key, String(value))
|
||||
}
|
||||
}
|
||||
const suffix = params.toString() ? `?${params.toString()}` : ''
|
||||
window.location.href = `${normalized}/auth/oauth/${provider}/callback${suffix}`
|
||||
}
|
||||
|
||||
async function finalizeTokenResponse(tokenResponse: OAuthTokenResponse, redirect: string) {
|
||||
persistOAuthTokenContext(tokenResponse)
|
||||
await authStore.setToken(tokenResponse.access_token)
|
||||
if (typeof window !== 'undefined') {
|
||||
window.sessionStorage.removeItem(EMAIL_OAUTH_PENDING_PROVIDER_KEY)
|
||||
}
|
||||
clearAllAffiliateReferralCodes()
|
||||
appStore.showSuccess(t('auth.loginSuccess'))
|
||||
await router.replace(sanitizeRedirectPath(redirect))
|
||||
@ -274,6 +303,11 @@ onMounted(async () => {
|
||||
}
|
||||
if (!tokenResponse) {
|
||||
if (route.path === '/auth/oauth/callback') {
|
||||
const pendingEmailOAuthProvider = readPendingEmailOAuthProvider()
|
||||
if (pendingEmailOAuthProvider && code.value && state.value) {
|
||||
redirectProviderCallbackToBackend(pendingEmailOAuthProvider)
|
||||
return
|
||||
}
|
||||
await resumePendingEmailOAuth()
|
||||
}
|
||||
return
|
||||
|
||||
@ -4,6 +4,7 @@ import OAuthCallbackView from '@/views/auth/OAuthCallbackView.vue'
|
||||
|
||||
const {
|
||||
routeState,
|
||||
locationState,
|
||||
routerReplaceMock,
|
||||
showErrorMock,
|
||||
showSuccessMock,
|
||||
@ -16,6 +17,12 @@ const {
|
||||
path: '/auth/callback',
|
||||
query: {} as Record<string, unknown>,
|
||||
},
|
||||
locationState: {
|
||||
current: {
|
||||
href: 'http://localhost/auth/callback',
|
||||
hash: '',
|
||||
} as { href: string; hash: string },
|
||||
},
|
||||
routerReplaceMock: vi.fn(),
|
||||
showErrorMock: vi.fn(),
|
||||
showSuccessMock: vi.fn(),
|
||||
@ -73,7 +80,14 @@ describe('OAuthCallbackView', () => {
|
||||
beforeEach(() => {
|
||||
routeState.path = '/auth/callback'
|
||||
routeState.query = {}
|
||||
window.location.hash = ''
|
||||
locationState.current = {
|
||||
href: 'http://localhost/auth/callback',
|
||||
hash: '',
|
||||
}
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: locationState.current,
|
||||
})
|
||||
routerReplaceMock.mockReset()
|
||||
showErrorMock.mockReset()
|
||||
showSuccessMock.mockReset()
|
||||
@ -124,6 +138,23 @@ describe('OAuthCallbackView', () => {
|
||||
expect(wrapper.find('input[readonly]').exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('forwards frontend email oauth provider callbacks back to the backend callback endpoint', async () => {
|
||||
routeState.path = '/auth/oauth/callback'
|
||||
routeState.query = {
|
||||
code: 'provider-code',
|
||||
state: 'provider-state',
|
||||
}
|
||||
window.sessionStorage.setItem('email_oauth_pending_provider', 'google')
|
||||
|
||||
mount(OAuthCallbackView)
|
||||
await vi.dynamicImportSettled()
|
||||
|
||||
expect(locationState.current.href).toBe(
|
||||
'/api/v1/auth/oauth/google/callback?code=provider-code&state=provider-state'
|
||||
)
|
||||
expect(exchangePendingOAuthCompletionMock).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('submits stored affiliate code when completing invited email oauth registration', async () => {
|
||||
routeState.path = '/auth/oauth/callback'
|
||||
exchangePendingOAuthCompletionMock.mockResolvedValue({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user