create-fluxstack 1.4.1 → 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/plugin.ts +324 -34
- package/core/cli/generators/template-engine.ts +5 -0
- 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/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,65 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@fluxstack/crypto-auth-plugin",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Plugin de autenticação criptográfica Ed25519 para FluxStack",
|
|
5
|
-
"main": "index.ts",
|
|
6
|
-
"types": "index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./index.ts",
|
|
10
|
-
"types": "./index.ts"
|
|
11
|
-
},
|
|
12
|
-
"./client": {
|
|
13
|
-
"import": "./client/index.ts",
|
|
14
|
-
"types": "./client/index.ts"
|
|
15
|
-
},
|
|
16
|
-
"./server": {
|
|
17
|
-
"import": "./server/index.ts",
|
|
18
|
-
"types": "./server/index.ts"
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
"keywords": [
|
|
22
|
-
"fluxstack",
|
|
23
|
-
"plugin",
|
|
24
|
-
"authentication",
|
|
25
|
-
"ed25519",
|
|
26
|
-
"cryptography",
|
|
27
|
-
"security",
|
|
28
|
-
"react",
|
|
29
|
-
"typescript"
|
|
30
|
-
],
|
|
31
|
-
"author": "FluxStack Team",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"react": ">=16.8.0"
|
|
35
|
-
},
|
|
36
|
-
"peerDependenciesMeta": {
|
|
37
|
-
"react": {
|
|
38
|
-
"optional": true
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
"dependencies": {
|
|
42
|
-
"@noble/curves": "
|
|
43
|
-
"@noble/hashes": "
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@types/react": "^18.0.0",
|
|
47
|
-
"typescript": "^5.0.0"
|
|
48
|
-
},
|
|
49
|
-
"fluxstack": {
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@fluxstack/crypto-auth-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin de autenticação criptográfica Ed25519 para FluxStack",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"types": "index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./index.ts",
|
|
10
|
+
"types": "./index.ts"
|
|
11
|
+
},
|
|
12
|
+
"./client": {
|
|
13
|
+
"import": "./client/index.ts",
|
|
14
|
+
"types": "./client/index.ts"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"import": "./server/index.ts",
|
|
18
|
+
"types": "./server/index.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"fluxstack",
|
|
23
|
+
"plugin",
|
|
24
|
+
"authentication",
|
|
25
|
+
"ed25519",
|
|
26
|
+
"cryptography",
|
|
27
|
+
"security",
|
|
28
|
+
"react",
|
|
29
|
+
"typescript"
|
|
30
|
+
],
|
|
31
|
+
"author": "FluxStack Team",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=16.8.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependenciesMeta": {
|
|
37
|
+
"react": {
|
|
38
|
+
"optional": true
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@noble/curves": "1.2.0",
|
|
43
|
+
"@noble/hashes": "1.3.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/react": "^18.0.0",
|
|
47
|
+
"typescript": "^5.0.0"
|
|
48
|
+
},
|
|
49
|
+
"fluxstack": {
|
|
50
|
+
"plugin": true,
|
|
51
|
+
"version": "^1.0.0",
|
|
52
|
+
"hooks": [
|
|
53
|
+
"setup",
|
|
54
|
+
"onServerStart",
|
|
55
|
+
"onRequest",
|
|
56
|
+
"onResponse"
|
|
57
|
+
],
|
|
58
|
+
"category": "auth",
|
|
59
|
+
"tags": [
|
|
60
|
+
"authentication",
|
|
61
|
+
"ed25519",
|
|
62
|
+
"cryptography",
|
|
63
|
+
"security"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
65
66
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Middleware de Autenticação
|
|
3
|
-
*
|
|
2
|
+
* Middleware de Autenticação Simplificado
|
|
3
|
+
* Apenas valida autenticação - routing é feito pelos middlewares Elysia
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { RequestContext } from '../../../core/plugins/types'
|
|
@@ -14,57 +14,35 @@ export interface Logger {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export interface AuthMiddlewareConfig {
|
|
17
|
-
protectedRoutes: string[]
|
|
18
|
-
publicRoutes: string[]
|
|
19
17
|
logger?: Logger
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
export interface AuthMiddlewareResult {
|
|
23
21
|
success: boolean
|
|
24
|
-
required: boolean
|
|
25
22
|
error?: string
|
|
26
23
|
user?: {
|
|
27
|
-
|
|
24
|
+
publicKey: string
|
|
28
25
|
isAdmin: boolean
|
|
29
|
-
isSuperAdmin: boolean
|
|
30
26
|
permissions: string[]
|
|
31
27
|
}
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
export class AuthMiddleware {
|
|
35
31
|
private authService: CryptoAuthService
|
|
36
|
-
private config: AuthMiddlewareConfig
|
|
37
32
|
private logger?: Logger
|
|
38
33
|
|
|
39
|
-
constructor(authService: CryptoAuthService, config: AuthMiddlewareConfig) {
|
|
34
|
+
constructor(authService: CryptoAuthService, config: AuthMiddlewareConfig = {}) {
|
|
40
35
|
this.authService = authService
|
|
41
|
-
this.config = config
|
|
42
36
|
this.logger = config.logger
|
|
43
37
|
}
|
|
44
38
|
|
|
45
39
|
/**
|
|
46
|
-
* Autenticar requisição
|
|
40
|
+
* Autenticar requisição (sem path matching - é responsabilidade dos middlewares Elysia)
|
|
47
41
|
*/
|
|
48
42
|
async authenticate(context: RequestContext): Promise<AuthMiddlewareResult> {
|
|
49
43
|
const path = context.path
|
|
50
44
|
const method = context.method
|
|
51
45
|
|
|
52
|
-
// Verificar se a rota é pública
|
|
53
|
-
if (this.isPublicRoute(path)) {
|
|
54
|
-
return {
|
|
55
|
-
success: true,
|
|
56
|
-
required: false
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Verificar se a rota requer autenticação
|
|
61
|
-
if (!this.isProtectedRoute(path)) {
|
|
62
|
-
return {
|
|
63
|
-
success: true,
|
|
64
|
-
required: false
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
46
|
// Extrair headers de autenticação
|
|
69
47
|
const authHeaders = this.extractAuthHeaders(context.headers)
|
|
70
48
|
if (!authHeaders) {
|
|
@@ -76,15 +54,14 @@ export class AuthMiddleware {
|
|
|
76
54
|
|
|
77
55
|
return {
|
|
78
56
|
success: false,
|
|
79
|
-
required: true,
|
|
80
57
|
error: "Headers de autenticação obrigatórios"
|
|
81
58
|
}
|
|
82
59
|
}
|
|
83
60
|
|
|
84
|
-
// Validar
|
|
61
|
+
// Validar assinatura da requisição
|
|
85
62
|
try {
|
|
86
|
-
const validationResult = await this.authService.
|
|
87
|
-
|
|
63
|
+
const validationResult = await this.authService.validateRequest({
|
|
64
|
+
publicKey: authHeaders.publicKey,
|
|
88
65
|
timestamp: authHeaders.timestamp,
|
|
89
66
|
nonce: authHeaders.nonce,
|
|
90
67
|
signature: authHeaders.signature,
|
|
@@ -92,16 +69,15 @@ export class AuthMiddleware {
|
|
|
92
69
|
})
|
|
93
70
|
|
|
94
71
|
if (!validationResult.success) {
|
|
95
|
-
this.logger?.warn("Falha na validação da
|
|
72
|
+
this.logger?.warn("Falha na validação da assinatura", {
|
|
96
73
|
path,
|
|
97
74
|
method,
|
|
98
|
-
|
|
75
|
+
publicKey: authHeaders.publicKey.substring(0, 8) + "...",
|
|
99
76
|
error: validationResult.error
|
|
100
77
|
})
|
|
101
78
|
|
|
102
79
|
return {
|
|
103
80
|
success: false,
|
|
104
|
-
required: true,
|
|
105
81
|
error: validationResult.error
|
|
106
82
|
}
|
|
107
83
|
}
|
|
@@ -109,13 +85,12 @@ export class AuthMiddleware {
|
|
|
109
85
|
this.logger?.debug("Requisição autenticada com sucesso", {
|
|
110
86
|
path,
|
|
111
87
|
method,
|
|
112
|
-
|
|
88
|
+
publicKey: authHeaders.publicKey.substring(0, 8) + "...",
|
|
113
89
|
isAdmin: validationResult.user?.isAdmin
|
|
114
90
|
})
|
|
115
91
|
|
|
116
92
|
return {
|
|
117
93
|
success: true,
|
|
118
|
-
required: true,
|
|
119
94
|
user: validationResult.user
|
|
120
95
|
}
|
|
121
96
|
} catch (error) {
|
|
@@ -127,53 +102,26 @@ export class AuthMiddleware {
|
|
|
127
102
|
|
|
128
103
|
return {
|
|
129
104
|
success: false,
|
|
130
|
-
required: true,
|
|
131
105
|
error: "Erro interno de autenticação"
|
|
132
106
|
}
|
|
133
107
|
}
|
|
134
108
|
}
|
|
135
109
|
|
|
136
|
-
/**
|
|
137
|
-
* Verificar se a rota é pública
|
|
138
|
-
*/
|
|
139
|
-
private isPublicRoute(path: string): boolean {
|
|
140
|
-
return this.config.publicRoutes.some(route => {
|
|
141
|
-
if (route.endsWith('/*')) {
|
|
142
|
-
const prefix = route.slice(0, -2)
|
|
143
|
-
return path.startsWith(prefix)
|
|
144
|
-
}
|
|
145
|
-
return path === route
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Verificar se a rota é protegida
|
|
151
|
-
*/
|
|
152
|
-
private isProtectedRoute(path: string): boolean {
|
|
153
|
-
return this.config.protectedRoutes.some(route => {
|
|
154
|
-
if (route.endsWith('/*')) {
|
|
155
|
-
const prefix = route.slice(0, -2)
|
|
156
|
-
return path.startsWith(prefix)
|
|
157
|
-
}
|
|
158
|
-
return path === route
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
|
-
|
|
162
110
|
/**
|
|
163
111
|
* Extrair headers de autenticação
|
|
164
112
|
*/
|
|
165
113
|
private extractAuthHeaders(headers: Record<string, string>): {
|
|
166
|
-
|
|
114
|
+
publicKey: string
|
|
167
115
|
timestamp: number
|
|
168
116
|
nonce: string
|
|
169
117
|
signature: string
|
|
170
118
|
} | null {
|
|
171
|
-
const
|
|
119
|
+
const publicKey = headers['x-public-key']
|
|
172
120
|
const timestampStr = headers['x-timestamp']
|
|
173
121
|
const nonce = headers['x-nonce']
|
|
174
122
|
const signature = headers['x-signature']
|
|
175
123
|
|
|
176
|
-
if (!
|
|
124
|
+
if (!publicKey || !timestampStr || !nonce || !signature) {
|
|
177
125
|
return null
|
|
178
126
|
}
|
|
179
127
|
|
|
@@ -183,7 +131,7 @@ export class AuthMiddleware {
|
|
|
183
131
|
}
|
|
184
132
|
|
|
185
133
|
return {
|
|
186
|
-
|
|
134
|
+
publicKey,
|
|
187
135
|
timestamp,
|
|
188
136
|
nonce,
|
|
189
137
|
signature
|
|
@@ -207,7 +155,7 @@ export class AuthMiddleware {
|
|
|
207
155
|
}
|
|
208
156
|
|
|
209
157
|
/**
|
|
210
|
-
* Verificar se usuário tem permissão
|
|
158
|
+
* Verificar se usuário tem permissão
|
|
211
159
|
*/
|
|
212
160
|
hasPermission(user: any, requiredPermission: string): boolean {
|
|
213
161
|
if (!user || !user.permissions) {
|
|
@@ -225,13 +173,9 @@ export class AuthMiddleware {
|
|
|
225
173
|
}
|
|
226
174
|
|
|
227
175
|
/**
|
|
228
|
-
* Obter estatísticas do
|
|
176
|
+
* Obter estatísticas do serviço de autenticação
|
|
229
177
|
*/
|
|
230
178
|
getStats() {
|
|
231
|
-
return
|
|
232
|
-
protectedRoutes: this.config.protectedRoutes.length,
|
|
233
|
-
publicRoutes: this.config.publicRoutes.length,
|
|
234
|
-
authService: this.authService.getStats()
|
|
235
|
-
}
|
|
179
|
+
return this.authService.getStats()
|
|
236
180
|
}
|
|
237
|
-
}
|
|
181
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Serviço de Autenticação Criptográfica
|
|
3
|
-
* Implementa autenticação baseada em Ed25519
|
|
3
|
+
* Implementa autenticação baseada em Ed25519 - SEM SESSÕES
|
|
4
|
+
* Cada requisição é validada pela assinatura da chave pública
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { ed25519 } from '@noble/curves/ed25519'
|
|
7
8
|
import { sha256 } from '@noble/hashes/sha256'
|
|
8
|
-
import {
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils'
|
|
10
|
+
|
|
9
11
|
export interface Logger {
|
|
10
12
|
debug(message: string, meta?: any): void
|
|
11
13
|
info(message: string, meta?: any): void
|
|
@@ -13,221 +15,128 @@ export interface Logger {
|
|
|
13
15
|
error(message: string, meta?: any): void
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export interface SessionData {
|
|
17
|
-
sessionId: string
|
|
18
|
-
publicKey: string
|
|
19
|
-
createdAt: Date
|
|
20
|
-
lastUsed: Date
|
|
21
|
-
isAdmin: boolean
|
|
22
|
-
permissions: string[]
|
|
23
|
-
}
|
|
24
|
-
|
|
25
18
|
export interface AuthResult {
|
|
26
19
|
success: boolean
|
|
27
|
-
sessionId?: string
|
|
28
20
|
error?: string
|
|
29
21
|
user?: {
|
|
30
|
-
|
|
22
|
+
publicKey: string
|
|
31
23
|
isAdmin: boolean
|
|
32
|
-
isSuperAdmin: boolean
|
|
33
24
|
permissions: string[]
|
|
34
25
|
}
|
|
35
26
|
}
|
|
36
27
|
|
|
37
28
|
export interface CryptoAuthConfig {
|
|
38
|
-
sessionTimeout: number
|
|
39
29
|
maxTimeDrift: number
|
|
40
30
|
adminKeys: string[]
|
|
41
31
|
logger?: Logger
|
|
42
32
|
}
|
|
43
33
|
|
|
44
34
|
export class CryptoAuthService {
|
|
45
|
-
private sessions: Map<string, SessionData> = new Map()
|
|
46
35
|
private config: CryptoAuthConfig
|
|
47
36
|
private logger?: Logger
|
|
37
|
+
private usedNonces: Map<string, number> = new Map() // Para prevenir replay attacks
|
|
48
38
|
|
|
49
39
|
constructor(config: CryptoAuthConfig) {
|
|
50
40
|
this.config = config
|
|
51
41
|
this.logger = config.logger
|
|
52
42
|
|
|
53
|
-
// Limpar
|
|
43
|
+
// Limpar nonces antigos a cada 5 minutos
|
|
54
44
|
setInterval(() => {
|
|
55
|
-
this.
|
|
45
|
+
this.cleanupOldNonces()
|
|
56
46
|
}, 5 * 60 * 1000)
|
|
57
47
|
}
|
|
58
48
|
|
|
59
49
|
/**
|
|
60
|
-
*
|
|
50
|
+
* Validar assinatura de requisição
|
|
51
|
+
* PRINCIPAL: Valida se assinatura é válida para a chave pública fornecida
|
|
61
52
|
*/
|
|
62
|
-
async
|
|
63
|
-
|
|
64
|
-
let publicKey: string
|
|
65
|
-
|
|
66
|
-
if (data.publicKey) {
|
|
67
|
-
// Validar chave pública fornecida
|
|
68
|
-
if (!this.isValidPublicKey(data.publicKey)) {
|
|
69
|
-
return {
|
|
70
|
-
success: false,
|
|
71
|
-
error: "Chave pública inválida"
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
publicKey = data.publicKey
|
|
75
|
-
} else {
|
|
76
|
-
// Gerar novo par de chaves
|
|
77
|
-
const privateKey = ed25519.utils.randomPrivateKey()
|
|
78
|
-
publicKey = bytesToHex(ed25519.getPublicKey(privateKey))
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const sessionId = publicKey
|
|
82
|
-
const isAdmin = this.config.adminKeys.includes(publicKey)
|
|
83
|
-
|
|
84
|
-
const sessionData: SessionData = {
|
|
85
|
-
sessionId,
|
|
86
|
-
publicKey,
|
|
87
|
-
createdAt: new Date(),
|
|
88
|
-
lastUsed: new Date(),
|
|
89
|
-
isAdmin,
|
|
90
|
-
permissions: isAdmin ? ['admin', 'read', 'write'] : ['read']
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.sessions.set(sessionId, sessionData)
|
|
94
|
-
|
|
95
|
-
this.logger?.info("Nova sessão inicializada", {
|
|
96
|
-
sessionId: sessionId.substring(0, 8) + "...",
|
|
97
|
-
isAdmin,
|
|
98
|
-
permissions: sessionData.permissions
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
success: true,
|
|
103
|
-
sessionId,
|
|
104
|
-
user: {
|
|
105
|
-
sessionId,
|
|
106
|
-
isAdmin,
|
|
107
|
-
isSuperAdmin: isAdmin,
|
|
108
|
-
permissions: sessionData.permissions
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch (error) {
|
|
112
|
-
this.logger?.error("Erro ao inicializar sessão", { error })
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: "Erro interno ao inicializar sessão"
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Validar uma sessão com assinatura
|
|
122
|
-
*/
|
|
123
|
-
async validateSession(data: {
|
|
124
|
-
sessionId: string
|
|
53
|
+
async validateRequest(data: {
|
|
54
|
+
publicKey: string
|
|
125
55
|
timestamp: number
|
|
126
56
|
nonce: string
|
|
127
57
|
signature: string
|
|
128
58
|
message?: string
|
|
129
59
|
}): Promise<AuthResult> {
|
|
130
60
|
try {
|
|
131
|
-
const {
|
|
61
|
+
const { publicKey, timestamp, nonce, signature, message = "" } = data
|
|
132
62
|
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
if (!session) {
|
|
63
|
+
// Validar chave pública
|
|
64
|
+
if (!this.isValidPublicKey(publicKey)) {
|
|
136
65
|
return {
|
|
137
66
|
success: false,
|
|
138
|
-
error: "
|
|
67
|
+
error: "Chave pública inválida"
|
|
139
68
|
}
|
|
140
69
|
}
|
|
141
70
|
|
|
142
|
-
// Verificar
|
|
71
|
+
// Verificar drift de tempo (previne replay de requisições antigas)
|
|
143
72
|
const now = Date.now()
|
|
144
|
-
const
|
|
145
|
-
if (
|
|
146
|
-
this.sessions.delete(sessionId)
|
|
73
|
+
const timeDrift = Math.abs(now - timestamp)
|
|
74
|
+
if (timeDrift > this.config.maxTimeDrift) {
|
|
147
75
|
return {
|
|
148
76
|
success: false,
|
|
149
|
-
error: "
|
|
77
|
+
error: "Timestamp inválido ou expirado"
|
|
150
78
|
}
|
|
151
79
|
}
|
|
152
80
|
|
|
153
|
-
// Verificar
|
|
154
|
-
const
|
|
155
|
-
if (
|
|
81
|
+
// Verificar nonce (previne replay attacks)
|
|
82
|
+
const nonceKey = `${publicKey}:${nonce}`
|
|
83
|
+
if (this.usedNonces.has(nonceKey)) {
|
|
156
84
|
return {
|
|
157
85
|
success: false,
|
|
158
|
-
error: "
|
|
86
|
+
error: "Nonce já utilizado (possível replay attack)"
|
|
159
87
|
}
|
|
160
88
|
}
|
|
161
89
|
|
|
162
90
|
// Construir mensagem para verificação
|
|
163
|
-
const messageToVerify = `${
|
|
91
|
+
const messageToVerify = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
164
92
|
const messageHash = sha256(new TextEncoder().encode(messageToVerify))
|
|
165
93
|
|
|
166
|
-
// Verificar assinatura
|
|
167
|
-
const publicKeyBytes = hexToBytes(
|
|
94
|
+
// Verificar assinatura usando chave pública
|
|
95
|
+
const publicKeyBytes = hexToBytes(publicKey)
|
|
168
96
|
const signatureBytes = hexToBytes(signature)
|
|
169
|
-
|
|
97
|
+
|
|
170
98
|
const isValidSignature = ed25519.verify(signatureBytes, messageHash, publicKeyBytes)
|
|
171
|
-
|
|
99
|
+
|
|
172
100
|
if (!isValidSignature) {
|
|
101
|
+
this.logger?.warn("Assinatura inválida", {
|
|
102
|
+
publicKey: publicKey.substring(0, 8) + "..."
|
|
103
|
+
})
|
|
173
104
|
return {
|
|
174
105
|
success: false,
|
|
175
106
|
error: "Assinatura inválida"
|
|
176
107
|
}
|
|
177
108
|
}
|
|
178
109
|
|
|
179
|
-
//
|
|
180
|
-
|
|
110
|
+
// Marcar nonce como usado
|
|
111
|
+
this.usedNonces.set(nonceKey, timestamp)
|
|
112
|
+
|
|
113
|
+
// Verificar se é admin
|
|
114
|
+
const isAdmin = this.config.adminKeys.includes(publicKey)
|
|
115
|
+
const permissions = isAdmin ? ['admin', 'read', 'write', 'delete'] : ['read']
|
|
116
|
+
|
|
117
|
+
this.logger?.debug("Requisição autenticada", {
|
|
118
|
+
publicKey: publicKey.substring(0, 8) + "...",
|
|
119
|
+
isAdmin,
|
|
120
|
+
permissions
|
|
121
|
+
})
|
|
181
122
|
|
|
182
123
|
return {
|
|
183
124
|
success: true,
|
|
184
|
-
sessionId,
|
|
185
125
|
user: {
|
|
186
|
-
|
|
187
|
-
isAdmin
|
|
188
|
-
|
|
189
|
-
permissions: session.permissions
|
|
126
|
+
publicKey,
|
|
127
|
+
isAdmin,
|
|
128
|
+
permissions
|
|
190
129
|
}
|
|
191
130
|
}
|
|
192
131
|
} catch (error) {
|
|
193
|
-
this.logger?.error("Erro ao validar
|
|
132
|
+
this.logger?.error("Erro ao validar requisição", { error })
|
|
194
133
|
return {
|
|
195
134
|
success: false,
|
|
196
|
-
error: "Erro interno ao validar
|
|
135
|
+
error: "Erro interno ao validar requisição"
|
|
197
136
|
}
|
|
198
137
|
}
|
|
199
138
|
}
|
|
200
139
|
|
|
201
|
-
/**
|
|
202
|
-
* Obter informações da sessão
|
|
203
|
-
*/
|
|
204
|
-
async getSessionInfo(sessionId: string): Promise<SessionData | null> {
|
|
205
|
-
const session = this.sessions.get(sessionId)
|
|
206
|
-
if (!session) {
|
|
207
|
-
return null
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Verificar se não expirou
|
|
211
|
-
const now = Date.now()
|
|
212
|
-
const sessionAge = now - session.lastUsed.getTime()
|
|
213
|
-
if (sessionAge > this.config.sessionTimeout) {
|
|
214
|
-
this.sessions.delete(sessionId)
|
|
215
|
-
return null
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return { ...session }
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Destruir uma sessão
|
|
223
|
-
*/
|
|
224
|
-
async destroySession(sessionId: string): Promise<void> {
|
|
225
|
-
this.sessions.delete(sessionId)
|
|
226
|
-
this.logger?.info("Sessão destruída", {
|
|
227
|
-
sessionId: sessionId.substring(0, 8) + "..."
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
|
|
231
140
|
/**
|
|
232
141
|
* Verificar se uma chave pública é válida
|
|
233
142
|
*/
|
|
@@ -245,49 +154,33 @@ export class CryptoAuthService {
|
|
|
245
154
|
}
|
|
246
155
|
|
|
247
156
|
/**
|
|
248
|
-
* Limpar
|
|
157
|
+
* Limpar nonces antigos (previne crescimento infinito da memória)
|
|
249
158
|
*/
|
|
250
|
-
private
|
|
159
|
+
private cleanupOldNonces(): void {
|
|
251
160
|
const now = Date.now()
|
|
161
|
+
const maxAge = this.config.maxTimeDrift * 2 // Dobro do tempo máximo permitido
|
|
252
162
|
let cleanedCount = 0
|
|
253
163
|
|
|
254
|
-
for (const [
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.sessions.delete(sessionId)
|
|
164
|
+
for (const [nonceKey, timestamp] of this.usedNonces.entries()) {
|
|
165
|
+
if (now - timestamp > maxAge) {
|
|
166
|
+
this.usedNonces.delete(nonceKey)
|
|
258
167
|
cleanedCount++
|
|
259
168
|
}
|
|
260
169
|
}
|
|
261
170
|
|
|
262
171
|
if (cleanedCount > 0) {
|
|
263
|
-
this.logger?.debug(`Limpeza de
|
|
172
|
+
this.logger?.debug(`Limpeza de nonces: ${cleanedCount} nonces antigos removidos`)
|
|
264
173
|
}
|
|
265
174
|
}
|
|
266
175
|
|
|
267
176
|
/**
|
|
268
|
-
* Obter estatísticas
|
|
177
|
+
* Obter estatísticas do serviço
|
|
269
178
|
*/
|
|
270
179
|
getStats() {
|
|
271
|
-
const now = Date.now()
|
|
272
|
-
let activeSessions = 0
|
|
273
|
-
let adminSessions = 0
|
|
274
|
-
|
|
275
|
-
for (const session of this.sessions.values()) {
|
|
276
|
-
const sessionAge = now - session.lastUsed.getTime()
|
|
277
|
-
if (sessionAge <= this.config.sessionTimeout) {
|
|
278
|
-
activeSessions++
|
|
279
|
-
if (session.isAdmin) {
|
|
280
|
-
adminSessions++
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
180
|
return {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
sessionTimeout: this.config.sessionTimeout,
|
|
290
|
-
adminKeys: this.config.adminKeys.length
|
|
181
|
+
usedNoncesCount: this.usedNonces.size,
|
|
182
|
+
adminKeys: this.config.adminKeys.length,
|
|
183
|
+
maxTimeDrift: this.config.maxTimeDrift
|
|
291
184
|
}
|
|
292
185
|
}
|
|
293
186
|
}
|