@juspay/neurolink 9.31.2 → 9.32.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 (161) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/auth/AuthProviderFactory.d.ts +71 -0
  3. package/dist/auth/AuthProviderFactory.js +111 -0
  4. package/dist/auth/AuthProviderRegistry.d.ts +33 -0
  5. package/dist/auth/AuthProviderRegistry.js +190 -0
  6. package/dist/auth/RequestContext.d.ts +23 -0
  7. package/dist/auth/RequestContext.js +78 -0
  8. package/dist/auth/authContext.d.ts +198 -0
  9. package/dist/auth/authContext.js +314 -0
  10. package/dist/auth/errors.d.ts +63 -0
  11. package/dist/auth/errors.js +39 -0
  12. package/dist/auth/index.d.ts +20 -8
  13. package/dist/auth/index.js +35 -7
  14. package/dist/auth/middleware/AuthMiddleware.d.ts +181 -0
  15. package/dist/auth/middleware/AuthMiddleware.js +519 -0
  16. package/dist/auth/middleware/rateLimitByUser.d.ts +282 -0
  17. package/dist/auth/middleware/rateLimitByUser.js +554 -0
  18. package/dist/auth/providers/BaseAuthProvider.d.ts +259 -0
  19. package/dist/auth/providers/BaseAuthProvider.js +723 -0
  20. package/dist/auth/providers/CognitoProvider.d.ts +61 -0
  21. package/dist/auth/providers/CognitoProvider.js +304 -0
  22. package/dist/auth/providers/KeycloakProvider.d.ts +61 -0
  23. package/dist/auth/providers/KeycloakProvider.js +393 -0
  24. package/dist/auth/providers/auth0.d.ts +59 -0
  25. package/dist/auth/providers/auth0.js +274 -0
  26. package/dist/auth/providers/betterAuth.d.ts +51 -0
  27. package/dist/auth/providers/betterAuth.js +182 -0
  28. package/dist/auth/providers/clerk.d.ts +65 -0
  29. package/dist/auth/providers/clerk.js +317 -0
  30. package/dist/auth/providers/custom.d.ts +64 -0
  31. package/dist/auth/providers/custom.js +112 -0
  32. package/dist/auth/providers/firebase.d.ts +63 -0
  33. package/dist/auth/providers/firebase.js +226 -0
  34. package/dist/auth/providers/jwt.d.ts +68 -0
  35. package/dist/auth/providers/jwt.js +212 -0
  36. package/dist/auth/providers/oauth2.d.ts +73 -0
  37. package/dist/auth/providers/oauth2.js +303 -0
  38. package/dist/auth/providers/supabase.d.ts +63 -0
  39. package/dist/auth/providers/supabase.js +259 -0
  40. package/dist/auth/providers/workos.d.ts +61 -0
  41. package/dist/auth/providers/workos.js +284 -0
  42. package/dist/auth/serverBridge.d.ts +14 -0
  43. package/dist/auth/serverBridge.js +25 -0
  44. package/dist/auth/sessionManager.d.ts +142 -0
  45. package/dist/auth/sessionManager.js +437 -0
  46. package/dist/cli/commands/authProviders.d.ts +43 -0
  47. package/dist/cli/commands/authProviders.js +399 -0
  48. package/dist/cli/factories/authCommandFactory.d.ts +23 -5
  49. package/dist/cli/factories/authCommandFactory.js +108 -5
  50. package/dist/cli/parser.js +1 -1
  51. package/dist/client/auth/AuthProviderFactory.js +111 -0
  52. package/dist/client/auth/AuthProviderRegistry.js +190 -0
  53. package/dist/client/auth/RequestContext.js +78 -0
  54. package/dist/client/auth/accountPool.js +178 -0
  55. package/dist/client/auth/authContext.js +314 -0
  56. package/dist/client/auth/errors.js +39 -0
  57. package/dist/client/auth/index.js +61 -0
  58. package/dist/client/auth/middleware/AuthMiddleware.js +519 -0
  59. package/dist/client/auth/middleware/rateLimitByUser.js +554 -0
  60. package/dist/client/auth/providers/BaseAuthProvider.js +723 -0
  61. package/dist/client/auth/providers/CognitoProvider.js +304 -0
  62. package/dist/client/auth/providers/KeycloakProvider.js +393 -0
  63. package/dist/client/auth/providers/auth0.js +274 -0
  64. package/dist/client/auth/providers/betterAuth.js +182 -0
  65. package/dist/client/auth/providers/clerk.js +317 -0
  66. package/dist/client/auth/providers/custom.js +112 -0
  67. package/dist/client/auth/providers/firebase.js +226 -0
  68. package/dist/client/auth/providers/jwt.js +212 -0
  69. package/dist/client/auth/providers/oauth2.js +303 -0
  70. package/dist/client/auth/providers/supabase.js +259 -0
  71. package/dist/client/auth/providers/workos.js +284 -0
  72. package/dist/client/auth/serverBridge.js +25 -0
  73. package/dist/client/auth/sessionManager.js +437 -0
  74. package/dist/client/core/infrastructure/baseRegistry.js +5 -1
  75. package/dist/client/index.js +25 -0
  76. package/dist/client/mcp/toolRegistry.js +11 -1
  77. package/dist/client/neurolink.js +218 -0
  78. package/dist/client/rag/ChunkerRegistry.js +2 -2
  79. package/dist/client/rag/metadata/MetadataExtractorRegistry.js +2 -2
  80. package/dist/client/rag/reranker/RerankerRegistry.js +2 -2
  81. package/dist/client/server/routes/agentRoutes.js +20 -2
  82. package/dist/client/types/authTypes.js +2 -1
  83. package/dist/core/infrastructure/baseRegistry.d.ts +3 -1
  84. package/dist/core/infrastructure/baseRegistry.js +5 -1
  85. package/dist/index.d.ts +1 -0
  86. package/dist/index.js +25 -0
  87. package/dist/lib/auth/AuthProviderFactory.d.ts +71 -0
  88. package/dist/lib/auth/AuthProviderFactory.js +112 -0
  89. package/dist/lib/auth/AuthProviderRegistry.d.ts +33 -0
  90. package/dist/lib/auth/AuthProviderRegistry.js +191 -0
  91. package/dist/lib/auth/RequestContext.d.ts +23 -0
  92. package/dist/lib/auth/RequestContext.js +79 -0
  93. package/dist/lib/auth/authContext.d.ts +198 -0
  94. package/dist/lib/auth/authContext.js +315 -0
  95. package/dist/lib/auth/errors.d.ts +63 -0
  96. package/dist/lib/auth/errors.js +40 -0
  97. package/dist/lib/auth/index.d.ts +20 -8
  98. package/dist/lib/auth/index.js +35 -7
  99. package/dist/lib/auth/middleware/AuthMiddleware.d.ts +181 -0
  100. package/dist/lib/auth/middleware/AuthMiddleware.js +520 -0
  101. package/dist/lib/auth/middleware/rateLimitByUser.d.ts +282 -0
  102. package/dist/lib/auth/middleware/rateLimitByUser.js +555 -0
  103. package/dist/lib/auth/providers/BaseAuthProvider.d.ts +259 -0
  104. package/dist/lib/auth/providers/BaseAuthProvider.js +724 -0
  105. package/dist/lib/auth/providers/CognitoProvider.d.ts +61 -0
  106. package/dist/lib/auth/providers/CognitoProvider.js +305 -0
  107. package/dist/lib/auth/providers/KeycloakProvider.d.ts +61 -0
  108. package/dist/lib/auth/providers/KeycloakProvider.js +394 -0
  109. package/dist/lib/auth/providers/auth0.d.ts +59 -0
  110. package/dist/lib/auth/providers/auth0.js +275 -0
  111. package/dist/lib/auth/providers/betterAuth.d.ts +51 -0
  112. package/dist/lib/auth/providers/betterAuth.js +183 -0
  113. package/dist/lib/auth/providers/clerk.d.ts +65 -0
  114. package/dist/lib/auth/providers/clerk.js +318 -0
  115. package/dist/lib/auth/providers/custom.d.ts +64 -0
  116. package/dist/lib/auth/providers/custom.js +113 -0
  117. package/dist/lib/auth/providers/firebase.d.ts +63 -0
  118. package/dist/lib/auth/providers/firebase.js +227 -0
  119. package/dist/lib/auth/providers/jwt.d.ts +68 -0
  120. package/dist/lib/auth/providers/jwt.js +213 -0
  121. package/dist/lib/auth/providers/oauth2.d.ts +73 -0
  122. package/dist/lib/auth/providers/oauth2.js +304 -0
  123. package/dist/lib/auth/providers/supabase.d.ts +63 -0
  124. package/dist/lib/auth/providers/supabase.js +260 -0
  125. package/dist/lib/auth/providers/workos.d.ts +61 -0
  126. package/dist/lib/auth/providers/workos.js +285 -0
  127. package/dist/lib/auth/serverBridge.d.ts +14 -0
  128. package/dist/lib/auth/serverBridge.js +26 -0
  129. package/dist/lib/auth/sessionManager.d.ts +142 -0
  130. package/dist/lib/auth/sessionManager.js +438 -0
  131. package/dist/lib/core/infrastructure/baseRegistry.d.ts +3 -1
  132. package/dist/lib/core/infrastructure/baseRegistry.js +5 -1
  133. package/dist/lib/index.d.ts +1 -0
  134. package/dist/lib/index.js +25 -0
  135. package/dist/lib/mcp/toolRegistry.js +11 -1
  136. package/dist/lib/neurolink.d.ts +42 -1
  137. package/dist/lib/neurolink.js +218 -0
  138. package/dist/lib/rag/ChunkerRegistry.js +2 -2
  139. package/dist/lib/rag/metadata/MetadataExtractorRegistry.js +2 -2
  140. package/dist/lib/rag/reranker/RerankerRegistry.js +2 -2
  141. package/dist/lib/server/routes/agentRoutes.js +20 -2
  142. package/dist/lib/types/authTypes.d.ts +937 -1
  143. package/dist/lib/types/authTypes.js +2 -1
  144. package/dist/lib/types/configTypes.d.ts +46 -0
  145. package/dist/lib/types/generateTypes.d.ts +6 -0
  146. package/dist/lib/types/index.d.ts +1 -0
  147. package/dist/lib/types/streamTypes.d.ts +6 -0
  148. package/dist/mcp/toolRegistry.js +11 -1
  149. package/dist/neurolink.d.ts +42 -1
  150. package/dist/neurolink.js +218 -0
  151. package/dist/rag/ChunkerRegistry.js +2 -2
  152. package/dist/rag/metadata/MetadataExtractorRegistry.js +2 -2
  153. package/dist/rag/reranker/RerankerRegistry.js +2 -2
  154. package/dist/server/routes/agentRoutes.js +20 -2
  155. package/dist/types/authTypes.d.ts +937 -1
  156. package/dist/types/authTypes.js +2 -1
  157. package/dist/types/configTypes.d.ts +46 -0
  158. package/dist/types/generateTypes.d.ts +6 -0
  159. package/dist/types/index.d.ts +1 -0
  160. package/dist/types/streamTypes.d.ts +6 -0
  161. package/package.json +2 -1
@@ -0,0 +1,724 @@
1
+ /**
2
+ * BaseAuthProvider - Abstract base class for authentication providers
3
+ *
4
+ * Provides common functionality for all auth providers including:
5
+ * - Token extraction (header, cookie, query param, custom function)
6
+ * - Session management (create, validate, refresh, revoke)
7
+ * - RBAC authorization (roles, permissions, wildcards, hierarchy)
8
+ * - Token validation utilities (JWT parsing, expiry checks)
9
+ * - Event emission for auth lifecycle hooks
10
+ * - Error handling via unified AuthError factory
11
+ */
12
+ import { randomUUID } from "crypto";
13
+ import { EventEmitter } from "events";
14
+ import { logger } from "../../utils/logger.js";
15
+ import { AuthError } from "../errors.js";
16
+ // =============================================================================
17
+ // BACKWARD-COMPAT RE-EXPORTS
18
+ // =============================================================================
19
+ /**
20
+ * @deprecated Use `AuthError` from `../errors.js` instead.
21
+ * Kept for backward compatibility with CognitoProvider / KeycloakProvider.
22
+ */
23
+ export const AuthProviderError = AuthError;
24
+ // =============================================================================
25
+ // IN-MEMORY SESSION STORAGE
26
+ // =============================================================================
27
+ /**
28
+ * Default in-memory session storage
29
+ */
30
+ export class InMemorySessionStorage {
31
+ sessions = new Map();
32
+ userSessions = new Map();
33
+ async get(sessionId) {
34
+ return this.sessions.get(sessionId) ?? null;
35
+ }
36
+ async save(session) {
37
+ this.sessions.set(session.id, session);
38
+ // Track sessions by user
39
+ const userSessionSet = this.userSessions.get(session.user.id) ?? new Set();
40
+ userSessionSet.add(session.id);
41
+ this.userSessions.set(session.user.id, userSessionSet);
42
+ }
43
+ async delete(sessionId) {
44
+ const session = this.sessions.get(sessionId);
45
+ if (session) {
46
+ this.sessions.delete(sessionId);
47
+ // Remove from user tracking
48
+ const userSessionSet = this.userSessions.get(session.user.id);
49
+ if (userSessionSet) {
50
+ userSessionSet.delete(sessionId);
51
+ if (userSessionSet.size === 0) {
52
+ this.userSessions.delete(session.user.id);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ async deleteAllForUser(userId) {
58
+ const userSessionSet = this.userSessions.get(userId);
59
+ if (userSessionSet) {
60
+ for (const sessionId of userSessionSet) {
61
+ this.sessions.delete(sessionId);
62
+ }
63
+ this.userSessions.delete(userId);
64
+ }
65
+ }
66
+ async getForUser(userId) {
67
+ const userSessionSet = this.userSessions.get(userId);
68
+ if (!userSessionSet) {
69
+ return [];
70
+ }
71
+ const now = Date.now();
72
+ const sessions = [];
73
+ const expiredIds = [];
74
+ for (const sessionId of userSessionSet) {
75
+ const session = this.sessions.get(sessionId);
76
+ if (!session) {
77
+ continue;
78
+ }
79
+ // Filter out expired and revoked sessions so maxSessionsPerUser counts are accurate
80
+ if (session.expiresAt && session.expiresAt.getTime() < now) {
81
+ expiredIds.push(sessionId);
82
+ continue;
83
+ }
84
+ if (!session.isValid) {
85
+ expiredIds.push(sessionId);
86
+ continue;
87
+ }
88
+ sessions.push(session);
89
+ }
90
+ // Clean up expired sessions lazily
91
+ for (const id of expiredIds) {
92
+ this.sessions.delete(id);
93
+ userSessionSet.delete(id);
94
+ }
95
+ if (userSessionSet.size === 0) {
96
+ this.userSessions.delete(userId);
97
+ }
98
+ return sessions;
99
+ }
100
+ async exists(sessionId) {
101
+ return this.sessions.has(sessionId);
102
+ }
103
+ async touch(sessionId) {
104
+ const session = this.sessions.get(sessionId);
105
+ if (session) {
106
+ session.lastActivityAt = new Date();
107
+ this.sessions.set(sessionId, session);
108
+ }
109
+ }
110
+ async clear() {
111
+ this.sessions.clear();
112
+ this.userSessions.clear();
113
+ }
114
+ /**
115
+ * Get session count (for testing/monitoring)
116
+ */
117
+ get size() {
118
+ return this.sessions.size;
119
+ }
120
+ }
121
+ // =============================================================================
122
+ // BASE PROVIDER IMPLEMENTATION
123
+ // =============================================================================
124
+ /**
125
+ * BaseAuthProvider - Abstract base class for all auth providers
126
+ *
127
+ * Subclasses must implement:
128
+ * - authenticateToken() - Validate and decode JWT/access tokens
129
+ *
130
+ * Optionally override:
131
+ * - getUser() - Fetch user by ID from provider
132
+ * - updateUserRoles() - Update user roles in provider
133
+ * - updateUserPermissions() - Update user permissions in provider
134
+ * - dispose() - Clean up resources
135
+ */
136
+ export class BaseAuthProvider {
137
+ config;
138
+ sessionStorage;
139
+ sessionConfig;
140
+ rbacConfig;
141
+ emitter = new EventEmitter();
142
+ constructor(config) {
143
+ // Deep-merge tokenExtraction: preserve header defaults when partial config given
144
+ const defaultTokenExtraction = {
145
+ fromHeader: { name: "Authorization", scheme: "Bearer" },
146
+ };
147
+ this.config = {
148
+ required: true,
149
+ ...config,
150
+ tokenExtraction: {
151
+ ...defaultTokenExtraction,
152
+ ...config.tokenExtraction,
153
+ },
154
+ };
155
+ // Initialize session configuration
156
+ this.sessionConfig = {
157
+ storage: "memory",
158
+ duration: 3600, // 1 hour default
159
+ autoRefresh: true,
160
+ refreshThreshold: 300, // 5 minutes
161
+ allowMultipleSessions: true,
162
+ maxSessionsPerUser: 10,
163
+ prefix: "neurolink:session:",
164
+ ...config.session,
165
+ };
166
+ // Initialize RBAC configuration
167
+ this.rbacConfig = {
168
+ enabled: true,
169
+ defaultRoles: [],
170
+ roleHierarchy: {},
171
+ rolePermissions: {},
172
+ superAdminRoles: ["super_admin", "root"],
173
+ ...config.rbac,
174
+ };
175
+ // Initialize session storage
176
+ this.sessionStorage =
177
+ config.session?.customStorage ?? new InMemorySessionStorage();
178
+ logger.debug(`[BaseAuthProvider] Initialized`);
179
+ }
180
+ // ===========================================================================
181
+ // TOKEN EXTRACTION
182
+ // ===========================================================================
183
+ /**
184
+ * Extract token using configured strategy
185
+ *
186
+ * Attempts extraction in order:
187
+ * 1. Header (Authorization: Bearer <token> by default)
188
+ * 2. Cookie
189
+ * 3. Query parameter
190
+ * 4. Custom function
191
+ *
192
+ * @param context - Request context containing headers, cookies, etc.
193
+ * @returns Extracted token or null if not found
194
+ */
195
+ async extractToken(context) {
196
+ const strategy = this.config.tokenExtraction;
197
+ // Try header extraction (case-insensitive header lookup)
198
+ if (strategy?.fromHeader) {
199
+ const headerName = strategy.fromHeader.name.toLowerCase();
200
+ // Find header value with case-insensitive lookup
201
+ let headerValue;
202
+ for (const [key, value] of Object.entries(context.headers)) {
203
+ if (key.toLowerCase() === headerName && typeof value === "string") {
204
+ headerValue = value;
205
+ break;
206
+ }
207
+ }
208
+ if (typeof headerValue === "string") {
209
+ if (strategy.fromHeader.scheme) {
210
+ const prefix = `${strategy.fromHeader.scheme} `;
211
+ if (headerValue.startsWith(prefix)) {
212
+ return headerValue.slice(prefix.length);
213
+ }
214
+ }
215
+ else {
216
+ return headerValue;
217
+ }
218
+ }
219
+ }
220
+ // Try cookie extraction
221
+ if (strategy?.fromCookie && context.cookies) {
222
+ const cookieValue = context.cookies[strategy.fromCookie.name];
223
+ if (cookieValue) {
224
+ return cookieValue;
225
+ }
226
+ }
227
+ // Try query parameter extraction
228
+ if (strategy?.fromQuery && context.path) {
229
+ try {
230
+ const url = new URL(context.path, "http://localhost");
231
+ const queryValue = url.searchParams.get(strategy.fromQuery.name);
232
+ if (queryValue) {
233
+ return queryValue;
234
+ }
235
+ }
236
+ catch {
237
+ // Invalid URL, skip query extraction
238
+ }
239
+ }
240
+ // Try custom extraction (may be sync or async)
241
+ if (strategy?.custom) {
242
+ return await Promise.resolve(strategy.custom(context));
243
+ }
244
+ return null;
245
+ }
246
+ // ===========================================================================
247
+ // SESSION MANAGEMENT
248
+ // ===========================================================================
249
+ /**
250
+ * Create a new session for an authenticated user
251
+ *
252
+ * Session duration and metadata are derived from `this.sessionConfig` and
253
+ * the optional `context`. This matches the `AuthSessionManager` type
254
+ * signature: `createSession(user, context?)`.
255
+ */
256
+ async createSession(user, context) {
257
+ const now = new Date();
258
+ const duration = this.sessionConfig.duration ?? 3600;
259
+ // Check session limits
260
+ if (!this.sessionConfig.allowMultipleSessions) {
261
+ await this.revokeAllSessions(user.id);
262
+ }
263
+ else if (this.sessionConfig.maxSessionsPerUser) {
264
+ const existingSessions = await this.sessionStorage.getForUser(user.id);
265
+ if (existingSessions.length >= this.sessionConfig.maxSessionsPerUser) {
266
+ // Remove oldest session
267
+ const oldestSession = existingSessions.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())[0];
268
+ if (oldestSession) {
269
+ await this.sessionStorage.delete(oldestSession.id);
270
+ }
271
+ }
272
+ }
273
+ const session = {
274
+ id: randomUUID(),
275
+ user,
276
+ accessToken: randomUUID(), // Internal session token
277
+ isValid: true,
278
+ expiresAt: new Date(now.getTime() + duration * 1000),
279
+ createdAt: now,
280
+ lastActivityAt: now,
281
+ ipAddress: context?.ip ?? context?.ipAddress,
282
+ userAgent: context?.userAgent,
283
+ };
284
+ await this.sessionStorage.save(session);
285
+ logger.debug(`[BaseAuthProvider] Created session ${session.id} for user ${user.id}`);
286
+ return session;
287
+ }
288
+ /**
289
+ * Validate an existing session
290
+ */
291
+ async validateSession(sessionId) {
292
+ const session = await this.sessionStorage.get(sessionId);
293
+ if (!session) {
294
+ return {
295
+ valid: false,
296
+ error: "Session not found",
297
+ errorCode: "AUTH-010",
298
+ };
299
+ }
300
+ // Check expiration
301
+ if (session.expiresAt && session.expiresAt.getTime() < Date.now()) {
302
+ await this.sessionStorage.delete(sessionId);
303
+ return {
304
+ valid: false,
305
+ error: "Session expired",
306
+ errorCode: "AUTH-011",
307
+ };
308
+ }
309
+ // Check if revoked
310
+ if (!session.isValid) {
311
+ return {
312
+ valid: false,
313
+ error: "Session revoked",
314
+ errorCode: "AUTH-012",
315
+ };
316
+ }
317
+ // Auto-refresh if near expiration
318
+ let refreshed = false;
319
+ if (this.sessionConfig.autoRefresh &&
320
+ this.sessionConfig.refreshThreshold &&
321
+ session.expiresAt &&
322
+ session.expiresAt.getTime() - Date.now() <
323
+ this.sessionConfig.refreshThreshold * 1000) {
324
+ const refreshedSession = await this.refreshSession(sessionId);
325
+ refreshed = true;
326
+ return {
327
+ valid: true,
328
+ session: refreshedSession ?? undefined,
329
+ refreshed,
330
+ };
331
+ }
332
+ // Update last activity
333
+ await this.sessionStorage.touch(sessionId);
334
+ return {
335
+ valid: true,
336
+ session,
337
+ refreshed,
338
+ };
339
+ }
340
+ /**
341
+ * Refresh a session (extend expiration)
342
+ */
343
+ async refreshSession(sessionId) {
344
+ const session = await this.sessionStorage.get(sessionId);
345
+ if (!session) {
346
+ throw AuthError.create("SESSION_NOT_FOUND", `Session not found: ${sessionId}`, { details: { sessionId } });
347
+ }
348
+ // Don't refresh revoked sessions
349
+ if (!session.isValid) {
350
+ throw AuthError.create("SESSION_REVOKED", `Cannot refresh revoked session: ${sessionId}`, { details: { sessionId } });
351
+ }
352
+ // Don't refresh expired sessions
353
+ if (session.expiresAt && session.expiresAt.getTime() < Date.now()) {
354
+ await this.sessionStorage.delete(sessionId);
355
+ throw AuthError.create("SESSION_EXPIRED", `Cannot refresh expired session: ${sessionId}`, { details: { sessionId } });
356
+ }
357
+ const duration = this.sessionConfig.duration ?? 3600;
358
+ session.expiresAt = new Date(Date.now() + duration * 1000);
359
+ session.lastActivityAt = new Date();
360
+ await this.sessionStorage.save(session);
361
+ logger.debug(`[BaseAuthProvider] Refreshed session ${sessionId}`);
362
+ return session;
363
+ }
364
+ /**
365
+ * Revoke a session
366
+ *
367
+ * Marks the session as invalid rather than deleting it immediately.
368
+ * This keeps a tombstone so that "revoked" is distinguishable from
369
+ * "not found" during subsequent validation attempts.
370
+ */
371
+ async revokeSession(sessionId) {
372
+ const session = await this.sessionStorage.get(sessionId);
373
+ if (session) {
374
+ session.isValid = false;
375
+ await this.sessionStorage.save(session);
376
+ logger.debug(`[BaseAuthProvider] Revoked session ${sessionId}`);
377
+ }
378
+ }
379
+ /**
380
+ * Revoke all sessions for a user
381
+ */
382
+ async revokeAllSessions(userId) {
383
+ await this.sessionStorage.deleteAllForUser(userId);
384
+ logger.debug(`[BaseAuthProvider] Revoked all sessions for user ${userId}`);
385
+ }
386
+ // ===========================================================================
387
+ // AUTHORIZATION (RBAC)
388
+ // ===========================================================================
389
+ /**
390
+ * Check if a user is authorized for specific roles/permissions
391
+ */
392
+ async authorize(user, options) {
393
+ // Check if RBAC is enabled
394
+ if (!this.rbacConfig.enabled) {
395
+ return { authorized: true, user };
396
+ }
397
+ // Super admin bypass
398
+ if (this.isSuperAdmin(user)) {
399
+ return { authorized: true, user };
400
+ }
401
+ const result = {
402
+ authorized: true,
403
+ user,
404
+ requiredRoles: options.roles,
405
+ requiredPermissions: options.permissions,
406
+ missingRoles: [],
407
+ missingPermissions: [],
408
+ };
409
+ // Check roles
410
+ if (options.roles && options.roles.length > 0) {
411
+ const userRoles = this.getEffectiveRoles(user);
412
+ const missingRoles = options.roles.filter((r) => !userRoles.has(r));
413
+ if (options.requireAllRoles) {
414
+ // All roles required
415
+ if (missingRoles.length > 0) {
416
+ result.authorized = false;
417
+ result.missingRoles = missingRoles;
418
+ result.reason = `Missing required roles: ${missingRoles.join(", ")}`;
419
+ }
420
+ }
421
+ else {
422
+ // Any role is sufficient
423
+ const hasAnyRole = options.roles.some((r) => userRoles.has(r));
424
+ if (!hasAnyRole) {
425
+ result.authorized = false;
426
+ result.missingRoles = options.roles;
427
+ result.reason = `Missing any of required roles: ${options.roles.join(", ")}`;
428
+ }
429
+ }
430
+ }
431
+ // Check permissions (all required)
432
+ if (options.permissions && options.permissions.length > 0) {
433
+ const userPermissions = this.getEffectivePermissions(user);
434
+ const missingPermissions = options.permissions.filter((p) => !this.hasPermission(userPermissions, p));
435
+ if (missingPermissions.length > 0) {
436
+ result.authorized = false;
437
+ result.missingPermissions = missingPermissions;
438
+ result.reason = result.reason
439
+ ? `${result.reason}; Missing permissions: ${missingPermissions.join(", ")}`
440
+ : `Missing required permissions: ${missingPermissions.join(", ")}`;
441
+ }
442
+ }
443
+ return result;
444
+ }
445
+ /**
446
+ * Check if user is a super admin
447
+ */
448
+ isSuperAdmin(user) {
449
+ const superAdminRoles = this.rbacConfig.superAdminRoles ?? [];
450
+ return user.roles.some((r) => superAdminRoles.includes(r));
451
+ }
452
+ /**
453
+ * Get effective roles including inherited roles from hierarchy (transitive)
454
+ */
455
+ getEffectiveRoles(user) {
456
+ const effectiveRoles = new Set(user.roles);
457
+ // Transitive closure: keep expanding until no new roles are added
458
+ const hierarchy = this.rbacConfig.roleHierarchy ?? {};
459
+ let added = true;
460
+ while (added) {
461
+ added = false;
462
+ for (const role of effectiveRoles) {
463
+ const inheritedRoles = hierarchy[role] ?? [];
464
+ for (const inherited of inheritedRoles) {
465
+ if (!effectiveRoles.has(inherited)) {
466
+ effectiveRoles.add(inherited);
467
+ added = true;
468
+ }
469
+ }
470
+ }
471
+ }
472
+ return effectiveRoles;
473
+ }
474
+ /**
475
+ * Get effective permissions including role-based permissions
476
+ */
477
+ getEffectivePermissions(user) {
478
+ const effectivePermissions = new Set(user.permissions);
479
+ // Add permissions from roles
480
+ const rolePermissions = this.rbacConfig.rolePermissions ?? {};
481
+ const effectiveRoles = this.getEffectiveRoles(user);
482
+ for (const role of effectiveRoles) {
483
+ const permissions = rolePermissions[role] ?? [];
484
+ for (const permission of permissions) {
485
+ effectivePermissions.add(permission);
486
+ }
487
+ }
488
+ return effectivePermissions;
489
+ }
490
+ /**
491
+ * Check if a permission set grants a given permission.
492
+ * Supports exact match, global wildcard ("*"), and hierarchical wildcards
493
+ * (e.g. "tools:*" grants "tools:execute").
494
+ */
495
+ hasPermission(permissions, required) {
496
+ if (permissions.has(required)) {
497
+ return true;
498
+ }
499
+ if (permissions.has("*")) {
500
+ return true;
501
+ }
502
+ const parts = required.split(":");
503
+ for (let i = parts.length - 1; i > 0; i--) {
504
+ const wildcard = [...parts.slice(0, i), "*"].join(":");
505
+ if (permissions.has(wildcard)) {
506
+ return true;
507
+ }
508
+ }
509
+ return false;
510
+ }
511
+ // ===========================================================================
512
+ // UTILITY METHODS
513
+ // ===========================================================================
514
+ /**
515
+ * Parse JWT token (without validation)
516
+ */
517
+ parseJWT(token) {
518
+ try {
519
+ const parts = token.split(".");
520
+ if (parts.length !== 3) {
521
+ return null;
522
+ }
523
+ const payload = parts[1];
524
+ const decoded = Buffer.from(payload, "base64url").toString("utf-8");
525
+ return JSON.parse(decoded);
526
+ }
527
+ catch {
528
+ return null;
529
+ }
530
+ }
531
+ /**
532
+ * Check if token is expired
533
+ */
534
+ isTokenExpired(claims, clockTolerance = 0) {
535
+ if (!claims.exp) {
536
+ return false; // No expiration claim
537
+ }
538
+ const now = Math.floor(Date.now() / 1000);
539
+ return claims.exp + clockTolerance < now;
540
+ }
541
+ /**
542
+ * Check if token is not yet valid
543
+ */
544
+ isTokenNotYetValid(claims, clockTolerance = 0) {
545
+ if (!claims.nbf) {
546
+ return false; // No nbf claim
547
+ }
548
+ const now = Math.floor(Date.now() / 1000);
549
+ return claims.nbf - clockTolerance > now;
550
+ }
551
+ /**
552
+ * Extract user from token claims
553
+ */
554
+ extractUserFromClaims(claims, options) {
555
+ const rolesKey = options?.rolesClaimKey ?? "roles";
556
+ const permissionsKey = options?.permissionsClaimKey ?? "permissions";
557
+ const idKey = options?.idClaimKey ?? "sub";
558
+ const roles = Array.isArray(claims[rolesKey])
559
+ ? claims[rolesKey]
560
+ : (this.rbacConfig.defaultRoles ?? []);
561
+ const permissions = Array.isArray(claims[permissionsKey])
562
+ ? claims[permissionsKey]
563
+ : [];
564
+ return {
565
+ id: claims[idKey] ?? "",
566
+ email: claims.email,
567
+ name: claims.name,
568
+ picture: claims.picture,
569
+ roles,
570
+ permissions,
571
+ emailVerified: claims.email_verified,
572
+ providerData: claims,
573
+ };
574
+ }
575
+ // ===========================================================================
576
+ // OPTIONAL METHODS (can be overridden by subclasses)
577
+ // ===========================================================================
578
+ /**
579
+ * Get user by ID
580
+ * Override in subclass if provider supports user lookup
581
+ */
582
+ async getUser(_userId) {
583
+ logger.debug(`[BaseAuthProvider] getUser not implemented for ${this.type}`);
584
+ return null;
585
+ }
586
+ /**
587
+ * Update user roles
588
+ * Override in subclass if provider supports role updates.
589
+ * Returns the user with updated roles.
590
+ */
591
+ async updateUserRoles(_userId, _roles) {
592
+ throw AuthError.create("PROVIDER_ERROR", `updateUserRoles not supported by ${this.type} provider`);
593
+ }
594
+ /**
595
+ * Update user permissions
596
+ * Override in subclass if provider supports permission updates.
597
+ * Returns the user with updated permissions.
598
+ */
599
+ async updateUserPermissions(_userId, _permissions) {
600
+ throw AuthError.create("PROVIDER_ERROR", `updateUserPermissions not supported by ${this.type} provider`);
601
+ }
602
+ /**
603
+ * Clean up resources
604
+ */
605
+ async dispose() {
606
+ await this.sessionStorage.clear();
607
+ logger.debug(`[BaseAuthProvider] Disposed ${this.type} provider`);
608
+ }
609
+ // ===========================================================================
610
+ // METHODS FROM MastraAuthProvider INTERFACE
611
+ // ===========================================================================
612
+ /**
613
+ * Check if a user is authorized to perform an action
614
+ */
615
+ async authorizeUser(user, permission) {
616
+ return this.authorize(user, { permissions: [permission] });
617
+ }
618
+ /**
619
+ * Check if user has specific roles
620
+ */
621
+ async authorizeRoles(user, roles) {
622
+ return this.authorize(user, { roles });
623
+ }
624
+ /**
625
+ * Check if user has all specified permissions
626
+ */
627
+ async authorizePermissions(user, permissions) {
628
+ return this.authorize(user, { permissions });
629
+ }
630
+ /**
631
+ * Get an existing session by ID
632
+ */
633
+ async getSession(sessionId) {
634
+ return this.sessionStorage.get(sessionId);
635
+ }
636
+ /**
637
+ * Invalidate/destroy a session
638
+ */
639
+ async destroySession(sessionId) {
640
+ await this.revokeSession(sessionId);
641
+ }
642
+ /**
643
+ * Get all active sessions for a user
644
+ */
645
+ async getUserSessions(userId) {
646
+ return this.sessionStorage.getForUser(userId);
647
+ }
648
+ /**
649
+ * Invalidate all sessions for a user (global logout)
650
+ */
651
+ async destroyAllUserSessions(userId) {
652
+ await this.revokeAllSessions(userId);
653
+ }
654
+ /**
655
+ * Full request authentication flow
656
+ *
657
+ * Combines token extraction (with full strategy support), validation,
658
+ * and session creation/reuse.
659
+ *
660
+ * @param context - Request context
661
+ * @returns Authenticated context with user and session, or null
662
+ */
663
+ async authenticateRequest(context) {
664
+ // Extract token (async to support custom extractors)
665
+ const token = await this.extractToken(context);
666
+ if (!token) {
667
+ if (!this.config.required) {
668
+ return null;
669
+ }
670
+ this.emitter.emit("auth:unauthorized", context, "No token provided");
671
+ return null;
672
+ }
673
+ // Validate token
674
+ const validation = await this.authenticateToken(token, context);
675
+ if (!validation.valid || !validation.user) {
676
+ this.emitter.emit("auth:unauthorized", context, validation.error ?? "Invalid token");
677
+ return null;
678
+ }
679
+ // Reuse existing session if one exists for this user
680
+ const existingSessions = await this.getUserSessions(validation.user.id);
681
+ const validSession = existingSessions.find((s) => s.isValid && (!s.expiresAt || s.expiresAt.getTime() > Date.now()));
682
+ const session = validSession ?? (await this.createSession(validation.user, context));
683
+ return {
684
+ ...context,
685
+ user: validation.user,
686
+ session,
687
+ request: context,
688
+ authenticatedAt: new Date(),
689
+ provider: this.type,
690
+ };
691
+ }
692
+ /**
693
+ * Check provider health
694
+ */
695
+ async healthCheck() {
696
+ return {
697
+ healthy: true,
698
+ providerConnected: true,
699
+ sessionStorageHealthy: true,
700
+ };
701
+ }
702
+ // ===========================================================================
703
+ // EVENT HELPERS
704
+ // ===========================================================================
705
+ /**
706
+ * Subscribe to auth events
707
+ */
708
+ on(event, listener) {
709
+ this.emitter.on(event, listener);
710
+ }
711
+ /**
712
+ * Unsubscribe from auth events
713
+ */
714
+ off(event, listener) {
715
+ this.emitter.off(event, listener);
716
+ }
717
+ /**
718
+ * Emit an auth event
719
+ */
720
+ emit(event, ...args) {
721
+ this.emitter.emit(event, ...args);
722
+ }
723
+ }
724
+ //# sourceMappingURL=BaseAuthProvider.js.map