Files
vtuber-awards/frontend/src/stores/auth.ts
T
2026-06-25 19:52:46 +02:00

196 lines
5.8 KiB
TypeScript

import { defineStore } from 'pinia'
import { AUTH_TOKEN_KEY, api } from '../lib/api'
import { ApiRequestError } from '../lib/http'
import type {
AuthSession,
BindTeamTwitchPayload,
ChangePasswordPayload,
DemoLoginPayload,
LoginPayload,
TeamLoginPayload,
TeamTwitchLoginPayload,
TwitchAuthorizePayload,
} from '../types/awards'
const adminAccessRoles = new Set(['member', 'reviewer', 'organization_team', 'content_admin', 'admin', 'owner', 'creator'])
const contentManageRoles = new Set(['content_admin', 'admin', 'owner', 'creator'])
const adminWorkspaceRoles = new Set(['admin', 'owner', 'creator'])
const adminWorkspacePermissions = new Set([
'dashboard',
'years',
'nominations',
'categories',
'candidates',
'clips',
'risk',
'audit',
'analytics',
'winners',
])
function readStoredToken() {
if (typeof window === 'undefined') return null
return window.localStorage.getItem(AUTH_TOKEN_KEY)
}
function writeStoredToken(token: string | null) {
if (typeof window === 'undefined') return
if (token) {
window.localStorage.setItem(AUTH_TOKEN_KEY, token)
} else {
window.localStorage.removeItem(AUTH_TOKEN_KEY)
}
}
export const useAuthStore = defineStore('auth', {
state: () => ({
session: null as AuthSession | null,
hydrated: false,
loading: false,
}),
getters: {
isLoggedIn: (state) => Boolean(state.session),
isAdmin: (state) => adminAccessRoles.has(state.session?.role ?? ''),
canAccessAdmin: (state) => adminAccessRoles.has(state.session?.role ?? ''),
canManageContent: (state) => contentManageRoles.has(state.session?.role ?? '') || (state.session?.permissionKeys ?? []).includes('content'),
canManageAdminWorkspace: (state) => adminWorkspaceRoles.has(state.session?.role ?? '') || (state.session?.permissionKeys ?? []).some((key) => adminWorkspacePermissions.has(key)),
canManageOperationalSettings: (state) => state.session?.role === 'owner' || state.session?.role === 'creator',
canManageTeam: (state) => (state.session?.permissionKeys ?? []).includes('team'),
hasPermission: (state) => (permissionKey: string) => (state.session?.permissionKeys ?? []).includes(permissionKey),
isOwner: (state) => state.session?.role === 'owner',
isCreator: (state) => state.session?.role === 'creator',
isOwnerOrCreator: (state) => state.session?.role === 'owner' || state.session?.role === 'creator',
isTeamSession: (state) => Boolean(state.session?.teamLogin),
},
actions: {
async hydrate() {
if (!readStoredToken()) {
this.hydrated = true
return
}
try {
this.session = await api.getSession()
} catch (error) {
this.session = null
if (error instanceof ApiRequestError && (error.status === 401 || error.status === 403)) {
writeStoredToken(null)
}
} finally {
this.hydrated = true
}
},
async login(payload: LoginPayload) {
this.loading = true
try {
const session = await api.login(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async demoLogin(payload: DemoLoginPayload) {
this.loading = true
try {
const session = await api.demoLogin(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async teamLogin(payload: TeamLoginPayload) {
this.loading = true
try {
const session = await api.teamLogin(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async teamTwitchLogin(payload: TeamTwitchLoginPayload) {
this.loading = true
try {
const session = await api.teamTwitchLogin(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async changePassword(payload: ChangePasswordPayload) {
this.loading = true
try {
const session = await api.changePassword(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async bindTeamTwitch(payload: BindTeamTwitchPayload) {
this.loading = true
try {
const session = await api.bindTeamTwitch(payload)
this.session = session
writeStoredToken(session.sessionToken)
} finally {
this.loading = false
}
},
async startTwitchAuthorization(payload: Omit<TwitchAuthorizePayload, 'frontendOrigin'>) {
this.loading = true
try {
const response = await api.startTwitchAuthorization({
...payload,
frontendOrigin: window.location.origin,
})
window.location.assign(response.authorizationUrl)
} finally {
this.loading = false
}
},
async completeOAuthSession(sessionToken: string) {
this.loading = true
try {
writeStoredToken(sessionToken)
this.session = await api.getSession()
this.hydrated = true
} catch (error) {
writeStoredToken(null)
this.session = null
throw error
} finally {
this.loading = false
}
},
async logout() {
this.loading = true
try {
await api.logout()
} finally {
this.session = null
writeStoredToken(null)
this.loading = false
}
},
async deleteMyData() {
this.loading = true
try {
const result = await api.deleteMyData()
this.session = null
writeStoredToken(null)
return result
} finally {
this.loading = false
}
},
},
})
export type AuthStore = ReturnType<typeof useAuthStore>