@vatts/auth 1.0.1 → 1.0.2-alpha.2
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/client.js +5 -5
- package/dist/core.d.ts +1 -1
- package/dist/core.js +14 -10
- package/dist/jwt.js +51 -9
- package/dist/providers/credentials.d.ts +1 -1
- package/dist/providers/credentials.js +24 -3
- package/dist/react.js +3 -3
- package/dist/routes.d.ts +2 -2
- package/dist/routes.js +6 -5
- package/package.json +2 -2
package/dist/client.js
CHANGED
|
@@ -26,7 +26,7 @@ async function getSession() {
|
|
|
26
26
|
return data.session || null;
|
|
27
27
|
}
|
|
28
28
|
catch (error) {
|
|
29
|
-
console.error('[
|
|
29
|
+
console.error('[vatts-auth] Error fetching session:', error);
|
|
30
30
|
return null;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -45,7 +45,7 @@ async function getCsrfToken() {
|
|
|
45
45
|
return data.csrfToken || null;
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
|
-
console.error('[
|
|
48
|
+
console.error('[vatts-auth] Error fetching CSRF token:', error);
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
@@ -64,7 +64,7 @@ async function getProviders() {
|
|
|
64
64
|
return data.providers || [];
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
|
-
console.error('[
|
|
67
|
+
console.error('[vatts-auth] Error searching for providers:', error);
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -119,7 +119,7 @@ async function signIn(provider = 'credentials', options = {}) {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
catch (error) {
|
|
122
|
-
console.error('[
|
|
122
|
+
console.error('[vatts-auth] Error on signIn:', error);
|
|
123
123
|
return {
|
|
124
124
|
error: 'Network error',
|
|
125
125
|
status: 500,
|
|
@@ -141,6 +141,6 @@ async function signOut(options = {}) {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
catch (error) {
|
|
144
|
-
console.error('[
|
|
144
|
+
console.error('[vatts-auth] Error on signOut:', error);
|
|
145
145
|
}
|
|
146
146
|
}
|
package/dist/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VattsRequest, VattsResponse } from 'vatts';
|
|
2
2
|
import type { AuthConfig, AuthProviderClass, Session } from './types';
|
|
3
|
-
export declare class
|
|
3
|
+
export declare class VattsAuth {
|
|
4
4
|
private config;
|
|
5
5
|
private sessionManager;
|
|
6
6
|
constructor(config: AuthConfig);
|
package/dist/core.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.VattsAuth = void 0;
|
|
4
4
|
/*
|
|
5
5
|
* This file is part of the Vatts.js Project.
|
|
6
6
|
* Copyright (c) 2026 itsmuzin
|
|
@@ -19,7 +19,7 @@ exports.HWebAuth = void 0;
|
|
|
19
19
|
*/
|
|
20
20
|
const vatts_1 = require("vatts");
|
|
21
21
|
const jwt_1 = require("./jwt");
|
|
22
|
-
class
|
|
22
|
+
class VattsAuth {
|
|
23
23
|
constructor(config) {
|
|
24
24
|
this.config = {
|
|
25
25
|
session: { strategy: 'jwt', maxAge: 86400, ...config.session },
|
|
@@ -48,7 +48,7 @@ class HWebAuth {
|
|
|
48
48
|
async signIn(providerId, credentials) {
|
|
49
49
|
const provider = this.config.providers.find(p => p.id === providerId);
|
|
50
50
|
if (!provider) {
|
|
51
|
-
console.error(`[
|
|
51
|
+
console.error(`[vatts-auth] Provider not found: ${providerId}`);
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
54
|
try {
|
|
@@ -76,7 +76,7 @@ class HWebAuth {
|
|
|
76
76
|
return sessionResult;
|
|
77
77
|
}
|
|
78
78
|
catch (error) {
|
|
79
|
-
console.error(`[
|
|
79
|
+
console.error(`[vatts-auth] Error signing in with provider ${providerId}:`, error);
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
82
82
|
}
|
|
@@ -93,13 +93,13 @@ class HWebAuth {
|
|
|
93
93
|
await provider.handleSignOut();
|
|
94
94
|
}
|
|
95
95
|
catch (error) {
|
|
96
|
-
console.error(`[
|
|
96
|
+
console.error(`[vatts-auth] Signout error on provider ${provider.id}:`, error);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
return vatts_1.VattsResponse
|
|
101
101
|
.json({ success: true })
|
|
102
|
-
.clearCookie('
|
|
102
|
+
.clearCookie('vatts-auth-token', {
|
|
103
103
|
path: '/',
|
|
104
104
|
httpOnly: true,
|
|
105
105
|
secure: this.config.secureCookies || false,
|
|
@@ -157,7 +157,7 @@ class HWebAuth {
|
|
|
157
157
|
createAuthResponse(token, data) {
|
|
158
158
|
return vatts_1.VattsResponse
|
|
159
159
|
.json(data)
|
|
160
|
-
.cookie('
|
|
160
|
+
.cookie('vatts-auth-token', token, {
|
|
161
161
|
httpOnly: true,
|
|
162
162
|
secure: this.config.secureCookies || false, // Always secure, even in development
|
|
163
163
|
sameSite: 'strict', // Prevent CSRF attacks
|
|
@@ -165,17 +165,21 @@ class HWebAuth {
|
|
|
165
165
|
path: '/',
|
|
166
166
|
domain: undefined // Let browser set automatically for security
|
|
167
167
|
})
|
|
168
|
+
// SECURITY: Comprehensive security headers
|
|
168
169
|
.header('X-Content-Type-Options', 'nosniff')
|
|
169
170
|
.header('X-Frame-Options', 'DENY')
|
|
170
171
|
.header('X-XSS-Protection', '1; mode=block')
|
|
171
|
-
.header('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
172
|
+
.header('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
173
|
+
.header('Content-Security-Policy', "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';")
|
|
174
|
+
.header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
|
|
175
|
+
.header('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
|
172
176
|
}
|
|
173
177
|
/**
|
|
174
178
|
* Extrai token da requisição (cookie ou header)
|
|
175
179
|
*/
|
|
176
180
|
getTokenFromRequest(req) {
|
|
177
181
|
// Primeiro tenta pegar do cookie
|
|
178
|
-
const cookieToken = req.cookie('
|
|
182
|
+
const cookieToken = req.cookie('vatts-auth-token');
|
|
179
183
|
if (cookieToken)
|
|
180
184
|
return cookieToken;
|
|
181
185
|
// Depois tenta do header Authorization
|
|
@@ -186,4 +190,4 @@ class HWebAuth {
|
|
|
186
190
|
return null;
|
|
187
191
|
}
|
|
188
192
|
}
|
|
189
|
-
exports.
|
|
193
|
+
exports.VattsAuth = VattsAuth;
|
package/dist/jwt.js
CHANGED
|
@@ -23,13 +23,39 @@ exports.SessionManager = exports.JWTManager = void 0;
|
|
|
23
23
|
const crypto_1 = __importDefault(require("crypto"));
|
|
24
24
|
class JWTManager {
|
|
25
25
|
constructor(secret) {
|
|
26
|
-
if (!secret && !process.env.
|
|
27
|
-
throw new Error('JWT secret is required. Set
|
|
26
|
+
if (!secret && !process.env.VATTS_AUTH_SECRET) {
|
|
27
|
+
throw new Error('JWT secret is required. Set VATTS_AUTH_SECRET environment variable or provide secret parameter.');
|
|
28
28
|
}
|
|
29
|
-
this.secret = secret || process.env.
|
|
29
|
+
this.secret = secret || process.env.VATTS_AUTH_SECRET;
|
|
30
|
+
// SECURITY: Enforce minimum secret length
|
|
30
31
|
if (this.secret.length < 32) {
|
|
31
32
|
throw new Error('JWT secret must be at least 32 characters long for security.');
|
|
32
33
|
}
|
|
34
|
+
// SECURITY: Warn about weak/common secrets in development
|
|
35
|
+
const weakSecrets = [
|
|
36
|
+
'your-secret-key',
|
|
37
|
+
'vatts-test-secret-key-change-in-production',
|
|
38
|
+
'secret',
|
|
39
|
+
'changeme',
|
|
40
|
+
'password',
|
|
41
|
+
'12345678',
|
|
42
|
+
'test-secret',
|
|
43
|
+
'development-secret'
|
|
44
|
+
];
|
|
45
|
+
if (weakSecrets.some(weak => this.secret.toLowerCase().includes(weak))) {
|
|
46
|
+
console.warn('\x1b[33m%s\x1b[0m', '⚠️ SECURITY WARNING: You are using a weak/common JWT secret!');
|
|
47
|
+
console.warn('\x1b[33m%s\x1b[0m', ' This is a CRITICAL security risk in production.');
|
|
48
|
+
console.warn('\x1b[33m%s\x1b[0m', ' Generate a strong secret: node -e "console.log(require(\'crypto\').randomBytes(64).toString(\'base64\'))"');
|
|
49
|
+
// SECURITY: Refuse to start in production with weak secret
|
|
50
|
+
if (process.env.NODE_ENV === 'production') {
|
|
51
|
+
throw new Error('PRODUCTION: Weak JWT secret detected. Application refused to start for security reasons.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// SECURITY: Check for sufficient entropy (basic check)
|
|
55
|
+
const uniqueChars = new Set(this.secret).size;
|
|
56
|
+
if (uniqueChars < 16) {
|
|
57
|
+
console.warn('\x1b[33m%s\x1b[0m', '⚠️ WARNING: JWT secret has low entropy (few unique characters). Consider using a more random secret.');
|
|
58
|
+
}
|
|
33
59
|
}
|
|
34
60
|
/**
|
|
35
61
|
* Cria um JWT token com validação de algoritmo
|
|
@@ -75,13 +101,14 @@ class JWTManager {
|
|
|
75
101
|
// Validate algorithm in payload matches header
|
|
76
102
|
if (decodedPayload.alg !== 'HS256')
|
|
77
103
|
return null;
|
|
78
|
-
// Verifica expiração com margem de erro de
|
|
104
|
+
// SECURITY: Verifica expiração com margem de erro de 5 segundos (clock skew)
|
|
105
|
+
// Reduzido de 30s para minimizar janela de replay attacks
|
|
79
106
|
const now = Math.floor(Date.now() / 1000);
|
|
80
|
-
if (decodedPayload.exp && decodedPayload.exp < (now -
|
|
107
|
+
if (decodedPayload.exp && decodedPayload.exp < (now - 5)) {
|
|
81
108
|
return null;
|
|
82
109
|
}
|
|
83
|
-
// Validate issued at time (not too far in future)
|
|
84
|
-
if (decodedPayload.iat && decodedPayload.iat > (now +
|
|
110
|
+
// SECURITY: Validate issued at time (not too far in future - 60s tolerance)
|
|
111
|
+
if (decodedPayload.iat && decodedPayload.iat > (now + 60)) {
|
|
85
112
|
return null;
|
|
86
113
|
}
|
|
87
114
|
return decodedPayload;
|
|
@@ -96,11 +123,21 @@ class JWTManager {
|
|
|
96
123
|
}
|
|
97
124
|
const sanitized = {};
|
|
98
125
|
for (const [key, value] of Object.entries(payload)) {
|
|
99
|
-
// Skip dangerous properties
|
|
126
|
+
// SECURITY: Skip dangerous properties that could lead to prototype pollution
|
|
100
127
|
if (key.startsWith('__') || key === 'constructor' || key === 'prototype') {
|
|
101
128
|
continue;
|
|
102
129
|
}
|
|
103
|
-
|
|
130
|
+
// SECURITY: Recursively sanitize nested objects to prevent deep prototype pollution
|
|
131
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
132
|
+
sanitized[key] = this.sanitizePayload(value);
|
|
133
|
+
}
|
|
134
|
+
else if (Array.isArray(value)) {
|
|
135
|
+
// Sanitize arrays recursively
|
|
136
|
+
sanitized[key] = value.map(item => (item && typeof item === 'object') ? this.sanitizePayload(item) : item);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
sanitized[key] = value;
|
|
140
|
+
}
|
|
104
141
|
}
|
|
105
142
|
return sanitized;
|
|
106
143
|
}
|
|
@@ -121,6 +158,11 @@ class JWTManager {
|
|
|
121
158
|
.replace(/=/g, '');
|
|
122
159
|
}
|
|
123
160
|
base64UrlDecode(str) {
|
|
161
|
+
// SECURITY: Prevent DoS attacks with extremely large strings
|
|
162
|
+
// JWT tokens are typically < 4KB, we allow up to 16KB to be safe
|
|
163
|
+
if (str.length > 16384) {
|
|
164
|
+
throw new Error('Token too large');
|
|
165
|
+
}
|
|
124
166
|
str += '='.repeat(4 - str.length % 4);
|
|
125
167
|
return Buffer.from(str.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString();
|
|
126
168
|
}
|
|
@@ -54,7 +54,7 @@ export declare class CredentialsProvider implements AuthProviderClass {
|
|
|
54
54
|
*/
|
|
55
55
|
validateCredentials(credentials: Record<string, string>): boolean;
|
|
56
56
|
/**
|
|
57
|
-
* Validação
|
|
57
|
+
* Validação robusta de email (RFC 5322 simplificado)
|
|
58
58
|
*/
|
|
59
59
|
private isValidEmail;
|
|
60
60
|
}
|
|
@@ -87,11 +87,32 @@ class CredentialsProvider {
|
|
|
87
87
|
return true;
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
* Validação
|
|
90
|
+
* Validação robusta de email (RFC 5322 simplificado)
|
|
91
91
|
*/
|
|
92
92
|
isValidEmail(email) {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
// SECURITY: Validate email length to prevent DoS
|
|
94
|
+
if (!email || email.length > 320) { // RFC 5321: max 320 chars
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// SECURITY: More robust email validation regex
|
|
98
|
+
// Prevents common bypasses like multiple @, script tags, etc.
|
|
99
|
+
const emailRegex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
100
|
+
if (!emailRegex.test(email)) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
// SECURITY: Validate parts separately
|
|
104
|
+
const parts = email.split('@');
|
|
105
|
+
if (parts.length !== 2)
|
|
106
|
+
return false;
|
|
107
|
+
const [local, domain] = parts;
|
|
108
|
+
if (local.length > 64 || domain.length > 255) { // RFC 5321 limits
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
// SECURITY: Prevent consecutive dots
|
|
112
|
+
if (email.includes('..')) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
95
116
|
}
|
|
96
117
|
}
|
|
97
118
|
exports.CredentialsProvider = CredentialsProvider;
|
package/dist/react.js
CHANGED
|
@@ -50,7 +50,7 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
|
-
console.error('[
|
|
53
|
+
console.error('[vatts-auth] Error fetching session:', error);
|
|
54
54
|
setSession(null);
|
|
55
55
|
setStatus('unauthenticated');
|
|
56
56
|
return null;
|
|
@@ -106,7 +106,7 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
catch (error) {
|
|
109
|
-
console.error('[
|
|
109
|
+
console.error('[vatts-auth] Error on signIn:', error);
|
|
110
110
|
return {
|
|
111
111
|
error: 'Network error',
|
|
112
112
|
status: 500,
|
|
@@ -133,7 +133,7 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
catch (error) {
|
|
136
|
-
console.error('[
|
|
136
|
+
console.error('[vatts-auth] Error on signOut:', error);
|
|
137
137
|
}
|
|
138
138
|
}, [basePath]);
|
|
139
139
|
// Update session
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VattsRequest } from 'vatts';
|
|
2
2
|
import type { AuthConfig } from './types';
|
|
3
|
-
import {
|
|
3
|
+
import { VattsAuth } from './core';
|
|
4
4
|
/**
|
|
5
5
|
* Cria o handler catch-all para /api/auth/[...value]
|
|
6
6
|
*/
|
|
@@ -12,5 +12,5 @@ export declare function createAuthRoutes(config: AuthConfig): {
|
|
|
12
12
|
POST(req: VattsRequest, params: {
|
|
13
13
|
[key: string]: string;
|
|
14
14
|
}): Promise<any>;
|
|
15
|
-
auth:
|
|
15
|
+
auth: VattsAuth;
|
|
16
16
|
};
|
package/dist/routes.js
CHANGED
|
@@ -23,7 +23,7 @@ const core_1 = require("./core");
|
|
|
23
23
|
* Cria o handler catch-all para /api/auth/[...value]
|
|
24
24
|
*/
|
|
25
25
|
function createAuthRoutes(config) {
|
|
26
|
-
const auth = new core_1.
|
|
26
|
+
const auth = new core_1.VattsAuth(config);
|
|
27
27
|
/**
|
|
28
28
|
* Handler principal que gerencia todas as rotas de auth
|
|
29
29
|
* Uso: /api/auth/[...value].ts
|
|
@@ -102,9 +102,10 @@ async function handleSession(req, auth) {
|
|
|
102
102
|
* Handler para GET /api/auth/csrf
|
|
103
103
|
*/
|
|
104
104
|
async function handleCsrf(req) {
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
// SECURITY: Usa crypto.randomBytes para token criptograficamente seguro
|
|
106
|
+
// 32 bytes = 256 bits de entropia, codificado em base64url (URL-safe)
|
|
107
|
+
const crypto = await import('crypto');
|
|
108
|
+
const csrfToken = crypto.randomBytes(32).toString('base64url');
|
|
108
109
|
return vatts_1.VattsResponse.json({ csrfToken });
|
|
109
110
|
}
|
|
110
111
|
/**
|
|
@@ -140,7 +141,7 @@ async function handleSignIn(req, auth) {
|
|
|
140
141
|
});
|
|
141
142
|
}
|
|
142
143
|
catch (error) {
|
|
143
|
-
console.error('[
|
|
144
|
+
console.error('[vatts-auth] Error on handleSignIn:', error);
|
|
144
145
|
return vatts_1.VattsResponse.json({ error: 'Authentication failed' }, { status: 500 });
|
|
145
146
|
}
|
|
146
147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vatts/auth",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2-alpha.2",
|
|
4
4
|
"description": "Authentication package for Vatts.js framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": "^19.2.0",
|
|
44
44
|
"react-dom": "^19.2.0",
|
|
45
|
-
"vatts": "^1.0.
|
|
45
|
+
"vatts": "^1.0.2-alpha.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependenciesMeta": {
|
|
48
48
|
"react-dom": {
|