create-fluxstack 1.4.0 → 1.5.0
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/.env.example +8 -1
- package/CRYPTO-AUTH-MIDDLEWARE-GUIDE.md +475 -0
- package/CRYPTO-AUTH-MIDDLEWARES.md +473 -0
- package/CRYPTO-AUTH-USAGE.md +491 -0
- package/EXEMPLO-ROTA-PROTEGIDA.md +347 -0
- package/QUICK-START-CRYPTO-AUTH.md +221 -0
- package/app/client/src/App.tsx +4 -1
- package/app/client/src/pages/CryptoAuthPage.tsx +394 -0
- package/app/server/index.ts +4 -0
- package/app/server/routes/crypto-auth-demo.routes.ts +167 -0
- package/app/server/routes/example-with-crypto-auth.routes.ts +235 -0
- package/app/server/routes/exemplo-posts.routes.ts +161 -0
- package/app/server/routes/index.ts +5 -1
- package/config/index.ts +9 -1
- package/core/cli/generators/index.ts +5 -2
- package/core/cli/generators/plugin.ts +580 -0
- package/core/cli/generators/template-engine.ts +5 -0
- package/core/cli/index.ts +88 -3
- package/core/cli/plugin-discovery.ts +33 -12
- package/core/framework/server.ts +10 -0
- package/core/plugins/dependency-manager.ts +89 -22
- package/core/plugins/index.ts +4 -0
- package/core/plugins/manager.ts +3 -2
- package/core/plugins/module-resolver.ts +216 -0
- package/core/plugins/registry.ts +28 -1
- package/core/utils/logger/index.ts +4 -0
- package/core/utils/version.ts +1 -1
- package/create-fluxstack.ts +117 -8
- package/fluxstack.config.ts +253 -114
- package/package.json +117 -117
- package/plugins/crypto-auth/README.md +722 -172
- package/plugins/crypto-auth/ai-context.md +1282 -0
- package/plugins/crypto-auth/cli/make-protected-route.command.ts +383 -0
- package/plugins/crypto-auth/client/CryptoAuthClient.ts +136 -159
- package/plugins/crypto-auth/client/components/AuthProvider.tsx +35 -94
- package/plugins/crypto-auth/client/components/LoginButton.tsx +36 -53
- package/plugins/crypto-auth/client/components/ProtectedRoute.tsx +17 -37
- package/plugins/crypto-auth/client/components/index.ts +1 -4
- package/plugins/crypto-auth/client/index.ts +1 -1
- package/plugins/crypto-auth/config/index.ts +34 -0
- package/plugins/crypto-auth/index.ts +84 -152
- package/plugins/crypto-auth/package.json +65 -64
- package/plugins/crypto-auth/server/AuthMiddleware.ts +19 -75
- package/plugins/crypto-auth/server/CryptoAuthService.ts +60 -167
- package/plugins/crypto-auth/server/index.ts +15 -2
- package/plugins/crypto-auth/server/middlewares/cryptoAuthAdmin.ts +65 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthOptional.ts +26 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthPermissions.ts +76 -0
- package/plugins/crypto-auth/server/middlewares/cryptoAuthRequired.ts +45 -0
- package/plugins/crypto-auth/server/middlewares/helpers.ts +140 -0
- package/plugins/crypto-auth/server/middlewares/index.ts +22 -0
- package/plugins/crypto-auth/server/middlewares.ts +19 -0
- package/test-crypto-auth.ts +101 -0
- package/plugins/crypto-auth/client/components/SessionInfo.tsx +0 -242
- package/plugins/crypto-auth/plugin.json +0 -29
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Cliente de Autenticação Criptográfica
|
|
3
|
-
*
|
|
3
|
+
* Sistema baseado em assinatura Ed25519 SEM sessões no servidor
|
|
4
|
+
*
|
|
5
|
+
* Funcionamento:
|
|
6
|
+
* 1. Cliente gera par de chaves Ed25519 localmente
|
|
7
|
+
* 2. Chave privada NUNCA sai do navegador
|
|
8
|
+
* 3. Cada requisição é assinada automaticamente
|
|
9
|
+
* 4. Servidor valida assinatura usando chave pública recebida
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
import { ed25519 } from '@noble/curves/ed25519'
|
|
7
13
|
import { sha256 } from '@noble/hashes/sha256'
|
|
8
14
|
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
9
15
|
|
|
10
|
-
export interface
|
|
11
|
-
sessionId: string
|
|
16
|
+
export interface KeyPair {
|
|
12
17
|
publicKey: string
|
|
13
18
|
privateKey: string
|
|
14
|
-
isAdmin: boolean
|
|
15
|
-
permissions: string[]
|
|
16
19
|
createdAt: Date
|
|
17
|
-
lastUsed: Date
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export interface AuthConfig {
|
|
21
|
-
apiBaseUrl?: string
|
|
22
23
|
storage?: 'localStorage' | 'sessionStorage' | 'memory'
|
|
23
24
|
autoInit?: boolean
|
|
24
|
-
sessionTimeout?: number
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export interface SignedRequestOptions extends RequestInit {
|
|
@@ -29,16 +29,15 @@ export interface SignedRequestOptions extends RequestInit {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export class CryptoAuthClient {
|
|
32
|
-
private
|
|
32
|
+
private keys: KeyPair | null = null
|
|
33
33
|
private config: AuthConfig
|
|
34
34
|
private storage: Storage | Map<string, string>
|
|
35
|
+
private readonly STORAGE_KEY = 'fluxstack_crypto_keys'
|
|
35
36
|
|
|
36
37
|
constructor(config: AuthConfig = {}) {
|
|
37
38
|
this.config = {
|
|
38
|
-
apiBaseUrl: '',
|
|
39
39
|
storage: 'localStorage',
|
|
40
40
|
autoInit: true,
|
|
41
|
-
sessionTimeout: 1800000, // 30 minutos
|
|
42
41
|
...config
|
|
43
42
|
}
|
|
44
43
|
|
|
@@ -58,75 +57,43 @@ export class CryptoAuthClient {
|
|
|
58
57
|
}
|
|
59
58
|
|
|
60
59
|
/**
|
|
61
|
-
* Inicializar
|
|
60
|
+
* Inicializar (gerar ou carregar chaves)
|
|
62
61
|
*/
|
|
63
|
-
|
|
64
|
-
// Tentar carregar
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
this.
|
|
68
|
-
return
|
|
62
|
+
initialize(): KeyPair {
|
|
63
|
+
// Tentar carregar chaves existentes
|
|
64
|
+
const existingKeys = this.loadKeys()
|
|
65
|
+
if (existingKeys) {
|
|
66
|
+
this.keys = existingKeys
|
|
67
|
+
return existingKeys
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
// Criar
|
|
72
|
-
return this.
|
|
70
|
+
// Criar novo par de chaves
|
|
71
|
+
return this.createNewKeys()
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
/**
|
|
76
|
-
* Criar
|
|
75
|
+
* Criar novo par de chaves
|
|
76
|
+
* NUNCA envia chave privada ao servidor!
|
|
77
77
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const response = await fetch(`${this.config.apiBaseUrl}/api/auth/session/init`, {
|
|
89
|
-
method: 'POST',
|
|
90
|
-
headers: {
|
|
91
|
-
'Content-Type': 'application/json'
|
|
92
|
-
},
|
|
93
|
-
body: JSON.stringify({
|
|
94
|
-
publicKey: sessionId
|
|
95
|
-
})
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
throw new Error(`Erro ao inicializar sessão: ${response.statusText}`)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const result = await response.json()
|
|
103
|
-
if (!result.success) {
|
|
104
|
-
throw new Error(result.error || 'Erro desconhecido ao inicializar sessão')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Criar objeto de sessão
|
|
108
|
-
const session: SessionInfo = {
|
|
109
|
-
sessionId,
|
|
110
|
-
publicKey: sessionId,
|
|
111
|
-
privateKey: privateKeyHex,
|
|
112
|
-
isAdmin: result.user?.isAdmin || false,
|
|
113
|
-
permissions: result.user?.permissions || ['read'],
|
|
114
|
-
createdAt: new Date(),
|
|
115
|
-
lastUsed: new Date()
|
|
116
|
-
}
|
|
78
|
+
createNewKeys(): KeyPair {
|
|
79
|
+
// Gerar par de chaves Ed25519
|
|
80
|
+
const privateKey = ed25519.utils.randomPrivateKey()
|
|
81
|
+
const publicKey = ed25519.getPublicKey(privateKey)
|
|
82
|
+
|
|
83
|
+
const keys: KeyPair = {
|
|
84
|
+
publicKey: bytesToHex(publicKey),
|
|
85
|
+
privateKey: bytesToHex(privateKey),
|
|
86
|
+
createdAt: new Date()
|
|
87
|
+
}
|
|
117
88
|
|
|
118
|
-
|
|
119
|
-
|
|
89
|
+
this.keys = keys
|
|
90
|
+
this.saveKeys(keys)
|
|
120
91
|
|
|
121
|
-
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error('Erro ao criar nova sessão:', error)
|
|
124
|
-
throw error
|
|
125
|
-
}
|
|
92
|
+
return keys
|
|
126
93
|
}
|
|
127
94
|
|
|
128
95
|
/**
|
|
129
|
-
* Fazer requisição autenticada
|
|
96
|
+
* Fazer requisição autenticada com assinatura
|
|
130
97
|
*/
|
|
131
98
|
async fetch(url: string, options: SignedRequestOptions = {}): Promise<Response> {
|
|
132
99
|
const { skipAuth = false, ...fetchOptions } = options
|
|
@@ -135,33 +102,30 @@ export class CryptoAuthClient {
|
|
|
135
102
|
return fetch(url, fetchOptions)
|
|
136
103
|
}
|
|
137
104
|
|
|
138
|
-
if (!this.
|
|
139
|
-
|
|
105
|
+
if (!this.keys) {
|
|
106
|
+
this.initialize()
|
|
140
107
|
}
|
|
141
108
|
|
|
142
|
-
if (!this.
|
|
143
|
-
throw new Error('
|
|
109
|
+
if (!this.keys) {
|
|
110
|
+
throw new Error('Chaves não inicializadas')
|
|
144
111
|
}
|
|
145
112
|
|
|
146
|
-
// Preparar
|
|
113
|
+
// Preparar dados de autenticação
|
|
147
114
|
const timestamp = Date.now()
|
|
148
115
|
const nonce = this.generateNonce()
|
|
149
116
|
const message = this.buildMessage(fetchOptions.method || 'GET', url, fetchOptions.body)
|
|
150
117
|
const signature = this.signMessage(message, timestamp, nonce)
|
|
151
118
|
|
|
119
|
+
// Adicionar headers de autenticação
|
|
152
120
|
const headers = {
|
|
153
121
|
'Content-Type': 'application/json',
|
|
154
122
|
...fetchOptions.headers,
|
|
155
|
-
'x-
|
|
123
|
+
'x-public-key': this.keys.publicKey,
|
|
156
124
|
'x-timestamp': timestamp.toString(),
|
|
157
125
|
'x-nonce': nonce,
|
|
158
126
|
'x-signature': signature
|
|
159
127
|
}
|
|
160
128
|
|
|
161
|
-
// Atualizar último uso
|
|
162
|
-
this.session.lastUsed = new Date()
|
|
163
|
-
this.saveSession(this.session)
|
|
164
|
-
|
|
165
129
|
return fetch(url, {
|
|
166
130
|
...fetchOptions,
|
|
167
131
|
headers
|
|
@@ -169,74 +133,102 @@ export class CryptoAuthClient {
|
|
|
169
133
|
}
|
|
170
134
|
|
|
171
135
|
/**
|
|
172
|
-
* Obter
|
|
136
|
+
* Obter chaves atuais
|
|
173
137
|
*/
|
|
174
|
-
|
|
175
|
-
return this.
|
|
138
|
+
getKeys(): KeyPair | null {
|
|
139
|
+
return this.keys
|
|
176
140
|
}
|
|
177
141
|
|
|
178
142
|
/**
|
|
179
|
-
* Verificar se
|
|
143
|
+
* Verificar se tem chaves
|
|
180
144
|
*/
|
|
181
|
-
|
|
182
|
-
return this.
|
|
145
|
+
isInitialized(): boolean {
|
|
146
|
+
return this.keys !== null
|
|
183
147
|
}
|
|
184
148
|
|
|
185
149
|
/**
|
|
186
|
-
*
|
|
150
|
+
* Limpar chaves (logout)
|
|
187
151
|
*/
|
|
188
|
-
|
|
189
|
-
|
|
152
|
+
clearKeys(): void {
|
|
153
|
+
this.keys = null
|
|
154
|
+
if (this.storage instanceof Map) {
|
|
155
|
+
this.storage.delete(this.STORAGE_KEY)
|
|
156
|
+
} else {
|
|
157
|
+
this.storage.removeItem(this.STORAGE_KEY)
|
|
158
|
+
}
|
|
190
159
|
}
|
|
191
160
|
|
|
192
161
|
/**
|
|
193
|
-
*
|
|
162
|
+
* Importar chave privada existente
|
|
163
|
+
* @param privateKeyHex - Chave privada em formato hexadecimal (64 caracteres)
|
|
164
|
+
* @returns KeyPair com as chaves importadas
|
|
165
|
+
* @throws Error se a chave privada for inválida
|
|
194
166
|
*/
|
|
195
|
-
|
|
196
|
-
|
|
167
|
+
importPrivateKey(privateKeyHex: string): KeyPair {
|
|
168
|
+
// Validar formato
|
|
169
|
+
if (!/^[a-fA-F0-9]{64}$/.test(privateKeyHex)) {
|
|
170
|
+
throw new Error('Chave privada inválida. Deve ter 64 caracteres hexadecimais.')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
// Converter hex para bytes
|
|
175
|
+
const privateKeyBytes = hexToBytes(privateKeyHex)
|
|
176
|
+
|
|
177
|
+
// Derivar chave pública da privada
|
|
178
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes)
|
|
179
|
+
|
|
180
|
+
const keys: KeyPair = {
|
|
181
|
+
publicKey: bytesToHex(publicKeyBytes),
|
|
182
|
+
privateKey: privateKeyHex.toLowerCase(),
|
|
183
|
+
createdAt: new Date()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
this.keys = keys
|
|
187
|
+
this.saveKeys(keys)
|
|
188
|
+
|
|
189
|
+
return keys
|
|
190
|
+
} catch (error) {
|
|
191
|
+
throw new Error('Erro ao importar chave privada: ' + (error as Error).message)
|
|
192
|
+
}
|
|
197
193
|
}
|
|
198
194
|
|
|
199
195
|
/**
|
|
200
|
-
*
|
|
196
|
+
* Exportar chave privada (para backup)
|
|
197
|
+
* @returns Chave privada em formato hexadecimal
|
|
198
|
+
* @throws Error se não houver chaves inicializadas
|
|
201
199
|
*/
|
|
202
|
-
|
|
203
|
-
if (this.
|
|
204
|
-
|
|
205
|
-
await this.fetch(`${this.config.apiBaseUrl}/api/auth/session/logout`, {
|
|
206
|
-
method: 'POST'
|
|
207
|
-
})
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.warn('Erro ao fazer logout no servidor:', error)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.session = null
|
|
213
|
-
this.clearSession()
|
|
200
|
+
exportPrivateKey(): string {
|
|
201
|
+
if (!this.keys) {
|
|
202
|
+
throw new Error('Nenhuma chave inicializada para exportar')
|
|
214
203
|
}
|
|
204
|
+
|
|
205
|
+
return this.keys.privateKey
|
|
215
206
|
}
|
|
216
207
|
|
|
217
208
|
/**
|
|
218
209
|
* Assinar mensagem
|
|
219
210
|
*/
|
|
220
211
|
private signMessage(message: string, timestamp: number, nonce: string): string {
|
|
221
|
-
if (!this.
|
|
222
|
-
throw new Error('
|
|
212
|
+
if (!this.keys) {
|
|
213
|
+
throw new Error('Chaves não inicializadas')
|
|
223
214
|
}
|
|
224
215
|
|
|
225
|
-
|
|
216
|
+
// Construir mensagem completa: publicKey:timestamp:nonce:message
|
|
217
|
+
const fullMessage = `${this.keys.publicKey}:${timestamp}:${nonce}:${message}`
|
|
226
218
|
const messageHash = sha256(new TextEncoder().encode(fullMessage))
|
|
227
|
-
|
|
219
|
+
|
|
220
|
+
const privateKeyBytes = hexToBytes(this.keys.privateKey)
|
|
228
221
|
const signature = ed25519.sign(messageHash, privateKeyBytes)
|
|
229
|
-
|
|
222
|
+
|
|
230
223
|
return bytesToHex(signature)
|
|
231
224
|
}
|
|
232
225
|
|
|
233
226
|
/**
|
|
234
227
|
* Construir mensagem para assinatura
|
|
235
228
|
*/
|
|
236
|
-
private buildMessage(method: string, url: string, body?:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
229
|
+
private buildMessage(method: string, url: string, body?: BodyInit | null): string {
|
|
230
|
+
let message = `${method}:${url}`
|
|
231
|
+
|
|
240
232
|
if (body) {
|
|
241
233
|
if (typeof body === 'string') {
|
|
242
234
|
message += `:${body}`
|
|
@@ -252,74 +244,59 @@ export class CryptoAuthClient {
|
|
|
252
244
|
* Gerar nonce aleatório
|
|
253
245
|
*/
|
|
254
246
|
private generateNonce(): string {
|
|
255
|
-
const
|
|
256
|
-
crypto.getRandomValues(
|
|
257
|
-
return bytesToHex(
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Verificar se sessão é válida
|
|
262
|
-
*/
|
|
263
|
-
private isSessionValid(session: SessionInfo): boolean {
|
|
264
|
-
const now = Date.now()
|
|
265
|
-
const sessionAge = now - session.lastUsed.getTime()
|
|
266
|
-
return sessionAge < (this.config.sessionTimeout || 1800000)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Salvar sessão no storage
|
|
271
|
-
*/
|
|
272
|
-
private saveSession(session: SessionInfo): void {
|
|
273
|
-
const sessionData = JSON.stringify({
|
|
274
|
-
...session,
|
|
275
|
-
createdAt: session.createdAt.toISOString(),
|
|
276
|
-
lastUsed: session.lastUsed.toISOString()
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
if (this.storage instanceof Map) {
|
|
280
|
-
this.storage.set('crypto-auth-session', sessionData)
|
|
281
|
-
} else {
|
|
282
|
-
this.storage.setItem('crypto-auth-session', sessionData)
|
|
283
|
-
}
|
|
247
|
+
const bytes = new Uint8Array(16)
|
|
248
|
+
crypto.getRandomValues(bytes)
|
|
249
|
+
return bytesToHex(bytes)
|
|
284
250
|
}
|
|
285
251
|
|
|
286
252
|
/**
|
|
287
|
-
* Carregar
|
|
253
|
+
* Carregar chaves do storage
|
|
288
254
|
*/
|
|
289
|
-
private
|
|
255
|
+
private loadKeys(): KeyPair | null {
|
|
290
256
|
try {
|
|
291
|
-
let
|
|
257
|
+
let data: string | null
|
|
292
258
|
|
|
293
259
|
if (this.storage instanceof Map) {
|
|
294
|
-
|
|
260
|
+
data = this.storage.get(this.STORAGE_KEY) || null
|
|
295
261
|
} else {
|
|
296
|
-
|
|
262
|
+
data = this.storage.getItem(this.STORAGE_KEY)
|
|
297
263
|
}
|
|
298
264
|
|
|
299
|
-
if (!
|
|
265
|
+
if (!data) {
|
|
300
266
|
return null
|
|
301
267
|
}
|
|
302
268
|
|
|
303
|
-
const parsed = JSON.parse(
|
|
269
|
+
const parsed = JSON.parse(data)
|
|
270
|
+
|
|
304
271
|
return {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
272
|
+
publicKey: parsed.publicKey,
|
|
273
|
+
privateKey: parsed.privateKey,
|
|
274
|
+
createdAt: new Date(parsed.createdAt)
|
|
308
275
|
}
|
|
309
276
|
} catch (error) {
|
|
310
|
-
console.
|
|
277
|
+
console.error('Erro ao carregar chaves:', error)
|
|
311
278
|
return null
|
|
312
279
|
}
|
|
313
280
|
}
|
|
314
281
|
|
|
315
282
|
/**
|
|
316
|
-
*
|
|
283
|
+
* Salvar chaves no storage
|
|
317
284
|
*/
|
|
318
|
-
private
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
285
|
+
private saveKeys(keys: KeyPair): void {
|
|
286
|
+
try {
|
|
287
|
+
const data = JSON.stringify({
|
|
288
|
+
publicKey: keys.publicKey,
|
|
289
|
+
privateKey: keys.privateKey,
|
|
290
|
+
createdAt: keys.createdAt.toISOString()
|
|
291
|
+
})
|
|
292
|
+
|
|
293
|
+
if (this.storage instanceof Map) {
|
|
294
|
+
this.storage.set(this.STORAGE_KEY, data)
|
|
295
|
+
} else {
|
|
296
|
+
this.storage.setItem(this.STORAGE_KEY, data)
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
console.error('Erro ao salvar chaves:', error)
|
|
323
300
|
}
|
|
324
301
|
}
|
|
325
|
-
}
|
|
302
|
+
}
|
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Provedor de Contexto de Autenticação
|
|
3
|
-
* Context Provider React para gerenciar
|
|
3
|
+
* Context Provider React para gerenciar chaves criptográficas
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react'
|
|
7
|
-
import { CryptoAuthClient,
|
|
6
|
+
import React, { createContext, useContext, useEffect, useState, type ReactNode } from 'react'
|
|
7
|
+
import { CryptoAuthClient, type KeyPair, type AuthConfig } from '../CryptoAuthClient'
|
|
8
8
|
|
|
9
9
|
export interface AuthContextValue {
|
|
10
10
|
client: CryptoAuthClient
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
isAdmin: boolean
|
|
14
|
-
permissions: string[]
|
|
11
|
+
keys: KeyPair | null
|
|
12
|
+
hasKeys: boolean
|
|
15
13
|
isLoading: boolean
|
|
16
14
|
error: string | null
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
refresh: () => Promise<void>
|
|
15
|
+
createKeys: () => void
|
|
16
|
+
clearKeys: () => void
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
@@ -24,71 +21,59 @@ const AuthContext = createContext<AuthContextValue | null>(null)
|
|
|
24
21
|
export interface AuthProviderProps {
|
|
25
22
|
children: ReactNode
|
|
26
23
|
config?: AuthConfig
|
|
27
|
-
|
|
24
|
+
onKeysChange?: (hasKeys: boolean, keys: KeyPair | null) => void
|
|
28
25
|
onError?: (error: string) => void
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
export const AuthProvider: React.FC<AuthProviderProps> = ({
|
|
32
29
|
children,
|
|
33
30
|
config = {},
|
|
34
|
-
|
|
31
|
+
onKeysChange,
|
|
35
32
|
onError
|
|
36
33
|
}) => {
|
|
37
34
|
const [client] = useState(() => new CryptoAuthClient({ ...config, autoInit: false }))
|
|
38
|
-
const [
|
|
35
|
+
const [keys, setKeys] = useState<KeyPair | null>(null)
|
|
39
36
|
const [isLoading, setIsLoading] = useState(true)
|
|
40
37
|
const [error, setError] = useState<string | null>(null)
|
|
41
38
|
|
|
42
|
-
const
|
|
43
|
-
const isAdmin = session?.isAdmin || false
|
|
44
|
-
const permissions = session?.permissions || []
|
|
39
|
+
const hasKeys = keys !== null
|
|
45
40
|
|
|
46
41
|
useEffect(() => {
|
|
47
|
-
|
|
42
|
+
initializeKeys()
|
|
48
43
|
}, [])
|
|
49
44
|
|
|
50
45
|
useEffect(() => {
|
|
51
|
-
|
|
52
|
-
}, [
|
|
46
|
+
onKeysChange?.(hasKeys, keys)
|
|
47
|
+
}, [hasKeys, keys, onKeysChange])
|
|
53
48
|
|
|
54
|
-
const
|
|
49
|
+
const initializeKeys = () => {
|
|
55
50
|
setIsLoading(true)
|
|
56
51
|
setError(null)
|
|
57
|
-
|
|
52
|
+
|
|
58
53
|
try {
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
61
|
-
|
|
62
|
-
} else {
|
|
63
|
-
// Tentar inicializar automaticamente se não houver sessão
|
|
64
|
-
try {
|
|
65
|
-
const newSession = await client.initialize()
|
|
66
|
-
setSession(newSession)
|
|
67
|
-
} catch (initError) {
|
|
68
|
-
// Falha na inicialização automática é normal se não houver sessão salva
|
|
69
|
-
console.debug('Inicialização automática falhou:', initError)
|
|
70
|
-
setSession(null)
|
|
71
|
-
}
|
|
54
|
+
const existingKeys = client.getKeys()
|
|
55
|
+
if (existingKeys) {
|
|
56
|
+
setKeys(existingKeys)
|
|
72
57
|
}
|
|
73
58
|
} catch (err) {
|
|
74
59
|
const errorMessage = err instanceof Error ? err.message : 'Erro desconhecido'
|
|
75
60
|
setError(errorMessage)
|
|
76
61
|
onError?.(errorMessage)
|
|
77
|
-
console.error('Erro ao inicializar
|
|
62
|
+
console.error('Erro ao inicializar chaves:', err)
|
|
78
63
|
} finally {
|
|
79
64
|
setIsLoading(false)
|
|
80
65
|
}
|
|
81
66
|
}
|
|
82
67
|
|
|
83
|
-
const
|
|
68
|
+
const createKeys = () => {
|
|
84
69
|
setIsLoading(true)
|
|
85
70
|
setError(null)
|
|
86
|
-
|
|
71
|
+
|
|
87
72
|
try {
|
|
88
|
-
const
|
|
89
|
-
|
|
73
|
+
const newKeys = client.createNewKeys()
|
|
74
|
+
setKeys(newKeys)
|
|
90
75
|
} catch (err) {
|
|
91
|
-
const errorMessage = err instanceof Error ? err.message : 'Erro ao
|
|
76
|
+
const errorMessage = err instanceof Error ? err.message : 'Erro ao criar chaves'
|
|
92
77
|
setError(errorMessage)
|
|
93
78
|
onError?.(errorMessage)
|
|
94
79
|
throw err
|
|
@@ -97,60 +82,19 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({
|
|
|
97
82
|
}
|
|
98
83
|
}
|
|
99
84
|
|
|
100
|
-
const
|
|
85
|
+
const clearKeys = () => {
|
|
101
86
|
setIsLoading(true)
|
|
102
87
|
setError(null)
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
await client.logout()
|
|
106
|
-
setSession(null)
|
|
107
|
-
} catch (err) {
|
|
108
|
-
const errorMessage = err instanceof Error ? err.message : 'Erro ao fazer logout'
|
|
109
|
-
setError(errorMessage)
|
|
110
|
-
onError?.(errorMessage)
|
|
111
|
-
// Mesmo com erro, limpar a sessão local
|
|
112
|
-
setSession(null)
|
|
113
|
-
} finally {
|
|
114
|
-
setIsLoading(false)
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
88
|
|
|
118
|
-
const refresh = async () => {
|
|
119
|
-
setIsLoading(true)
|
|
120
|
-
setError(null)
|
|
121
|
-
|
|
122
89
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (currentSession && client.isAuthenticated()) {
|
|
126
|
-
// Tentar fazer uma requisição de teste para validar no servidor
|
|
127
|
-
const response = await client.fetch('/api/auth/session/info')
|
|
128
|
-
if (response.ok) {
|
|
129
|
-
const result = await response.json()
|
|
130
|
-
if (result.success && result.session) {
|
|
131
|
-
// Atualizar informações da sessão
|
|
132
|
-
const updatedSession = {
|
|
133
|
-
...currentSession,
|
|
134
|
-
...result.session,
|
|
135
|
-
lastUsed: new Date()
|
|
136
|
-
}
|
|
137
|
-
setSession(updatedSession)
|
|
138
|
-
} else {
|
|
139
|
-
// Sessão inválida no servidor
|
|
140
|
-
setSession(null)
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
// Erro na requisição, sessão pode estar inválida
|
|
144
|
-
setSession(null)
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
setSession(null)
|
|
148
|
-
}
|
|
90
|
+
client.clearKeys()
|
|
91
|
+
setKeys(null)
|
|
149
92
|
} catch (err) {
|
|
150
|
-
const errorMessage = err instanceof Error ? err.message : 'Erro ao
|
|
93
|
+
const errorMessage = err instanceof Error ? err.message : 'Erro ao limpar chaves'
|
|
151
94
|
setError(errorMessage)
|
|
152
95
|
onError?.(errorMessage)
|
|
153
|
-
|
|
96
|
+
// Mesmo com erro, limpar as chaves locais
|
|
97
|
+
setKeys(null)
|
|
154
98
|
} finally {
|
|
155
99
|
setIsLoading(false)
|
|
156
100
|
}
|
|
@@ -158,15 +102,12 @@ export const AuthProvider: React.FC<AuthProviderProps> = ({
|
|
|
158
102
|
|
|
159
103
|
const contextValue: AuthContextValue = {
|
|
160
104
|
client,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
isAdmin,
|
|
164
|
-
permissions,
|
|
105
|
+
keys,
|
|
106
|
+
hasKeys,
|
|
165
107
|
isLoading,
|
|
166
108
|
error,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
refresh
|
|
109
|
+
createKeys,
|
|
110
|
+
clearKeys
|
|
170
111
|
}
|
|
171
112
|
|
|
172
113
|
return (
|