feat: Implement multi-tenancy support in middleware and security layers
- Enhanced TenantMiddleware to validate tenant information from JWT tokens. - Added LicenseValidationMiddleware to check tenant licenses before processing requests. - Updated security utilities to extract tenant information from tokens and validate company access. - Introduced CompanyStore to manage active company state and handle company switching in the frontend. - Modified API routes to include company_id in requests for better resource management. - Improved logging and error handling throughout the middleware and API layers. - Updated frontend components to reflect changes in company management and selection. - Added new API route for fetching user's companies with proper authentication handling.
This commit is contained in:
122
frontend/src/lib/stores/company.svelte.ts
Normal file
122
frontend/src/lib/stores/company.svelte.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Store para manejar la compañía activa del usuario
|
||||
* Permite cambiar entre las compañías que pertenecen al tenant
|
||||
*/
|
||||
|
||||
interface Company {
|
||||
id: number;
|
||||
name: string;
|
||||
rfc?: string;
|
||||
logo?: string;
|
||||
tenant_id: number;
|
||||
}
|
||||
|
||||
class CompanyStore {
|
||||
private _activeCompany = $state<Company | null>(null);
|
||||
private _companies = $state<Company[]>([]);
|
||||
private _loading = $state(false);
|
||||
|
||||
get activeCompany() {
|
||||
return this._activeCompany;
|
||||
}
|
||||
|
||||
get companies() {
|
||||
return this._companies;
|
||||
}
|
||||
|
||||
get loading() {
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga las compañías del tenant del usuario desde el backend
|
||||
*/
|
||||
async loadCompanies() {
|
||||
this._loading = true;
|
||||
try {
|
||||
const response = await fetch('/api/company/my-companies');
|
||||
if (response.ok) {
|
||||
this._companies = await response.json();
|
||||
|
||||
// Si hay compañías y no hay una activa, seleccionar la primera
|
||||
if (this._companies.length > 0 && !this._activeCompany) {
|
||||
this.setActiveCompany(this._companies[0]);
|
||||
}
|
||||
} else {
|
||||
console.error('Error loading companies:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading companies:', error);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Establece la compañía activa
|
||||
*/
|
||||
setActiveCompany(company: Company) {
|
||||
this._activeCompany = company;
|
||||
|
||||
// Guardar en localStorage para persistencia
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('activeCompanyId', company.id.toString());
|
||||
}
|
||||
|
||||
// Guardar en cookie para acceso desde el servidor (SSR)
|
||||
if (typeof document !== 'undefined') {
|
||||
document.cookie = `active_company_id=${company.id}; path=/; max-age=${60 * 60 * 24 * 30}; SameSite=Lax`;
|
||||
}
|
||||
|
||||
// Despachar evento personalizado para que otros componentes reaccionen
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent('companyChanged', {
|
||||
detail: { companyId: company.id }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restaura la compañía activa desde localStorage
|
||||
*/
|
||||
restoreActiveCompany() {
|
||||
if (typeof window !== 'undefined') {
|
||||
const savedId = localStorage.getItem('activeCompanyId');
|
||||
if (savedId && this._companies.length > 0) {
|
||||
const company = this._companies.find(c => c.id === parseInt(savedId));
|
||||
if (company) {
|
||||
this._activeCompany = company;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia el store (útil al cambiar de tenant o cerrar sesión)
|
||||
*/
|
||||
clear() {
|
||||
this._activeCompany = null;
|
||||
this._companies = [];
|
||||
this._loading = false;
|
||||
|
||||
// Limpiar localStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('activeCompanyId');
|
||||
}
|
||||
|
||||
// Limpiar cookie
|
||||
if (typeof document !== 'undefined') {
|
||||
document.cookie = 'active_company_id=; path=/; max-age=0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa el store cargando las compañías
|
||||
*/
|
||||
async initialize() {
|
||||
await this.loadCompanies();
|
||||
this.restoreActiveCompany();
|
||||
}
|
||||
}
|
||||
|
||||
export const companyStore = new CompanyStore();
|
||||
Reference in New Issue
Block a user