Files
openteam/web/src/stores/auth.ts
T
SakurasanandClaude 9324a782d5 Passkey: 账户设置绑定 + 免密登录(WebAuthn)
- 引入 go-webauthn, Passkey 表存凭据, challenge 会话内存存储(带过期)
- API: /webauthn/register|login begin/complete, /webauthn/passkeys 列表/删除
- 配置 OT_WEBAUTHN_RP_ID/RP_ORIGIN/RP_NAME;登录成功发 JWT+refresh cookie
- 前端 lib/webauthn(编解码+凭据序列化+安全上下文检测), 账户设置绑定区, 登录页免密按钮
- 需 HTTPS 或 localhost(安全上下文)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-16 02:00:08 +08:00

63 lines
1.7 KiB
TypeScript

import { defineStore } from 'pinia'
import { http } from '@/api/client'
import type { User } from '@/types'
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null as User | null,
accessToken: localStorage.getItem('ot_access') ?? '',
ready: false,
}),
getters: {
isAuthed: (s) => !!s.accessToken && !!s.user,
isAdmin: (s) => s.user?.role === 'admin',
},
actions: {
async bootstrap() {
try {
if (!this.accessToken) {
await this.refresh()
}
await this.fetchMe()
} catch {
this.clear()
}
this.ready = true
},
async register(username: string, email: string, password: string) {
await http.post('/auth/register', { username, email, password })
},
async login(username: string, password: string) {
const { data } = await http.post('/auth/login', { username, password })
this.setSession(data.data)
},
setSession(d: { access_token: string; user: User }) {
this.accessToken = d.access_token
this.user = d.user
localStorage.setItem('ot_access', d.access_token)
},
async refresh() {
const { data } = await http.post('/auth/refresh')
this.accessToken = data.data.access_token as string
localStorage.setItem('ot_access', this.accessToken)
},
async fetchMe() {
const { data } = await http.get('/auth/me')
this.user = data.data.user as User
},
async logout() {
try {
await http.post('/auth/logout')
} catch {
/* ignore */
}
this.clear()
},
clear() {
this.accessToken = ''
this.user = null
localStorage.removeItem('ot_access')
},
},
})