s3db.js 13.6.0 → 14.0.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.
Files changed (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
@@ -30,13 +30,19 @@ import { Plugin } from '../plugin.class.js';
30
30
  import { requirePluginDependency } from '../concerns/plugin-dependencies.js';
31
31
  import tryFn from '../../concerns/try-fn.js';
32
32
  import { OAuth2Server } from './oauth2-server.js';
33
+ import { RateLimiter } from './concerns/rate-limit.js';
34
+ import { resolveResourceNames } from '../concerns/resource-names.js';
35
+ import { prepareResourceConfigs } from './concerns/config.js';
36
+ import { PluginError } from '../../errors.js';
33
37
  import {
34
38
  BASE_USER_ATTRIBUTES,
35
39
  BASE_TENANT_ATTRIBUTES,
36
40
  BASE_CLIENT_ATTRIBUTES,
37
- validateResourcesConfig,
38
41
  mergeResourceConfig
39
42
  } from './concerns/resource-schemas.js';
43
+ import { verifyPassword } from './concerns/password.js';
44
+ import { createBuiltInAuthDrivers } from './drivers/index.js';
45
+ import { AuthDriver } from './drivers/auth-driver.interface.js';
40
46
 
41
47
  /**
42
48
  * Identity Provider Plugin class
@@ -51,31 +57,32 @@ export class IdentityPlugin extends Plugin {
51
57
  constructor(options = {}) {
52
58
  super(options);
53
59
 
54
- // Validate required resources configuration
55
- const resourcesValidation = validateResourcesConfig(options.resources);
56
- if (!resourcesValidation.valid) {
57
- throw new Error(
58
- 'IdentityPlugin configuration error:\n' +
59
- resourcesValidation.errors.join('\n')
60
- );
61
- }
60
+ this._internalResourceOverrides = options.resourceNames || options.internalResources || {};
61
+ this._internalResourceDescriptors = {
62
+ oauthKeys: {
63
+ defaultName: 'plg_identity_oauth_keys',
64
+ override: this._internalResourceOverrides.oauthKeys
65
+ },
66
+ authCodes: {
67
+ defaultName: 'plg_identity_auth_codes',
68
+ override: this._internalResourceOverrides.authCodes
69
+ },
70
+ sessions: {
71
+ defaultName: 'plg_identity_sessions',
72
+ override: this._internalResourceOverrides.sessions
73
+ },
74
+ passwordResetTokens: {
75
+ defaultName: 'plg_identity_password_reset_tokens',
76
+ override: this._internalResourceOverrides.passwordResetTokens
77
+ },
78
+ mfaDevices: {
79
+ defaultName: 'plg_identity_mfa_devices',
80
+ override: this._internalResourceOverrides.mfaDevices
81
+ }
82
+ };
83
+ this.internalResourceNames = this._resolveInternalResourceNames();
62
84
 
63
- // Validate resource configs (will throw if invalid)
64
- mergeResourceConfig(
65
- { attributes: BASE_USER_ATTRIBUTES },
66
- options.resources.users,
67
- 'users'
68
- );
69
- mergeResourceConfig(
70
- { attributes: BASE_TENANT_ATTRIBUTES },
71
- options.resources.tenants,
72
- 'tenants'
73
- );
74
- mergeResourceConfig(
75
- { attributes: BASE_CLIENT_ATTRIBUTES },
76
- options.resources.clients,
77
- 'clients'
78
- );
85
+ const normalizedResources = prepareResourceConfigs(options.resources);
79
86
 
80
87
  this.config = {
81
88
  // Server configuration
@@ -86,7 +93,7 @@ export class IdentityPlugin extends Plugin {
86
93
  // OAuth2/OIDC configuration
87
94
  issuer: options.issuer || `http://localhost:${options.port || 4000}`,
88
95
  supportedScopes: options.supportedScopes || ['openid', 'profile', 'email', 'offline_access'],
89
- supportedGrantTypes: options.supportedGrantTypes || ['authorization_code', 'client_credentials', 'refresh_token'],
96
+ supportedGrantTypes: options.supportedGrantTypes || ['authorization_code', 'refresh_token', 'client_credentials', 'password'],
90
97
  supportedResponseTypes: options.supportedResponseTypes || ['code', 'token', 'id_token'],
91
98
 
92
99
  // Token expiration
@@ -96,21 +103,9 @@ export class IdentityPlugin extends Plugin {
96
103
  authCodeExpiry: options.authCodeExpiry || '10m',
97
104
 
98
105
  // Resource configuration (REQUIRED)
99
- // User must declare: users, tenants, clients with full resource config
100
- resources: {
101
- users: {
102
- userConfig: options.resources.users, // Store user's full config
103
- mergedConfig: null // Will be populated in _createResources()
104
- },
105
- tenants: {
106
- userConfig: options.resources.tenants,
107
- mergedConfig: null
108
- },
109
- clients: {
110
- userConfig: options.resources.clients,
111
- mergedConfig: null
112
- }
113
- },
106
+ resources: normalizedResources,
107
+
108
+ resourceNames: this.internalResourceNames,
114
109
 
115
110
  // CORS configuration
116
111
  cors: {
@@ -318,6 +313,23 @@ export class IdentityPlugin extends Plugin {
318
313
  }
319
314
  },
320
315
 
316
+ // Rate Limiting Configuration
317
+ rateLimit: {
318
+ enabled: options.rateLimit?.enabled !== false,
319
+ login: {
320
+ windowMs: options.rateLimit?.login?.windowMs || 60000,
321
+ max: options.rateLimit?.login?.max ?? 10
322
+ },
323
+ token: {
324
+ windowMs: options.rateLimit?.token?.windowMs || 60000,
325
+ max: options.rateLimit?.token?.max ?? 60
326
+ },
327
+ authorize: {
328
+ windowMs: options.rateLimit?.authorize?.windowMs || 60000,
329
+ max: options.rateLimit?.authorize?.max ?? 30
330
+ }
331
+ },
332
+
321
333
  // Features (MVP - Phase 1)
322
334
  features: {
323
335
  // Endpoints (can be disabled individually)
@@ -353,7 +365,9 @@ export class IdentityPlugin extends Plugin {
353
365
  // emailVerification: { enabled: false, required: false },
354
366
  // passwordPolicy: { enabled: false, minLength: 8, ... },
355
367
  // webhooks: { enabled: false, endpoints: [], events: [] }
356
- }
368
+ },
369
+
370
+ authDrivers: options.authDrivers || {}
357
371
  };
358
372
 
359
373
  this.server = null;
@@ -375,6 +389,24 @@ export class IdentityPlugin extends Plugin {
375
389
  this.usersResource = null;
376
390
  this.tenantsResource = null;
377
391
  this.clientsResource = null;
392
+
393
+ // Rate limiters
394
+ this.rateLimiters = this._createRateLimiters();
395
+ this.authDrivers = new Map();
396
+ this.authDriverInstances = [];
397
+ }
398
+
399
+ _resolveInternalResourceNames() {
400
+ return resolveResourceNames('identity', this._internalResourceDescriptors, {
401
+ namespace: this.namespace
402
+ });
403
+ }
404
+
405
+ onNamespaceChanged() {
406
+ this.internalResourceNames = this._resolveInternalResourceNames();
407
+ if (this.config) {
408
+ this.config.resourceNames = this.internalResourceNames;
409
+ }
378
410
  }
379
411
 
380
412
  /**
@@ -388,6 +420,34 @@ export class IdentityPlugin extends Plugin {
388
420
  });
389
421
  }
390
422
 
423
+ /**
424
+ * Initialize rate limiters for sensitive endpoints
425
+ * @private
426
+ * @returns {Object<string, RateLimiter>}
427
+ */
428
+ _createRateLimiters() {
429
+ if (!this.config.rateLimit.enabled) {
430
+ return {};
431
+ }
432
+
433
+ const limiters = {};
434
+ const { login, token, authorize } = this.config.rateLimit;
435
+
436
+ if (login?.max > 0 && login?.windowMs > 0) {
437
+ limiters.login = new RateLimiter(login);
438
+ }
439
+
440
+ if (token?.max > 0 && token?.windowMs > 0) {
441
+ limiters.token = new RateLimiter(token);
442
+ }
443
+
444
+ if (authorize?.max > 0 && authorize?.windowMs > 0) {
445
+ limiters.authorize = new RateLimiter(authorize);
446
+ }
447
+
448
+ return limiters;
449
+ }
450
+
391
451
  /**
392
452
  * Install plugin
393
453
  */
@@ -428,6 +488,9 @@ export class IdentityPlugin extends Plugin {
428
488
  // Initialize MFA Manager
429
489
  await this._initializeMFAManager();
430
490
 
491
+ // Initialize authentication drivers
492
+ await this._initializeAuthDrivers();
493
+
431
494
  if (this.config.verbose) {
432
495
  console.log('[Identity Plugin] Installed successfully');
433
496
  }
@@ -438,10 +501,12 @@ export class IdentityPlugin extends Plugin {
438
501
  * @private
439
502
  */
440
503
  async _createOAuth2Resources() {
504
+ const names = this.internalResourceNames;
505
+
441
506
  // 1. OAuth Keys Resource (RSA keys for token signing)
442
507
  const [okKeys, errKeys, keysResource] = await tryFn(() =>
443
508
  this.database.createResource({
444
- name: 'plg_oauth_keys',
509
+ name: names.oauthKeys,
445
510
  attributes: {
446
511
  kid: 'string|required',
447
512
  publicKey: 'string|required',
@@ -460,12 +525,12 @@ export class IdentityPlugin extends Plugin {
460
525
  if (okKeys) {
461
526
  this.oauth2KeysResource = keysResource;
462
527
  if (this.config.verbose) {
463
- console.log('[Identity Plugin] Created plg_oauth_keys resource');
528
+ console.log(`[Identity Plugin] Created ${names.oauthKeys} resource`);
464
529
  }
465
- } else if (this.database.resources.plg_oauth_keys) {
466
- this.oauth2KeysResource = this.database.resources.plg_oauth_keys;
530
+ } else if (this.database.resources[names.oauthKeys]) {
531
+ this.oauth2KeysResource = this.database.resources[names.oauthKeys];
467
532
  if (this.config.verbose) {
468
- console.log('[Identity Plugin] Using existing plg_oauth_keys resource');
533
+ console.log(`[Identity Plugin] Using existing ${names.oauthKeys} resource`);
469
534
  }
470
535
  } else {
471
536
  throw errKeys;
@@ -474,7 +539,7 @@ export class IdentityPlugin extends Plugin {
474
539
  // 2. OAuth Authorization Codes Resource (authorization_code flow)
475
540
  const [okCodes, errCodes, codesResource] = await tryFn(() =>
476
541
  this.database.createResource({
477
- name: 'plg_auth_codes',
542
+ name: names.authCodes,
478
543
  attributes: {
479
544
  code: 'string|required',
480
545
  clientId: 'string|required',
@@ -496,12 +561,12 @@ export class IdentityPlugin extends Plugin {
496
561
  if (okCodes) {
497
562
  this.oauth2AuthCodesResource = codesResource;
498
563
  if (this.config.verbose) {
499
- console.log('[Identity Plugin] Created plg_auth_codes resource');
564
+ console.log(`[Identity Plugin] Created ${names.authCodes} resource`);
500
565
  }
501
- } else if (this.database.resources.plg_auth_codes) {
502
- this.oauth2AuthCodesResource = this.database.resources.plg_auth_codes;
566
+ } else if (this.database.resources[names.authCodes]) {
567
+ this.oauth2AuthCodesResource = this.database.resources[names.authCodes];
503
568
  if (this.config.verbose) {
504
- console.log('[Identity Plugin] Using existing plg_auth_codes resource');
569
+ console.log(`[Identity Plugin] Using existing ${names.authCodes} resource`);
505
570
  }
506
571
  } else {
507
572
  throw errCodes;
@@ -510,7 +575,7 @@ export class IdentityPlugin extends Plugin {
510
575
  // 3. Sessions Resource (user sessions for UI/admin)
511
576
  const [okSessions, errSessions, sessionsResource] = await tryFn(() =>
512
577
  this.database.createResource({
513
- name: 'plg_sessions',
578
+ name: names.sessions,
514
579
  attributes: {
515
580
  userId: 'string|required',
516
581
  expiresAt: 'string|required',
@@ -528,12 +593,12 @@ export class IdentityPlugin extends Plugin {
528
593
  if (okSessions) {
529
594
  this.sessionsResource = sessionsResource;
530
595
  if (this.config.verbose) {
531
- console.log('[Identity Plugin] Created plg_sessions resource');
596
+ console.log(`[Identity Plugin] Created ${names.sessions} resource`);
532
597
  }
533
- } else if (this.database.resources.plg_sessions) {
534
- this.sessionsResource = this.database.resources.plg_sessions;
598
+ } else if (this.database.resources[names.sessions]) {
599
+ this.sessionsResource = this.database.resources[names.sessions];
535
600
  if (this.config.verbose) {
536
- console.log('[Identity Plugin] Using existing plg_sessions resource');
601
+ console.log(`[Identity Plugin] Using existing ${names.sessions} resource`);
537
602
  }
538
603
  } else {
539
604
  throw errSessions;
@@ -542,7 +607,7 @@ export class IdentityPlugin extends Plugin {
542
607
  // 4. Password Reset Tokens Resource (for password reset flow)
543
608
  const [okResetTokens, errResetTokens, resetTokensResource] = await tryFn(() =>
544
609
  this.database.createResource({
545
- name: 'plg_password_reset_tokens',
610
+ name: names.passwordResetTokens,
546
611
  attributes: {
547
612
  userId: 'string|required',
548
613
  token: 'string|required',
@@ -559,12 +624,12 @@ export class IdentityPlugin extends Plugin {
559
624
  if (okResetTokens) {
560
625
  this.passwordResetTokensResource = resetTokensResource;
561
626
  if (this.config.verbose) {
562
- console.log('[Identity Plugin] Created plg_password_reset_tokens resource');
627
+ console.log(`[Identity Plugin] Created ${names.passwordResetTokens} resource`);
563
628
  }
564
- } else if (this.database.resources.plg_password_reset_tokens) {
565
- this.passwordResetTokensResource = this.database.resources.plg_password_reset_tokens;
629
+ } else if (this.database.resources[names.passwordResetTokens]) {
630
+ this.passwordResetTokensResource = this.database.resources[names.passwordResetTokens];
566
631
  if (this.config.verbose) {
567
- console.log('[Identity Plugin] Using existing plg_password_reset_tokens resource');
632
+ console.log(`[Identity Plugin] Using existing ${names.passwordResetTokens} resource`);
568
633
  }
569
634
  } else {
570
635
  throw errResetTokens;
@@ -574,7 +639,7 @@ export class IdentityPlugin extends Plugin {
574
639
  if (this.config.mfa.enabled) {
575
640
  const [okMFA, errMFA, mfaResource] = await tryFn(() =>
576
641
  this.database.createResource({
577
- name: 'plg_mfa_devices',
642
+ name: names.mfaDevices,
578
643
  attributes: {
579
644
  userId: 'string|required',
580
645
  type: 'string|required', // 'totp', 'sms', 'email'
@@ -600,15 +665,15 @@ export class IdentityPlugin extends Plugin {
600
665
  if (okMFA) {
601
666
  this.mfaDevicesResource = mfaResource;
602
667
  if (this.config.verbose) {
603
- console.log('[Identity Plugin] Created plg_mfa_devices resource');
668
+ console.log(`[Identity Plugin] Created ${names.mfaDevices} resource`);
604
669
  }
605
- } else if (this.database.resources.plg_mfa_devices) {
606
- this.mfaDevicesResource = this.database.resources.plg_mfa_devices;
670
+ } else if (this.database.resources[names.mfaDevices]) {
671
+ this.mfaDevicesResource = this.database.resources[names.mfaDevices];
607
672
  if (this.config.verbose) {
608
- console.log('[Identity Plugin] Using existing plg_mfa_devices resource');
673
+ console.log(`[Identity Plugin] Using existing ${names.mfaDevices} resource`);
609
674
  }
610
675
  } else {
611
- console.warn('[Identity Plugin] MFA enabled but failed to create plg_mfa_devices resource:', errMFA?.message);
676
+ console.warn(`[Identity Plugin] MFA enabled but failed to create ${names.mfaDevices} resource:`, errMFA?.message);
612
677
  }
613
678
  }
614
679
  }
@@ -748,6 +813,7 @@ export class IdentityPlugin extends Plugin {
748
813
  });
749
814
 
750
815
  await this.oauth2Server.initialize();
816
+ this.oauth2Server.setIdentityPlugin(this);
751
817
 
752
818
  if (this.config.verbose) {
753
819
  console.log('[Identity Plugin] OAuth2 Server initialized');
@@ -938,6 +1004,277 @@ export class IdentityPlugin extends Plugin {
938
1004
  }
939
1005
  }
940
1006
 
1007
+ async _initializeAuthDrivers() {
1008
+ const driverConfig = this.config.authDrivers;
1009
+
1010
+ if (driverConfig === false) {
1011
+ this.authDrivers = new Map();
1012
+ this.authDriverInstances = [];
1013
+ return;
1014
+ }
1015
+ const disableBuiltIns = this._isPlainObject(driverConfig) && driverConfig.disableBuiltIns === true;
1016
+
1017
+ const context = {
1018
+ database: this.database,
1019
+ config: this.config,
1020
+ resources: {
1021
+ users: this.usersResource,
1022
+ tenants: this.tenantsResource,
1023
+ clients: this.clientsResource
1024
+ },
1025
+ helpers: {
1026
+ password: {
1027
+ verify: verifyPassword
1028
+ }
1029
+ }
1030
+ };
1031
+
1032
+ const drivers = [];
1033
+
1034
+ if (!disableBuiltIns) {
1035
+ const builtInOptions = this._extractBuiltInDriverOptions(driverConfig);
1036
+ drivers.push(...createBuiltInAuthDrivers(builtInOptions));
1037
+ }
1038
+
1039
+ drivers.push(...this._collectCustomAuthDrivers(driverConfig));
1040
+
1041
+ if (!drivers.length) {
1042
+ this.authDrivers = new Map();
1043
+ this.authDriverInstances = [];
1044
+ return;
1045
+ }
1046
+
1047
+ this.authDrivers = new Map();
1048
+ this.authDriverInstances = [];
1049
+
1050
+ for (const driver of drivers) {
1051
+ if (!driver || typeof driver.initialize !== 'function') {
1052
+ throw new PluginError('Auth drivers must implement initialize(context)', {
1053
+ pluginName: 'IdentityPlugin',
1054
+ operation: 'initializeAuthDrivers',
1055
+ statusCode: 500,
1056
+ retriable: false,
1057
+ suggestion: 'Ensure custom auth drivers extend AuthDriver and implement initialize(context).'
1058
+ });
1059
+ }
1060
+
1061
+ try {
1062
+ await driver.initialize(context);
1063
+ } catch (error) {
1064
+ const driverName = driver.name || driver.constructor?.name || 'UnknownDriver';
1065
+ throw new PluginError(`Failed to initialize auth driver "${driverName}": ${error.message}`, {
1066
+ pluginName: 'IdentityPlugin',
1067
+ operation: 'initializeAuthDrivers',
1068
+ statusCode: 500,
1069
+ retriable: false,
1070
+ suggestion: 'Review driver configuration and ensure required dependencies are available.',
1071
+ original: error,
1072
+ metadata: { driverName }
1073
+ });
1074
+ }
1075
+
1076
+ const supportedTypes = Array.isArray(driver.supportedTypes) && driver.supportedTypes.length > 0
1077
+ ? driver.supportedTypes
1078
+ : driver.name
1079
+ ? [driver.name]
1080
+ : [];
1081
+
1082
+ if (!supportedTypes.length) {
1083
+ const driverName = driver.constructor?.name || 'AuthDriver';
1084
+ throw new PluginError(`Auth driver "${driverName}" must declare supportedTypes or name`, {
1085
+ pluginName: 'IdentityPlugin',
1086
+ operation: 'registerAuthDriver',
1087
+ statusCode: 500,
1088
+ retriable: false,
1089
+ suggestion: 'Set driver.supportedTypes = ["password"] or provide a name property to map grants.',
1090
+ metadata: { driverName }
1091
+ });
1092
+ }
1093
+
1094
+ for (const type of supportedTypes) {
1095
+ if (!type) continue;
1096
+ if (this.authDrivers.has(type)) {
1097
+ const existingDriver = this.authDrivers.get(type);
1098
+ const existingName = existingDriver?.name || existingDriver?.constructor?.name || 'AuthDriver';
1099
+ const newName = driver.name || driver.constructor?.name || 'AuthDriver';
1100
+ throw new PluginError(`Duplicate auth driver registration for type "${type}"`, {
1101
+ pluginName: 'IdentityPlugin',
1102
+ operation: 'registerAuthDriver',
1103
+ statusCode: 409,
1104
+ retriable: false,
1105
+ suggestion: 'Remove duplicate registrations or use distinct driver types for each grant.',
1106
+ metadata: { type, existingDriver: existingName, newDriver: newName }
1107
+ });
1108
+ }
1109
+ this.authDrivers.set(type, driver);
1110
+ }
1111
+
1112
+ this.authDriverInstances.push(driver);
1113
+ }
1114
+ }
1115
+
1116
+ getAuthDriver(type) {
1117
+ return this.authDrivers.get(type);
1118
+ }
1119
+
1120
+ _sanitizeAuthSubject(subject) {
1121
+ if (!subject || typeof subject !== 'object') {
1122
+ return subject;
1123
+ }
1124
+
1125
+ const sensitiveFields = [
1126
+ 'password',
1127
+ 'passwordHash',
1128
+ 'password_hash',
1129
+ 'salt',
1130
+ 'secret',
1131
+ 'secrets',
1132
+ 'clientSecret',
1133
+ 'mfaSecret',
1134
+ 'totpSecret',
1135
+ 'backupCodes'
1136
+ ];
1137
+
1138
+ const sanitized = { ...subject };
1139
+ for (const field of sensitiveFields) {
1140
+ if (sanitized[field] !== undefined) {
1141
+ delete sanitized[field];
1142
+ }
1143
+ }
1144
+
1145
+ return sanitized;
1146
+ }
1147
+
1148
+ async authenticateWithPassword({ email, password, user }) {
1149
+ const driver = this.getAuthDriver('password');
1150
+ if (!driver) {
1151
+ return {
1152
+ success: false,
1153
+ error: 'password_driver_not_configured',
1154
+ statusCode: 500
1155
+ };
1156
+ }
1157
+
1158
+ const result = await driver.authenticate({
1159
+ type: 'password',
1160
+ email,
1161
+ password,
1162
+ user
1163
+ });
1164
+
1165
+ if (result?.success && result.user) {
1166
+ return {
1167
+ ...result,
1168
+ user: this._sanitizeAuthSubject(result.user)
1169
+ };
1170
+ }
1171
+
1172
+ return result;
1173
+ }
1174
+
1175
+ _collectCustomAuthDrivers(config) {
1176
+ const candidates = [];
1177
+
1178
+ const addCandidate = (candidate) => {
1179
+ if (!candidate) return;
1180
+
1181
+ if (Array.isArray(candidate)) {
1182
+ if (candidate.length > 0 && typeof candidate[0] === 'function') {
1183
+ const [Ctor, options] = candidate;
1184
+ const instance = new Ctor(options);
1185
+ if (!(instance instanceof AuthDriver)) {
1186
+ throw new PluginError('Custom auth driver constructors must extend AuthDriver', {
1187
+ pluginName: 'IdentityPlugin',
1188
+ operation: 'collectCustomAuthDrivers',
1189
+ statusCode: 500,
1190
+ retriable: false,
1191
+ suggestion: 'Extend AuthDriver to ensure consistent interface for initialize/authenticate.'
1192
+ });
1193
+ }
1194
+ candidates.push(instance);
1195
+ return;
1196
+ }
1197
+ for (const item of candidate) {
1198
+ addCandidate(item);
1199
+ }
1200
+ return;
1201
+ }
1202
+
1203
+ if (candidate instanceof AuthDriver) {
1204
+ candidates.push(candidate);
1205
+ return;
1206
+ }
1207
+
1208
+ if (typeof candidate === 'function') {
1209
+ const instance = new candidate();
1210
+ if (!(instance instanceof AuthDriver)) {
1211
+ throw new PluginError('Custom auth driver constructors must extend AuthDriver', {
1212
+ pluginName: 'IdentityPlugin',
1213
+ operation: 'collectCustomAuthDrivers',
1214
+ statusCode: 500,
1215
+ retriable: false,
1216
+ suggestion: 'Update the constructor to extend AuthDriver before registering it.'
1217
+ });
1218
+ }
1219
+ candidates.push(instance);
1220
+ return;
1221
+ }
1222
+
1223
+ if (candidate && typeof candidate === 'object' &&
1224
+ typeof candidate.initialize === 'function' &&
1225
+ typeof candidate.authenticate === 'function'
1226
+ ) {
1227
+ candidates.push(candidate);
1228
+ return;
1229
+ }
1230
+
1231
+ throw new PluginError('Invalid auth driver provided. Drivers must extend AuthDriver.', {
1232
+ pluginName: 'IdentityPlugin',
1233
+ operation: 'collectCustomAuthDrivers',
1234
+ statusCode: 400,
1235
+ retriable: false,
1236
+ suggestion: 'Provide an AuthDriver instance, subclass, or plain object with initialize/authenticate methods.'
1237
+ });
1238
+ };
1239
+
1240
+ if (Array.isArray(config)) {
1241
+ addCandidate(config);
1242
+ return candidates;
1243
+ }
1244
+
1245
+ if (this._isPlainObject(config)) {
1246
+ addCandidate(config.drivers);
1247
+ addCandidate(config.custom);
1248
+ addCandidate(config.customDrivers);
1249
+ }
1250
+
1251
+ return candidates;
1252
+ }
1253
+
1254
+ _extractBuiltInDriverOptions(config) {
1255
+ if (!this._isPlainObject(config)) {
1256
+ return {};
1257
+ }
1258
+
1259
+ if (this._isPlainObject(config.builtIns)) {
1260
+ return config.builtIns;
1261
+ }
1262
+
1263
+ const {
1264
+ drivers,
1265
+ custom,
1266
+ customDrivers,
1267
+ disableBuiltIns,
1268
+ ...builtInOptions
1269
+ } = config;
1270
+
1271
+ return builtInOptions;
1272
+ }
1273
+
1274
+ _isPlainObject(value) {
1275
+ return value != null && typeof value === 'object' && !Array.isArray(value);
1276
+ }
1277
+
941
1278
  /**
942
1279
  * Start plugin
943
1280
  */
@@ -1019,7 +1356,14 @@ export class IdentityPlugin extends Plugin {
1019
1356
 
1020
1357
  // Optionally delete OAuth2 resources
1021
1358
  if (purgeData) {
1022
- const resourcesToDelete = ['plg_oauth_keys', 'plg_oauth_clients', 'plg_auth_codes'];
1359
+ const resourcesToDelete = new Set([
1360
+ this.internalResourceNames.oauthKeys,
1361
+ this.internalResourceNames.authCodes,
1362
+ this.internalResourceNames.sessions,
1363
+ this.internalResourceNames.passwordResetTokens,
1364
+ this.internalResourceNames.mfaDevices,
1365
+ 'plg_oauth_clients'
1366
+ ]);
1023
1367
 
1024
1368
  for (const resourceName of resourcesToDelete) {
1025
1369
  const [ok] = await tryFn(() => this.database.deleteResource(resourceName));