voltjs-framework 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1265 -0
  3. package/bin/volt.js +139 -0
  4. package/package.json +56 -0
  5. package/src/api/graphql.js +399 -0
  6. package/src/api/rest.js +204 -0
  7. package/src/api/websocket.js +285 -0
  8. package/src/cli/build.js +111 -0
  9. package/src/cli/create.js +371 -0
  10. package/src/cli/db.js +106 -0
  11. package/src/cli/dev.js +114 -0
  12. package/src/cli/generate.js +278 -0
  13. package/src/cli/lint.js +172 -0
  14. package/src/cli/routes.js +118 -0
  15. package/src/cli/start.js +42 -0
  16. package/src/cli/test.js +138 -0
  17. package/src/core/app.js +701 -0
  18. package/src/core/config.js +232 -0
  19. package/src/core/middleware.js +133 -0
  20. package/src/core/plugins.js +88 -0
  21. package/src/core/react-renderer.js +244 -0
  22. package/src/core/renderer.js +337 -0
  23. package/src/core/router.js +183 -0
  24. package/src/database/index.js +461 -0
  25. package/src/database/migration.js +192 -0
  26. package/src/database/model.js +285 -0
  27. package/src/database/query.js +394 -0
  28. package/src/database/seeder.js +89 -0
  29. package/src/index.js +156 -0
  30. package/src/security/auth.js +425 -0
  31. package/src/security/cors.js +80 -0
  32. package/src/security/csrf.js +125 -0
  33. package/src/security/encryption.js +110 -0
  34. package/src/security/helmet.js +103 -0
  35. package/src/security/index.js +75 -0
  36. package/src/security/rateLimit.js +119 -0
  37. package/src/security/sanitizer.js +113 -0
  38. package/src/security/xss.js +110 -0
  39. package/src/ui/component.js +224 -0
  40. package/src/ui/reactive.js +503 -0
  41. package/src/ui/template.js +448 -0
  42. package/src/utils/cache.js +216 -0
  43. package/src/utils/collection.js +772 -0
  44. package/src/utils/cron.js +213 -0
  45. package/src/utils/date.js +223 -0
  46. package/src/utils/events.js +181 -0
  47. package/src/utils/excel.js +482 -0
  48. package/src/utils/form.js +547 -0
  49. package/src/utils/hash.js +121 -0
  50. package/src/utils/http.js +461 -0
  51. package/src/utils/logger.js +186 -0
  52. package/src/utils/mail.js +347 -0
  53. package/src/utils/paginator.js +179 -0
  54. package/src/utils/pdf.js +417 -0
  55. package/src/utils/queue.js +199 -0
  56. package/src/utils/schema.js +985 -0
  57. package/src/utils/sms.js +243 -0
  58. package/src/utils/storage.js +348 -0
  59. package/src/utils/string.js +236 -0
  60. package/src/utils/validation.js +318 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * VoltJS Database Seeder
3
+ *
4
+ * @example
5
+ * // seeders/users.js
6
+ * module.exports = async (db) => {
7
+ * const User = db.model('users', { name: 'string', email: 'string' });
8
+ * await User.create({ name: 'Admin', email: 'admin@example.com' });
9
+ * await User.create({ name: 'John', email: 'john@example.com' });
10
+ * };
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ class Seeder {
19
+ constructor(db, seedersDir) {
20
+ this._db = db;
21
+ this._dir = seedersDir || path.join(process.cwd(), 'seeders');
22
+ }
23
+
24
+ /** Run all seeders */
25
+ async run() {
26
+ const files = this._getSeederFiles();
27
+ if (files.length === 0) {
28
+ console.log('\x1b[33m No seeders found.\x1b[0m');
29
+ return [];
30
+ }
31
+
32
+ const results = [];
33
+ for (const file of files) {
34
+ try {
35
+ const seeder = require(path.join(this._dir, file));
36
+ console.log(`\x1b[36m Seeding: ${file}\x1b[0m`);
37
+ if (typeof seeder === 'function') {
38
+ await seeder(this._db);
39
+ } else if (typeof seeder.run === 'function') {
40
+ await seeder.run(this._db);
41
+ }
42
+ console.log(`\x1b[32m ✓ Seeded: ${file}\x1b[0m`);
43
+ results.push({ file, status: 'success' });
44
+ } catch (err) {
45
+ console.error(`\x1b[31m ✗ Seeder failed: ${file} — ${err.message}\x1b[0m`);
46
+ results.push({ file, status: 'error', error: err.message });
47
+ }
48
+ }
49
+
50
+ return results;
51
+ }
52
+
53
+ /** Get seeder files sorted by name */
54
+ _getSeederFiles() {
55
+ try {
56
+ if (!fs.existsSync(this._dir)) return [];
57
+ return fs.readdirSync(this._dir).filter(f => f.endsWith('.js')).sort();
58
+ } catch {
59
+ return [];
60
+ }
61
+ }
62
+
63
+ /** Generate a new seeder file */
64
+ static generate(name, seedersDir) {
65
+ const dir = seedersDir || path.join(process.cwd(), 'seeders');
66
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
67
+
68
+ const filename = `${name}.seed.js`;
69
+ const filepath = path.join(dir, filename);
70
+
71
+ const template = `/**
72
+ * Seeder: ${name}
73
+ * Created: ${new Date().toISOString()}
74
+ */
75
+
76
+ module.exports = async (db) => {
77
+ // Example: seed ${name} table
78
+ // const Model = db.model('${name}', { /* schema */ });
79
+ // await Model.create({ ... });
80
+ };
81
+ `;
82
+
83
+ fs.writeFileSync(filepath, template);
84
+ console.log(`\x1b[32m ✓ Created seeder: ${filename}\x1b[0m`);
85
+ return filepath;
86
+ }
87
+ }
88
+
89
+ module.exports = { Seeder };
package/src/index.js ADDED
@@ -0,0 +1,156 @@
1
+ /**
2
+ * VoltJS - Lightning-fast, batteries-included, security-first JavaScript framework
3
+ *
4
+ * @description One framework to rule them all. Zero boilerplate. Maximum power.
5
+ *
6
+ * Features:
7
+ * - File-based routing + programmatic routing
8
+ * - Built-in security (CSRF, XSS, CORS, Rate Limiting, Helmet)
9
+ * - Built-in authentication (JWT, Sessions, OAuth)
10
+ * - Built-in database ORM with migrations
11
+ * - Built-in utilities (Excel, Mail, SMS, PDF, Validation, Cache, Queue, Cron)
12
+ * - Reactive UI components (compiled, no virtual DOM)
13
+ * - Full-stack: SSR + CSR + API in one
14
+ * - Plugin architecture
15
+ * - WebSocket support
16
+ * - GraphQL support
17
+ * - Zero config needed
18
+ *
19
+ * @example
20
+ * const { Volt } = require('voltjs');
21
+ *
22
+ * const app = new Volt();
23
+ *
24
+ * app.get('/', (req, res) => {
25
+ * res.render('Welcome to VoltJS!');
26
+ * });
27
+ *
28
+ * app.listen(3000);
29
+ */
30
+
31
+ 'use strict';
32
+
33
+ // Core
34
+ const { Volt } = require('./core/app');
35
+ const { Router } = require('./core/router');
36
+ const { Middleware } = require('./core/middleware');
37
+ const { Config } = require('./core/config');
38
+ const { Renderer } = require('./core/renderer');
39
+ const { PluginManager } = require('./core/plugins');
40
+ const { ReactRenderer } = require('./core/react-renderer');
41
+
42
+ // Security
43
+ const { Security } = require('./security');
44
+ const { Auth } = require('./security/auth');
45
+ const { CSRF } = require('./security/csrf');
46
+ const { XSSProtection } = require('./security/xss');
47
+ const { CORSHandler } = require('./security/cors');
48
+ const { RateLimiter } = require('./security/rateLimit');
49
+ const { Encryption } = require('./security/encryption');
50
+ const { SecurityHeaders } = require('./security/helmet');
51
+ const { InputSanitizer } = require('./security/sanitizer');
52
+
53
+ // Database
54
+ const { Database } = require('./database');
55
+ const { Model } = require('./database/model');
56
+ const { QueryBuilder } = require('./database/query');
57
+ const { Migration } = require('./database/migration');
58
+ const { Seeder } = require('./database/seeder');
59
+
60
+ // Utilities
61
+ const { Excel } = require('./utils/excel');
62
+ const { Mail } = require('./utils/mail');
63
+ const { SMS } = require('./utils/sms');
64
+ const { PDF } = require('./utils/pdf');
65
+ const { Validator } = require('./utils/validation');
66
+ const { Cache } = require('./utils/cache');
67
+ const { Logger } = require('./utils/logger');
68
+ const { Storage } = require('./utils/storage');
69
+ const { Queue } = require('./utils/queue');
70
+ const { Cron } = require('./utils/cron');
71
+ const { Hash } = require('./utils/hash');
72
+ const { DateHelper } = require('./utils/date');
73
+ const { StringHelper } = require('./utils/string');
74
+ const { HttpClient } = require('./utils/http');
75
+ const { EventBus } = require('./utils/events');
76
+ const { Paginator } = require('./utils/paginator');
77
+ const { Form } = require('./utils/form');
78
+ const { _, Collection } = require('./utils/collection');
79
+ const { Schema } = require('./utils/schema');
80
+
81
+ // UI
82
+ const { Component } = require('./ui/component');
83
+ const { Reactive } = require('./ui/reactive');
84
+ const { TemplateEngine } = require('./ui/template');
85
+
86
+ // API
87
+ const { RestAPI } = require('./api/rest');
88
+ const { WebSocketServer } = require('./api/websocket');
89
+ const { GraphQLHandler } = require('./api/graphql');
90
+
91
+ // Export everything - one import, full power
92
+ module.exports = {
93
+ // Main framework
94
+ Volt,
95
+ Router,
96
+ Middleware,
97
+ Config,
98
+ Renderer,
99
+ ReactRenderer,
100
+ PluginManager,
101
+
102
+ // Security
103
+ Security,
104
+ Auth,
105
+ CSRF,
106
+ XSSProtection,
107
+ CORSHandler,
108
+ RateLimiter,
109
+ Encryption,
110
+ SecurityHeaders,
111
+ InputSanitizer,
112
+
113
+ // Database
114
+ Database,
115
+ Model,
116
+ QueryBuilder,
117
+ Migration,
118
+ Seeder,
119
+
120
+ // Utilities
121
+ Excel,
122
+ Mail,
123
+ SMS,
124
+ PDF,
125
+ Validator,
126
+ Cache,
127
+ Logger,
128
+ Storage,
129
+ Queue,
130
+ Cron,
131
+ Hash,
132
+ DateHelper,
133
+ StringHelper,
134
+ HttpClient,
135
+ EventBus,
136
+ Paginator,
137
+ Form,
138
+ _,
139
+ Collection,
140
+ Schema,
141
+
142
+ // UI
143
+ Component,
144
+ Reactive,
145
+ TemplateEngine,
146
+
147
+ // API
148
+ RestAPI,
149
+ WebSocketServer,
150
+ GraphQLHandler,
151
+
152
+ // Convenience: create a new app instance
153
+ create(config = {}) {
154
+ return new Volt(config);
155
+ },
156
+ };
@@ -0,0 +1,425 @@
1
+ /**
2
+ * VoltJS Authentication
3
+ *
4
+ * Built-in auth with support for:
5
+ * - JWT tokens (access + refresh)
6
+ * - Session-based auth
7
+ * - API key auth
8
+ * - OAuth2 helpers
9
+ * - Password hashing (bcrypt-compatible using native crypto)
10
+ * - Role-based access control (RBAC)
11
+ * - Two-factor authentication (TOTP)
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const crypto = require('crypto');
17
+
18
+ class Auth {
19
+ constructor(options = {}) {
20
+ this._secret = options.secret || crypto.randomBytes(64).toString('hex');
21
+ this._algorithm = options.algorithm || 'HS256';
22
+ this._tokenExpiry = options.tokenExpiry || '24h';
23
+ this._refreshExpiry = options.refreshExpiry || '7d';
24
+ this._sessions = new Map();
25
+ this._apiKeys = new Map();
26
+ this._users = options.userProvider || null; // Function to look up users
27
+ this._roles = new Map();
28
+ this._permissions = new Map();
29
+ }
30
+
31
+ // ===== JWT =====
32
+
33
+ /** Generate a JWT token */
34
+ generateToken(payload, expiresIn) {
35
+ const header = { alg: this._algorithm, typ: 'JWT' };
36
+ const exp = this._parseExpiry(expiresIn || this._tokenExpiry);
37
+
38
+ const tokenPayload = {
39
+ ...payload,
40
+ iat: Math.floor(Date.now() / 1000),
41
+ exp: Math.floor(Date.now() / 1000) + exp,
42
+ };
43
+
44
+ const headerB64 = this._base64url(JSON.stringify(header));
45
+ const payloadB64 = this._base64url(JSON.stringify(tokenPayload));
46
+ const signature = this._sign(`${headerB64}.${payloadB64}`);
47
+
48
+ return `${headerB64}.${payloadB64}.${signature}`;
49
+ }
50
+
51
+ /** Generate access + refresh token pair */
52
+ generateTokenPair(payload) {
53
+ return {
54
+ accessToken: this.generateToken(payload, this._tokenExpiry),
55
+ refreshToken: this.generateToken({ ...payload, type: 'refresh' }, this._refreshExpiry),
56
+ expiresIn: this._parseExpiry(this._tokenExpiry),
57
+ tokenType: 'Bearer',
58
+ };
59
+ }
60
+
61
+ /** Verify and decode a JWT token */
62
+ verifyToken(token) {
63
+ try {
64
+ const parts = token.split('.');
65
+ if (parts.length !== 3) return { valid: false, error: 'Invalid token format' };
66
+
67
+ const [headerB64, payloadB64, signature] = parts;
68
+
69
+ // Verify signature
70
+ const expected = this._sign(`${headerB64}.${payloadB64}`);
71
+ if (signature !== expected) {
72
+ return { valid: false, error: 'Invalid signature' };
73
+ }
74
+
75
+ // Decode payload
76
+ const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
77
+
78
+ // Check expiration
79
+ if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) {
80
+ return { valid: false, error: 'Token expired' };
81
+ }
82
+
83
+ return { valid: true, payload };
84
+ } catch (err) {
85
+ return { valid: false, error: err.message };
86
+ }
87
+ }
88
+
89
+ /** Middleware: Require valid JWT token */
90
+ requireAuth(options = {}) {
91
+ return (req, res) => {
92
+ const token = this._extractToken(req);
93
+ if (!token) {
94
+ res.json({ error: 'Authentication required' }, 401);
95
+ return false;
96
+ }
97
+
98
+ const result = this.verifyToken(token);
99
+ if (!result.valid) {
100
+ res.json({ error: result.error }, 401);
101
+ return false;
102
+ }
103
+
104
+ req.user = result.payload;
105
+ req.token = token;
106
+
107
+ // Check roles if specified
108
+ if (options.roles) {
109
+ const userRoles = result.payload.roles || [];
110
+ const hasRole = options.roles.some(r => userRoles.includes(r));
111
+ if (!hasRole) {
112
+ res.json({ error: 'Insufficient permissions' }, 403);
113
+ return false;
114
+ }
115
+ }
116
+
117
+ // Check permissions if specified
118
+ if (options.permissions) {
119
+ const userPerms = result.payload.permissions || [];
120
+ const hasPermission = options.permissions.every(p => userPerms.includes(p));
121
+ if (!hasPermission) {
122
+ res.json({ error: 'Insufficient permissions' }, 403);
123
+ return false;
124
+ }
125
+ }
126
+ };
127
+ }
128
+
129
+ /** Middleware: Optional auth (populates req.user if token exists) */
130
+ optionalAuth() {
131
+ return (req, res) => {
132
+ const token = this._extractToken(req);
133
+ if (token) {
134
+ const result = this.verifyToken(token);
135
+ if (result.valid) {
136
+ req.user = result.payload;
137
+ req.token = token;
138
+ }
139
+ }
140
+ };
141
+ }
142
+
143
+ // ===== SESSIONS =====
144
+
145
+ /** Create a new session */
146
+ createSession(userId, data = {}) {
147
+ const sessionId = crypto.randomBytes(32).toString('hex');
148
+ const session = {
149
+ id: sessionId,
150
+ userId,
151
+ data,
152
+ createdAt: new Date(),
153
+ expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
154
+ };
155
+ this._sessions.set(sessionId, session);
156
+ return session;
157
+ }
158
+
159
+ /** Get session by ID */
160
+ getSession(sessionId) {
161
+ const session = this._sessions.get(sessionId);
162
+ if (!session) return null;
163
+ if (session.expiresAt < new Date()) {
164
+ this._sessions.delete(sessionId);
165
+ return null;
166
+ }
167
+ return session;
168
+ }
169
+
170
+ /** Destroy a session */
171
+ destroySession(sessionId) {
172
+ this._sessions.delete(sessionId);
173
+ }
174
+
175
+ /** Middleware: Session-based auth */
176
+ requireSession(options = {}) {
177
+ const cookieName = options.cookieName || 'volt.sid';
178
+ return (req, res) => {
179
+ const sessionId = req.cookies[cookieName];
180
+ if (!sessionId) {
181
+ res.json({ error: 'Session required' }, 401);
182
+ return false;
183
+ }
184
+
185
+ const session = this.getSession(sessionId);
186
+ if (!session) {
187
+ res.json({ error: 'Invalid or expired session' }, 401);
188
+ return false;
189
+ }
190
+
191
+ req.session = session;
192
+ req.userId = session.userId;
193
+ };
194
+ }
195
+
196
+ // ===== API KEYS =====
197
+
198
+ /** Generate an API key */
199
+ generateApiKey(name, permissions = []) {
200
+ const key = `vk_${crypto.randomBytes(32).toString('hex')}`;
201
+ this._apiKeys.set(key, { name, permissions, createdAt: new Date() });
202
+ return key;
203
+ }
204
+
205
+ /** Middleware: Require API key */
206
+ requireApiKey(requiredPermissions = []) {
207
+ return (req, res) => {
208
+ const key = req.headers['x-api-key'] || req.query.api_key;
209
+ if (!key) {
210
+ res.json({ error: 'API key required' }, 401);
211
+ return false;
212
+ }
213
+
214
+ const keyData = this._apiKeys.get(key);
215
+ if (!keyData) {
216
+ res.json({ error: 'Invalid API key' }, 401);
217
+ return false;
218
+ }
219
+
220
+ // Check permissions
221
+ if (requiredPermissions.length > 0) {
222
+ const hasAll = requiredPermissions.every(p => keyData.permissions.includes(p));
223
+ if (!hasAll) {
224
+ res.json({ error: 'Insufficient API key permissions' }, 403);
225
+ return false;
226
+ }
227
+ }
228
+
229
+ req.apiKey = keyData;
230
+ };
231
+ }
232
+
233
+ // ===== PASSWORD =====
234
+
235
+ /** Hash a password using PBKDF2 (no external deps needed) */
236
+ async hashPassword(password, rounds = 100000) {
237
+ const salt = crypto.randomBytes(16).toString('hex');
238
+ return new Promise((resolve, reject) => {
239
+ crypto.pbkdf2(password, salt, rounds, 64, 'sha512', (err, derivedKey) => {
240
+ if (err) reject(err);
241
+ else resolve(`$volt$${rounds}$${salt}$${derivedKey.toString('hex')}`);
242
+ });
243
+ });
244
+ }
245
+
246
+ /** Verify a password against a hash */
247
+ async verifyPassword(password, hash) {
248
+ const parts = hash.split('$').filter(Boolean);
249
+ if (parts[0] !== 'volt') return false;
250
+
251
+ const rounds = parseInt(parts[1], 10);
252
+ const salt = parts[2];
253
+ const key = parts[3];
254
+
255
+ return new Promise((resolve, reject) => {
256
+ crypto.pbkdf2(password, salt, rounds, 64, 'sha512', (err, derivedKey) => {
257
+ if (err) reject(err);
258
+ else resolve(crypto.timingSafeEqual(Buffer.from(key, 'hex'), derivedKey));
259
+ });
260
+ });
261
+ }
262
+
263
+ // ===== RBAC =====
264
+
265
+ /** Define a role with permissions */
266
+ defineRole(roleName, permissions) {
267
+ this._roles.set(roleName, new Set(permissions));
268
+ return this;
269
+ }
270
+
271
+ /** Check if a role has a permission */
272
+ roleHasPermission(roleName, permission) {
273
+ const role = this._roles.get(roleName);
274
+ return role ? role.has(permission) : false;
275
+ }
276
+
277
+ /** Middleware: Require specific roles */
278
+ requireRole(...roles) {
279
+ return (req, res) => {
280
+ if (!req.user || !req.user.roles) {
281
+ res.json({ error: 'Authentication required' }, 401);
282
+ return false;
283
+ }
284
+ const hasRole = roles.some(r => req.user.roles.includes(r));
285
+ if (!hasRole) {
286
+ res.json({ error: 'Insufficient role permissions' }, 403);
287
+ return false;
288
+ }
289
+ };
290
+ }
291
+
292
+ /** Middleware: Require specific permissions */
293
+ requirePermission(...permissions) {
294
+ return (req, res) => {
295
+ if (!req.user) {
296
+ res.json({ error: 'Authentication required' }, 401);
297
+ return false;
298
+ }
299
+ const userPerms = req.user.permissions || [];
300
+ const userRoles = req.user.roles || [];
301
+
302
+ // Check direct permissions
303
+ const hasDirectPerm = permissions.every(p => userPerms.includes(p));
304
+ if (hasDirectPerm) return;
305
+
306
+ // Check role-based permissions
307
+ const hasRolePerm = permissions.every(p => {
308
+ return userRoles.some(role => this.roleHasPermission(role, p));
309
+ });
310
+
311
+ if (!hasRolePerm) {
312
+ res.json({ error: 'Insufficient permissions' }, 403);
313
+ return false;
314
+ }
315
+ };
316
+ }
317
+
318
+ // ===== TOTP (Two-Factor Authentication) =====
319
+
320
+ /** Generate a TOTP secret */
321
+ generateTOTPSecret(length = 20) {
322
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
323
+ let secret = '';
324
+ const bytes = crypto.randomBytes(length);
325
+ for (let i = 0; i < length; i++) {
326
+ secret += chars[bytes[i] % chars.length];
327
+ }
328
+ return secret;
329
+ }
330
+
331
+ /** Generate current TOTP code */
332
+ generateTOTP(secret, timeStep = 30) {
333
+ const epoch = Math.floor(Date.now() / 1000);
334
+ const counter = Math.floor(epoch / timeStep);
335
+ return this._hotpGenerate(secret, counter);
336
+ }
337
+
338
+ /** Verify a TOTP code (with window for clock drift) */
339
+ verifyTOTP(token, secret, window = 1) {
340
+ const epoch = Math.floor(Date.now() / 1000);
341
+ const timeStep = 30;
342
+
343
+ for (let i = -window; i <= window; i++) {
344
+ const counter = Math.floor(epoch / timeStep) + i;
345
+ const expected = this._hotpGenerate(secret, counter);
346
+ if (token === expected) return true;
347
+ }
348
+ return false;
349
+ }
350
+
351
+ // ===== INTERNAL =====
352
+
353
+ _sign(input) {
354
+ return crypto
355
+ .createHmac('sha256', this._secret)
356
+ .update(input)
357
+ .digest('base64url');
358
+ }
359
+
360
+ _base64url(str) {
361
+ return Buffer.from(str).toString('base64url');
362
+ }
363
+
364
+ _extractToken(req) {
365
+ // Check Authorization header
366
+ const authHeader = req.headers.authorization;
367
+ if (authHeader && authHeader.startsWith('Bearer ')) {
368
+ return authHeader.substring(7);
369
+ }
370
+
371
+ // Check query parameter
372
+ if (req.query && req.query.token) {
373
+ return req.query.token;
374
+ }
375
+
376
+ // Check cookie
377
+ if (req.cookies && req.cookies.token) {
378
+ return req.cookies.token;
379
+ }
380
+
381
+ return null;
382
+ }
383
+
384
+ _parseExpiry(expiry) {
385
+ if (typeof expiry === 'number') return expiry;
386
+ const match = String(expiry).match(/^(\d+)(s|m|h|d)$/);
387
+ if (!match) return 86400; // Default: 24 hours
388
+
389
+ const value = parseInt(match[1], 10);
390
+ const unit = match[2];
391
+ const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
392
+ return value * (multipliers[unit] || 1);
393
+ }
394
+
395
+ _hotpGenerate(secret, counter) {
396
+ const decodedSecret = Buffer.from(this._base32Decode(secret));
397
+ const buffer = Buffer.alloc(8);
398
+ for (let i = 7; i >= 0; i--) {
399
+ buffer[i] = counter & 0xff;
400
+ counter = counter >> 8;
401
+ }
402
+ const hmac = crypto.createHmac('sha1', decodedSecret).update(buffer).digest();
403
+ const offset = hmac[hmac.length - 1] & 0xf;
404
+ const code = ((hmac[offset] & 0x7f) << 24) | ((hmac[offset + 1] & 0xff) << 16) |
405
+ ((hmac[offset + 2] & 0xff) << 8) | (hmac[offset + 3] & 0xff);
406
+ return String(code % 1000000).padStart(6, '0');
407
+ }
408
+
409
+ _base32Decode(input) {
410
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
411
+ let bits = '';
412
+ for (const c of input.toUpperCase()) {
413
+ const val = chars.indexOf(c);
414
+ if (val === -1) continue;
415
+ bits += val.toString(2).padStart(5, '0');
416
+ }
417
+ const bytes = [];
418
+ for (let i = 0; i + 8 <= bits.length; i += 8) {
419
+ bytes.push(parseInt(bits.substr(i, 8), 2));
420
+ }
421
+ return bytes;
422
+ }
423
+ }
424
+
425
+ module.exports = { Auth };