Files
member-platform/frontend/src/pages/LoginPage.vue

115 lines
3.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="flex items-center justify-center min-h-[70vh]">
<el-card class="w-full max-w-md shadow-md">
<template #header>
<div class="text-center">
<h1 class="text-xl font-bold text-gray-800">member.ose.tw</h1>
<p class="text-sm text-gray-500 mt-1">按下按鈕前往身分提供者登入</p>
</div>
</template>
<el-alert
v-if="error"
:title="error"
type="error"
show-icon
:closable="false"
class="mb-4"
/>
<el-button
type="primary"
class="w-full"
:loading="loginLoading"
@click="handleLogin"
>
前往登入
</el-button>
<div class="mt-4 text-xs text-gray-400 text-center space-y-1">
<p>登入會統一跳轉到身分提供者登入頁完成後自動返回</p>
<p>登入成功後 access token 會存於本機 localStorage</p>
</div>
</el-card>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { getOidcAuthorizeUrl } from '@/api/auth'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
const router = useRouter()
const authStore = useAuthStore()
const loginLoading = ref(false)
const error = ref('')
function getPostLoginRedirect() {
const redirect = route.query.redirect || '/me'
return typeof redirect === 'string' ? redirect : '/me'
}
onMounted(async () => {
if (!authStore.isLoggedIn) return
try {
await authStore.fetchMe()
router.replace(getPostLoginRedirect())
} catch (_err) {
authStore.logout()
}
})
async function handleLogin() {
loginLoading.value = true
error.value = ''
try {
await redirectToOidc({
prompt: 'login'
})
} catch (err) {
error.value = err.message || '登入失敗,請稍後再試'
} finally {
loginLoading.value = false
}
}
async function redirectToOidc(options = {}) {
const pkce = await generatePkcePair()
sessionStorage.setItem('oidc_pkce_verifier', pkce.codeVerifier)
sessionStorage.setItem('post_login_redirect', getPostLoginRedirect())
const callbackUrl = `${window.location.origin}/auth/callback`
const res = await getOidcAuthorizeUrl(callbackUrl, {
...options,
codeChallenge: pkce.codeChallenge,
codeChallengeMethod: 'S256'
})
const authorizeUrl = res.data.authorize_url
const parsed = new URL(authorizeUrl)
const state = parsed.searchParams.get('state')
if (state) {
sessionStorage.setItem('oidc_expected_state', state)
}
window.location.href = authorizeUrl
}
async function generatePkcePair() {
const randomBytes = new Uint8Array(32)
window.crypto.getRandomValues(randomBytes)
const codeVerifier = toBase64Url(randomBytes)
const digest = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier))
const codeChallenge = toBase64Url(new Uint8Array(digest))
return { codeVerifier, codeChallenge }
}
function toBase64Url(bytes) {
let binary = ''
for (let i = 0; i < bytes.length; i += 1) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
}
</script>