114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import type { Writable } from 'svelte/store';
|
|
import { writable } from 'svelte/store';
|
|
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
first_name: string;
|
|
last_name: string;
|
|
tenant_id: string;
|
|
tenant_slug: string;
|
|
role: 'CLIENT_ADMIN' | 'CLIENT_USER';
|
|
is_active: boolean;
|
|
is_two_factor_enabled: boolean;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface AuthState {
|
|
user: User | null;
|
|
token: string | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
}
|
|
|
|
export interface LoginRequest {
|
|
email: string;
|
|
password: string;
|
|
tenant_slug?: string;
|
|
totp_code?: string;
|
|
}
|
|
|
|
export interface LoginResponse {
|
|
access_token: string;
|
|
token_type: string;
|
|
expires_in: number;
|
|
user: User;
|
|
}
|
|
|
|
const initialState: AuthState = {
|
|
user: null,
|
|
token: null,
|
|
isAuthenticated: false,
|
|
isLoading: false
|
|
};
|
|
|
|
function createAuthStore() {
|
|
const { subscribe, set, update }: Writable<AuthState> = writable(initialState);
|
|
let _state = initialState;
|
|
subscribe(s => { _state = s; });
|
|
|
|
return {
|
|
subscribe,
|
|
init: async () => {
|
|
if (typeof window !== 'undefined') {
|
|
try {
|
|
const response = await fetch('/api/v1/auth/me', {
|
|
credentials: 'include',
|
|
headers: { 'X-App': 'internal' }
|
|
});
|
|
if (response.ok) {
|
|
const user = await response.json();
|
|
set({ user, token: null, isAuthenticated: true, isLoading: false });
|
|
}
|
|
} catch (error) { }
|
|
}
|
|
},
|
|
login: async (credentials: LoginRequest): Promise<void> => {
|
|
update(state => ({ ...state, isLoading: true }));
|
|
try {
|
|
const response = await fetch('/api/v1/auth/login', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-App': 'client',
|
|
},
|
|
body: JSON.stringify(credentials)
|
|
});
|
|
if (!response.ok) {
|
|
const error = await response.json();
|
|
throw new Error(error.detail || 'Login failed');
|
|
}
|
|
const data: LoginResponse = await response.json();
|
|
set({ user: data.user, token: data.access_token, isAuthenticated: true, isLoading: false });
|
|
} catch (error) {
|
|
update(state => ({ ...state, isLoading: false }));
|
|
throw error;
|
|
}
|
|
},
|
|
logout: async () => {
|
|
try {
|
|
const token = _state.token;
|
|
const slug = _state.user?.tenant_slug || _state.user?.tenant_id || '';
|
|
await fetch('/api/v1/auth/logout', {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: {
|
|
'X-App': 'internal',
|
|
'X-Tenant-Slug': slug,
|
|
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
}
|
|
});
|
|
} catch { }
|
|
set(initialState);
|
|
if (typeof window !== 'undefined') {
|
|
window.location.href = '/login';
|
|
}
|
|
},
|
|
updateUser: (user: User) => { update(state => ({ ...state, user })); },
|
|
setUser: (user: User) => { set({ user, token: null, isAuthenticated: true, isLoading: false }); },
|
|
setLoading: (isLoading: boolean) => { update(state => ({ ...state, isLoading })); }
|
|
};
|
|
}
|
|
|
|
export const auth = createAuthStore(); |