Initial commit: Nexus Mission Control Platform
- ASP.NET Core 10 Backend (JWT Auth, Agent config API) - Vue 3 Frontend (Dashboard, Team, Agents, Config Editor) - PostgreSQL Database - Docker Compose setup - Mission Control Dashboard redesign
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface AuthUser {
|
||||
id: string
|
||||
email: string
|
||||
displayName: string
|
||||
role: string
|
||||
}
|
||||
|
||||
interface AuthPayload {
|
||||
accessToken: string
|
||||
expiresAt: string
|
||||
user: AuthUser
|
||||
}
|
||||
|
||||
let refreshInFlight: Promise<boolean> | null = null
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: () => ({
|
||||
accessToken: null as string | null,
|
||||
expiresAt: null as string | null,
|
||||
user: null as AuthUser | null,
|
||||
initialized: false,
|
||||
loading: false,
|
||||
}),
|
||||
getters: {
|
||||
isAuthenticated: state => Boolean(state.accessToken && state.user),
|
||||
},
|
||||
actions: {
|
||||
applySession(payload: AuthPayload) {
|
||||
this.accessToken = payload.accessToken
|
||||
this.expiresAt = payload.expiresAt
|
||||
this.user = payload.user
|
||||
},
|
||||
clearSession() {
|
||||
this.accessToken = null
|
||||
this.expiresAt = null
|
||||
this.user = null
|
||||
},
|
||||
async initialize() {
|
||||
if (this.initialized) return this.isAuthenticated
|
||||
this.initialized = true
|
||||
return this.refresh()
|
||||
},
|
||||
async login(email: string, password: string) {
|
||||
this.loading = true
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 429) throw new Error('Too many attempts. Please wait one minute.')
|
||||
throw new Error('Invalid email or password.')
|
||||
}
|
||||
|
||||
this.applySession(await response.json() as AuthPayload)
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
async refresh(): Promise<boolean> {
|
||||
if (refreshInFlight) return refreshInFlight
|
||||
|
||||
refreshInFlight = (async () => {
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/refresh', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
if (!response.ok) {
|
||||
this.clearSession()
|
||||
return false
|
||||
}
|
||||
|
||||
this.applySession(await response.json() as AuthPayload)
|
||||
return true
|
||||
} catch {
|
||||
this.clearSession()
|
||||
return false
|
||||
} finally {
|
||||
refreshInFlight = null
|
||||
}
|
||||
})()
|
||||
|
||||
return refreshInFlight
|
||||
},
|
||||
async logout() {
|
||||
try {
|
||||
await fetch('/api/v1/auth/logout', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
headers: this.accessToken ? { Authorization: `Bearer ${this.accessToken}` } : undefined,
|
||||
})
|
||||
} finally {
|
||||
this.clearSession()
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user