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:
2025-11-11 14:00:56 -06:00
parent e1eb6bbd01
commit 52b8fcd434
242 changed files with 7067 additions and 3274 deletions

View 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();