174 lines
4.9 KiB
TypeScript
174 lines
4.9 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { computed, ref } from 'vue'
|
|
import { authApi } from '@/api'
|
|
import { mapApiUser } from '@/api/mappers'
|
|
import { buildApiUrl, setApiAccessToken } from '@/api/client'
|
|
import type { AuthResponse } from '@/api/types'
|
|
import type { User } from '@/types'
|
|
|
|
const TOKEN_STORAGE_KEY = 'universe.accessToken'
|
|
const ACTIVE_ROLE_STORAGE_KEY = 'universe.activeRole'
|
|
|
|
function restoreStoredActiveRole(mappedUser: User) {
|
|
const storedRole = localStorage.getItem(ACTIVE_ROLE_STORAGE_KEY) as User['activeRole'] | null
|
|
|
|
if (!storedRole || !mappedUser.roles.includes(storedRole)) return mappedUser
|
|
|
|
return { ...mappedUser, activeRole: storedRole }
|
|
}
|
|
|
|
function applyAuthResponse(response: AuthResponse) {
|
|
localStorage.setItem(TOKEN_STORAGE_KEY, response.accessToken)
|
|
setApiAccessToken(response.accessToken)
|
|
return restoreStoredActiveRole(mapApiUser(response.user))
|
|
}
|
|
|
|
function getAuthReturnUrl() {
|
|
return import.meta.env.VITE_AUTH_RETURN_URL || '/auth/callback'
|
|
}
|
|
|
|
function getAbsoluteAuthReturnUrl() {
|
|
return new URL(getAuthReturnUrl(), window.location.origin).toString()
|
|
}
|
|
|
|
export const useAuthStore = defineStore('auth', () => {
|
|
const user = ref<User | null>(null)
|
|
const loading = ref(false)
|
|
const initialized = ref(false)
|
|
const error = ref<string | null>(null)
|
|
const accessToken = ref<string | null>(localStorage.getItem(TOKEN_STORAGE_KEY))
|
|
|
|
if (accessToken.value) setApiAccessToken(accessToken.value)
|
|
|
|
const isAuthenticated = computed(() => Boolean(user.value && accessToken.value))
|
|
|
|
async function hydrateFromResponse(response: AuthResponse) {
|
|
accessToken.value = response.accessToken
|
|
user.value = applyAuthResponse(response)
|
|
error.value = null
|
|
}
|
|
|
|
async function initialize() {
|
|
if (initialized.value) return isAuthenticated.value
|
|
loading.value = true
|
|
error.value = null
|
|
|
|
try {
|
|
const refreshed = await authApi.refresh()
|
|
await hydrateFromResponse(refreshed)
|
|
const me = await authApi.me()
|
|
user.value = restoreStoredActiveRole(mapApiUser(me))
|
|
return true
|
|
} catch (refreshError) {
|
|
if (accessToken.value) {
|
|
try {
|
|
const me = await authApi.me()
|
|
user.value = restoreStoredActiveRole(mapApiUser(me))
|
|
return true
|
|
} catch {
|
|
// Fall through to local cleanup below.
|
|
}
|
|
}
|
|
clearSession()
|
|
error.value = refreshError instanceof Error ? refreshError.message : null
|
|
return false
|
|
} finally {
|
|
initialized.value = true
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function startMicrosoftLogin() {
|
|
window.location.assign(buildApiUrl('/auth/login/microsoft', { returnUrl: getAuthReturnUrl() }))
|
|
return true
|
|
}
|
|
|
|
async function completeMicrosoftLogin(code: string, _state: string | null) {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
const redirectUri = getAbsoluteAuthReturnUrl()
|
|
const response = await authApi.loginMicrosoft(code, redirectUri)
|
|
await hydrateFromResponse(response)
|
|
initialized.value = true
|
|
return true
|
|
} catch (err) {
|
|
clearSession()
|
|
error.value = err instanceof Error ? err.message : 'Ошибка авторизации через Microsoft.'
|
|
throw err
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function completeTokenLogin(token: string) {
|
|
loading.value = true
|
|
error.value = null
|
|
try {
|
|
accessToken.value = token
|
|
localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
|
setApiAccessToken(token)
|
|
const me = await authApi.me()
|
|
user.value = restoreStoredActiveRole(mapApiUser(me))
|
|
initialized.value = true
|
|
return true
|
|
} catch (err) {
|
|
clearSession()
|
|
error.value =
|
|
err instanceof Error ? err.message : 'Не удалось получить пользователя после входа.'
|
|
throw err
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
loading.value = true
|
|
try {
|
|
await authApi.logout()
|
|
} catch {
|
|
// Local cleanup is still correct if the server session is already gone.
|
|
} finally {
|
|
clearSession()
|
|
initialized.value = true
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function clearSession() {
|
|
user.value = null
|
|
accessToken.value = null
|
|
localStorage.removeItem(TOKEN_STORAGE_KEY)
|
|
localStorage.removeItem(ACTIVE_ROLE_STORAGE_KEY)
|
|
setApiAccessToken(null)
|
|
}
|
|
|
|
function setUser(nextUser: User) {
|
|
user.value = nextUser
|
|
}
|
|
|
|
function setActiveRole(role: User['activeRole']) {
|
|
if (!user.value || !user.value.roles.includes(role)) return false
|
|
localStorage.setItem(ACTIVE_ROLE_STORAGE_KEY, role)
|
|
user.value = { ...user.value, activeRole: role }
|
|
return true
|
|
}
|
|
|
|
return {
|
|
user,
|
|
accessToken,
|
|
isAuthenticated,
|
|
loading,
|
|
initialized,
|
|
error,
|
|
initialize,
|
|
startMicrosoftLogin,
|
|
completeMicrosoftLogin,
|
|
completeTokenLogin,
|
|
logout,
|
|
clearSession,
|
|
setUser,
|
|
setActiveRole,
|
|
}
|
|
})
|