s3db.js 13.4.0 → 13.6.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 (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -107,6 +107,60 @@ export const PLUGIN_DEPENDENCIES = {
107
107
  description: 'Swagger UI integration for Hono',
108
108
  installCommand: 'pnpm add @hono/swagger-ui',
109
109
  npmUrl: 'https://www.npmjs.com/package/@hono/swagger-ui'
110
+ },
111
+ 'jose': {
112
+ version: '^5.0.0 || ^6.0.0',
113
+ description: 'Universal JOSE and JWE implementation (for OAuth2 token validation)',
114
+ installCommand: 'pnpm add jose',
115
+ npmUrl: 'https://www.npmjs.com/package/jose'
116
+ }
117
+ }
118
+ },
119
+ 'identity-plugin': {
120
+ name: 'Identity Provider Plugin',
121
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/identity.md',
122
+ dependencies: {
123
+ 'hono': {
124
+ version: '^4.0.0',
125
+ description: 'Ultra-light HTTP server framework',
126
+ installCommand: 'pnpm add hono',
127
+ npmUrl: 'https://www.npmjs.com/package/hono'
128
+ },
129
+ '@hono/node-server': {
130
+ version: '^1.0.0',
131
+ description: 'Node.js adapter for Hono',
132
+ installCommand: 'pnpm add @hono/node-server',
133
+ npmUrl: 'https://www.npmjs.com/package/@hono/node-server'
134
+ },
135
+ 'jose': {
136
+ version: '^5.0.0 || ^6.0.0',
137
+ description: 'Universal JOSE and JWE implementation (for RSA key generation and JWT signing)',
138
+ installCommand: 'pnpm add jose',
139
+ npmUrl: 'https://www.npmjs.com/package/jose'
140
+ },
141
+ 'bcrypt': {
142
+ version: '^5.1.0 || ^6.0.0',
143
+ description: 'Secure password hashing library',
144
+ installCommand: 'pnpm add bcrypt',
145
+ npmUrl: 'https://www.npmjs.com/package/bcrypt'
146
+ },
147
+ 'nodemailer': {
148
+ version: '^6.9.0 || ^7.0.0',
149
+ description: 'Email sending library for password reset and verification',
150
+ installCommand: 'pnpm add nodemailer',
151
+ npmUrl: 'https://www.npmjs.com/package/nodemailer'
152
+ }
153
+ }
154
+ },
155
+ 'cloud-inventory-plugin': {
156
+ name: 'Cloud Inventory Plugin',
157
+ docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/cloud-inventory.md',
158
+ dependencies: {
159
+ 'node-cron': {
160
+ version: '^4.0.0',
161
+ description: 'Cron scheduler for automated discovery',
162
+ installCommand: 'pnpm add -D node-cron',
163
+ npmUrl: 'https://www.npmjs.com/package/node-cron'
110
164
  }
111
165
  }
112
166
  },
@@ -127,12 +181,18 @@ export const PLUGIN_DEPENDENCIES = {
127
181
  /**
128
182
  * Simple semver comparison for major version checking
129
183
  * @param {string} actual - Actual version (e.g., "8.11.3")
130
- * @param {string} required - Required version range (e.g., "^8.0.0")
184
+ * @param {string} required - Required version range (e.g., "^8.0.0" or "^5.0.0 || ^6.0.0")
131
185
  * @returns {boolean} True if version is compatible
132
186
  */
133
187
  function isVersionCompatible(actual, required) {
134
188
  if (!actual || !required) return false;
135
189
 
190
+ // Handle OR operators (||)
191
+ if (required.includes('||')) {
192
+ const ranges = required.split('||').map(r => r.trim());
193
+ return ranges.some(range => isVersionCompatible(actual, range));
194
+ }
195
+
136
196
  // Remove ^ and ~ prefixes
137
197
  const cleanRequired = required.replace(/^[\^~]/, '');
138
198
 
@@ -170,7 +230,7 @@ async function tryLoadPackage(packageName) {
170
230
  let version = null;
171
231
  try {
172
232
  const pkgJson = await import(`${packageName}/package.json`, {
173
- assert: { type: 'json' }
233
+ with: { type: 'json' }
174
234
  });
175
235
  version = pkgJson.default?.version || pkgJson.version || null;
176
236
  } catch (e) {
@@ -1196,6 +1196,7 @@ export async function getLastNMonths(resourceName, field, months = 12, options,
1196
1196
  const dataMap = new Map(data.map(d => [d.cohort, d]));
1197
1197
 
1198
1198
  const current = new Date(monthsAgo);
1199
+ current.setDate(1); // Set to 1st of month to avoid day overflow bugs
1199
1200
  for (let i = 0; i < months; i++) {
1200
1201
  const cohort = current.toISOString().substring(0, 7);
1201
1202
  result.push(dataMap.get(cohort) || { cohort, ...emptyRecord });
@@ -140,7 +140,7 @@ export async function runConsolidation(transactionResource, consolidateRecordFn,
140
140
  }
141
141
 
142
142
  if (emitFn) {
143
- emitFn('eventual-consistency.consolidated', {
143
+ emitFn('plg:eventual-consistency:consolidated', {
144
144
  resource: config.resource,
145
145
  field: config.field,
146
146
  recordCount: uniqueIds.length,
@@ -157,7 +157,7 @@ export async function runConsolidation(transactionResource, consolidateRecordFn,
157
157
  error
158
158
  );
159
159
  if (emitFn) {
160
- emitFn('eventual-consistency.consolidation-error', error);
160
+ emitFn('plg:eventual-consistency:consolidation-error', error);
161
161
  }
162
162
  }
163
163
  }
@@ -103,7 +103,7 @@ export async function runGarbageCollection(transactionResource, storage, config,
103
103
  }
104
104
 
105
105
  if (emitFn) {
106
- emitFn('eventual-consistency.gc-completed', {
106
+ emitFn('plg:eventual-consistency:gc-completed', {
107
107
  resource: config.resource,
108
108
  field: config.field,
109
109
  deletedCount: results.length,
@@ -115,7 +115,7 @@ export async function runGarbageCollection(transactionResource, storage, config,
115
115
  console.warn(`[EventualConsistency] GC error:`, error.message);
116
116
  }
117
117
  if (emitFn) {
118
- emitFn('eventual-consistency.gc-error', error);
118
+ emitFn('plg:eventual-consistency:gc-error', error);
119
119
  }
120
120
  } finally {
121
121
  // Always release GC lock
@@ -255,7 +255,7 @@ export async function onStart(fieldHandlers, config, runConsolidationFn, runGCFn
255
255
  }
256
256
 
257
257
  if (emitFn) {
258
- emitFn('eventual-consistency.started', {
258
+ emitFn('plg:eventual-consistency:started', {
259
259
  resource: resourceName,
260
260
  field: fieldName,
261
261
  cohort: config.cohort
@@ -295,7 +295,7 @@ export async function onStop(fieldHandlers, emitFn) {
295
295
  }
296
296
 
297
297
  if (emitFn) {
298
- emitFn('eventual-consistency.stopped', {
298
+ emitFn('plg:eventual-consistency:stopped', {
299
299
  resource: resourceName,
300
300
  field: fieldName
301
301
  });
@@ -0,0 +1,335 @@
1
+ # Identity Provider Plugin
2
+
3
+ Complete OAuth2/OpenID Connect Identity Provider for S3DB.
4
+
5
+ ## Quick Start
6
+
7
+ ```javascript
8
+ import { Database } from 's3db.js';
9
+ import { IdentityPlugin } from 's3db.js/plugins/identity';
10
+
11
+ const db = new Database({
12
+ connectionString: 'http://minioadmin:minioadmin@localhost:9000/myapp'
13
+ });
14
+
15
+ await db.initialize();
16
+
17
+ const identityPlugin = new IdentityPlugin({
18
+ issuer: 'http://localhost:4000',
19
+ database: db
20
+ });
21
+
22
+ await identityPlugin.initialize();
23
+ ```
24
+
25
+ Access at: http://localhost:4000/login
26
+
27
+ ## Features
28
+
29
+ ✅ **Complete Authentication System**
30
+ - Login/Logout with session management
31
+ - User registration with email verification
32
+ - Password reset flow
33
+ - Profile management
34
+
35
+ ✅ **OAuth2/OpenID Connect Server**
36
+ - Authorization code flow with PKCE
37
+ - Client credentials flow
38
+ - Refresh token support
39
+ - Full OIDC compliance
40
+
41
+ ✅ **Admin Panel**
42
+ - User management (CRUD, status, roles)
43
+ - OAuth2 client management
44
+ - Session monitoring
45
+
46
+ ✅ **100% White-Label UI**
47
+ - 30+ theme customization options (colors, fonts, logos)
48
+ - **Custom CSS injection** - Poder total para customização
49
+ - Tailwind 4 CDN - Classes utilitárias prontas
50
+ - Custom page overrides
51
+ - Responsive design
52
+ - Social media integration
53
+
54
+ ✅ **Registration Controls**
55
+ - Enable/disable public registration
56
+ - Email domain whitelist/blacklist
57
+ - Email verification requirement
58
+
59
+ ✅ **Security**
60
+ - bcrypt password hashing
61
+ - Configurable password policy
62
+ - Session management with device tracking
63
+ - CSRF protection
64
+
65
+ ## Documentation
66
+
67
+ - **[Complete Documentation](../../../docs/plugins/identity-plugin.md)** - Full guide with all features
68
+ - **[Configuration Reference](../../../docs/plugins/identity-config-reference.md)** - All configuration options
69
+ - **[White-Label Guide](../../../docs/plugins/identity/WHITELABEL.md)** - 🎨 Complete branding customization guide
70
+ - **[Examples Index](../../../docs/plugins/identity-examples.md)** - Example code and use cases
71
+
72
+ ## Examples
73
+
74
+ | Example | Description |
75
+ |---------|-------------|
76
+ | `e85-identity-whitelabel.js` | S3dbCorp complete white-label branding |
77
+ | `e86-custom-login-page.js` | Custom login page with HTML override |
78
+ | `e87-identity-no-registration.js` | Disabled public registration |
79
+
80
+ Run examples:
81
+ ```bash
82
+ node docs/examples/e85-identity-whitelabel.js
83
+ ```
84
+
85
+ ## Basic Configuration
86
+
87
+ ### Minimal Setup
88
+
89
+ ```javascript
90
+ new IdentityPlugin({
91
+ issuer: 'http://localhost:4000',
92
+ database: db
93
+ })
94
+ ```
95
+
96
+ ### Production Setup
97
+
98
+ ```javascript
99
+ new IdentityPlugin({
100
+ issuer: 'https://auth.company.com',
101
+ database: db,
102
+
103
+ registration: {
104
+ enabled: true,
105
+ requireEmailVerification: true,
106
+ allowedDomains: ['company.com']
107
+ },
108
+
109
+ passwordPolicy: {
110
+ minLength: 12,
111
+ requireSymbols: true,
112
+ bcryptRounds: 12
113
+ },
114
+
115
+ session: {
116
+ sessionExpiry: '8h',
117
+ cookieSecure: true,
118
+ cookieSameSite: 'Strict'
119
+ },
120
+
121
+ email: {
122
+ enabled: true,
123
+ smtp: {
124
+ host: process.env.SMTP_HOST,
125
+ port: 587,
126
+ auth: {
127
+ user: process.env.SMTP_USER,
128
+ pass: process.env.SMTP_PASS
129
+ }
130
+ }
131
+ },
132
+
133
+ ui: {
134
+ companyName: 'My Company',
135
+ primaryColor: '#0066CC',
136
+ logoUrl: 'https://company.com/logo.svg'
137
+ }
138
+ })
139
+ ```
140
+
141
+ ## Access Points
142
+
143
+ After starting the server:
144
+
145
+ - **Login**: `/login`
146
+ - **Register**: `/register`
147
+ - **Profile**: `/profile` (requires login)
148
+ - **Admin**: `/admin` (requires admin role)
149
+ - **OIDC Discovery**: `/.well-known/openid-configuration`
150
+ - **JWKS**: `/.well-known/jwks.json`
151
+
152
+ ## OAuth2 Endpoints
153
+
154
+ - `GET /oauth/authorize` - Authorization (consent screen)
155
+ - `POST /oauth/token` - Token exchange
156
+ - `GET /oauth/userinfo` - OIDC UserInfo
157
+ - `POST /oauth/introspect` - Token introspection
158
+ - `POST /oauth/revoke` - Token revocation
159
+ - `POST /oauth/register` - Client registration
160
+
161
+ ## Admin Panel
162
+
163
+ Create admin user:
164
+
165
+ ```javascript
166
+ const usersResource = db.resources.users;
167
+
168
+ await usersResource.insert({
169
+ email: 'admin@example.com',
170
+ name: 'Admin User',
171
+ passwordHash: await hashPassword('SecurePass123!'),
172
+ status: 'active',
173
+ emailVerified: true,
174
+ role: 'admin'
175
+ });
176
+ ```
177
+
178
+ Access admin panel at `/admin` after login.
179
+
180
+ ## White-Label Customization
181
+
182
+ ### Basic Branding
183
+
184
+ ```javascript
185
+ ui: {
186
+ companyName: 'Acme Corp',
187
+ tagline: 'Secure Cloud Solutions',
188
+ logoUrl: 'https://acme.com/logo.svg',
189
+ primaryColor: '#ff6600',
190
+ supportEmail: 'support@acme.com'
191
+ }
192
+ ```
193
+
194
+ ### Custom Pages
195
+
196
+ ```javascript
197
+ import { html } from 'hono/html';
198
+
199
+ function MyCustomLoginPage(props) {
200
+ const { error, email, config } = props;
201
+
202
+ return html`<!DOCTYPE html>
203
+ <html>
204
+ <head><title>Login - ${config.companyName}</title></head>
205
+ <body>
206
+ <form method="POST" action="/login">
207
+ <input type="email" name="email" value="${email}" />
208
+ <input type="password" name="password" />
209
+ <button>Sign In</button>
210
+ </form>
211
+ </body>
212
+ </html>`;
213
+ }
214
+
215
+ ui: {
216
+ customPages: {
217
+ login: MyCustomLoginPage
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## Registration Controls
223
+
224
+ ### Disable Public Registration
225
+
226
+ ```javascript
227
+ registration: {
228
+ enabled: false,
229
+ customMessage: 'Contact admin for access'
230
+ }
231
+ ```
232
+
233
+ ### Corporate Emails Only
234
+
235
+ ```javascript
236
+ registration: {
237
+ enabled: true,
238
+ allowedDomains: ['company.com', 'partner.com']
239
+ }
240
+ ```
241
+
242
+ ### Block Temporary Emails
243
+
244
+ ```javascript
245
+ registration: {
246
+ enabled: true,
247
+ blockedDomains: ['tempmail.com', 'guerrillamail.com']
248
+ }
249
+ ```
250
+
251
+ ## Architecture
252
+
253
+ ```
254
+ Identity Plugin
255
+ ├── OAuth2/OIDC Server (authorization, tokens, PKCE)
256
+ ├── UI Pages (login, register, profile, admin)
257
+ ├── Session Manager (cookie-based sessions)
258
+ ├── Email Service (SMTP integration)
259
+ └── S3DB Resources
260
+ ├── plg_oauth_keys (RSA keys)
261
+ ├── plg_oauth_clients (registered clients)
262
+ ├── plg_auth_codes (authorization codes)
263
+ ├── plg_sessions (user sessions)
264
+ ├── plg_password_reset_tokens (reset tokens)
265
+ └── users (user accounts)
266
+ ```
267
+
268
+ ## Security Best Practices
269
+
270
+ 1. **Use HTTPS in production**
271
+ ```javascript
272
+ session: { cookieSecure: true },
273
+ server: { security: { hsts: true } }
274
+ ```
275
+
276
+ 2. **Strong password policy**
277
+ ```javascript
278
+ passwordPolicy: {
279
+ minLength: 12,
280
+ requireSymbols: true,
281
+ bcryptRounds: 12
282
+ }
283
+ ```
284
+
285
+ 3. **Email verification required**
286
+ ```javascript
287
+ registration: { requireEmailVerification: true }
288
+ ```
289
+
290
+ 4. **Restrict origins**
291
+ ```javascript
292
+ server: {
293
+ cors: { origin: ['https://app.company.com'] }
294
+ }
295
+ ```
296
+
297
+ ## Troubleshooting
298
+
299
+ ### Session not persisting
300
+
301
+ Check cookie settings match deployment (HTTP vs HTTPS):
302
+ ```javascript
303
+ session: {
304
+ cookieSecure: false, // Development (HTTP)
305
+ // cookieSecure: true, // Production (HTTPS)
306
+ }
307
+ ```
308
+
309
+ ### Email not sending
310
+
311
+ Test SMTP connection:
312
+ ```bash
313
+ # Use MailHog for testing
314
+ docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
315
+
316
+ SMTP_HOST=localhost SMTP_PORT=1025 node your-app.js
317
+ ```
318
+
319
+ ### Admin panel not accessible
320
+
321
+ User needs admin role:
322
+ ```javascript
323
+ await usersResource.update(userId, { role: 'admin' });
324
+ ```
325
+
326
+ ## Resources
327
+
328
+ - [Full Documentation](../../../docs/plugins/identity-plugin.md)
329
+ - [Configuration Reference](../../../docs/plugins/identity-config-reference.md)
330
+ - [Examples](../../../docs/plugins/identity-examples.md)
331
+ - [S3DB Documentation](../../../README.md)
332
+
333
+ ## License
334
+
335
+ MIT
@@ -0,0 +1,204 @@
1
+ /**
2
+ * MFA Manager - Multi-Factor Authentication for Identity Plugin
3
+ *
4
+ * Handles TOTP (Time-based One-Time Password) generation, verification,
5
+ * and backup codes management.
6
+ *
7
+ * Compatible with: Google Authenticator, Authy, Microsoft Authenticator, 1Password
8
+ *
9
+ * @example
10
+ * import { MFAManager } from './concerns/mfa-manager.js';
11
+ *
12
+ * const mfaManager = new MFAManager({
13
+ * issuer: 'MyApp',
14
+ * algorithm: 'SHA1',
15
+ * digits: 6,
16
+ * period: 30
17
+ * });
18
+ *
19
+ * // Enroll user
20
+ * const enrollment = await mfaManager.generateEnrollment('user@example.com');
21
+ * console.log(enrollment.qrCodeUrl); // Display QR code
22
+ * console.log(enrollment.secret); // Manual entry key
23
+ *
24
+ * // Verify TOTP token
25
+ * const isValid = mfaManager.verifyTOTP(enrollment.secret, '123456');
26
+ *
27
+ * // Generate backup codes
28
+ * const backupCodes = mfaManager.generateBackupCodes(10);
29
+ */
30
+
31
+ import { requirePluginDependency } from '../../concerns/plugin-dependencies.js';
32
+ import { idGenerator } from '../../../concerns/id.js';
33
+
34
+ export class MFAManager {
35
+ constructor(options = {}) {
36
+ this.options = {
37
+ issuer: options.issuer || 'S3DB Identity',
38
+ algorithm: options.algorithm || 'SHA1', // SHA1, SHA256, SHA512
39
+ digits: options.digits || 6, // 6 or 8 digits
40
+ period: options.period || 30, // 30 seconds
41
+ window: options.window || 1, // Allow ±1 time step (90s total)
42
+ backupCodesCount: options.backupCodesCount || 10,
43
+ backupCodeLength: options.backupCodeLength || 8
44
+ };
45
+
46
+ this.OTPAuth = null;
47
+ }
48
+
49
+ /**
50
+ * Initialize MFA Manager (load otpauth library)
51
+ */
52
+ async initialize() {
53
+ this.OTPAuth = await requirePluginDependency(
54
+ 'otpauth',
55
+ 'IdentityPlugin (MFA)',
56
+ 'Multi-Factor Authentication'
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Generate MFA enrollment data for a user
62
+ * @param {string} accountName - User email or username
63
+ * @returns {Object} Enrollment data with secret, QR code URL, and backup codes
64
+ */
65
+ generateEnrollment(accountName) {
66
+ if (!this.OTPAuth) {
67
+ throw new Error('[MFA] OTPAuth library not initialized');
68
+ }
69
+
70
+ // Generate TOTP secret
71
+ const totp = new this.OTPAuth.TOTP({
72
+ issuer: this.options.issuer,
73
+ label: accountName,
74
+ algorithm: this.options.algorithm,
75
+ digits: this.options.digits,
76
+ period: this.options.period,
77
+ secret: this.OTPAuth.Secret.fromBase32(
78
+ this.OTPAuth.Secret.generate().base32
79
+ )
80
+ });
81
+
82
+ // Generate QR code URL
83
+ const qrCodeUrl = totp.toString();
84
+
85
+ // Generate backup codes
86
+ const backupCodes = this.generateBackupCodes(this.options.backupCodesCount);
87
+
88
+ return {
89
+ secret: totp.secret.base32, // For manual entry
90
+ qrCodeUrl, // For QR code scanning
91
+ backupCodes, // Emergency access codes
92
+ algorithm: this.options.algorithm,
93
+ digits: this.options.digits,
94
+ period: this.options.period
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Verify a TOTP token
100
+ * @param {string} secret - Base32 encoded secret
101
+ * @param {string} token - 6-digit token from authenticator app
102
+ * @returns {boolean} True if valid
103
+ */
104
+ verifyTOTP(secret, token) {
105
+ if (!this.OTPAuth) {
106
+ throw new Error('[MFA] OTPAuth library not initialized');
107
+ }
108
+
109
+ try {
110
+ const totp = new this.OTPAuth.TOTP({
111
+ issuer: this.options.issuer,
112
+ algorithm: this.options.algorithm,
113
+ digits: this.options.digits,
114
+ period: this.options.period,
115
+ secret: this.OTPAuth.Secret.fromBase32(secret)
116
+ });
117
+
118
+ // Validate token with time window
119
+ const delta = totp.validate({
120
+ token,
121
+ window: this.options.window
122
+ });
123
+
124
+ // delta is null if invalid, or number if valid
125
+ return delta !== null;
126
+ } catch (error) {
127
+ console.error('[MFA] TOTP verification error:', error.message);
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Generate backup codes for emergency access
134
+ * @param {number} count - Number of codes to generate
135
+ * @returns {Array<string>} Array of backup codes
136
+ */
137
+ generateBackupCodes(count = 10) {
138
+ const codes = [];
139
+ const length = this.options.backupCodeLength;
140
+
141
+ for (let i = 0; i < count; i++) {
142
+ // Generate random alphanumeric code
143
+ const code = idGenerator()
144
+ .replace(/[^a-zA-Z0-9]/g, '')
145
+ .substring(0, length)
146
+ .toUpperCase();
147
+
148
+ codes.push(code);
149
+ }
150
+
151
+ return codes;
152
+ }
153
+
154
+ /**
155
+ * Hash backup codes for storage
156
+ * @param {Array<string>} codes - Backup codes
157
+ * @returns {Array<string>} Hashed codes
158
+ */
159
+ async hashBackupCodes(codes) {
160
+ const crypto = await import('crypto');
161
+ return codes.map(code => {
162
+ return crypto.createHash('sha256')
163
+ .update(code)
164
+ .digest('hex');
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Verify a backup code
170
+ * @param {string} code - Backup code to verify
171
+ * @param {Array<string>} hashedCodes - Array of hashed backup codes
172
+ * @returns {number|null} Index of matched code, or null if not found
173
+ */
174
+ async verifyBackupCode(code, hashedCodes) {
175
+ const crypto = await import('crypto');
176
+ const hashedInput = crypto.createHash('sha256')
177
+ .update(code.toUpperCase())
178
+ .digest('hex');
179
+
180
+ return hashedCodes.findIndex(hash => hash === hashedInput);
181
+ }
182
+
183
+ /**
184
+ * Generate QR code data URL for display
185
+ * @param {string} qrCodeUrl - OTP auth URL
186
+ * @returns {Promise<string>} Data URL for QR code image
187
+ */
188
+ async generateQRCodeDataURL(qrCodeUrl) {
189
+ try {
190
+ const QRCode = await requirePluginDependency(
191
+ 'qrcode',
192
+ 'IdentityPlugin (MFA)',
193
+ 'QR code generation for MFA enrollment'
194
+ );
195
+
196
+ return await QRCode.toDataURL(qrCodeUrl);
197
+ } catch (error) {
198
+ console.error('[MFA] QR code generation error:', error.message);
199
+ return null;
200
+ }
201
+ }
202
+ }
203
+
204
+ export default MFAManager;