webspresso 0.0.98 → 0.0.100

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 (88) hide show
  1. package/.agents/ADMIN.md +11 -1
  2. package/.agents/AGENTS.md +2 -2
  3. package/.agents/AUTH.md +415 -0
  4. package/.agents/CLI.md +47 -0
  5. package/.agents/ERRORS.md +59 -0
  6. package/.agents/ORM.md +178 -4
  7. package/.agents/PLUGINS.md +113 -8
  8. package/.agents/QUEUE.md +92 -0
  9. package/.agents/ROUTING.md +160 -7
  10. package/.agents/STREAMING.md +35 -0
  11. package/AGENTS.md +2 -0
  12. package/README.md +145 -93
  13. package/bin/commands/add-docker.js +467 -0
  14. package/bin/commands/deploy-check.js +339 -0
  15. package/bin/webspresso.js +4 -0
  16. package/core/auth/index.js +123 -1
  17. package/core/auth/magic-link.js +170 -0
  18. package/core/auth/manager.js +477 -3
  19. package/core/auth/middleware.js +118 -5
  20. package/core/auth/oauth.js +517 -0
  21. package/core/auth/qrcode.js +499 -0
  22. package/core/auth/session-manager.js +255 -0
  23. package/core/auth/sessions.js +351 -0
  24. package/core/auth/tokens.js +1 -0
  25. package/core/auth/totp.js +507 -0
  26. package/core/auth/user-agent.js +161 -0
  27. package/core/orm/eager-loader.js +144 -10
  28. package/core/orm/index.js +10 -1
  29. package/core/orm/model.js +55 -1
  30. package/core/orm/query-builder.js +90 -4
  31. package/core/orm/repository.js +327 -16
  32. package/core/orm/scopes.js +3 -2
  33. package/core/orm/search.js +213 -0
  34. package/core/orm/types.js +6 -1
  35. package/core/queue/cron.js +544 -0
  36. package/core/queue/index.js +8 -1
  37. package/core/queue/manager.js +100 -1
  38. package/core/queue/scheduler.js +415 -0
  39. package/core/sse/index.js +19 -0
  40. package/core/sse/stream.js +430 -0
  41. package/core/ssr/stream.js +62 -2
  42. package/index.d.ts +794 -6
  43. package/index.js +48 -0
  44. package/package.json +7 -5
  45. package/plugins/admin-panel/client/parts/05-rich-text-file-helpers.js +323 -31
  46. package/plugins/admin-panel/client/parts/08-record-list.js +5 -0
  47. package/plugins/admin-panel/client/verify-spa-parts.js +2 -0
  48. package/plugins/admin-panel/field-renderers/file-upload.js +187 -104
  49. package/plugins/admin-panel/field-renderers/index.js +2 -0
  50. package/plugins/admin-panel/modules/admin-users.js +1 -1
  51. package/plugins/api-key/crypto.js +101 -0
  52. package/plugins/api-key/index.js +288 -0
  53. package/plugins/api-key/middleware.js +461 -0
  54. package/plugins/api-key/migration-template.js +48 -0
  55. package/plugins/api-key/model.js +50 -0
  56. package/plugins/api-key/scopes.js +125 -0
  57. package/plugins/audit-log/admin-component.js +487 -70
  58. package/plugins/audit-log/api-handlers.js +114 -0
  59. package/plugins/audit-log/index.js +24 -0
  60. package/plugins/content/client/inline-edit.js +2 -46
  61. package/plugins/cors.js +13 -2
  62. package/plugins/email/auth-bridge.js +66 -0
  63. package/plugins/email/bundled-templates.js +15 -0
  64. package/plugins/email/templates/auth-magic-link.mjml +13 -0
  65. package/plugins/file-manager/admin-component.js +69 -53
  66. package/plugins/file-manager/index.js +25 -8
  67. package/plugins/index.js +8 -0
  68. package/plugins/oauth/index.js +338 -0
  69. package/plugins/queue/index.js +4 -0
  70. package/plugins/webhooks/admin-component.js +608 -0
  71. package/plugins/webhooks/api-handlers.js +237 -0
  72. package/plugins/webhooks/crypto.js +144 -0
  73. package/plugins/webhooks/dispatcher.js +708 -0
  74. package/plugins/webhooks/index.js +263 -0
  75. package/plugins/webhooks/matcher.js +113 -0
  76. package/plugins/webhooks/migration-template.js +75 -0
  77. package/plugins/webhooks/models.js +98 -0
  78. package/plugins/webhooks/sender.js +176 -0
  79. package/src/api/api-loader.js +1 -0
  80. package/src/bundle/index.js +2 -1
  81. package/src/discovery/file-route-parser.js +44 -1
  82. package/src/discovery/scan-routes.js +2 -2
  83. package/src/file-router.js +310 -104
  84. package/src/helpers.js +16 -1
  85. package/src/njk-frontmatter.js +3 -0
  86. package/src/routing/cascading.js +490 -0
  87. package/src/routing/middleware-resolver.js +92 -0
  88. package/src/server.js +6 -0
@@ -4,6 +4,7 @@
4
4
  * @module core/auth/manager
5
5
  */
6
6
 
7
+ const EventEmitter = require('events');
7
8
  const { generateToken, hashToken } = require('./hash');
8
9
  const { PolicyManager } = require('./policy');
9
10
  const { signJwt, verifyJwt, decodeJwt } = require('./jwt');
@@ -60,13 +61,14 @@ const DEFAULT_CONFIG = {
60
61
  logout: '/logout',
61
62
  home: '/',
62
63
  redirectAfterLogin: '/dashboard',
64
+ twoFactor: '/2fa',
63
65
  },
64
66
  };
65
67
 
66
68
  /**
67
69
  * AuthManager - Handles authentication operations
68
70
  */
69
- class AuthManager {
71
+ class AuthManager extends EventEmitter {
70
72
  /**
71
73
  * Create AuthManager instance
72
74
  * @param {Object} config - Configuration
@@ -82,6 +84,7 @@ class AuthManager {
82
84
  * @param {Object} [config.routes] - Route configuration
83
85
  */
84
86
  constructor(config) {
87
+ super();
85
88
  this.validateConfig(config);
86
89
 
87
90
  this.config = {
@@ -105,8 +108,13 @@ class AuthManager {
105
108
  passwordReset: config.notifications?.passwordReset || null,
106
109
  emailVerification: config.notifications?.emailVerification || null,
107
110
  welcome: config.notifications?.welcome || null,
111
+ magicLink: config.notifications?.magicLink || null,
108
112
  };
109
113
 
114
+ // TOTP & 2FA initialization
115
+ this.totp = require('./totp');
116
+ this.twoFactor = config.twoFactor || { enabled: false };
117
+
110
118
  if (config.jwt) {
111
119
  const jwtSecret = typeof config.jwt === 'object' && config.jwt.secret
112
120
  ? config.jwt.secret
@@ -133,6 +141,30 @@ class AuthManager {
133
141
 
134
142
  // Policy manager instance
135
143
  this.policies = new PolicyManager();
144
+
145
+ // Session tracking and revocation manager
146
+ const { SessionManager } = require('./session-manager');
147
+ const { createKnexSessionTracker, createMemorySessionTracker } = require('./sessions');
148
+
149
+ let tracker = config.sessionTracker || null;
150
+ if (!tracker && (config.sessionTracking || config.trackSessions)) {
151
+ if (config.db || this.config.db) {
152
+ tracker = createKnexSessionTracker(config.db || this.config.db);
153
+ } else {
154
+ tracker = createMemorySessionTracker();
155
+ }
156
+ }
157
+
158
+ if (tracker || config.sessionTracking || config.trackSessions) {
159
+ this.sessionManager = new SessionManager({
160
+ tracker: tracker || createMemorySessionTracker(),
161
+ sessionStore: config.sessionStore || this.config.session?.store,
162
+ touchThrottleMs: config.sessionTouchThrottleMs,
163
+ emitter: this,
164
+ });
165
+ } else {
166
+ this.sessionManager = null;
167
+ }
136
168
  }
137
169
 
138
170
  /**
@@ -280,7 +312,7 @@ class AuthManager {
280
312
 
281
313
  // Set req.user
282
314
  req.user = user;
283
-
315
+
284
316
  // Handle remember me
285
317
  if (options.remember && self.rememberTokens && self.config.rememberMe.enabled) {
286
318
  await self.createRememberToken(user.id, res);
@@ -289,18 +321,34 @@ class AuthManager {
289
321
  // Regenerate session ID for security if supported
290
322
  if (req.session && typeof req.session.regenerate === 'function') {
291
323
  return new Promise((resolve, reject) => {
292
- req.session.regenerate((err) => {
324
+ req.session.regenerate(async (err) => {
293
325
  if (err) {
294
326
  reject(new AuthenticationError('Session regeneration failed', 'SESSION_ERROR'));
295
327
  } else {
296
328
  // Restore userId after regenerate
297
329
  req.session.userId = user.id;
298
330
  req.session.loggedInAt = Date.now();
331
+ if (req.sessionID && self.sessionManager) {
332
+ try {
333
+ await self.sessionManager.track(req.sessionID, user.id, req);
334
+ } catch (e) {
335
+ // Non-blocking
336
+ }
337
+ }
299
338
  resolve();
300
339
  }
301
340
  });
302
341
  });
303
342
  }
343
+
344
+ // Track active session if sessionManager is active and not regenerated
345
+ if (req.sessionID && self.sessionManager) {
346
+ try {
347
+ await self.sessionManager.track(req.sessionID, user.id, req);
348
+ } catch (e) {
349
+ // Non-blocking
350
+ }
351
+ }
304
352
  },
305
353
 
306
354
  /**
@@ -329,6 +377,22 @@ class AuthManager {
329
377
  }
330
378
  }
331
379
 
380
+ // Clear 2FA session state
381
+ this.clear2Fa();
382
+
383
+ // Revoke session if sessionManager is active
384
+ if (req.sessionID && self.sessionManager) {
385
+ try {
386
+ if (options.everywhere && userId) {
387
+ await self.sessionManager.revokeAllSessions(userId);
388
+ } else {
389
+ await self.sessionManager.revokeSession(req.sessionID, { userId });
390
+ }
391
+ } catch (e) {
392
+ // Non-blocking
393
+ }
394
+ }
395
+
332
396
  // Clear user
333
397
  req.user = null;
334
398
 
@@ -377,6 +441,119 @@ class AuthManager {
377
441
  return req.user?.id || null;
378
442
  },
379
443
 
444
+ /**
445
+ * TOTP helper utilities (RFC 6238 / RFC 4226)
446
+ */
447
+ totp: self.totp,
448
+
449
+ /**
450
+ * Check if two-factor authentication has been verified in current session/request
451
+ * @returns {boolean}
452
+ */
453
+ is2FaVerified() {
454
+ if (req.session?.twoFactorVerified) return true;
455
+ if (req.jwtPayload?.twoFactorVerified) return true;
456
+ return false;
457
+ },
458
+
459
+ /**
460
+ * Check if two-factor authentication is pending verification
461
+ * @returns {boolean}
462
+ */
463
+ is2FaPending() {
464
+ return Boolean(req.session?.twoFactorPending);
465
+ },
466
+
467
+ /**
468
+ * Mark two-factor authentication as verified
469
+ * @param {boolean} [verified=true]
470
+ */
471
+ set2FaVerified(verified = true) {
472
+ if (req.session) {
473
+ req.session.twoFactorVerified = Boolean(verified);
474
+ if (verified) {
475
+ delete req.session.twoFactorPending;
476
+ delete req.session.twoFactorUserId;
477
+ }
478
+ }
479
+ },
480
+
481
+ /**
482
+ * Mark two-factor authentication as pending verification for a user
483
+ * @param {Object} [user] - User object or user ID
484
+ */
485
+ set2FaPending(user) {
486
+ if (req.session) {
487
+ req.session.twoFactorPending = true;
488
+ req.session.twoFactorVerified = false;
489
+ if (user?.id) {
490
+ req.session.twoFactorUserId = user.id;
491
+ } else if (typeof user === 'string' || typeof user === 'number') {
492
+ req.session.twoFactorUserId = user;
493
+ }
494
+ }
495
+ },
496
+
497
+ /**
498
+ * Clear two-factor authentication status from session
499
+ */
500
+ clear2Fa() {
501
+ if (req.session) {
502
+ delete req.session.twoFactorVerified;
503
+ delete req.session.twoFactorPending;
504
+ delete req.session.twoFactorUserId;
505
+ }
506
+ },
507
+
508
+ /**
509
+ * Verify submitted TOTP token or recovery code
510
+ * Automatically marks 2FA as verified upon success
511
+ * @param {string} tokenOrCode - Submitted 6-digit TOTP code or recovery code
512
+ * @param {Object|string} secretOrUser - Secret string or user object with totp_secret / recovery_codes
513
+ * @param {Object} [options]
514
+ * @returns {{ success: boolean, type?: 'totp'|'recovery_code', delta?: number, remainingHashedCodes?: string[], matchedIndex?: number }}
515
+ */
516
+ verify2Fa(tokenOrCode, secretOrUser, options = {}) {
517
+ if (!tokenOrCode) {
518
+ return { success: false };
519
+ }
520
+
521
+ let secret = null;
522
+ let recoveryCodes = null;
523
+
524
+ if (typeof secretOrUser === 'string') {
525
+ secret = secretOrUser;
526
+ } else if (secretOrUser && typeof secretOrUser === 'object') {
527
+ secret = secretOrUser.totp_secret || secretOrUser.totpSecret || secretOrUser.two_factor_secret || secretOrUser.twoFactorSecret;
528
+ recoveryCodes = secretOrUser.recovery_codes || secretOrUser.recoveryCodes || secretOrUser.two_factor_recovery_codes;
529
+ }
530
+
531
+ // Try TOTP code first
532
+ if (secret) {
533
+ const totpResult = self.totp.verifyTotp(tokenOrCode, secret, { ...options, detailed: true });
534
+ if (totpResult.valid) {
535
+ this.set2FaVerified(true);
536
+ return { success: true, type: 'totp', delta: totpResult.delta };
537
+ }
538
+ }
539
+
540
+ // Try recovery codes if provided
541
+ if (recoveryCodes && Array.isArray(recoveryCodes) && recoveryCodes.length > 0) {
542
+ const recResult = self.totp.verifyRecoveryCode(tokenOrCode, recoveryCodes);
543
+ if (recResult.valid) {
544
+ this.set2FaVerified(true);
545
+ return {
546
+ success: true,
547
+ type: 'recovery_code',
548
+ matchedIndex: recResult.matchedIndex,
549
+ remainingHashedCodes: recResult.remainingHashedCodes,
550
+ };
551
+ }
552
+ }
553
+
554
+ return { success: false };
555
+ },
556
+
380
557
  /**
381
558
  * Check if user can perform action (policy check)
382
559
  * @param {string} action - Action name
@@ -409,6 +586,91 @@ class AuthManager {
409
586
  authorize(action, policy = null, resource = null) {
410
587
  return self.policies.authorize(req.user, action, policy, resource);
411
588
  },
589
+
590
+ /**
591
+ * Request a magic link for passwordless login
592
+ * @param {string} identifier - User email or username
593
+ * @param {Object} [options]
594
+ * @returns {Promise<{ sent: boolean, expiresAt?: Date, token?: string, magicLinkUrl?: string, userFound: boolean }>}
595
+ */
596
+ requestMagicLink(identifier, options = {}) {
597
+ return self.requestMagicLink(identifier, options);
598
+ },
599
+
600
+ /**
601
+ * Verify magic link token
602
+ * @param {string} rawToken
603
+ * @param {Object} [options]
604
+ * @returns {Promise<{ ok: true, user: Object, userId: * }>}
605
+ */
606
+ verifyMagicLink(rawToken, options = {}) {
607
+ return self.verifyMagicLink(rawToken, options);
608
+ },
609
+
610
+ /**
611
+ * Verify magic link token and log user into current request session
612
+ * @param {string} rawToken
613
+ * @param {Object} [options]
614
+ * @returns {Promise<{ ok: true, user: Object, userId: *, accessToken?: string, refreshToken?: string }>}
615
+ */
616
+ async loginWithMagicLink(rawToken, options = {}) {
617
+ const result = await self.verifyMagicLink(rawToken, options);
618
+ await this.login(result.user, options.remember);
619
+
620
+ const response = { ...result };
621
+ if (options.jwt) {
622
+ response.accessToken = self.generateUserToken(result.user, options.jwtAccessOptions);
623
+ response.refreshToken = self.generateRefreshToken(result.user, options.jwtRefreshOptions);
624
+ }
625
+
626
+ return response;
627
+ },
628
+
629
+ /**
630
+ * List active sessions/devices for current user
631
+ * @returns {Promise<Array<Object>>}
632
+ */
633
+ sessions() {
634
+ if (!req.user?.id) return Promise.resolve([]);
635
+ return self.listSessions(req.user.id, req.sessionID);
636
+ },
637
+
638
+ /**
639
+ * Revoke a specific session
640
+ * @param {string} sessionId
641
+ * @returns {Promise<boolean>}
642
+ */
643
+ async revokeSession(sessionId) {
644
+ if (sessionId === req.sessionID) {
645
+ await this.logout();
646
+ return true;
647
+ }
648
+ return await self.revokeSession(sessionId, { userId: req.user?.id });
649
+ },
650
+
651
+ /**
652
+ * Revoke all other active sessions except current
653
+ * @returns {Promise<number>}
654
+ */
655
+ revokeOtherSessions() {
656
+ if (!req.user?.id) return Promise.resolve(0);
657
+ return self.revokeOtherSessions(req.user.id, req.sessionID);
658
+ },
659
+
660
+ /**
661
+ * Revoke all active sessions for user (force logout from all devices)
662
+ * @param {string|number} [userId]
663
+ * @returns {Promise<number>}
664
+ */
665
+ async revokeAllSessions(userId = null) {
666
+ const targetUserId = userId || req.user?.id;
667
+ if (!targetUserId) return 0;
668
+ const count = await self.revokeAllSessions(targetUserId);
669
+ if (targetUserId === req.user?.id) {
670
+ await this.logout();
671
+ }
672
+ return count;
673
+ },
412
674
  };
413
675
  }
414
676
 
@@ -666,6 +928,122 @@ class AuthManager {
666
928
  return { ok: true, userId: verified.userId };
667
929
  }
668
930
 
931
+ /**
932
+ * Request magic link passwordless login — safe against user enumeration
933
+ * @param {string} identifier - Email or username
934
+ * @param {Object} [options]
935
+ * @param {number} [options.tokenTtlMs=900000] - Default 15 minutes (900,000ms)
936
+ * @param {Function} [options.buildMagicLinkUrl] - (rawToken) => string
937
+ * @param {string} [options.path='/auth/magic-link/verify']
938
+ * @param {boolean} [options.hideUserNotFound=true]
939
+ * @returns {Promise<{ sent: boolean, expiresAt?: Date, token?: string, magicLinkUrl?: string, userFound: boolean }>}
940
+ */
941
+ async requestMagicLink(identifier, options = {}) {
942
+ if (!this.authTokens) {
943
+ throw new Error('authTokens adapter is required for magic link authentication');
944
+ }
945
+
946
+ const user = await this.resolveUserByIdentifier(identifier);
947
+ if (!user) {
948
+ return {
949
+ sent: options.hideUserNotFound !== false,
950
+ userFound: false,
951
+ };
952
+ }
953
+
954
+ const tokenTtlMs = options.tokenTtlMs ?? 15 * 60 * 1000;
955
+ const { rawToken, expiresAt } = await createAuthToken(
956
+ this.authTokens,
957
+ TOKEN_TYPES.MAGIC_LINK,
958
+ user.id,
959
+ tokenTtlMs
960
+ );
961
+
962
+ let baseUrl = String(options.baseUrl || this.config.baseUrl || process.env.BASE_URL || 'http://localhost:3000');
963
+ while (baseUrl.endsWith('/')) {
964
+ baseUrl = baseUrl.slice(0, -1);
965
+ }
966
+ const pathSegment = options.path || '/auth/magic-link/verify';
967
+ const cleanPath = pathSegment.startsWith('/') ? pathSegment : `/${pathSegment}`;
968
+ const sep = cleanPath.includes('?') ? '&' : '?';
969
+
970
+ const hasCustomUrl = typeof options.buildMagicLinkUrl === 'function' || Boolean(options.path) || Boolean(options.baseUrl);
971
+ const customMagicLinkUrl = typeof options.buildMagicLinkUrl === 'function'
972
+ ? options.buildMagicLinkUrl(rawToken)
973
+ : hasCustomUrl
974
+ ? `${baseUrl}${cleanPath}${sep}token=${encodeURIComponent(rawToken)}`
975
+ : null;
976
+
977
+ const defaultMagicLinkUrl = `${baseUrl}${cleanPath}${sep}token=${encodeURIComponent(rawToken)}`;
978
+
979
+ if (typeof this.notifications.magicLink === 'function') {
980
+ await this.notifications.magicLink(user, {
981
+ token: rawToken,
982
+ expiresAt,
983
+ magicLinkUrl: customMagicLinkUrl,
984
+ expiresMinutes: Math.round(tokenTtlMs / 60000),
985
+ });
986
+ }
987
+
988
+ this.emit('magicLink:requested', { userId: user.id, email: user.email, expiresAt });
989
+
990
+ return {
991
+ sent: true,
992
+ userFound: true,
993
+ expiresAt,
994
+ token: rawToken,
995
+ magicLinkUrl: customMagicLinkUrl || defaultMagicLinkUrl,
996
+ };
997
+ }
998
+
999
+ /**
1000
+ * Verify magic link token and resolve user
1001
+ * @param {string} rawToken
1002
+ * @param {Object} [options]
1003
+ * @param {boolean} [options.markEmailVerified=true]
1004
+ * @param {boolean} [options.consumeAllForUser=true]
1005
+ * @returns {Promise<{ ok: true, user: Object, userId: * }>}
1006
+ */
1007
+ async verifyMagicLink(rawToken, options = {}) {
1008
+ if (!this.authTokens) {
1009
+ throw new Error('authTokens adapter is required for magic link authentication');
1010
+ }
1011
+
1012
+ const verified = await verifyAuthToken(
1013
+ this.authTokens,
1014
+ TOKEN_TYPES.MAGIC_LINK,
1015
+ rawToken
1016
+ );
1017
+ if (!verified) {
1018
+ throw new AuthenticationError('Invalid or expired magic link', 'INVALID_MAGIC_LINK');
1019
+ }
1020
+
1021
+ const user = await this.findUserById(verified.userId);
1022
+ if (!user) {
1023
+ throw new AuthenticationError('User not found for magic link', 'USER_NOT_FOUND');
1024
+ }
1025
+
1026
+ await consumeAuthToken(this.authTokens, verified.tokenHash);
1027
+
1028
+ if (options.consumeAllForUser !== false) {
1029
+ await this.authTokens.deleteAllForUser(verified.userId, TOKEN_TYPES.MAGIC_LINK);
1030
+ }
1031
+
1032
+ // Auto mark email verified if user has not verified it yet
1033
+ if (options.markEmailVerified !== false && this.updateUser && this.verifiedField && !user[this.verifiedField]) {
1034
+ try {
1035
+ await this.updateUser(user.id, { [this.verifiedField]: new Date() });
1036
+ user[this.verifiedField] = new Date();
1037
+ } catch (e) {
1038
+ // Continue even if update fails
1039
+ }
1040
+ }
1041
+
1042
+ this.emit('magicLink:verified', { userId: user.id });
1043
+
1044
+ return { ok: true, user, userId: verified.userId };
1045
+ }
1046
+
669
1047
  /**
670
1048
  * Send welcome notification if configured
671
1049
  * @param {Object} user
@@ -801,6 +1179,102 @@ class AuthManager {
801
1179
  }
802
1180
  return this._authMiddleware.requireJwt(options);
803
1181
  }
1182
+
1183
+ /**
1184
+ * Get require2Fa guard middleware for Express routes
1185
+ * @param {Object} [options]
1186
+ * @returns {Function} Express middleware
1187
+ */
1188
+ require2Fa(options = {}) {
1189
+ if (!this._authMiddleware) {
1190
+ const { createAuthMiddleware } = require('./middleware');
1191
+ this._authMiddleware = createAuthMiddleware(this);
1192
+ }
1193
+ return this._authMiddleware.require2Fa(options);
1194
+ }
1195
+
1196
+ /**
1197
+ * Track session for a request
1198
+ * @param {string} sessionId
1199
+ * @param {string|number} userId
1200
+ * @param {Object} req
1201
+ */
1202
+ async trackSession(sessionId, userId, req) {
1203
+ if (this.sessionManager) {
1204
+ return await this.sessionManager.track(sessionId, userId, req);
1205
+ }
1206
+ return null;
1207
+ }
1208
+
1209
+ /**
1210
+ * Touch session to update activity timestamp
1211
+ * @param {string} sessionId
1212
+ */
1213
+ async touchSession(sessionId) {
1214
+ if (this.sessionManager) {
1215
+ return await this.sessionManager.touch(sessionId);
1216
+ }
1217
+ return false;
1218
+ }
1219
+
1220
+ /**
1221
+ * List active sessions for user
1222
+ * @param {string|number} userId
1223
+ * @param {string} [currentSessionId]
1224
+ */
1225
+ async listSessions(userId, currentSessionId = null) {
1226
+ if (this.sessionManager) {
1227
+ return await this.sessionManager.listSessions(userId, currentSessionId);
1228
+ }
1229
+ return [];
1230
+ }
1231
+
1232
+ /**
1233
+ * Check if session is valid in store
1234
+ * @param {string} sessionId
1235
+ * @param {string|number} [userId]
1236
+ */
1237
+ async isSessionValid(sessionId, userId = null) {
1238
+ if (this.sessionManager) {
1239
+ return await this.sessionManager.isSessionValid(sessionId, userId);
1240
+ }
1241
+ return true;
1242
+ }
1243
+
1244
+ /**
1245
+ * Revoke a specific session
1246
+ * @param {string} sessionId
1247
+ * @param {Object} [options]
1248
+ */
1249
+ async revokeSession(sessionId, options = {}) {
1250
+ if (this.sessionManager) {
1251
+ return await this.sessionManager.revokeSession(sessionId, options);
1252
+ }
1253
+ return false;
1254
+ }
1255
+
1256
+ /**
1257
+ * Revoke all other sessions for user
1258
+ * @param {string|number} userId
1259
+ * @param {string} currentSessionId
1260
+ */
1261
+ async revokeOtherSessions(userId, currentSessionId) {
1262
+ if (this.sessionManager) {
1263
+ return await this.sessionManager.revokeOtherSessions(userId, currentSessionId);
1264
+ }
1265
+ return 0;
1266
+ }
1267
+
1268
+ /**
1269
+ * Revoke all sessions for user
1270
+ * @param {string|number} userId
1271
+ */
1272
+ async revokeAllSessions(userId) {
1273
+ if (this.sessionManager) {
1274
+ return await this.sessionManager.revokeAllSessions(userId);
1275
+ }
1276
+ return 0;
1277
+ }
804
1278
  }
805
1279
 
806
1280
  module.exports = {