@allanfsouza/aether-sdk 2.4.5 → 2.4.7
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/dist/http-client.d.ts +1 -0
- package/dist/http-client.js +91 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/tenant-auth.d.ts +172 -0
- package/dist/tenant-auth.js +221 -0
- package/package.json +1 -1
- package/src/http-client.ts +117 -5
- package/src/index.ts +10 -1
- package/src/tenant-auth.ts +333 -0
package/dist/http-client.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { PlataformaClient } from "./index.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Cria uma instância do Axios pré-configurada.
|
|
5
5
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
6
|
+
* - Renova token automaticamente quando expira (401).
|
|
6
7
|
* - Converte erros em AetherError.
|
|
7
8
|
*/
|
|
8
9
|
export declare function createHttpClient(client: PlataformaClient): AxiosInstance;
|
package/dist/http-client.js
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import { handleAxiosError } from "./errors.js";
|
|
4
|
+
// Flag para evitar múltiplos refreshes simultâneos
|
|
5
|
+
let isRefreshing = false;
|
|
6
|
+
// Fila de requisições aguardando o refresh
|
|
7
|
+
let failedQueue = [];
|
|
8
|
+
/**
|
|
9
|
+
* Processa a fila de requisições após o refresh.
|
|
10
|
+
*/
|
|
11
|
+
const processQueue = (error, token = null) => {
|
|
12
|
+
failedQueue.forEach((prom) => {
|
|
13
|
+
if (error) {
|
|
14
|
+
prom.reject(error);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
prom.resolve(token);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
failedQueue = [];
|
|
21
|
+
};
|
|
4
22
|
/**
|
|
5
23
|
* Cria uma instância do Axios pré-configurada.
|
|
6
24
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
25
|
+
* - Renova token automaticamente quando expira (401).
|
|
7
26
|
* - Converte erros em AetherError.
|
|
8
27
|
*/
|
|
9
28
|
export function createHttpClient(client) {
|
|
10
29
|
const http = axios.create({
|
|
11
|
-
// Adiciona o /v1 automaticamente em todas as chamadas do SDK
|
|
12
30
|
baseURL: `${client.apiUrl}/v1`,
|
|
13
31
|
headers: {
|
|
14
32
|
"Content-Type": "application/json",
|
|
15
33
|
},
|
|
16
34
|
});
|
|
17
|
-
//
|
|
35
|
+
// ===========================================================================
|
|
36
|
+
// INTERCEPTOR DE REQUEST
|
|
37
|
+
// Injeta token e projectId em todas as requisições
|
|
38
|
+
// ===========================================================================
|
|
18
39
|
http.interceptors.request.use((config) => {
|
|
19
40
|
const token = client.getToken();
|
|
20
41
|
if (token) {
|
|
@@ -25,7 +46,73 @@ export function createHttpClient(client) {
|
|
|
25
46
|
}
|
|
26
47
|
return config;
|
|
27
48
|
});
|
|
28
|
-
//
|
|
29
|
-
|
|
49
|
+
// ===========================================================================
|
|
50
|
+
// INTERCEPTOR DE RESPONSE
|
|
51
|
+
// Detecta 401 e tenta refresh automático do token
|
|
52
|
+
// ===========================================================================
|
|
53
|
+
http.interceptors.response.use((response) => response, async (error) => {
|
|
54
|
+
const originalRequest = error.config;
|
|
55
|
+
// Verifica se é erro 401 (não autorizado) e não é retry
|
|
56
|
+
const isUnauthorized = error.response?.status === 401;
|
|
57
|
+
const isRetry = originalRequest?._retry;
|
|
58
|
+
const isAuthRoute = originalRequest?.url?.includes("/auth/");
|
|
59
|
+
// Não tenta refresh se:
|
|
60
|
+
// - Não é 401
|
|
61
|
+
// - Já é um retry
|
|
62
|
+
// - É uma rota de auth (login, register, refresh)
|
|
63
|
+
// - Não tem refresh token disponível
|
|
64
|
+
if (!isUnauthorized || isRetry || isAuthRoute) {
|
|
65
|
+
return handleAxiosError(error);
|
|
66
|
+
}
|
|
67
|
+
const refreshToken = client.getRefreshToken();
|
|
68
|
+
if (!refreshToken) {
|
|
69
|
+
// Sem refresh token, limpa sessão e propaga erro
|
|
70
|
+
client.clearSession();
|
|
71
|
+
return handleAxiosError(error);
|
|
72
|
+
}
|
|
73
|
+
// Se já está fazendo refresh, aguarda na fila
|
|
74
|
+
if (isRefreshing) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
failedQueue.push({ resolve, reject });
|
|
77
|
+
})
|
|
78
|
+
.then((token) => {
|
|
79
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
80
|
+
return http(originalRequest);
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => handleAxiosError(err));
|
|
83
|
+
}
|
|
84
|
+
// Marca como retry e inicia refresh
|
|
85
|
+
originalRequest._retry = true;
|
|
86
|
+
isRefreshing = true;
|
|
87
|
+
try {
|
|
88
|
+
// Chama endpoint de refresh diretamente (sem interceptor)
|
|
89
|
+
const { data } = await axios.post(`${client.apiUrl}/v1/auth/refresh`, { refreshToken }, { headers: { "Content-Type": "application/json" } });
|
|
90
|
+
const newAccessToken = data.accessToken;
|
|
91
|
+
const newRefreshToken = data.refreshToken;
|
|
92
|
+
// Atualiza tokens no client (persiste no localStorage)
|
|
93
|
+
client.setToken(newAccessToken);
|
|
94
|
+
if (newRefreshToken) {
|
|
95
|
+
client.setRefreshToken(newRefreshToken);
|
|
96
|
+
}
|
|
97
|
+
// Processa fila de requisições pendentes
|
|
98
|
+
processQueue(null, newAccessToken);
|
|
99
|
+
// Refaz a requisição original com novo token
|
|
100
|
+
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
101
|
+
return http(originalRequest);
|
|
102
|
+
}
|
|
103
|
+
catch (refreshError) {
|
|
104
|
+
// Refresh falhou - sessão expirada
|
|
105
|
+
processQueue(refreshError, null);
|
|
106
|
+
client.clearSession();
|
|
107
|
+
// Emite evento de sessão expirada (se houver listener)
|
|
108
|
+
if (typeof window !== "undefined") {
|
|
109
|
+
window.dispatchEvent(new CustomEvent("aether:session-expired"));
|
|
110
|
+
}
|
|
111
|
+
return handleAxiosError(refreshError);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
isRefreshing = false;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
30
117
|
return http;
|
|
31
118
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { DatabaseModule } from "./database.js";
|
|
|
4
4
|
import { StorageModule } from "./storage.js";
|
|
5
5
|
import { FunctionsModule } from "./functions.js";
|
|
6
6
|
import { PushModule } from "./push.js";
|
|
7
|
+
import { TenantAuthModule } from "./tenant-auth.js";
|
|
7
8
|
/**
|
|
8
9
|
* Configuração usada para criar o cliente principal da plataforma.
|
|
9
10
|
*/
|
|
@@ -24,6 +25,7 @@ export declare class PlataformaClient {
|
|
|
24
25
|
storage: StorageModule;
|
|
25
26
|
functions: FunctionsModule;
|
|
26
27
|
push: PushModule;
|
|
28
|
+
tenantAuth: TenantAuthModule;
|
|
27
29
|
database: DatabaseModule;
|
|
28
30
|
apiUrl: string;
|
|
29
31
|
projectId: string;
|
|
@@ -76,3 +78,4 @@ export { AetherError } from "./errors.js";
|
|
|
76
78
|
export type { LoginResponse, Session, User } from "./auth.js";
|
|
77
79
|
export type { ListOptions } from "./database.js";
|
|
78
80
|
export type { PushPlatform, PushEnvironment, PushDevice, RegisterDeviceParams, SendPushResponse, PushStatus, PushLogEntry, ListPushLogsOptions, PushStats, } from "./push.js";
|
|
81
|
+
export type { TenantUser, TenantLoginResponse, TenantRegisterCredentials, TenantLoginCredentials, } from "./tenant-auth.js";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { DatabaseModule } from "./database.js";
|
|
|
4
4
|
import { StorageModule } from "./storage.js";
|
|
5
5
|
import { FunctionsModule } from "./functions.js";
|
|
6
6
|
import { PushModule } from "./push.js";
|
|
7
|
+
import { TenantAuthModule } from "./tenant-auth.js";
|
|
7
8
|
// =============================================================================
|
|
8
9
|
// CONSTANTES DE STORAGE
|
|
9
10
|
// Chaves padronizadas para localStorage - evita conflito com outros SDKs
|
|
@@ -44,6 +45,7 @@ export class PlataformaClient {
|
|
|
44
45
|
this.storage = new StorageModule(this, this.http);
|
|
45
46
|
this.functions = new FunctionsModule(this, this.http);
|
|
46
47
|
this.push = new PushModule(this, this.http);
|
|
48
|
+
this.tenantAuth = new TenantAuthModule(this, this.http);
|
|
47
49
|
// Cria o alias que o Showcase App espera
|
|
48
50
|
this.database = this.db;
|
|
49
51
|
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import type { PlataformaClient } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Representa um usuário tenant (end-user de um projeto)
|
|
5
|
+
*/
|
|
6
|
+
export interface TenantUser {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name: string;
|
|
10
|
+
data: Record<string, any>;
|
|
11
|
+
emailVerified: boolean;
|
|
12
|
+
status: "active" | "suspended" | string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resposta de login do tenant
|
|
17
|
+
*/
|
|
18
|
+
export interface TenantLoginResponse {
|
|
19
|
+
accessToken: string;
|
|
20
|
+
user: TenantUser;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Credenciais para registro de tenant
|
|
24
|
+
*/
|
|
25
|
+
export interface TenantRegisterCredentials {
|
|
26
|
+
email: string;
|
|
27
|
+
password: string;
|
|
28
|
+
name?: string;
|
|
29
|
+
data?: Record<string, any>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Credenciais para login de tenant
|
|
33
|
+
*/
|
|
34
|
+
export interface TenantLoginCredentials {
|
|
35
|
+
email: string;
|
|
36
|
+
password: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Módulo de Autenticação de Tenants
|
|
40
|
+
*
|
|
41
|
+
* Permite que usuários finais (clientes) de projetos Aether
|
|
42
|
+
* se registrem, façam login e gerenciem seus perfis.
|
|
43
|
+
*
|
|
44
|
+
* Os dados são isolados por projeto (tabela prj_{id}_users).
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const aether = new PlataformaClient({ ... });
|
|
49
|
+
*
|
|
50
|
+
* // Registrar usuário no projeto
|
|
51
|
+
* const { user, error } = await aether.tenantAuth.signUp('project-id', {
|
|
52
|
+
* email: 'user@example.com',
|
|
53
|
+
* password: '123456',
|
|
54
|
+
* name: 'John Doe',
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Login
|
|
58
|
+
* const { accessToken, user } = await aether.tenantAuth.signIn('project-id', {
|
|
59
|
+
* email: 'user@example.com',
|
|
60
|
+
* password: '123456',
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* // Obter perfil
|
|
64
|
+
* const profile = await aether.tenantAuth.getProfile('project-id');
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare class TenantAuthModule {
|
|
68
|
+
private client;
|
|
69
|
+
private http;
|
|
70
|
+
private currentToken;
|
|
71
|
+
private currentUser;
|
|
72
|
+
constructor(client: PlataformaClient, http: AxiosInstance);
|
|
73
|
+
/**
|
|
74
|
+
* Registra um novo usuário tenant no projeto.
|
|
75
|
+
* A tabela de usuários é criada automaticamente se não existir.
|
|
76
|
+
*
|
|
77
|
+
* @param projectId - ID do projeto
|
|
78
|
+
* @param credentials - Email, senha, nome e dados opcionais
|
|
79
|
+
*/
|
|
80
|
+
register(projectId: string, credentials: TenantRegisterCredentials): Promise<{
|
|
81
|
+
user: TenantUser;
|
|
82
|
+
message: string;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Realiza login de usuário tenant.
|
|
86
|
+
* Armazena o token para uso em requisições subsequentes.
|
|
87
|
+
*
|
|
88
|
+
* @param projectId - ID do projeto
|
|
89
|
+
* @param credentials - Email e senha
|
|
90
|
+
*/
|
|
91
|
+
login(projectId: string, credentials: TenantLoginCredentials): Promise<TenantLoginResponse>;
|
|
92
|
+
/**
|
|
93
|
+
* Solicita reset de senha.
|
|
94
|
+
* Retorna mensagem genérica para prevenir enumeração de usuários.
|
|
95
|
+
*
|
|
96
|
+
* @param projectId - ID do projeto
|
|
97
|
+
* @param email - Email do usuário
|
|
98
|
+
*/
|
|
99
|
+
forgotPassword(projectId: string, email: string): Promise<{
|
|
100
|
+
message: string;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* Redefine a senha usando o token recebido por email.
|
|
104
|
+
*
|
|
105
|
+
* @param projectId - ID do projeto
|
|
106
|
+
* @param email - Email do usuário
|
|
107
|
+
* @param token - Token de reset (recebido por email)
|
|
108
|
+
* @param newPassword - Nova senha
|
|
109
|
+
*/
|
|
110
|
+
resetPassword(projectId: string, email: string, token: string, newPassword: string): Promise<{
|
|
111
|
+
message: string;
|
|
112
|
+
}>;
|
|
113
|
+
/**
|
|
114
|
+
* Obtém o perfil do usuário autenticado.
|
|
115
|
+
* Requer que login() tenha sido chamado antes.
|
|
116
|
+
*
|
|
117
|
+
* @param projectId - ID do projeto (opcional, usa do token se não fornecido)
|
|
118
|
+
*/
|
|
119
|
+
getProfile(projectId?: string): Promise<TenantUser>;
|
|
120
|
+
/**
|
|
121
|
+
* Atualiza o perfil do usuário autenticado.
|
|
122
|
+
*
|
|
123
|
+
* @param projectId - ID do projeto
|
|
124
|
+
* @param updates - Campos a atualizar (name, data)
|
|
125
|
+
*/
|
|
126
|
+
updateProfile(projectId: string, updates: {
|
|
127
|
+
name?: string;
|
|
128
|
+
data?: Record<string, any>;
|
|
129
|
+
}): Promise<{
|
|
130
|
+
user: TenantUser;
|
|
131
|
+
message: string;
|
|
132
|
+
}>;
|
|
133
|
+
/**
|
|
134
|
+
* Faz logout do tenant (limpa token local).
|
|
135
|
+
*/
|
|
136
|
+
logout(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Alias para register com retorno { user, error }
|
|
139
|
+
*/
|
|
140
|
+
signUp(projectId: string, credentials: TenantRegisterCredentials): Promise<{
|
|
141
|
+
user: TenantUser | null;
|
|
142
|
+
error: string | null;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Alias para login com retorno { user, accessToken, error }
|
|
146
|
+
*/
|
|
147
|
+
signIn(projectId: string, credentials: TenantLoginCredentials): Promise<{
|
|
148
|
+
user: TenantUser | null;
|
|
149
|
+
accessToken: string | null;
|
|
150
|
+
error: string | null;
|
|
151
|
+
}>;
|
|
152
|
+
/**
|
|
153
|
+
* Alias para logout
|
|
154
|
+
*/
|
|
155
|
+
signOut(): void;
|
|
156
|
+
/**
|
|
157
|
+
* Retorna o token atual do tenant
|
|
158
|
+
*/
|
|
159
|
+
getToken(): string | null;
|
|
160
|
+
/**
|
|
161
|
+
* Define o token do tenant (útil para restaurar sessão)
|
|
162
|
+
*/
|
|
163
|
+
setToken(token: string | null): void;
|
|
164
|
+
/**
|
|
165
|
+
* Retorna o usuário atual do tenant
|
|
166
|
+
*/
|
|
167
|
+
getCurrentUser(): TenantUser | null;
|
|
168
|
+
/**
|
|
169
|
+
* Verifica se há um usuário autenticado
|
|
170
|
+
*/
|
|
171
|
+
isAuthenticated(): boolean;
|
|
172
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// src/tenant-auth.ts
|
|
2
|
+
// [FEATURE] Módulo de Autenticação de Tenants para SDK
|
|
3
|
+
// Permite autenticação de end-users em projetos Aether
|
|
4
|
+
// Compatible with Firebase Auth-style usage
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// MODULE
|
|
7
|
+
// ============================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Módulo de Autenticação de Tenants
|
|
10
|
+
*
|
|
11
|
+
* Permite que usuários finais (clientes) de projetos Aether
|
|
12
|
+
* se registrem, façam login e gerenciem seus perfis.
|
|
13
|
+
*
|
|
14
|
+
* Os dados são isolados por projeto (tabela prj_{id}_users).
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const aether = new PlataformaClient({ ... });
|
|
19
|
+
*
|
|
20
|
+
* // Registrar usuário no projeto
|
|
21
|
+
* const { user, error } = await aether.tenantAuth.signUp('project-id', {
|
|
22
|
+
* email: 'user@example.com',
|
|
23
|
+
* password: '123456',
|
|
24
|
+
* name: 'John Doe',
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Login
|
|
28
|
+
* const { accessToken, user } = await aether.tenantAuth.signIn('project-id', {
|
|
29
|
+
* email: 'user@example.com',
|
|
30
|
+
* password: '123456',
|
|
31
|
+
* });
|
|
32
|
+
*
|
|
33
|
+
* // Obter perfil
|
|
34
|
+
* const profile = await aether.tenantAuth.getProfile('project-id');
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class TenantAuthModule {
|
|
38
|
+
constructor(client, http) {
|
|
39
|
+
// Armazena o token do tenant atual em memória
|
|
40
|
+
this.currentToken = null;
|
|
41
|
+
this.currentUser = null;
|
|
42
|
+
this.client = client;
|
|
43
|
+
this.http = http;
|
|
44
|
+
}
|
|
45
|
+
// ==========================================================================
|
|
46
|
+
// MÉTODOS PRINCIPAIS
|
|
47
|
+
// ==========================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Registra um novo usuário tenant no projeto.
|
|
50
|
+
* A tabela de usuários é criada automaticamente se não existir.
|
|
51
|
+
*
|
|
52
|
+
* @param projectId - ID do projeto
|
|
53
|
+
* @param credentials - Email, senha, nome e dados opcionais
|
|
54
|
+
*/
|
|
55
|
+
async register(projectId, credentials) {
|
|
56
|
+
const { data } = await this.http.post("/auth/tenant/register", {
|
|
57
|
+
projectId,
|
|
58
|
+
...credentials,
|
|
59
|
+
});
|
|
60
|
+
return data;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Realiza login de usuário tenant.
|
|
64
|
+
* Armazena o token para uso em requisições subsequentes.
|
|
65
|
+
*
|
|
66
|
+
* @param projectId - ID do projeto
|
|
67
|
+
* @param credentials - Email e senha
|
|
68
|
+
*/
|
|
69
|
+
async login(projectId, credentials) {
|
|
70
|
+
const { data } = await this.http.post("/auth/tenant/login", {
|
|
71
|
+
projectId,
|
|
72
|
+
...credentials,
|
|
73
|
+
});
|
|
74
|
+
// Armazena token e usuário
|
|
75
|
+
this.currentToken = data.accessToken;
|
|
76
|
+
this.currentUser = data.user;
|
|
77
|
+
return data;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Solicita reset de senha.
|
|
81
|
+
* Retorna mensagem genérica para prevenir enumeração de usuários.
|
|
82
|
+
*
|
|
83
|
+
* @param projectId - ID do projeto
|
|
84
|
+
* @param email - Email do usuário
|
|
85
|
+
*/
|
|
86
|
+
async forgotPassword(projectId, email) {
|
|
87
|
+
const { data } = await this.http.post("/auth/tenant/forgot-password", {
|
|
88
|
+
projectId,
|
|
89
|
+
email,
|
|
90
|
+
});
|
|
91
|
+
return data;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Redefine a senha usando o token recebido por email.
|
|
95
|
+
*
|
|
96
|
+
* @param projectId - ID do projeto
|
|
97
|
+
* @param email - Email do usuário
|
|
98
|
+
* @param token - Token de reset (recebido por email)
|
|
99
|
+
* @param newPassword - Nova senha
|
|
100
|
+
*/
|
|
101
|
+
async resetPassword(projectId, email, token, newPassword) {
|
|
102
|
+
const { data } = await this.http.post("/auth/tenant/reset-password", {
|
|
103
|
+
projectId,
|
|
104
|
+
email,
|
|
105
|
+
token,
|
|
106
|
+
newPassword,
|
|
107
|
+
});
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Obtém o perfil do usuário autenticado.
|
|
112
|
+
* Requer que login() tenha sido chamado antes.
|
|
113
|
+
*
|
|
114
|
+
* @param projectId - ID do projeto (opcional, usa do token se não fornecido)
|
|
115
|
+
*/
|
|
116
|
+
async getProfile(projectId) {
|
|
117
|
+
const headers = this.currentToken
|
|
118
|
+
? { Authorization: `Bearer ${this.currentToken}` }
|
|
119
|
+
: {};
|
|
120
|
+
const { data } = await this.http.get("/auth/tenant/me", { headers });
|
|
121
|
+
this.currentUser = data.user;
|
|
122
|
+
return data.user;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Atualiza o perfil do usuário autenticado.
|
|
126
|
+
*
|
|
127
|
+
* @param projectId - ID do projeto
|
|
128
|
+
* @param updates - Campos a atualizar (name, data)
|
|
129
|
+
*/
|
|
130
|
+
async updateProfile(projectId, updates) {
|
|
131
|
+
const headers = this.currentToken
|
|
132
|
+
? { Authorization: `Bearer ${this.currentToken}` }
|
|
133
|
+
: {};
|
|
134
|
+
const { data } = await this.http.put("/auth/tenant/profile", {
|
|
135
|
+
projectId,
|
|
136
|
+
...updates,
|
|
137
|
+
}, { headers });
|
|
138
|
+
this.currentUser = data.user;
|
|
139
|
+
return data;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Faz logout do tenant (limpa token local).
|
|
143
|
+
*/
|
|
144
|
+
logout() {
|
|
145
|
+
this.currentToken = null;
|
|
146
|
+
this.currentUser = null;
|
|
147
|
+
}
|
|
148
|
+
// ==========================================================================
|
|
149
|
+
// MÉTODOS ESTILO SUPABASE/FIREBASE
|
|
150
|
+
// ==========================================================================
|
|
151
|
+
/**
|
|
152
|
+
* Alias para register com retorno { user, error }
|
|
153
|
+
*/
|
|
154
|
+
async signUp(projectId, credentials) {
|
|
155
|
+
try {
|
|
156
|
+
const { user } = await this.register(projectId, credentials);
|
|
157
|
+
return { user, error: null };
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const axiosError = err;
|
|
161
|
+
return {
|
|
162
|
+
user: null,
|
|
163
|
+
error: axiosError.response?.data?.error ||
|
|
164
|
+
axiosError.response?.data?.message ||
|
|
165
|
+
err.message,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Alias para login com retorno { user, accessToken, error }
|
|
171
|
+
*/
|
|
172
|
+
async signIn(projectId, credentials) {
|
|
173
|
+
try {
|
|
174
|
+
const { user, accessToken } = await this.login(projectId, credentials);
|
|
175
|
+
return { user, accessToken, error: null };
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
const axiosError = err;
|
|
179
|
+
return {
|
|
180
|
+
user: null,
|
|
181
|
+
accessToken: null,
|
|
182
|
+
error: axiosError.response?.data?.error ||
|
|
183
|
+
axiosError.response?.data?.message ||
|
|
184
|
+
err.message,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Alias para logout
|
|
190
|
+
*/
|
|
191
|
+
signOut() {
|
|
192
|
+
this.logout();
|
|
193
|
+
}
|
|
194
|
+
// ==========================================================================
|
|
195
|
+
// ACCESSORS
|
|
196
|
+
// ==========================================================================
|
|
197
|
+
/**
|
|
198
|
+
* Retorna o token atual do tenant
|
|
199
|
+
*/
|
|
200
|
+
getToken() {
|
|
201
|
+
return this.currentToken;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Define o token do tenant (útil para restaurar sessão)
|
|
205
|
+
*/
|
|
206
|
+
setToken(token) {
|
|
207
|
+
this.currentToken = token;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Retorna o usuário atual do tenant
|
|
211
|
+
*/
|
|
212
|
+
getCurrentUser() {
|
|
213
|
+
return this.currentUser;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Verifica se há um usuário autenticado
|
|
217
|
+
*/
|
|
218
|
+
isAuthenticated() {
|
|
219
|
+
return this.currentToken !== null;
|
|
220
|
+
}
|
|
221
|
+
}
|
package/package.json
CHANGED
package/src/http-client.ts
CHANGED
|
@@ -1,23 +1,53 @@
|
|
|
1
1
|
// src/http-client.ts
|
|
2
|
-
import axios, {
|
|
2
|
+
import axios, {
|
|
3
|
+
type AxiosInstance,
|
|
4
|
+
type AxiosError,
|
|
5
|
+
type InternalAxiosRequestConfig,
|
|
6
|
+
} from "axios";
|
|
3
7
|
import type { PlataformaClient } from "./index.js";
|
|
4
8
|
import { handleAxiosError } from "./errors.js";
|
|
5
9
|
|
|
10
|
+
// Flag para evitar múltiplos refreshes simultâneos
|
|
11
|
+
let isRefreshing = false;
|
|
12
|
+
|
|
13
|
+
// Fila de requisições aguardando o refresh
|
|
14
|
+
let failedQueue: Array<{
|
|
15
|
+
resolve: (token: string) => void;
|
|
16
|
+
reject: (error: any) => void;
|
|
17
|
+
}> = [];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Processa a fila de requisições após o refresh.
|
|
21
|
+
*/
|
|
22
|
+
const processQueue = (error: any, token: string | null = null) => {
|
|
23
|
+
failedQueue.forEach((prom) => {
|
|
24
|
+
if (error) {
|
|
25
|
+
prom.reject(error);
|
|
26
|
+
} else {
|
|
27
|
+
prom.resolve(token!);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
failedQueue = [];
|
|
31
|
+
};
|
|
32
|
+
|
|
6
33
|
/**
|
|
7
34
|
* Cria uma instância do Axios pré-configurada.
|
|
8
35
|
* - Injeta automaticamente headers de Auth e Projeto.
|
|
36
|
+
* - Renova token automaticamente quando expira (401).
|
|
9
37
|
* - Converte erros em AetherError.
|
|
10
38
|
*/
|
|
11
39
|
export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
12
40
|
const http = axios.create({
|
|
13
|
-
// Adiciona o /v1 automaticamente em todas as chamadas do SDK
|
|
14
41
|
baseURL: `${client.apiUrl}/v1`,
|
|
15
42
|
headers: {
|
|
16
43
|
"Content-Type": "application/json",
|
|
17
44
|
},
|
|
18
45
|
});
|
|
19
46
|
|
|
20
|
-
//
|
|
47
|
+
// ===========================================================================
|
|
48
|
+
// INTERCEPTOR DE REQUEST
|
|
49
|
+
// Injeta token e projectId em todas as requisições
|
|
50
|
+
// ===========================================================================
|
|
21
51
|
http.interceptors.request.use((config) => {
|
|
22
52
|
const token = client.getToken();
|
|
23
53
|
if (token) {
|
|
@@ -31,10 +61,92 @@ export function createHttpClient(client: PlataformaClient): AxiosInstance {
|
|
|
31
61
|
return config;
|
|
32
62
|
});
|
|
33
63
|
|
|
34
|
-
//
|
|
64
|
+
// ===========================================================================
|
|
65
|
+
// INTERCEPTOR DE RESPONSE
|
|
66
|
+
// Detecta 401 e tenta refresh automático do token
|
|
67
|
+
// ===========================================================================
|
|
35
68
|
http.interceptors.response.use(
|
|
36
69
|
(response) => response,
|
|
37
|
-
(error) =>
|
|
70
|
+
async (error: AxiosError) => {
|
|
71
|
+
const originalRequest = error.config as InternalAxiosRequestConfig & {
|
|
72
|
+
_retry?: boolean;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Verifica se é erro 401 (não autorizado) e não é retry
|
|
76
|
+
const isUnauthorized = error.response?.status === 401;
|
|
77
|
+
const isRetry = originalRequest?._retry;
|
|
78
|
+
const isAuthRoute = originalRequest?.url?.includes("/auth/");
|
|
79
|
+
|
|
80
|
+
// Não tenta refresh se:
|
|
81
|
+
// - Não é 401
|
|
82
|
+
// - Já é um retry
|
|
83
|
+
// - É uma rota de auth (login, register, refresh)
|
|
84
|
+
// - Não tem refresh token disponível
|
|
85
|
+
if (!isUnauthorized || isRetry || isAuthRoute) {
|
|
86
|
+
return handleAxiosError(error);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const refreshToken = client.getRefreshToken();
|
|
90
|
+
if (!refreshToken) {
|
|
91
|
+
// Sem refresh token, limpa sessão e propaga erro
|
|
92
|
+
client.clearSession();
|
|
93
|
+
return handleAxiosError(error);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Se já está fazendo refresh, aguarda na fila
|
|
97
|
+
if (isRefreshing) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
failedQueue.push({ resolve, reject });
|
|
100
|
+
})
|
|
101
|
+
.then((token) => {
|
|
102
|
+
originalRequest.headers.Authorization = `Bearer ${token}`;
|
|
103
|
+
return http(originalRequest);
|
|
104
|
+
})
|
|
105
|
+
.catch((err) => handleAxiosError(err));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Marca como retry e inicia refresh
|
|
109
|
+
originalRequest._retry = true;
|
|
110
|
+
isRefreshing = true;
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
// Chama endpoint de refresh diretamente (sem interceptor)
|
|
114
|
+
const { data } = await axios.post(
|
|
115
|
+
`${client.apiUrl}/v1/auth/refresh`,
|
|
116
|
+
{ refreshToken },
|
|
117
|
+
{ headers: { "Content-Type": "application/json" } }
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const newAccessToken = data.accessToken;
|
|
121
|
+
const newRefreshToken = data.refreshToken;
|
|
122
|
+
|
|
123
|
+
// Atualiza tokens no client (persiste no localStorage)
|
|
124
|
+
client.setToken(newAccessToken);
|
|
125
|
+
if (newRefreshToken) {
|
|
126
|
+
client.setRefreshToken(newRefreshToken);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Processa fila de requisições pendentes
|
|
130
|
+
processQueue(null, newAccessToken);
|
|
131
|
+
|
|
132
|
+
// Refaz a requisição original com novo token
|
|
133
|
+
originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
|
|
134
|
+
return http(originalRequest);
|
|
135
|
+
} catch (refreshError: any) {
|
|
136
|
+
// Refresh falhou - sessão expirada
|
|
137
|
+
processQueue(refreshError, null);
|
|
138
|
+
client.clearSession();
|
|
139
|
+
|
|
140
|
+
// Emite evento de sessão expirada (se houver listener)
|
|
141
|
+
if (typeof window !== "undefined") {
|
|
142
|
+
window.dispatchEvent(new CustomEvent("aether:session-expired"));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return handleAxiosError(refreshError);
|
|
146
|
+
} finally {
|
|
147
|
+
isRefreshing = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
38
150
|
);
|
|
39
151
|
|
|
40
152
|
return http;
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { DatabaseModule } from "./database.js";
|
|
|
6
6
|
import { StorageModule } from "./storage.js";
|
|
7
7
|
import { FunctionsModule } from "./functions.js";
|
|
8
8
|
import { PushModule } from "./push.js";
|
|
9
|
+
import { TenantAuthModule, TenantUser, TenantLoginResponse, TenantRegisterCredentials, TenantLoginCredentials } from "./tenant-auth.js";
|
|
9
10
|
|
|
10
11
|
// =============================================================================
|
|
11
12
|
// CONSTANTES DE STORAGE
|
|
@@ -51,6 +52,7 @@ export class PlataformaClient {
|
|
|
51
52
|
public storage: StorageModule;
|
|
52
53
|
public functions: FunctionsModule;
|
|
53
54
|
public push: PushModule;
|
|
55
|
+
public tenantAuth: TenantAuthModule;
|
|
54
56
|
|
|
55
57
|
// Alias para 'db' que o showcase tenta usar como 'database'
|
|
56
58
|
public database: DatabaseModule;
|
|
@@ -90,6 +92,7 @@ export class PlataformaClient {
|
|
|
90
92
|
this.storage = new StorageModule(this, this.http);
|
|
91
93
|
this.functions = new FunctionsModule(this, this.http);
|
|
92
94
|
this.push = new PushModule(this, this.http);
|
|
95
|
+
this.tenantAuth = new TenantAuthModule(this, this.http);
|
|
93
96
|
|
|
94
97
|
// Cria o alias que o Showcase App espera
|
|
95
98
|
this.database = this.db;
|
|
@@ -241,4 +244,10 @@ export type {
|
|
|
241
244
|
PushLogEntry,
|
|
242
245
|
ListPushLogsOptions,
|
|
243
246
|
PushStats,
|
|
244
|
-
} from "./push.js";
|
|
247
|
+
} from "./push.js";
|
|
248
|
+
export type {
|
|
249
|
+
TenantUser,
|
|
250
|
+
TenantLoginResponse,
|
|
251
|
+
TenantRegisterCredentials,
|
|
252
|
+
TenantLoginCredentials,
|
|
253
|
+
} from "./tenant-auth.js";
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// src/tenant-auth.ts
|
|
2
|
+
// [FEATURE] Módulo de Autenticação de Tenants para SDK
|
|
3
|
+
// Permite autenticação de end-users em projetos Aether
|
|
4
|
+
// Compatible with Firebase Auth-style usage
|
|
5
|
+
|
|
6
|
+
import type { AxiosInstance, AxiosError } from "axios";
|
|
7
|
+
import type { PlataformaClient } from "./index.js";
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// TYPES
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Representa um usuário tenant (end-user de um projeto)
|
|
15
|
+
*/
|
|
16
|
+
export interface TenantUser {
|
|
17
|
+
id: string;
|
|
18
|
+
email: string;
|
|
19
|
+
name: string;
|
|
20
|
+
data: Record<string, any>;
|
|
21
|
+
emailVerified: boolean;
|
|
22
|
+
status: "active" | "suspended" | string;
|
|
23
|
+
createdAt: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Resposta de login do tenant
|
|
28
|
+
*/
|
|
29
|
+
export interface TenantLoginResponse {
|
|
30
|
+
accessToken: string;
|
|
31
|
+
user: TenantUser;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Credenciais para registro de tenant
|
|
36
|
+
*/
|
|
37
|
+
export interface TenantRegisterCredentials {
|
|
38
|
+
email: string;
|
|
39
|
+
password: string;
|
|
40
|
+
name?: string;
|
|
41
|
+
data?: Record<string, any>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Credenciais para login de tenant
|
|
46
|
+
*/
|
|
47
|
+
export interface TenantLoginCredentials {
|
|
48
|
+
email: string;
|
|
49
|
+
password: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// MODULE
|
|
54
|
+
// ============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Módulo de Autenticação de Tenants
|
|
58
|
+
*
|
|
59
|
+
* Permite que usuários finais (clientes) de projetos Aether
|
|
60
|
+
* se registrem, façam login e gerenciem seus perfis.
|
|
61
|
+
*
|
|
62
|
+
* Os dados são isolados por projeto (tabela prj_{id}_users).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const aether = new PlataformaClient({ ... });
|
|
67
|
+
*
|
|
68
|
+
* // Registrar usuário no projeto
|
|
69
|
+
* const { user, error } = await aether.tenantAuth.signUp('project-id', {
|
|
70
|
+
* email: 'user@example.com',
|
|
71
|
+
* password: '123456',
|
|
72
|
+
* name: 'John Doe',
|
|
73
|
+
* });
|
|
74
|
+
*
|
|
75
|
+
* // Login
|
|
76
|
+
* const { accessToken, user } = await aether.tenantAuth.signIn('project-id', {
|
|
77
|
+
* email: 'user@example.com',
|
|
78
|
+
* password: '123456',
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* // Obter perfil
|
|
82
|
+
* const profile = await aether.tenantAuth.getProfile('project-id');
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export class TenantAuthModule {
|
|
86
|
+
private client: PlataformaClient;
|
|
87
|
+
private http: AxiosInstance;
|
|
88
|
+
|
|
89
|
+
// Armazena o token do tenant atual em memória
|
|
90
|
+
private currentToken: string | null = null;
|
|
91
|
+
private currentUser: TenantUser | null = null;
|
|
92
|
+
|
|
93
|
+
constructor(client: PlataformaClient, http: AxiosInstance) {
|
|
94
|
+
this.client = client;
|
|
95
|
+
this.http = http;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ==========================================================================
|
|
99
|
+
// MÉTODOS PRINCIPAIS
|
|
100
|
+
// ==========================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Registra um novo usuário tenant no projeto.
|
|
104
|
+
* A tabela de usuários é criada automaticamente se não existir.
|
|
105
|
+
*
|
|
106
|
+
* @param projectId - ID do projeto
|
|
107
|
+
* @param credentials - Email, senha, nome e dados opcionais
|
|
108
|
+
*/
|
|
109
|
+
async register(
|
|
110
|
+
projectId: string,
|
|
111
|
+
credentials: TenantRegisterCredentials
|
|
112
|
+
): Promise<{ user: TenantUser; message: string }> {
|
|
113
|
+
const { data } = await this.http.post("/auth/tenant/register", {
|
|
114
|
+
projectId,
|
|
115
|
+
...credentials,
|
|
116
|
+
});
|
|
117
|
+
return data;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Realiza login de usuário tenant.
|
|
122
|
+
* Armazena o token para uso em requisições subsequentes.
|
|
123
|
+
*
|
|
124
|
+
* @param projectId - ID do projeto
|
|
125
|
+
* @param credentials - Email e senha
|
|
126
|
+
*/
|
|
127
|
+
async login(
|
|
128
|
+
projectId: string,
|
|
129
|
+
credentials: TenantLoginCredentials
|
|
130
|
+
): Promise<TenantLoginResponse> {
|
|
131
|
+
const { data } = await this.http.post<TenantLoginResponse>(
|
|
132
|
+
"/auth/tenant/login",
|
|
133
|
+
{
|
|
134
|
+
projectId,
|
|
135
|
+
...credentials,
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Armazena token e usuário
|
|
140
|
+
this.currentToken = data.accessToken;
|
|
141
|
+
this.currentUser = data.user;
|
|
142
|
+
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Solicita reset de senha.
|
|
148
|
+
* Retorna mensagem genérica para prevenir enumeração de usuários.
|
|
149
|
+
*
|
|
150
|
+
* @param projectId - ID do projeto
|
|
151
|
+
* @param email - Email do usuário
|
|
152
|
+
*/
|
|
153
|
+
async forgotPassword(
|
|
154
|
+
projectId: string,
|
|
155
|
+
email: string
|
|
156
|
+
): Promise<{ message: string }> {
|
|
157
|
+
const { data } = await this.http.post("/auth/tenant/forgot-password", {
|
|
158
|
+
projectId,
|
|
159
|
+
email,
|
|
160
|
+
});
|
|
161
|
+
return data;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Redefine a senha usando o token recebido por email.
|
|
166
|
+
*
|
|
167
|
+
* @param projectId - ID do projeto
|
|
168
|
+
* @param email - Email do usuário
|
|
169
|
+
* @param token - Token de reset (recebido por email)
|
|
170
|
+
* @param newPassword - Nova senha
|
|
171
|
+
*/
|
|
172
|
+
async resetPassword(
|
|
173
|
+
projectId: string,
|
|
174
|
+
email: string,
|
|
175
|
+
token: string,
|
|
176
|
+
newPassword: string
|
|
177
|
+
): Promise<{ message: string }> {
|
|
178
|
+
const { data } = await this.http.post("/auth/tenant/reset-password", {
|
|
179
|
+
projectId,
|
|
180
|
+
email,
|
|
181
|
+
token,
|
|
182
|
+
newPassword,
|
|
183
|
+
});
|
|
184
|
+
return data;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Obtém o perfil do usuário autenticado.
|
|
189
|
+
* Requer que login() tenha sido chamado antes.
|
|
190
|
+
*
|
|
191
|
+
* @param projectId - ID do projeto (opcional, usa do token se não fornecido)
|
|
192
|
+
*/
|
|
193
|
+
async getProfile(projectId?: string): Promise<TenantUser> {
|
|
194
|
+
const headers = this.currentToken
|
|
195
|
+
? { Authorization: `Bearer ${this.currentToken}` }
|
|
196
|
+
: {};
|
|
197
|
+
|
|
198
|
+
const { data } = await this.http.get<{ user: TenantUser }>(
|
|
199
|
+
"/auth/tenant/me",
|
|
200
|
+
{ headers }
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
this.currentUser = data.user;
|
|
204
|
+
return data.user;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Atualiza o perfil do usuário autenticado.
|
|
209
|
+
*
|
|
210
|
+
* @param projectId - ID do projeto
|
|
211
|
+
* @param updates - Campos a atualizar (name, data)
|
|
212
|
+
*/
|
|
213
|
+
async updateProfile(
|
|
214
|
+
projectId: string,
|
|
215
|
+
updates: { name?: string; data?: Record<string, any> }
|
|
216
|
+
): Promise<{ user: TenantUser; message: string }> {
|
|
217
|
+
const headers = this.currentToken
|
|
218
|
+
? { Authorization: `Bearer ${this.currentToken}` }
|
|
219
|
+
: {};
|
|
220
|
+
|
|
221
|
+
const { data } = await this.http.put(
|
|
222
|
+
"/auth/tenant/profile",
|
|
223
|
+
{
|
|
224
|
+
projectId,
|
|
225
|
+
...updates,
|
|
226
|
+
},
|
|
227
|
+
{ headers }
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
this.currentUser = data.user;
|
|
231
|
+
return data;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Faz logout do tenant (limpa token local).
|
|
236
|
+
*/
|
|
237
|
+
logout(): void {
|
|
238
|
+
this.currentToken = null;
|
|
239
|
+
this.currentUser = null;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ==========================================================================
|
|
243
|
+
// MÉTODOS ESTILO SUPABASE/FIREBASE
|
|
244
|
+
// ==========================================================================
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Alias para register com retorno { user, error }
|
|
248
|
+
*/
|
|
249
|
+
async signUp(
|
|
250
|
+
projectId: string,
|
|
251
|
+
credentials: TenantRegisterCredentials
|
|
252
|
+
): Promise<{ user: TenantUser | null; error: string | null }> {
|
|
253
|
+
try {
|
|
254
|
+
const { user } = await this.register(projectId, credentials);
|
|
255
|
+
return { user, error: null };
|
|
256
|
+
} catch (err: any) {
|
|
257
|
+
const axiosError = err as AxiosError<{ error?: string; message?: string }>;
|
|
258
|
+
return {
|
|
259
|
+
user: null,
|
|
260
|
+
error:
|
|
261
|
+
axiosError.response?.data?.error ||
|
|
262
|
+
axiosError.response?.data?.message ||
|
|
263
|
+
err.message,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Alias para login com retorno { user, accessToken, error }
|
|
270
|
+
*/
|
|
271
|
+
async signIn(
|
|
272
|
+
projectId: string,
|
|
273
|
+
credentials: TenantLoginCredentials
|
|
274
|
+
): Promise<{
|
|
275
|
+
user: TenantUser | null;
|
|
276
|
+
accessToken: string | null;
|
|
277
|
+
error: string | null;
|
|
278
|
+
}> {
|
|
279
|
+
try {
|
|
280
|
+
const { user, accessToken } = await this.login(projectId, credentials);
|
|
281
|
+
return { user, accessToken, error: null };
|
|
282
|
+
} catch (err: any) {
|
|
283
|
+
const axiosError = err as AxiosError<{ error?: string; message?: string }>;
|
|
284
|
+
return {
|
|
285
|
+
user: null,
|
|
286
|
+
accessToken: null,
|
|
287
|
+
error:
|
|
288
|
+
axiosError.response?.data?.error ||
|
|
289
|
+
axiosError.response?.data?.message ||
|
|
290
|
+
err.message,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Alias para logout
|
|
297
|
+
*/
|
|
298
|
+
signOut(): void {
|
|
299
|
+
this.logout();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ==========================================================================
|
|
303
|
+
// ACCESSORS
|
|
304
|
+
// ==========================================================================
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Retorna o token atual do tenant
|
|
308
|
+
*/
|
|
309
|
+
getToken(): string | null {
|
|
310
|
+
return this.currentToken;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Define o token do tenant (útil para restaurar sessão)
|
|
315
|
+
*/
|
|
316
|
+
setToken(token: string | null): void {
|
|
317
|
+
this.currentToken = token;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Retorna o usuário atual do tenant
|
|
322
|
+
*/
|
|
323
|
+
getCurrentUser(): TenantUser | null {
|
|
324
|
+
return this.currentUser;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Verifica se há um usuário autenticado
|
|
329
|
+
*/
|
|
330
|
+
isAuthenticated(): boolean {
|
|
331
|
+
return this.currentToken !== null;
|
|
332
|
+
}
|
|
333
|
+
}
|