feat: rebuild auth identity foundation flow
This commit is contained in:
53
frontend/src/components/auth/WechatOAuthSection.vue
Normal file
53
frontend/src/components/auth/WechatOAuthSection.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<button type="button" :disabled="disabled" class="btn btn-secondary w-full" @click="startLogin">
|
||||
<span
|
||||
class="mr-2 inline-flex h-5 w-5 items-center justify-center rounded-full bg-green-100 text-xs font-semibold text-green-700 dark:bg-green-900/30 dark:text-green-300"
|
||||
>
|
||||
W
|
||||
</span>
|
||||
{{ t('auth.oidc.signIn', { providerName }) }}
|
||||
</button>
|
||||
|
||||
<div v-if="showDivider" class="flex items-center gap-3">
|
||||
<div class="h-px flex-1 bg-gray-200 dark:bg-dark-700"></div>
|
||||
<span class="text-xs text-gray-500 dark:text-dark-400">
|
||||
{{ t('auth.oauthOrContinue') }}
|
||||
</span>
|
||||
<div class="h-px flex-1 bg-gray-200 dark:bg-dark-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
disabled?: boolean
|
||||
showDivider?: boolean
|
||||
}>(), {
|
||||
showDivider: true,
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const { t } = useI18n()
|
||||
|
||||
const providerName = 'WeChat'
|
||||
|
||||
function resolveWeChatOAuthMode(): 'open' | 'mp' {
|
||||
if (typeof navigator === 'undefined') {
|
||||
return 'open'
|
||||
}
|
||||
return /MicroMessenger/i.test(navigator.userAgent) ? 'mp' : 'open'
|
||||
}
|
||||
|
||||
function startLogin(): void {
|
||||
const redirectTo = (route.query.redirect as string) || '/dashboard'
|
||||
const apiBase = (import.meta.env.VITE_API_BASE_URL as string | undefined) || '/api/v1'
|
||||
const normalized = apiBase.replace(/\/$/, '')
|
||||
const mode = resolveWeChatOAuthMode()
|
||||
const startURL = `${normalized}/auth/oauth/wechat/start?mode=${mode}&redirect=${encodeURIComponent(redirectTo)}`
|
||||
window.location.href = startURL
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,74 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import WechatOAuthSection from '@/components/auth/WechatOAuthSection.vue'
|
||||
|
||||
const routeState = vi.hoisted(() => ({
|
||||
query: {} as Record<string, unknown>,
|
||||
}))
|
||||
|
||||
const locationState = vi.hoisted(() => ({
|
||||
current: { href: 'http://localhost/login' } as { href: string },
|
||||
}))
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => routeState,
|
||||
}))
|
||||
|
||||
vi.mock('vue-i18n', () => ({
|
||||
useI18n: () => ({
|
||||
t: (key: string, params?: Record<string, string>) => {
|
||||
if (key === 'auth.oidc.signIn') {
|
||||
return `Continue with ${params?.providerName ?? ''}`.trim()
|
||||
}
|
||||
if (key === 'auth.oauthOrContinue') {
|
||||
return 'or continue'
|
||||
}
|
||||
return key
|
||||
},
|
||||
}),
|
||||
}))
|
||||
|
||||
describe('WechatOAuthSection', () => {
|
||||
beforeEach(() => {
|
||||
routeState.query = { redirect: '/billing?plan=pro' }
|
||||
locationState.current = { href: 'http://localhost/login' }
|
||||
Object.defineProperty(window, 'location', {
|
||||
configurable: true,
|
||||
value: locationState.current,
|
||||
})
|
||||
Object.defineProperty(window.navigator, 'userAgent', {
|
||||
configurable: true,
|
||||
value: 'Mozilla/5.0',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('starts the open WeChat OAuth flow with the current redirect target', async () => {
|
||||
const wrapper = mount(WechatOAuthSection)
|
||||
|
||||
expect(wrapper.text()).toContain('WeChat')
|
||||
|
||||
await wrapper.get('button').trigger('click')
|
||||
|
||||
expect(locationState.current.href).toContain(
|
||||
'/api/v1/auth/oauth/wechat/start?mode=open&redirect=%2Fbilling%3Fplan%3Dpro'
|
||||
)
|
||||
})
|
||||
|
||||
it('uses mp mode inside the WeChat browser', async () => {
|
||||
Object.defineProperty(window.navigator, 'userAgent', {
|
||||
configurable: true,
|
||||
value: 'Mozilla/5.0 MicroMessenger',
|
||||
})
|
||||
const wrapper = mount(WechatOAuthSection)
|
||||
|
||||
await wrapper.get('button').trigger('click')
|
||||
|
||||
expect(locationState.current.href).toContain(
|
||||
'/api/v1/auth/oauth/wechat/start?mode=mp&redirect=%2Fbilling%3Fplan%3Dpro'
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user