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
|
@@ -3,7 +3,20 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { CryptoAuthService } from './CryptoAuthService'
|
|
6
|
-
export type {
|
|
6
|
+
export type { AuthResult, CryptoAuthConfig } from './CryptoAuthService'
|
|
7
7
|
|
|
8
8
|
export { AuthMiddleware } from './AuthMiddleware'
|
|
9
|
-
export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
|
|
9
|
+
export type { AuthMiddlewareConfig, AuthMiddlewareResult } from './AuthMiddleware'
|
|
10
|
+
|
|
11
|
+
// Middlewares Elysia
|
|
12
|
+
export {
|
|
13
|
+
cryptoAuthRequired,
|
|
14
|
+
cryptoAuthAdmin,
|
|
15
|
+
cryptoAuthPermissions,
|
|
16
|
+
cryptoAuthOptional,
|
|
17
|
+
getCryptoAuthUser,
|
|
18
|
+
isCryptoAuthAuthenticated,
|
|
19
|
+
isCryptoAuthAdmin,
|
|
20
|
+
hasCryptoAuthPermission
|
|
21
|
+
} from './middlewares'
|
|
22
|
+
export type { CryptoAuthUser, CryptoAuthMiddlewareOptions } from './middlewares'
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware que REQUER privilégios de administrador
|
|
3
|
+
* Bloqueia requisições não autenticadas (401) ou sem privilégios admin (403)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Elysia } from 'elysia'
|
|
7
|
+
import { createGuard } from '@/core/server/middleware/elysia-helpers'
|
|
8
|
+
import type { Logger } from '@/core/utils/logger'
|
|
9
|
+
import { validateAuthSync, type CryptoAuthUser } from './helpers'
|
|
10
|
+
|
|
11
|
+
export interface CryptoAuthMiddlewareOptions {
|
|
12
|
+
logger?: Logger
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const cryptoAuthAdmin = (options: CryptoAuthMiddlewareOptions = {}) => {
|
|
16
|
+
return new Elysia({ name: 'crypto-auth-admin' })
|
|
17
|
+
.derive(async ({ request }) => {
|
|
18
|
+
const result = await validateAuthSync(request as Request, options.logger)
|
|
19
|
+
|
|
20
|
+
if (result.success && result.user) {
|
|
21
|
+
;(request as any).user = result.user
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {}
|
|
25
|
+
})
|
|
26
|
+
.use(
|
|
27
|
+
createGuard({
|
|
28
|
+
name: 'crypto-auth-admin-check',
|
|
29
|
+
check: ({ request }) => {
|
|
30
|
+
const user = (request as any).user as CryptoAuthUser | undefined
|
|
31
|
+
return user && user.isAdmin
|
|
32
|
+
},
|
|
33
|
+
onFail: (set, { request }) => {
|
|
34
|
+
const user = (request as any).user as CryptoAuthUser | undefined
|
|
35
|
+
|
|
36
|
+
if (!user) {
|
|
37
|
+
set.status = 401
|
|
38
|
+
return {
|
|
39
|
+
error: {
|
|
40
|
+
message: 'Authentication required',
|
|
41
|
+
code: 'CRYPTO_AUTH_REQUIRED',
|
|
42
|
+
statusCode: 401
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
options.logger?.warn('Admin access denied', {
|
|
48
|
+
publicKey: user.publicKey.substring(0, 8) + '...',
|
|
49
|
+
permissions: user.permissions
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
set.status = 403
|
|
53
|
+
return {
|
|
54
|
+
error: {
|
|
55
|
+
message: 'Admin privileges required',
|
|
56
|
+
code: 'ADMIN_REQUIRED',
|
|
57
|
+
statusCode: 403,
|
|
58
|
+
yourPermissions: user.permissions
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
)
|
|
64
|
+
.as('plugin')
|
|
65
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware OPCIONAL - adiciona user se autenticado, mas não requer
|
|
3
|
+
* Não bloqueia requisições não autenticadas - permite acesso público
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Elysia } from 'elysia'
|
|
7
|
+
import type { Logger } from '@/core/utils/logger'
|
|
8
|
+
import { validateAuthSync } from './helpers'
|
|
9
|
+
|
|
10
|
+
export interface CryptoAuthMiddlewareOptions {
|
|
11
|
+
logger?: Logger
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const cryptoAuthOptional = (options: CryptoAuthMiddlewareOptions = {}) => {
|
|
15
|
+
return new Elysia({ name: 'crypto-auth-optional' })
|
|
16
|
+
.derive(async ({ request }) => {
|
|
17
|
+
const result = await validateAuthSync(request as Request, options.logger)
|
|
18
|
+
|
|
19
|
+
if (result.success && result.user) {
|
|
20
|
+
;(request as any).user = result.user
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {}
|
|
24
|
+
})
|
|
25
|
+
.as('plugin')
|
|
26
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware que REQUER permissões específicas
|
|
3
|
+
* Bloqueia requisições sem as permissões necessárias (403)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Elysia } from 'elysia'
|
|
7
|
+
import { createGuard } from '@/core/server/middleware/elysia-helpers'
|
|
8
|
+
import type { Logger } from '@/core/utils/logger'
|
|
9
|
+
import { validateAuthSync, type CryptoAuthUser } from './helpers'
|
|
10
|
+
|
|
11
|
+
export interface CryptoAuthMiddlewareOptions {
|
|
12
|
+
logger?: Logger
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const cryptoAuthPermissions = (
|
|
16
|
+
requiredPermissions: string[],
|
|
17
|
+
options: CryptoAuthMiddlewareOptions = {}
|
|
18
|
+
) => {
|
|
19
|
+
return new Elysia({ name: 'crypto-auth-permissions' })
|
|
20
|
+
.derive(async ({ request }) => {
|
|
21
|
+
const result = await validateAuthSync(request as Request, options.logger)
|
|
22
|
+
|
|
23
|
+
if (result.success && result.user) {
|
|
24
|
+
;(request as any).user = result.user
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {}
|
|
28
|
+
})
|
|
29
|
+
.use(
|
|
30
|
+
createGuard({
|
|
31
|
+
name: 'crypto-auth-permissions-check',
|
|
32
|
+
check: ({ request }) => {
|
|
33
|
+
const user = (request as any).user as CryptoAuthUser | undefined
|
|
34
|
+
|
|
35
|
+
if (!user) return false
|
|
36
|
+
|
|
37
|
+
const userPermissions = user.permissions
|
|
38
|
+
return requiredPermissions.every(
|
|
39
|
+
perm => userPermissions.includes(perm) || userPermissions.includes('admin')
|
|
40
|
+
)
|
|
41
|
+
},
|
|
42
|
+
onFail: (set, { request }) => {
|
|
43
|
+
const user = (request as any).user as CryptoAuthUser | undefined
|
|
44
|
+
|
|
45
|
+
if (!user) {
|
|
46
|
+
set.status = 401
|
|
47
|
+
return {
|
|
48
|
+
error: {
|
|
49
|
+
message: 'Authentication required',
|
|
50
|
+
code: 'CRYPTO_AUTH_REQUIRED',
|
|
51
|
+
statusCode: 401
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
options.logger?.warn('Permission denied', {
|
|
57
|
+
publicKey: user.publicKey.substring(0, 8) + '...',
|
|
58
|
+
required: requiredPermissions,
|
|
59
|
+
has: user.permissions
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
set.status = 403
|
|
63
|
+
return {
|
|
64
|
+
error: {
|
|
65
|
+
message: 'Insufficient permissions',
|
|
66
|
+
code: 'PERMISSION_DENIED',
|
|
67
|
+
statusCode: 403,
|
|
68
|
+
required: requiredPermissions,
|
|
69
|
+
yours: user.permissions
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
)
|
|
75
|
+
.as('plugin')
|
|
76
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware que REQUER autenticação
|
|
3
|
+
* Bloqueia requisições não autenticadas com 401
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Elysia } from 'elysia'
|
|
7
|
+
import { createGuard } from '@/core/server/middleware/elysia-helpers'
|
|
8
|
+
import type { Logger } from '@/core/utils/logger'
|
|
9
|
+
import { validateAuthSync } from './helpers'
|
|
10
|
+
|
|
11
|
+
export interface CryptoAuthMiddlewareOptions {
|
|
12
|
+
logger?: Logger
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const cryptoAuthRequired = (options: CryptoAuthMiddlewareOptions = {}) => {
|
|
16
|
+
return new Elysia({ name: 'crypto-auth-required' })
|
|
17
|
+
.derive(async ({ request }) => {
|
|
18
|
+
const result = await validateAuthSync(request as Request, options.logger)
|
|
19
|
+
|
|
20
|
+
if (result.success && result.user) {
|
|
21
|
+
;(request as any).user = result.user
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {}
|
|
25
|
+
})
|
|
26
|
+
.use(
|
|
27
|
+
createGuard({
|
|
28
|
+
name: 'crypto-auth-check',
|
|
29
|
+
check: ({ request }) => {
|
|
30
|
+
return !!(request as any).user
|
|
31
|
+
},
|
|
32
|
+
onFail: (set) => {
|
|
33
|
+
set.status = 401
|
|
34
|
+
return {
|
|
35
|
+
error: {
|
|
36
|
+
message: 'Authentication required',
|
|
37
|
+
code: 'CRYPTO_AUTH_REQUIRED',
|
|
38
|
+
statusCode: 401
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
.as('plugin')
|
|
45
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Auth Middleware Helpers
|
|
3
|
+
* Funções compartilhadas para validação de autenticação
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Logger } from '@/core/utils/logger'
|
|
7
|
+
|
|
8
|
+
export interface CryptoAuthUser {
|
|
9
|
+
publicKey: string
|
|
10
|
+
isAdmin: boolean
|
|
11
|
+
permissions: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get auth service from global
|
|
16
|
+
*/
|
|
17
|
+
export function getAuthService() {
|
|
18
|
+
const service = (global as any).cryptoAuthService
|
|
19
|
+
if (!service) {
|
|
20
|
+
throw new Error('CryptoAuthService not initialized. Make sure crypto-auth plugin is loaded.')
|
|
21
|
+
}
|
|
22
|
+
return service
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get auth middleware from global
|
|
27
|
+
*/
|
|
28
|
+
export function getAuthMiddleware() {
|
|
29
|
+
const middleware = (global as any).cryptoAuthMiddleware
|
|
30
|
+
if (!middleware) {
|
|
31
|
+
throw new Error('AuthMiddleware not initialized. Make sure crypto-auth plugin is loaded.')
|
|
32
|
+
}
|
|
33
|
+
return middleware
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Extract and validate authentication from request
|
|
38
|
+
* Versão SÍNCRONA para evitar problemas com Elysia
|
|
39
|
+
*/
|
|
40
|
+
export function extractAuthHeaders(request: Request): {
|
|
41
|
+
publicKey: string
|
|
42
|
+
timestamp: number
|
|
43
|
+
nonce: string
|
|
44
|
+
signature: string
|
|
45
|
+
} | null {
|
|
46
|
+
const headers = request.headers
|
|
47
|
+
const publicKey = headers.get('x-public-key')
|
|
48
|
+
const timestampStr = headers.get('x-timestamp')
|
|
49
|
+
const nonce = headers.get('x-nonce')
|
|
50
|
+
const signature = headers.get('x-signature')
|
|
51
|
+
|
|
52
|
+
if (!publicKey || !timestampStr || !nonce || !signature) {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const timestamp = parseInt(timestampStr, 10)
|
|
57
|
+
if (isNaN(timestamp)) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { publicKey, timestamp, nonce, signature }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build message for signature verification
|
|
66
|
+
*/
|
|
67
|
+
export function buildMessage(request: Request): string {
|
|
68
|
+
const url = new URL(request.url)
|
|
69
|
+
return `${request.method}:${url.pathname}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Validate authentication synchronously
|
|
74
|
+
*/
|
|
75
|
+
export async function validateAuthSync(request: Request, logger?: Logger): Promise<{
|
|
76
|
+
success: boolean
|
|
77
|
+
user?: CryptoAuthUser
|
|
78
|
+
error?: string
|
|
79
|
+
}> {
|
|
80
|
+
try {
|
|
81
|
+
const authHeaders = extractAuthHeaders(request)
|
|
82
|
+
|
|
83
|
+
if (!authHeaders) {
|
|
84
|
+
return {
|
|
85
|
+
success: false,
|
|
86
|
+
error: 'Missing authentication headers'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const authService = getAuthService()
|
|
91
|
+
const message = buildMessage(request)
|
|
92
|
+
|
|
93
|
+
const result = await authService.validateRequest({
|
|
94
|
+
publicKey: authHeaders.publicKey,
|
|
95
|
+
timestamp: authHeaders.timestamp,
|
|
96
|
+
nonce: authHeaders.nonce,
|
|
97
|
+
signature: authHeaders.signature,
|
|
98
|
+
message
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return result
|
|
102
|
+
} catch (error) {
|
|
103
|
+
logger?.error('Auth validation error', { error })
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Helper: Obter usuário autenticado do request
|
|
113
|
+
*/
|
|
114
|
+
export function getCryptoAuthUser(request: Request): CryptoAuthUser | null {
|
|
115
|
+
return (request as any).user || null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Helper: Verificar se request está autenticado
|
|
120
|
+
*/
|
|
121
|
+
export function isCryptoAuthAuthenticated(request: Request): boolean {
|
|
122
|
+
return !!(request as any).user
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Helper: Verificar se usuário é admin
|
|
127
|
+
*/
|
|
128
|
+
export function isCryptoAuthAdmin(request: Request): boolean {
|
|
129
|
+
const user = getCryptoAuthUser(request)
|
|
130
|
+
return user?.isAdmin || false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Helper: Verificar se usuário tem permissão específica
|
|
135
|
+
*/
|
|
136
|
+
export function hasCryptoAuthPermission(request: Request, permission: string): boolean {
|
|
137
|
+
const user = getCryptoAuthUser(request)
|
|
138
|
+
if (!user) return false
|
|
139
|
+
return user.permissions.includes(permission) || user.permissions.includes('admin')
|
|
140
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Auth Middlewares
|
|
3
|
+
* Exports centralizados de todos os middlewares
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Middlewares
|
|
7
|
+
export { cryptoAuthRequired } from './cryptoAuthRequired'
|
|
8
|
+
export { cryptoAuthAdmin } from './cryptoAuthAdmin'
|
|
9
|
+
export { cryptoAuthOptional } from './cryptoAuthOptional'
|
|
10
|
+
export { cryptoAuthPermissions } from './cryptoAuthPermissions'
|
|
11
|
+
|
|
12
|
+
// Helpers
|
|
13
|
+
export {
|
|
14
|
+
getCryptoAuthUser,
|
|
15
|
+
isCryptoAuthAuthenticated,
|
|
16
|
+
isCryptoAuthAdmin,
|
|
17
|
+
hasCryptoAuthPermission,
|
|
18
|
+
type CryptoAuthUser
|
|
19
|
+
} from './helpers'
|
|
20
|
+
|
|
21
|
+
// Types
|
|
22
|
+
export type { CryptoAuthMiddlewareOptions } from './cryptoAuthRequired'
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crypto Auth Middlewares
|
|
3
|
+
* Middlewares Elysia para autenticação criptográfica
|
|
4
|
+
*
|
|
5
|
+
* Uso:
|
|
6
|
+
* ```typescript
|
|
7
|
+
* import { cryptoAuthRequired, cryptoAuthAdmin } from '@/plugins/crypto-auth/server'
|
|
8
|
+
*
|
|
9
|
+
* export const myRoutes = new Elysia()
|
|
10
|
+
* .use(cryptoAuthRequired())
|
|
11
|
+
* .get('/protected', ({ request }) => {
|
|
12
|
+
* const user = getCryptoAuthUser(request)
|
|
13
|
+
* return { user }
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Re-export tudo do módulo middlewares
|
|
19
|
+
export * from './middlewares/index'
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script de teste para validar autenticação criptográfica
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ed25519 } from '@noble/curves/ed25519'
|
|
6
|
+
import { sha256 } from '@noble/hashes/sha256'
|
|
7
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'
|
|
8
|
+
|
|
9
|
+
async function testCryptoAuth() {
|
|
10
|
+
console.log('🔐 Testando Autenticação Criptográfica Ed25519\n')
|
|
11
|
+
|
|
12
|
+
// 1. Gerar par de chaves
|
|
13
|
+
console.log('1️⃣ Gerando par de chaves Ed25519...')
|
|
14
|
+
const privateKey = ed25519.utils.randomPrivateKey()
|
|
15
|
+
const publicKeyBytes = ed25519.getPublicKey(privateKey)
|
|
16
|
+
const publicKey = bytesToHex(publicKeyBytes)
|
|
17
|
+
const privateKeyHex = bytesToHex(privateKey)
|
|
18
|
+
|
|
19
|
+
console.log(` ✅ Chave pública: ${publicKey.substring(0, 16)}...`)
|
|
20
|
+
console.log(` ✅ Chave privada: ${privateKeyHex.substring(0, 16)}... (NUNCA enviar ao servidor!)\n`)
|
|
21
|
+
|
|
22
|
+
// 2. Preparar requisição
|
|
23
|
+
console.log('2️⃣ Preparando requisição assinada...')
|
|
24
|
+
const url = '/api/crypto-auth/protected'
|
|
25
|
+
const method = 'GET'
|
|
26
|
+
const timestamp = Date.now()
|
|
27
|
+
const nonce = generateNonce()
|
|
28
|
+
|
|
29
|
+
// 3. Construir mensagem para assinar
|
|
30
|
+
const message = `${method}:${url}`
|
|
31
|
+
const fullMessage = `${publicKey}:${timestamp}:${nonce}:${message}`
|
|
32
|
+
|
|
33
|
+
console.log(` 📝 Mensagem: ${fullMessage.substring(0, 50)}...\n`)
|
|
34
|
+
|
|
35
|
+
// 4. Assinar mensagem
|
|
36
|
+
console.log('3️⃣ Assinando mensagem com chave privada...')
|
|
37
|
+
const messageHash = sha256(new TextEncoder().encode(fullMessage))
|
|
38
|
+
const signatureBytes = ed25519.sign(messageHash, privateKey)
|
|
39
|
+
const signature = bytesToHex(signatureBytes)
|
|
40
|
+
|
|
41
|
+
console.log(` ✅ Assinatura: ${signature.substring(0, 32)}...\n`)
|
|
42
|
+
|
|
43
|
+
// 5. Fazer requisição ao servidor
|
|
44
|
+
console.log('4️⃣ Enviando requisição ao servidor...')
|
|
45
|
+
const response = await fetch('http://localhost:3000/api/crypto-auth/protected', {
|
|
46
|
+
method: 'GET',
|
|
47
|
+
headers: {
|
|
48
|
+
'x-public-key': publicKey,
|
|
49
|
+
'x-timestamp': timestamp.toString(),
|
|
50
|
+
'x-nonce': nonce,
|
|
51
|
+
'x-signature': signature
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const data = await response.json()
|
|
56
|
+
|
|
57
|
+
console.log(` 📡 Status: ${response.status}`)
|
|
58
|
+
console.log(` 📦 Resposta:`, JSON.stringify(data, null, 2))
|
|
59
|
+
|
|
60
|
+
if (data.success) {
|
|
61
|
+
console.log('\n✅ SUCESSO! Assinatura validada corretamente pelo servidor!')
|
|
62
|
+
console.log(` 👤 Dados protegidos recebidos: ${data.data?.secretInfo}`)
|
|
63
|
+
} else {
|
|
64
|
+
console.log('\n❌ ERRO! Assinatura rejeitada pelo servidor')
|
|
65
|
+
console.log(` ⚠️ Erro: ${data.error}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 6. Testar replay attack (reutilizar mesma assinatura)
|
|
69
|
+
console.log('\n5️⃣ Testando proteção contra replay attack...')
|
|
70
|
+
const replayResponse = await fetch('http://localhost:3000/api/crypto-auth/protected', {
|
|
71
|
+
method: 'GET',
|
|
72
|
+
headers: {
|
|
73
|
+
'x-public-key': publicKey,
|
|
74
|
+
'x-timestamp': timestamp.toString(),
|
|
75
|
+
'x-nonce': nonce, // Mesmo nonce
|
|
76
|
+
'x-signature': signature // Mesma assinatura
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const replayData = await replayResponse.json()
|
|
81
|
+
|
|
82
|
+
console.log(` 📡 Replay Status: ${replayResponse.status}`)
|
|
83
|
+
console.log(` 📦 Replay Response:`, JSON.stringify(replayData, null, 2))
|
|
84
|
+
|
|
85
|
+
if (!replayData.success && replayData.error?.includes('nonce')) {
|
|
86
|
+
console.log(' ✅ Proteção funcionando! Replay attack bloqueado.')
|
|
87
|
+
} else if (replayResponse.status === 401) {
|
|
88
|
+
console.log(' ✅ Proteção funcionando! Replay attack bloqueado (status 401).')
|
|
89
|
+
} else {
|
|
90
|
+
console.log(' ⚠️ ATENÇÃO: Replay attack NÃO foi bloqueado!')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function generateNonce(): string {
|
|
95
|
+
const bytes = new Uint8Array(16)
|
|
96
|
+
crypto.getRandomValues(bytes)
|
|
97
|
+
return bytesToHex(bytes)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Executar teste
|
|
101
|
+
testCryptoAuth().catch(console.error)
|