295 lines
8.7 KiB
TypeScript
295 lines
8.7 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
import { GLOBAL_USER_ROLES, type GlobalUserRole } from '../types/roles';
|
|
|
|
export interface User {
|
|
id: string;
|
|
email: string;
|
|
full_name: string; // Updated to match backend
|
|
is_active: boolean;
|
|
is_verified: boolean;
|
|
created_at: string;
|
|
last_login?: string;
|
|
phone?: string;
|
|
language?: string;
|
|
timezone?: string;
|
|
avatar?: string; // User avatar image URL
|
|
tenant_id?: string;
|
|
role?: GlobalUserRole;
|
|
}
|
|
|
|
export interface AuthState {
|
|
// State
|
|
user: User | null;
|
|
token: string | null;
|
|
refreshToken: string | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
|
|
// Actions
|
|
login: (email: string, password: string) => Promise<void>;
|
|
register: (userData: { email: string; password: string; full_name: string; tenant_name?: string }) => Promise<void>;
|
|
logout: () => void;
|
|
refreshAuth: () => Promise<void>;
|
|
updateUser: (updates: Partial<User>) => void;
|
|
clearError: () => void;
|
|
setLoading: (loading: boolean) => void;
|
|
|
|
// Permission helpers
|
|
hasPermission: (permission: string) => boolean;
|
|
hasRole: (role: string) => boolean;
|
|
canAccess: (resource: string, action: string) => boolean;
|
|
}
|
|
|
|
import { authService, apiClient } from '../api';
|
|
|
|
export const useAuthStore = create<AuthState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
// Initial state
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
|
|
// Actions
|
|
login: async (email: string, password: string) => {
|
|
try {
|
|
set({ isLoading: true, error: null });
|
|
|
|
const response = await authService.login({ email, password });
|
|
|
|
if (response && response.access_token) {
|
|
// Set the auth tokens on the API client immediately
|
|
apiClient.setAuthToken(response.access_token);
|
|
if (response.refresh_token) {
|
|
apiClient.setRefreshToken(response.refresh_token);
|
|
}
|
|
|
|
set({
|
|
user: response.user || null,
|
|
token: response.access_token,
|
|
refreshToken: response.refresh_token || null,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
} else {
|
|
throw new Error('Login failed');
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: error instanceof Error ? error.message : 'Error de autenticación',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
register: async (userData: { email: string; password: string; full_name: string; tenant_name?: string }) => {
|
|
try {
|
|
set({ isLoading: true, error: null });
|
|
|
|
const response = await authService.register(userData);
|
|
|
|
if (response && response.access_token) {
|
|
// Set the auth tokens on the API client immediately
|
|
apiClient.setAuthToken(response.access_token);
|
|
if (response.refresh_token) {
|
|
apiClient.setRefreshToken(response.refresh_token);
|
|
}
|
|
|
|
set({
|
|
user: response.user || null,
|
|
token: response.access_token,
|
|
refreshToken: response.refresh_token || null,
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
} else {
|
|
throw new Error('Registration failed');
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: error instanceof Error ? error.message : 'Error de registro',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
logout: () => {
|
|
// Clear the auth tokens from API client
|
|
apiClient.setAuthToken(null);
|
|
apiClient.setRefreshToken(null);
|
|
apiClient.setTenantId(null);
|
|
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
},
|
|
|
|
refreshAuth: async () => {
|
|
try {
|
|
const { refreshToken } = get();
|
|
if (!refreshToken) {
|
|
throw new Error('No refresh token available');
|
|
}
|
|
|
|
set({ isLoading: true });
|
|
|
|
const response = await authService.refreshToken(refreshToken);
|
|
|
|
if (response && response.access_token) {
|
|
// Set the auth tokens on the API client immediately
|
|
apiClient.setAuthToken(response.access_token);
|
|
if (response.refresh_token) {
|
|
apiClient.setRefreshToken(response.refresh_token);
|
|
}
|
|
|
|
set({
|
|
token: response.access_token,
|
|
refreshToken: response.refresh_token || refreshToken,
|
|
isLoading: false,
|
|
error: null,
|
|
});
|
|
} else {
|
|
throw new Error('Token refresh failed');
|
|
}
|
|
} catch (error) {
|
|
set({
|
|
user: null,
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
isLoading: false,
|
|
error: error instanceof Error ? error.message : 'Error al renovar sesión',
|
|
});
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
updateUser: (updates: Partial<User>) => {
|
|
const { user } = get();
|
|
if (user) {
|
|
set({
|
|
user: { ...user, ...updates },
|
|
});
|
|
}
|
|
},
|
|
|
|
clearError: () => {
|
|
set({ error: null });
|
|
},
|
|
|
|
setLoading: (loading: boolean) => {
|
|
set({ isLoading: loading });
|
|
},
|
|
|
|
// Permission helpers - Global user permissions only
|
|
hasPermission: (_permission: string): boolean => {
|
|
const { user } = get();
|
|
if (!user || !user.is_active) return false;
|
|
|
|
// Super admin and admin have all global permissions
|
|
if (user.role === GLOBAL_USER_ROLES.SUPER_ADMIN || user.role === GLOBAL_USER_ROLES.ADMIN) {
|
|
return true;
|
|
}
|
|
|
|
// Manager has limited permissions
|
|
if (user.role === GLOBAL_USER_ROLES.MANAGER) {
|
|
return ['user_management', 'system_settings'].includes(_permission);
|
|
}
|
|
|
|
// Regular users have basic permissions
|
|
return false;
|
|
},
|
|
|
|
hasRole: (role: string): boolean => {
|
|
const { user } = get();
|
|
return user?.role === role;
|
|
},
|
|
|
|
canAccess: (resource: string, action: string): boolean => {
|
|
const { user } = get();
|
|
if (!user || !user.is_active) return false;
|
|
|
|
// Global role-based access control (system-wide)
|
|
switch (user.role) {
|
|
case GLOBAL_USER_ROLES.SUPER_ADMIN:
|
|
case GLOBAL_USER_ROLES.ADMIN:
|
|
return true;
|
|
case GLOBAL_USER_ROLES.MANAGER:
|
|
return ['users', 'system'].includes(resource);
|
|
case GLOBAL_USER_ROLES.USER:
|
|
return action === 'read';
|
|
default:
|
|
return false;
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
name: 'auth-storage',
|
|
storage: createJSONStorage(() => localStorage),
|
|
partialize: (state) => ({
|
|
user: state.user,
|
|
token: state.token,
|
|
refreshToken: state.refreshToken,
|
|
isAuthenticated: state.isAuthenticated,
|
|
}),
|
|
onRehydrateStorage: () => (state) => {
|
|
// Initialize API client with stored tokens when store rehydrates
|
|
if (state?.token) {
|
|
// Use direct import to avoid timing issues
|
|
apiClient.setAuthToken(state.token);
|
|
if (state.refreshToken) {
|
|
apiClient.setRefreshToken(state.refreshToken);
|
|
}
|
|
|
|
if (state.user?.tenant_id) {
|
|
apiClient.setTenantId(state.user.tenant_id);
|
|
}
|
|
}
|
|
},
|
|
}
|
|
)
|
|
);
|
|
|
|
// Selectors for common use cases
|
|
export const useAuthUser = () => useAuthStore((state) => state.user);
|
|
export const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated);
|
|
export const useAuthLoading = () => useAuthStore((state) => state.isLoading);
|
|
export const useAuthError = () => useAuthStore((state) => state.error);
|
|
export const usePermissions = () => useAuthStore((state) => ({
|
|
hasPermission: state.hasPermission,
|
|
hasRole: state.hasRole,
|
|
canAccess: state.canAccess,
|
|
}));
|
|
|
|
// Hook for auth actions
|
|
export const useAuthActions = () => useAuthStore((state) => ({
|
|
login: state.login,
|
|
register: state.register,
|
|
logout: state.logout,
|
|
refreshAuth: state.refreshAuth,
|
|
updateUser: state.updateUser,
|
|
clearError: state.clearError,
|
|
setLoading: state.setLoading,
|
|
})); |