forlogic-core 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +455 -20
- package/dist/README.md +455 -20
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/dist/assets/index-C_ZLBeXY.css +0 -1
- package/dist/assets/index-wEAMQwsw.js +0 -7898
- package/dist/docs/AI_RULES.md +0 -213
- package/dist/docs/README.md +0 -102
- package/dist/docs/TROUBLESHOOTING.md +0 -473
- package/dist/docs/architecture/TOKENS_ARCHITECTURE.md +0 -712
- package/dist/docs/templates/app-layout.tsx +0 -192
- package/dist/docs/templates/basic-crud-page.tsx +0 -97
- package/dist/docs/templates/basic-crud-working.tsx +0 -182
- package/dist/docs/templates/complete-crud-example.tsx +0 -307
- package/dist/docs/templates/custom-form.tsx +0 -99
- package/dist/docs/templates/custom-service.tsx +0 -194
- package/dist/docs/templates/permission-check-hook.tsx +0 -275
- package/dist/docs/templates/qualiex-config.ts +0 -137
- package/dist/docs/templates/qualiex-integration-example.tsx +0 -430
- package/dist/docs/templates/quick-start-example.tsx +0 -96
- package/dist/docs/templates/rls-policies.sql +0 -80
- package/dist/docs/templates/sidebar-config.tsx +0 -145
- package/dist/index.html +0 -19
|
@@ -1,712 +0,0 @@
|
|
|
1
|
-
# 🎫 Arquitetura de Tokens - Documentação Atualizada
|
|
2
|
-
|
|
3
|
-
## 📋 Visão Geral
|
|
4
|
-
|
|
5
|
-
Este projeto implementa um **sistema de autenticação tripla** que integra Qualiex OAuth 2.0 com backend Supabase, proporcionando autenticação robusta e controle de acesso multi-tenant.
|
|
6
|
-
|
|
7
|
-
## 🔑 Sistema de Três Tokens
|
|
8
|
-
|
|
9
|
-
### 1. **Qualiex Access Token** (`access_token`)
|
|
10
|
-
|
|
11
|
-
- **Origem**: Servidor OAuth 2.0 Qualiex
|
|
12
|
-
- **Formato**: JWT assinado pelo Qualiex
|
|
13
|
-
- **Propósito**: Valida identidade e autorização com serviços Qualiex
|
|
14
|
-
- **Armazenamento**: `localStorage` como `qualiex_access_token`
|
|
15
|
-
- **Validade**: Configurada pelo Qualiex (tipicamente 1-2 horas)
|
|
16
|
-
|
|
17
|
-
#### Estrutura do Payload:
|
|
18
|
-
```json
|
|
19
|
-
{
|
|
20
|
-
"default": "empresa_principal",
|
|
21
|
-
"co0": "empresa1;;;Nome Empresa 1;status",
|
|
22
|
-
"co1": "empresa2;;;Nome Empresa 2;status",
|
|
23
|
-
"co2": "empresa3;;;Nome Empresa 3;status",
|
|
24
|
-
"iat": 1234567890,
|
|
25
|
-
"exp": 1234567890
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
#### Campos de Empresa (`co*`):
|
|
30
|
-
```
|
|
31
|
-
Formato: alias;;;nome_empresa;status;;;;;;;company_id
|
|
32
|
-
Posições:
|
|
33
|
-
- [0]: alias da empresa
|
|
34
|
-
- [3]: nome da empresa
|
|
35
|
-
- [4]: status
|
|
36
|
-
- [7]: company_id (para RLS)
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### 2. **Qualiex ID Token** (`id_token`)
|
|
40
|
-
|
|
41
|
-
- **Origem**: Servidor OpenID Connect Qualiex
|
|
42
|
-
- **Formato**: JWT com informações do perfil do usuário
|
|
43
|
-
- **Propósito**: Contém dados de identidade e perfil detalhado
|
|
44
|
-
- **Armazenamento**: `localStorage` como `qualiex_id_token`
|
|
45
|
-
- **Validade**: Mesmo período do access token
|
|
46
|
-
|
|
47
|
-
#### Estrutura do Payload:
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"subNewId": "user_unique_id_123",
|
|
51
|
-
"email": "usuario@empresa.com",
|
|
52
|
-
"name": "Nome Completo do Usuário",
|
|
53
|
-
"identifier": "identificador_usuario",
|
|
54
|
-
"photo-date": "timestamp_foto",
|
|
55
|
-
"default": "empresa_principal",
|
|
56
|
-
"co0": "empresa1;;;Nome Empresa 1;status;;;;;;;id_empresa_1",
|
|
57
|
-
"co1": "empresa2;;;Nome Empresa 2;status;;;;;;;id_empresa_2",
|
|
58
|
-
"iat": 1234567890,
|
|
59
|
-
"exp": 1234567890
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 3. **Supabase Token** (`supabase_token`)
|
|
64
|
-
|
|
65
|
-
- **Origem**: Gerado pela Edge Function `validate-token`
|
|
66
|
-
- **Formato**: JWT assinado com secret do Supabase
|
|
67
|
-
- **Propósito**: Autentica usuário com backend Supabase e habilita RLS
|
|
68
|
-
- **Armazenamento**: `localStorage` como `supabase_token`
|
|
69
|
-
- **Validade**: 24 horas (renovação automática)
|
|
70
|
-
|
|
71
|
-
#### Estrutura do Payload:
|
|
72
|
-
```json
|
|
73
|
-
{
|
|
74
|
-
"sub": "user_unique_id_123",
|
|
75
|
-
"email": "usuario@empresa.com",
|
|
76
|
-
"alias": "empresa_selecionada",
|
|
77
|
-
"company_id": "id_empresa_extraido",
|
|
78
|
-
"user_metadata": {
|
|
79
|
-
"name": "Nome Completo do Usuário",
|
|
80
|
-
"identifier": "identificador_usuario"
|
|
81
|
-
},
|
|
82
|
-
"aud": "authenticated",
|
|
83
|
-
"role": "authenticated",
|
|
84
|
-
"iat": 1234567890,
|
|
85
|
-
"exp": 1234567890
|
|
86
|
-
}
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## 🔄 Fluxo de Autenticação
|
|
92
|
-
|
|
93
|
-
### Diagrama do Fluxo Completo
|
|
94
|
-
|
|
95
|
-
```mermaid
|
|
96
|
-
sequenceDiagram
|
|
97
|
-
participant U as Usuário
|
|
98
|
-
participant A as App Frontend
|
|
99
|
-
participant Q as Qualiex OAuth
|
|
100
|
-
participant E as Edge Function
|
|
101
|
-
participant S as Supabase
|
|
102
|
-
participant DB as PostgreSQL
|
|
103
|
-
|
|
104
|
-
Note over U,DB: Fluxo de Produção
|
|
105
|
-
|
|
106
|
-
U->>A: 1. Clique em "Login"
|
|
107
|
-
A->>Q: 2. Redirect para OAuth Qualiex
|
|
108
|
-
Q->>U: 3. Tela de login Qualiex
|
|
109
|
-
U->>Q: 4. Credenciais válidas
|
|
110
|
-
Q->>A: 5. Callback com tokens (access + id)
|
|
111
|
-
|
|
112
|
-
A->>A: 6. Extrai empresas do ID token
|
|
113
|
-
A->>A: 7. Usuário seleciona empresa
|
|
114
|
-
|
|
115
|
-
A->>E: 8. POST /validate-token
|
|
116
|
-
Note right of A: { access_token, alias }
|
|
117
|
-
|
|
118
|
-
E->>Q: 9. Valida access token
|
|
119
|
-
Q->>E: 10. Token válido ✓
|
|
120
|
-
|
|
121
|
-
E->>E: 11. Extrai company_id do token
|
|
122
|
-
E->>E: 12. Gera Supabase JWT
|
|
123
|
-
|
|
124
|
-
E->>A: 13. Retorna supabase_token
|
|
125
|
-
|
|
126
|
-
A->>A: 14. Armazena todos os tokens
|
|
127
|
-
A->>A: 15. Atualiza estado auth
|
|
128
|
-
|
|
129
|
-
A->>S: 16. Requests com supabase_token
|
|
130
|
-
S->>DB: 17. Query com RLS
|
|
131
|
-
Note right of S: WHERE alias = JWT.alias
|
|
132
|
-
DB->>S: 18. Dados filtrados
|
|
133
|
-
S->>A: 19. Resposta autorizada
|
|
134
|
-
|
|
135
|
-
Note over U,DB: Fluxo de Desenvolvimento
|
|
136
|
-
|
|
137
|
-
U->>A: 1. Login (Dev Mode)
|
|
138
|
-
A->>E: 2. POST /dev-tokens
|
|
139
|
-
E->>E: 3. Gera tokens mock
|
|
140
|
-
E->>A: 4. Retorna todos tokens
|
|
141
|
-
A->>A: 5. Mesmo fluxo de produção
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Estados do Fluxo
|
|
145
|
-
|
|
146
|
-
```mermaid
|
|
147
|
-
stateDiagram-v2
|
|
148
|
-
[*] --> Unauthenticated
|
|
149
|
-
|
|
150
|
-
Unauthenticated --> OAuth_Redirect: Login Click
|
|
151
|
-
OAuth_Redirect --> Token_Validation: Callback Received
|
|
152
|
-
|
|
153
|
-
Token_Validation --> Company_Selection: Tokens Valid
|
|
154
|
-
Token_Validation --> Unauthenticated: Tokens Invalid
|
|
155
|
-
|
|
156
|
-
Company_Selection --> Supabase_Auth: Company Selected
|
|
157
|
-
Company_Selection --> Unauthenticated: User Cancels
|
|
158
|
-
|
|
159
|
-
Supabase_Auth --> Authenticated: Success
|
|
160
|
-
Supabase_Auth --> Unauthenticated: Supabase Error
|
|
161
|
-
|
|
162
|
-
Authenticated --> Token_Refresh: Token Expiring
|
|
163
|
-
Token_Refresh --> Authenticated: Refresh Success
|
|
164
|
-
Token_Refresh --> Unauthenticated: Refresh Failed
|
|
165
|
-
|
|
166
|
-
Authenticated --> Unauthenticated: Logout
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## 🏗️ Arquitetura de Arquivos
|
|
172
|
-
|
|
173
|
-
### Frontend (src/auth/)
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
src/auth/
|
|
177
|
-
├── components/
|
|
178
|
-
│ ├── ProtectedRoute.tsx # Proteção de rotas
|
|
179
|
-
│ └── UserInfo.tsx # Exibição de dados do usuário
|
|
180
|
-
├── contexts/
|
|
181
|
-
│ └── AuthContext.tsx # Estado global de autenticação
|
|
182
|
-
├── pages/
|
|
183
|
-
│ └── CallbackPage.tsx # Processamento do callback OAuth
|
|
184
|
-
└── services/
|
|
185
|
-
├── AuthService.ts # Lógica de negócio de auth
|
|
186
|
-
└── TokenManager.ts # Gestão de armazenamento de tokens
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Backend (supabase/functions/)
|
|
190
|
-
|
|
191
|
-
```
|
|
192
|
-
supabase/functions/
|
|
193
|
-
├── validate-token/ # Validação em produção
|
|
194
|
-
│ └── index.ts # Valida Qualiex + gera Supabase token
|
|
195
|
-
├── dev-tokens/ # Desenvolvimento
|
|
196
|
-
│ └── index.ts # Gera tokens mock para dev
|
|
197
|
-
└── secure-cors-middleware/ # Middleware CORS
|
|
198
|
-
└── index.ts # Configurações CORS seguras
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## 🔧 Implementação Detalhada
|
|
204
|
-
|
|
205
|
-
### AuthContext.tsx (Estado Global)
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
interface AuthState {
|
|
209
|
-
isAuthenticated: boolean;
|
|
210
|
-
isLoading: boolean;
|
|
211
|
-
user: UserInfo | null;
|
|
212
|
-
alias: string | null;
|
|
213
|
-
companies: Company[];
|
|
214
|
-
tokens: {
|
|
215
|
-
accessToken: string | null;
|
|
216
|
-
idToken: string | null;
|
|
217
|
-
supabaseToken: string | null;
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const AuthContext = createContext<{
|
|
222
|
-
...AuthState;
|
|
223
|
-
login: () => void;
|
|
224
|
-
logout: () => void;
|
|
225
|
-
switchUnit: (alias: string) => Promise<void>;
|
|
226
|
-
refreshData: () => void;
|
|
227
|
-
}>({});
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### TokenManager.ts (Gestão de Tokens)
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
export class TokenManager {
|
|
234
|
-
// Armazenamento
|
|
235
|
-
static setAccessToken(token: string): void;
|
|
236
|
-
static setIdToken(token: string): void;
|
|
237
|
-
static setSupabaseToken(token: string): void;
|
|
238
|
-
|
|
239
|
-
// Recuperação
|
|
240
|
-
static getAccessToken(): string | null;
|
|
241
|
-
static getIdToken(): string | null;
|
|
242
|
-
static getSupabaseToken(): string | null;
|
|
243
|
-
|
|
244
|
-
// Validação
|
|
245
|
-
static isTokenExpired(token: string): boolean;
|
|
246
|
-
static isTokenValid(token: string): boolean;
|
|
247
|
-
|
|
248
|
-
// Limpeza
|
|
249
|
-
static clearTokens(): void;
|
|
250
|
-
static clearOAuthState(): void;
|
|
251
|
-
|
|
252
|
-
// Decodificação
|
|
253
|
-
static decodeJWT(token: string): any;
|
|
254
|
-
}
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
### AuthService.ts (Lógica de Negócio)
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
export class AuthService {
|
|
261
|
-
// OAuth Flow
|
|
262
|
-
static initiateLogin(): void;
|
|
263
|
-
static handleCallback(url: string): Promise<AuthResult>;
|
|
264
|
-
|
|
265
|
-
// Company Management
|
|
266
|
-
static extractCompanies(idToken: string): Company[];
|
|
267
|
-
static switchCompany(alias: string): Promise<void>;
|
|
268
|
-
|
|
269
|
-
// Token Operations
|
|
270
|
-
static validateTokens(accessToken: string, alias: string): Promise<string>;
|
|
271
|
-
static refreshSupabaseToken(): Promise<void>;
|
|
272
|
-
|
|
273
|
-
// State Management
|
|
274
|
-
static getCurrentUser(): UserInfo | null;
|
|
275
|
-
static isAuthenticated(): boolean;
|
|
276
|
-
static logout(): void;
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
---
|
|
281
|
-
|
|
282
|
-
## 🌍 Ambientes: Desenvolvimento vs Produção
|
|
283
|
-
|
|
284
|
-
### Desenvolvimento (`dev-tokens` function)
|
|
285
|
-
|
|
286
|
-
```typescript
|
|
287
|
-
// Edge Function: supabase/functions/dev-tokens/index.ts
|
|
288
|
-
const isDev = origin.includes('localhost') ||
|
|
289
|
-
origin.includes('sandbox.lovable.dev') ||
|
|
290
|
-
Deno.env.get('DENO_DEPLOYMENT_ID') === undefined;
|
|
291
|
-
|
|
292
|
-
if (isDev) {
|
|
293
|
-
return {
|
|
294
|
-
access_token: DEV_ACCESS_TOKEN, // Token mock Qualiex
|
|
295
|
-
id_token: DEV_ID_TOKEN // Token mock com dados de teste
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
**Características do Ambiente de Desenvolvimento:**
|
|
301
|
-
- ✅ Tokens mock pré-configurados
|
|
302
|
-
- ✅ Sem dependência de OAuth real
|
|
303
|
-
- ✅ Dados de teste realistas
|
|
304
|
-
- ✅ Múltiplas empresas de exemplo
|
|
305
|
-
- ✅ Fluxo completo simulado
|
|
306
|
-
|
|
307
|
-
### Produção (`validate-token` function)
|
|
308
|
-
|
|
309
|
-
```typescript
|
|
310
|
-
// Edge Function: supabase/functions/validate-token/index.ts
|
|
311
|
-
async function validateToken(accessToken: string, alias: string) {
|
|
312
|
-
// 1. Validar token com servidor Qualiex
|
|
313
|
-
const validation = await fetch(QUALIEX_VALIDATE_ENDPOINT, {
|
|
314
|
-
headers: {
|
|
315
|
-
'Authorization': `Bearer ${accessToken}`,
|
|
316
|
-
'un-alias': alias
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
// 2. Extrair company_id do token
|
|
321
|
-
const companyId = extractCompanyId(accessToken, alias);
|
|
322
|
-
|
|
323
|
-
// 3. Gerar Supabase JWT
|
|
324
|
-
const supabaseToken = await createJWT({
|
|
325
|
-
sub: userId,
|
|
326
|
-
alias: alias,
|
|
327
|
-
company_id: companyId,
|
|
328
|
-
// ... outros claims
|
|
329
|
-
}, SUPABASE_JWT_SECRET);
|
|
330
|
-
|
|
331
|
-
return supabaseToken;
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**Características do Ambiente de Produção:**
|
|
336
|
-
- ✅ OAuth real com Qualiex
|
|
337
|
-
- ✅ Validação de tokens em tempo real
|
|
338
|
-
- ✅ Dados reais de empresas
|
|
339
|
-
- ✅ Segurança completa
|
|
340
|
-
- ✅ Logs de auditoria
|
|
341
|
-
|
|
342
|
-
---
|
|
343
|
-
|
|
344
|
-
## 📦 Storage Strategy (Armazenamento)
|
|
345
|
-
|
|
346
|
-
### LocalStorage Mapping
|
|
347
|
-
|
|
348
|
-
| Token | Key | Conteúdo | Persistência |
|
|
349
|
-
|-------|-----|----------|--------------|
|
|
350
|
-
| Access Token | `qualiex_access_token` | JWT Qualiex para API | Até logout |
|
|
351
|
-
| ID Token | `qualiex_id_token` | Dados do usuário + empresas | Até logout |
|
|
352
|
-
| Supabase Token | `supabase_token` | JWT para RLS Supabase | Até logout |
|
|
353
|
-
|
|
354
|
-
### SessionStorage (Temporário)
|
|
355
|
-
|
|
356
|
-
| Item | Key | Conteúdo | Persistência |
|
|
357
|
-
|------|-----|----------|--------------|
|
|
358
|
-
| OAuth State | `oauth_state` | CSRF protection | Apenas sessão |
|
|
359
|
-
| Redirect URI | `qualiex_redirect_uri` | URL de retorno | Até uso |
|
|
360
|
-
|
|
361
|
-
### Estratégia de Limpeza
|
|
362
|
-
|
|
363
|
-
```typescript
|
|
364
|
-
// Limpeza automática em diferentes cenários
|
|
365
|
-
class TokenManager {
|
|
366
|
-
// Logout explícito
|
|
367
|
-
static logout(): void {
|
|
368
|
-
localStorage.removeItem('qualiex_access_token');
|
|
369
|
-
localStorage.removeItem('qualiex_id_token');
|
|
370
|
-
localStorage.removeItem('supabase_token');
|
|
371
|
-
sessionStorage.clear();
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Token expirado
|
|
375
|
-
static cleanExpiredTokens(): void {
|
|
376
|
-
Object.keys(localStorage).forEach(key => {
|
|
377
|
-
if (key.includes('token') && this.isTokenExpired(localStorage.getItem(key))) {
|
|
378
|
-
localStorage.removeItem(key);
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Mudança de empresa
|
|
384
|
-
static switchCompany(alias: string): void {
|
|
385
|
-
// Manter access_token e id_token
|
|
386
|
-
// Limpar apenas supabase_token (será regenerado)
|
|
387
|
-
localStorage.removeItem('supabase_token');
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
---
|
|
393
|
-
|
|
394
|
-
## 🐛 Debugging e Troubleshooting
|
|
395
|
-
|
|
396
|
-
### Logs Estruturados
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
// Habilitação de debug
|
|
400
|
-
const DEBUG_AUTH = import.meta.env.DEV || localStorage.getItem('debug_auth');
|
|
401
|
-
|
|
402
|
-
class AuthDebugger {
|
|
403
|
-
static logTokenDecoding(token: string, type: string): void {
|
|
404
|
-
if (DEBUG_AUTH) {
|
|
405
|
-
console.group(`[TokenDecoder] ${type}`);
|
|
406
|
-
console.log('Raw Token:', token.substring(0, 50) + '...');
|
|
407
|
-
console.log('Decoded:', this.decodeToken(token));
|
|
408
|
-
console.groupEnd();
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
static logAuthFlow(step: string, data: any): void {
|
|
413
|
-
if (DEBUG_AUTH) {
|
|
414
|
-
console.log(`[AuthFlow] ${step}:`, data);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
static logRLSContext(): void {
|
|
419
|
-
if (DEBUG_AUTH) {
|
|
420
|
-
console.group('[RLS Context]');
|
|
421
|
-
console.log('Current alias:', this.getCurrentAlias());
|
|
422
|
-
console.log('JWT Claims:', this.getJWTClaims());
|
|
423
|
-
console.groupEnd();
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Problemas Comuns e Soluções
|
|
430
|
-
|
|
431
|
-
#### 1. **Token Parsing Errors**
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// Problema: Erro ao decodificar JWT
|
|
435
|
-
// Causa: Base64 mal formado ou token corrompido
|
|
436
|
-
// Solução:
|
|
437
|
-
function safeDecodeJWT(token: string): any | null {
|
|
438
|
-
try {
|
|
439
|
-
const parts = token.split('.');
|
|
440
|
-
if (parts.length !== 3) throw new Error('Invalid JWT format');
|
|
441
|
-
|
|
442
|
-
const payload = parts[1];
|
|
443
|
-
const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
|
|
444
|
-
const decoded = atob(paddedPayload.replace(/-/g, '+').replace(/_/g, '/'));
|
|
445
|
-
|
|
446
|
-
return JSON.parse(decoded);
|
|
447
|
-
} catch (error) {
|
|
448
|
-
console.error('[AuthService] Token decode failed:', error);
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
#### 2. **Company Extraction Failures**
|
|
455
|
-
|
|
456
|
-
```typescript
|
|
457
|
-
// Problema: Não encontra company_id no token
|
|
458
|
-
// Causa: Formato incorreto ou campo faltando
|
|
459
|
-
// Solução:
|
|
460
|
-
function extractCompanyId(token: string, alias: string): string | null {
|
|
461
|
-
const payload = this.safeDecodeJWT(token);
|
|
462
|
-
if (!payload) return null;
|
|
463
|
-
|
|
464
|
-
// Buscar em todos os campos co* dinamicamente
|
|
465
|
-
for (const key in payload) {
|
|
466
|
-
if (key.startsWith('co') && /^co\d+$/.test(key)) {
|
|
467
|
-
const coField = payload[key];
|
|
468
|
-
if (typeof coField === 'string') {
|
|
469
|
-
const parts = coField.split(';');
|
|
470
|
-
// Verificar se tem alias na posição 0 e company_id na posição 7
|
|
471
|
-
if (parts.length > 7 && parts[0] === alias && parts[7]) {
|
|
472
|
-
return parts[7];
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
console.warn(`[AuthService] Company ID not found for alias: ${alias}`);
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
```
|
|
482
|
-
|
|
483
|
-
#### 3. **RLS Access Denied**
|
|
484
|
-
|
|
485
|
-
```typescript
|
|
486
|
-
// Problema: Usuário não consegue acessar dados
|
|
487
|
-
// Causa: JWT não tem claims corretos ou RLS mal configurado
|
|
488
|
-
// Diagnóstico:
|
|
489
|
-
async function debugRLSAccess(): Promise<void> {
|
|
490
|
-
const supabaseToken = TokenManager.getSupabaseToken();
|
|
491
|
-
if (!supabaseToken) {
|
|
492
|
-
console.error('[RLS Debug] No Supabase token found');
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const claims = TokenManager.decodeJWT(supabaseToken);
|
|
497
|
-
console.group('[RLS Debug] Token Analysis');
|
|
498
|
-
console.log('Token alias:', claims.alias);
|
|
499
|
-
console.log('Token company_id:', claims.company_id);
|
|
500
|
-
console.log('Token expiry:', new Date(claims.exp * 1000));
|
|
501
|
-
console.log('Is expired:', claims.exp < Date.now() / 1000);
|
|
502
|
-
console.groupEnd();
|
|
503
|
-
|
|
504
|
-
// Testar acesso direto
|
|
505
|
-
try {
|
|
506
|
-
const { data, error } = await supabase
|
|
507
|
-
.from('examples')
|
|
508
|
-
.select('id, alias, title')
|
|
509
|
-
.limit(1);
|
|
510
|
-
|
|
511
|
-
if (error) {
|
|
512
|
-
console.error('[RLS Debug] Query failed:', error);
|
|
513
|
-
} else {
|
|
514
|
-
console.log('[RLS Debug] Query success:', data);
|
|
515
|
-
}
|
|
516
|
-
} catch (err) {
|
|
517
|
-
console.error('[RLS Debug] Query exception:', err);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
#### 4. **Token Refresh Issues**
|
|
523
|
-
|
|
524
|
-
```typescript
|
|
525
|
-
// Problema: Token não renova automaticamente
|
|
526
|
-
// Causa: Lógica de refresh não executando
|
|
527
|
-
// Solução:
|
|
528
|
-
class TokenRefreshManager {
|
|
529
|
-
private static refreshTimer: NodeJS.Timeout | null = null;
|
|
530
|
-
|
|
531
|
-
static scheduleRefresh(token: string): void {
|
|
532
|
-
this.clearRefreshTimer();
|
|
533
|
-
|
|
534
|
-
const decoded = TokenManager.decodeJWT(token);
|
|
535
|
-
if (!decoded) return;
|
|
536
|
-
|
|
537
|
-
// Renovar 5 minutos antes de expirar
|
|
538
|
-
const expiryTime = decoded.exp * 1000;
|
|
539
|
-
const refreshTime = expiryTime - (5 * 60 * 1000);
|
|
540
|
-
const timeUntilRefresh = refreshTime - Date.now();
|
|
541
|
-
|
|
542
|
-
if (timeUntilRefresh > 0) {
|
|
543
|
-
this.refreshTimer = setTimeout(async () => {
|
|
544
|
-
try {
|
|
545
|
-
await AuthService.refreshSupabaseToken();
|
|
546
|
-
console.log('[TokenRefresh] Auto-refresh successful');
|
|
547
|
-
} catch (error) {
|
|
548
|
-
console.error('[TokenRefresh] Auto-refresh failed:', error);
|
|
549
|
-
// Forçar re-login
|
|
550
|
-
AuthService.logout();
|
|
551
|
-
}
|
|
552
|
-
}, timeUntilRefresh);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
static clearRefreshTimer(): void {
|
|
557
|
-
if (this.refreshTimer) {
|
|
558
|
-
clearTimeout(this.refreshTimer);
|
|
559
|
-
this.refreshTimer = null;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
---
|
|
566
|
-
|
|
567
|
-
## 🔒 Considerações de Segurança
|
|
568
|
-
|
|
569
|
-
### Tokens em localStorage
|
|
570
|
-
|
|
571
|
-
**Riscos:**
|
|
572
|
-
- ❌ Vulnerável a XSS (Cross-Site Scripting)
|
|
573
|
-
- ❌ Persistente entre sessões do browser
|
|
574
|
-
- ❌ Acessível via JavaScript
|
|
575
|
-
|
|
576
|
-
**Mitigações:**
|
|
577
|
-
- ✅ CSP (Content Security Policy) rigoroso
|
|
578
|
-
- ✅ HTTPS obrigatório em produção
|
|
579
|
-
- ✅ Validação de origem nas Edge Functions
|
|
580
|
-
- ✅ Tokens com TTL curto (24h max)
|
|
581
|
-
- ✅ Limpeza automática na logout
|
|
582
|
-
|
|
583
|
-
### CSRF Protection
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
// Estado OAuth para prevenir CSRF
|
|
587
|
-
function generateOAuthState(): string {
|
|
588
|
-
const state = crypto.randomUUID();
|
|
589
|
-
sessionStorage.setItem('oauth_state', state);
|
|
590
|
-
return state;
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
function validateOAuthState(receivedState: string): boolean {
|
|
594
|
-
const storedState = sessionStorage.getItem('oauth_state');
|
|
595
|
-
sessionStorage.removeItem('oauth_state');
|
|
596
|
-
return storedState === receivedState;
|
|
597
|
-
}
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
### Token Validation
|
|
601
|
-
|
|
602
|
-
```typescript
|
|
603
|
-
// Validação robusta de tokens
|
|
604
|
-
class TokenValidator {
|
|
605
|
-
static isValid(token: string): boolean {
|
|
606
|
-
if (!token) return false;
|
|
607
|
-
|
|
608
|
-
try {
|
|
609
|
-
const decoded = TokenManager.decodeJWT(token);
|
|
610
|
-
if (!decoded) return false;
|
|
611
|
-
|
|
612
|
-
// Verificar expiração
|
|
613
|
-
if (decoded.exp && decoded.exp < Date.now() / 1000) {
|
|
614
|
-
return false;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// Verificar campos obrigatórios
|
|
618
|
-
if (!decoded.sub || !decoded.aud) {
|
|
619
|
-
return false;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return true;
|
|
623
|
-
} catch {
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
static validateClaims(token: string, requiredClaims: string[]): boolean {
|
|
629
|
-
const decoded = TokenManager.decodeJWT(token);
|
|
630
|
-
if (!decoded) return false;
|
|
631
|
-
|
|
632
|
-
return requiredClaims.every(claim => decoded[claim] !== undefined);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
```
|
|
636
|
-
|
|
637
|
-
---
|
|
638
|
-
|
|
639
|
-
## 📊 Monitoramento e Métricas
|
|
640
|
-
|
|
641
|
-
### Edge Functions Analytics
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
// Logging estruturado nas Edge Functions
|
|
645
|
-
function logAuthEvent(event: string, data: any): void {
|
|
646
|
-
console.log(JSON.stringify({
|
|
647
|
-
timestamp: new Date().toISOString(),
|
|
648
|
-
event: event,
|
|
649
|
-
data: data,
|
|
650
|
-
environment: Deno.env.get('DENO_DEPLOYMENT_ID') ? 'production' : 'development'
|
|
651
|
-
}));
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Métricas de uso
|
|
655
|
-
logAuthEvent('token_validation_start', { alias });
|
|
656
|
-
logAuthEvent('token_validation_success', { userId, alias });
|
|
657
|
-
logAuthEvent('token_validation_error', { error: error.message });
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
### Frontend Analytics
|
|
661
|
-
|
|
662
|
-
```typescript
|
|
663
|
-
// Tracking de eventos de auth
|
|
664
|
-
class AuthAnalytics {
|
|
665
|
-
static trackLogin(method: 'oauth' | 'dev'): void {
|
|
666
|
-
// Implementar com seu analytics favorito
|
|
667
|
-
console.log('[Analytics] Login attempt:', method);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
static trackCompanySwitch(fromAlias: string, toAlias: string): void {
|
|
671
|
-
console.log('[Analytics] Company switch:', { fromAlias, toAlias });
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
static trackTokenRefresh(success: boolean): void {
|
|
675
|
-
console.log('[Analytics] Token refresh:', { success });
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
```
|
|
679
|
-
|
|
680
|
-
---
|
|
681
|
-
|
|
682
|
-
## 🚀 Próximos Passos e Melhorias
|
|
683
|
-
|
|
684
|
-
### Roadmap de Melhorias
|
|
685
|
-
|
|
686
|
-
1. **Segurança Avançada**
|
|
687
|
-
- [ ] Implementar httpOnly cookies para tokens
|
|
688
|
-
- [ ] Adicionar refresh token rotation
|
|
689
|
-
- [ ] Implementar device fingerprinting
|
|
690
|
-
- [ ] Rate limiting nas Edge Functions
|
|
691
|
-
|
|
692
|
-
2. **Performance**
|
|
693
|
-
- [ ] Cache de validação de tokens
|
|
694
|
-
- [ ] Preload de dados de empresas
|
|
695
|
-
- [ ] Lazy loading de módulos auth
|
|
696
|
-
- [ ] Service Worker para token refresh
|
|
697
|
-
|
|
698
|
-
3. **Monitoramento**
|
|
699
|
-
- [ ] Dashboard de métricas de auth
|
|
700
|
-
- [ ] Alertas de falhas de login
|
|
701
|
-
- [ ] Auditoria de mudanças de empresa
|
|
702
|
-
- [ ] Detecção de tentativas de ataque
|
|
703
|
-
|
|
704
|
-
4. **Developer Experience**
|
|
705
|
-
- [ ] CLI para gerar tokens de dev
|
|
706
|
-
- [ ] Interface visual para debug de tokens
|
|
707
|
-
- [ ] Documentação interativa
|
|
708
|
-
- [ ] Testes automatizados de auth
|
|
709
|
-
|
|
710
|
-
---
|
|
711
|
-
|
|
712
|
-
**🎫 Arquitetura robusta e escalável para autenticação multi-tenant segura.**
|