@mastra/auth-cloud 0.0.1 → 1.1.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 (44) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE.md +30 -0
  3. package/README.md +65 -1
  4. package/dist/auth-provider.d.ts +198 -0
  5. package/dist/auth-provider.d.ts.map +1 -0
  6. package/dist/client.d.ts +110 -0
  7. package/dist/client.d.ts.map +1 -0
  8. package/dist/error.d.ts +65 -0
  9. package/dist/error.d.ts.map +1 -0
  10. package/dist/index.cjs +855 -0
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.ts +19 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +850 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/oauth/index.d.ts +9 -0
  17. package/dist/oauth/index.d.ts.map +1 -0
  18. package/dist/oauth/network.d.ts +20 -0
  19. package/dist/oauth/network.d.ts.map +1 -0
  20. package/dist/oauth/oauth.d.ts +68 -0
  21. package/dist/oauth/oauth.d.ts.map +1 -0
  22. package/dist/oauth/state.d.ts +47 -0
  23. package/dist/oauth/state.d.ts.map +1 -0
  24. package/dist/pkce/cookie.d.ts +42 -0
  25. package/dist/pkce/cookie.d.ts.map +1 -0
  26. package/dist/pkce/error.d.ts +31 -0
  27. package/dist/pkce/error.d.ts.map +1 -0
  28. package/dist/pkce/index.d.ts +10 -0
  29. package/dist/pkce/index.d.ts.map +1 -0
  30. package/dist/pkce/pkce.d.ts +26 -0
  31. package/dist/pkce/pkce.d.ts.map +1 -0
  32. package/dist/rbac/index.d.ts +2 -0
  33. package/dist/rbac/index.d.ts.map +1 -0
  34. package/dist/rbac/rbac-provider.d.ts +124 -0
  35. package/dist/rbac/rbac-provider.d.ts.map +1 -0
  36. package/dist/session/cookie.d.ts +32 -0
  37. package/dist/session/cookie.d.ts.map +1 -0
  38. package/dist/session/index.d.ts +9 -0
  39. package/dist/session/index.d.ts.map +1 -0
  40. package/dist/session/session.d.ts +56 -0
  41. package/dist/session/session.d.ts.map +1 -0
  42. package/dist/types.d.ts +64 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +54 -3
package/dist/index.cjs ADDED
@@ -0,0 +1,855 @@
1
+ 'use strict';
2
+
3
+ var crypto$1 = require('crypto');
4
+ var server = require('@mastra/core/server');
5
+ var ee = require('@mastra/core/auth/ee');
6
+
7
+ // src/error.ts
8
+ var AuthError = class _AuthError extends Error {
9
+ code;
10
+ cause;
11
+ cloudCode;
12
+ cloudMessage;
13
+ constructor(code, message, options) {
14
+ super(message);
15
+ this.name = "AuthError";
16
+ this.code = code;
17
+ this.cause = options?.cause;
18
+ this.cloudCode = options?.cloudCode;
19
+ this.cloudMessage = options?.cloudMessage;
20
+ Object.setPrototypeOf(this, new.target.prototype);
21
+ }
22
+ /**
23
+ * Factory: OAuth state parameter is invalid or malformed.
24
+ */
25
+ static invalidState() {
26
+ return new _AuthError("invalid_state", "OAuth state parameter is invalid or malformed.");
27
+ }
28
+ /**
29
+ * Factory: OAuth state parameter does not match expected value.
30
+ */
31
+ static stateMismatch() {
32
+ return new _AuthError("state_mismatch", "OAuth state parameter does not match. Possible CSRF attack.");
33
+ }
34
+ /**
35
+ * Factory: Authorization code is missing from callback.
36
+ */
37
+ static missingCode() {
38
+ return new _AuthError("missing_code", "Authorization code is missing from OAuth callback.");
39
+ }
40
+ /**
41
+ * Factory: Token exchange with Cloud API failed.
42
+ */
43
+ static tokenExchangeFailed(options) {
44
+ return new _AuthError("token_exchange_failed", "Failed to exchange authorization code for tokens.", options);
45
+ }
46
+ /**
47
+ * Factory: Token verification failed.
48
+ */
49
+ static verificationFailed() {
50
+ return new _AuthError("verification_failed", "Token verification failed.");
51
+ }
52
+ /**
53
+ * Factory: Session is invalid.
54
+ */
55
+ static sessionInvalid() {
56
+ return new _AuthError("session_invalid", "Session is invalid or has been revoked.");
57
+ }
58
+ /**
59
+ * Factory: Session has expired.
60
+ */
61
+ static sessionExpired() {
62
+ return new _AuthError("session_expired", "Session has expired. Please log in again.");
63
+ }
64
+ /**
65
+ * Factory: Network error during API call.
66
+ */
67
+ static networkError(cause) {
68
+ return new _AuthError("network_error", "Network error occurred while communicating with Cloud API.", { cause });
69
+ }
70
+ /**
71
+ * Factory: Cloud API returned an error.
72
+ */
73
+ static cloudApiError(options) {
74
+ const message = options?.cloudMessage ?? "Cloud API returned an error.";
75
+ return new _AuthError("cloud_api_error", message, options);
76
+ }
77
+ };
78
+
79
+ // src/oauth/state.ts
80
+ function encodeState(csrf, returnTo) {
81
+ const data = { csrf, returnTo };
82
+ const json = JSON.stringify(data);
83
+ return Buffer.from(json).toString("base64url");
84
+ }
85
+ function decodeState(state) {
86
+ try {
87
+ const json = Buffer.from(state, "base64url").toString();
88
+ const data = JSON.parse(json);
89
+ if (typeof data.csrf !== "string" || typeof data.returnTo !== "string") {
90
+ throw new Error("Missing required fields");
91
+ }
92
+ return data;
93
+ } catch {
94
+ throw AuthError.invalidState();
95
+ }
96
+ }
97
+ function validateReturnTo(returnTo, requestOrigin) {
98
+ if (!returnTo) {
99
+ return "/";
100
+ }
101
+ if (returnTo.startsWith("/") && !returnTo.startsWith("//")) {
102
+ return returnTo;
103
+ }
104
+ try {
105
+ const parsed = new URL(returnTo);
106
+ const origin = new URL(requestOrigin);
107
+ if (parsed.origin === origin.origin) {
108
+ return returnTo;
109
+ }
110
+ } catch {
111
+ }
112
+ return "/";
113
+ }
114
+
115
+ // src/oauth/network.ts
116
+ async function fetchWithRetry(url, options) {
117
+ try {
118
+ return await fetch(url, options);
119
+ } catch {
120
+ try {
121
+ return await fetch(url, options);
122
+ } catch (retryError) {
123
+ throw AuthError.networkError(retryError instanceof Error ? retryError : void 0);
124
+ }
125
+ }
126
+ }
127
+
128
+ // src/pkce/error.ts
129
+ var PKCEError = class _PKCEError extends Error {
130
+ code;
131
+ cause;
132
+ constructor(code, message, cause) {
133
+ super(message);
134
+ this.name = "PKCEError";
135
+ this.code = code;
136
+ this.cause = cause;
137
+ Object.setPrototypeOf(this, new.target.prototype);
138
+ }
139
+ /**
140
+ * Factory: PKCE verifier cookie not found.
141
+ */
142
+ static missingVerifier() {
143
+ return new _PKCEError(
144
+ "MISSING_VERIFIER",
145
+ "PKCE verifier cookie not found. Authorization flow may have expired or was not initiated properly."
146
+ );
147
+ }
148
+ /**
149
+ * Factory: PKCE verifier has expired.
150
+ */
151
+ static expired() {
152
+ return new _PKCEError("EXPIRED", "PKCE verifier has expired. Please restart the authorization flow.");
153
+ }
154
+ /**
155
+ * Factory: PKCE verifier cookie is malformed.
156
+ */
157
+ static invalid(cause) {
158
+ return new _PKCEError("INVALID", "PKCE verifier cookie is malformed or invalid.", cause);
159
+ }
160
+ };
161
+
162
+ // src/pkce/cookie.ts
163
+ var PKCE_COOKIE_NAME = "mastra_pkce_verifier";
164
+ function setPKCECookie(verifier, state, isProduction) {
165
+ const ttlSeconds = 5 * 60;
166
+ const data = {
167
+ verifier,
168
+ state,
169
+ expiresAt: Date.now() + ttlSeconds * 1e3
170
+ };
171
+ const encoded = encodeURIComponent(JSON.stringify(data));
172
+ let cookie = `${PKCE_COOKIE_NAME}=${encoded}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${ttlSeconds}`;
173
+ if (isProduction) {
174
+ cookie += "; Secure";
175
+ }
176
+ return cookie;
177
+ }
178
+ function parsePKCECookie(cookieHeader) {
179
+ if (!cookieHeader) {
180
+ throw PKCEError.missingVerifier();
181
+ }
182
+ const match = cookieHeader.match(new RegExp(`${PKCE_COOKIE_NAME}=([^;]+)`));
183
+ if (!match?.[1]) {
184
+ throw PKCEError.missingVerifier();
185
+ }
186
+ let data;
187
+ try {
188
+ data = JSON.parse(decodeURIComponent(match[1]));
189
+ } catch (e) {
190
+ throw PKCEError.invalid(e instanceof Error ? e : void 0);
191
+ }
192
+ if (data.expiresAt < Date.now()) {
193
+ throw PKCEError.expired();
194
+ }
195
+ return data;
196
+ }
197
+ function clearPKCECookie() {
198
+ return `${PKCE_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`;
199
+ }
200
+ function generateCodeVerifier() {
201
+ return crypto$1.randomBytes(32).toString("base64url");
202
+ }
203
+ function computeCodeChallenge(verifier) {
204
+ return crypto$1.createHash("sha256").update(verifier).digest("base64url");
205
+ }
206
+ function generateState() {
207
+ return crypto$1.randomBytes(16).toString("base64url");
208
+ }
209
+
210
+ // src/oauth/oauth.ts
211
+ function getLoginUrl(options) {
212
+ const { projectId, cloudBaseUrl, callbackUrl, returnTo, requestOrigin, isProduction } = options;
213
+ const verifier = generateCodeVerifier();
214
+ const challenge = computeCodeChallenge(verifier);
215
+ const csrf = generateState();
216
+ const validatedReturnTo = validateReturnTo(returnTo, requestOrigin);
217
+ const state = encodeState(csrf, validatedReturnTo);
218
+ const url = new URL("/auth/oss", cloudBaseUrl);
219
+ url.searchParams.set("project_id", projectId);
220
+ url.searchParams.set("code_challenge", challenge);
221
+ url.searchParams.set("code_challenge_method", "S256");
222
+ url.searchParams.set("redirect_uri", callbackUrl);
223
+ url.searchParams.set("state", state);
224
+ const isProductionEnv = isProduction ?? process.env.NODE_ENV === "production";
225
+ const pkceCookie = setPKCECookie(verifier, csrf, isProductionEnv);
226
+ return {
227
+ url: url.toString(),
228
+ cookies: [pkceCookie]
229
+ };
230
+ }
231
+ async function handleCallback(options) {
232
+ const { projectId, cloudBaseUrl, redirectUri, code, state, cookieHeader } = options;
233
+ const pkceData = parsePKCECookie(cookieHeader);
234
+ const stateData = decodeState(state);
235
+ if (stateData.csrf !== pkceData.state) {
236
+ throw AuthError.stateMismatch();
237
+ }
238
+ const response = await fetchWithRetry(`${cloudBaseUrl}/auth/callback`, {
239
+ method: "POST",
240
+ headers: {
241
+ "Content-Type": "application/json",
242
+ "X-Project-ID": projectId
243
+ },
244
+ body: JSON.stringify({
245
+ code,
246
+ redirect_uri: redirectUri,
247
+ code_verifier: pkceData.verifier
248
+ })
249
+ });
250
+ if (!response.ok) {
251
+ let cloudCode;
252
+ let cloudMessage;
253
+ try {
254
+ const errorBody = await response.json();
255
+ cloudCode = errorBody.code;
256
+ cloudMessage = errorBody.message;
257
+ } catch {
258
+ }
259
+ throw AuthError.tokenExchangeFailed({ cloudCode, cloudMessage });
260
+ }
261
+ const body = await response.json();
262
+ const verifyResponse = await fetchWithRetry(`${cloudBaseUrl}/auth/verify`, {
263
+ method: "POST",
264
+ headers: {
265
+ Authorization: `Bearer ${body.access_token}`,
266
+ "X-Project-ID": projectId
267
+ }
268
+ });
269
+ if (!verifyResponse.ok) {
270
+ throw AuthError.verificationFailed();
271
+ }
272
+ const verifyBody = await verifyResponse.json();
273
+ const clearCookie = clearPKCECookie();
274
+ return {
275
+ user: {
276
+ id: verifyBody.sub,
277
+ email: verifyBody.email,
278
+ name: verifyBody.name,
279
+ avatar: verifyBody.avatar_url,
280
+ role: verifyBody.role
281
+ },
282
+ accessToken: body.access_token,
283
+ returnTo: stateData.returnTo,
284
+ cookies: [clearCookie]
285
+ };
286
+ }
287
+
288
+ // src/session/cookie.ts
289
+ var SESSION_COOKIE_NAME = "mastra_cloud_session";
290
+ function setSessionCookie(token, isProduction) {
291
+ const ttlSeconds = 24 * 60 * 60;
292
+ let cookie = `${SESSION_COOKIE_NAME}=${token}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${ttlSeconds}`;
293
+ if (isProduction) {
294
+ cookie += "; Secure";
295
+ }
296
+ return cookie;
297
+ }
298
+ function parseSessionCookie(cookieHeader) {
299
+ if (!cookieHeader) {
300
+ return null;
301
+ }
302
+ const match = cookieHeader.match(new RegExp(`${SESSION_COOKIE_NAME}=([^;]+)`));
303
+ return match?.[1] ?? null;
304
+ }
305
+ function clearSessionCookie() {
306
+ return `${SESSION_COOKIE_NAME}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`;
307
+ }
308
+
309
+ // src/session/session.ts
310
+ async function verifyToken(options) {
311
+ const { projectId, cloudBaseUrl, token } = options;
312
+ const response = await fetchWithRetry(`${cloudBaseUrl}/auth/verify`, {
313
+ method: "POST",
314
+ headers: {
315
+ Authorization: `Bearer ${token}`,
316
+ "X-Project-ID": projectId
317
+ }
318
+ });
319
+ if (!response.ok) {
320
+ throw AuthError.verificationFailed();
321
+ }
322
+ const body = await response.json();
323
+ if (body.token_type === "project_api_token") {
324
+ return {
325
+ user: {
326
+ id: "api-token",
327
+ email: void 0,
328
+ name: void 0,
329
+ avatar: void 0
330
+ },
331
+ role: body.role
332
+ };
333
+ }
334
+ return {
335
+ user: {
336
+ id: body.sub,
337
+ email: body.email,
338
+ name: body.name,
339
+ avatar: body.avatar_url
340
+ },
341
+ role: body.role
342
+ };
343
+ }
344
+ async function validateSession(options) {
345
+ const { projectId, cloudBaseUrl, sessionToken } = options;
346
+ try {
347
+ const response = await fetchWithRetry(`${cloudBaseUrl}/auth/session/validate`, {
348
+ method: "POST",
349
+ headers: {
350
+ "Content-Type": "application/json",
351
+ Authorization: `Bearer ${sessionToken}`,
352
+ "X-Project-ID": projectId
353
+ }
354
+ });
355
+ if (!response.ok) {
356
+ return null;
357
+ }
358
+ return await response.json();
359
+ } catch {
360
+ return null;
361
+ }
362
+ }
363
+ async function destroySession(options) {
364
+ const { cloudBaseUrl, sessionToken } = options;
365
+ await fetchWithRetry(`${cloudBaseUrl}/auth/session/destroy`, {
366
+ method: "POST",
367
+ headers: {
368
+ Authorization: `Bearer ${sessionToken}`
369
+ }
370
+ });
371
+ }
372
+ function getLogoutUrl(cloudBaseUrl, postLogoutRedirectUri, idTokenHint) {
373
+ const url = new URL("/auth/logout", cloudBaseUrl);
374
+ url.searchParams.set("post_logout_redirect_uri", postLogoutRedirectUri);
375
+ url.searchParams.set("id_token_hint", idTokenHint);
376
+ return url.toString();
377
+ }
378
+
379
+ // src/client.ts
380
+ var MastraCloudAuth = class {
381
+ config;
382
+ constructor(config) {
383
+ this.config = config;
384
+ }
385
+ /**
386
+ * Generate login URL for OAuth authorization.
387
+ *
388
+ * @param options - Login options
389
+ * @returns URL to redirect to and cookies to set
390
+ */
391
+ getLoginUrl(options) {
392
+ return getLoginUrl({
393
+ projectId: this.config.projectId,
394
+ cloudBaseUrl: this.config.cloudBaseUrl,
395
+ callbackUrl: this.config.callbackUrl,
396
+ returnTo: options.returnTo,
397
+ requestOrigin: options.requestOrigin,
398
+ isProduction: this.config.isProduction
399
+ });
400
+ }
401
+ /**
402
+ * Handle OAuth callback after authorization.
403
+ *
404
+ * @param options - Callback parameters
405
+ * @returns User info, tokens, and redirect URL
406
+ */
407
+ handleCallback(options) {
408
+ return handleCallback({
409
+ projectId: this.config.projectId,
410
+ cloudBaseUrl: this.config.cloudBaseUrl,
411
+ redirectUri: this.config.callbackUrl,
412
+ ...options
413
+ });
414
+ }
415
+ /**
416
+ * Verify an access token.
417
+ *
418
+ * @param token - Access token to verify
419
+ * @returns User and role information
420
+ */
421
+ verifyToken(token) {
422
+ return verifyToken({ projectId: this.config.projectId, cloudBaseUrl: this.config.cloudBaseUrl, token });
423
+ }
424
+ /**
425
+ * Validate an existing session.
426
+ *
427
+ * @param sessionToken - Session token to validate
428
+ * @returns Session data if valid, null otherwise
429
+ */
430
+ validateSession(sessionToken) {
431
+ return validateSession({ projectId: this.config.projectId, cloudBaseUrl: this.config.cloudBaseUrl, sessionToken });
432
+ }
433
+ /**
434
+ * Destroy a session (server-side logout).
435
+ *
436
+ * @param sessionToken - Session token to destroy
437
+ */
438
+ destroySession(sessionToken) {
439
+ return destroySession({ cloudBaseUrl: this.config.cloudBaseUrl, sessionToken });
440
+ }
441
+ /**
442
+ * Get the logout URL for client-side redirect.
443
+ *
444
+ * @param postLogoutRedirectUri - URL to redirect to after logout
445
+ * @param idTokenHint - The access token
446
+ * @returns Full logout URL with redirect and token parameters
447
+ */
448
+ getLogoutUrl(postLogoutRedirectUri, idTokenHint) {
449
+ return getLogoutUrl(this.config.cloudBaseUrl, postLogoutRedirectUri, idTokenHint);
450
+ }
451
+ /**
452
+ * Create Set-Cookie header value for session token.
453
+ *
454
+ * @param token - Session token to store
455
+ * @returns Set-Cookie header value
456
+ */
457
+ setSessionCookie(token) {
458
+ return setSessionCookie(token, this.config.isProduction ?? process.env.NODE_ENV === "production");
459
+ }
460
+ /**
461
+ * Create Set-Cookie header value to clear session cookie.
462
+ *
463
+ * @returns Set-Cookie header value
464
+ */
465
+ clearSessionCookie() {
466
+ return clearSessionCookie();
467
+ }
468
+ };
469
+ var MastraCloudAuthProvider = class extends server.MastraAuthProvider {
470
+ client;
471
+ /** Marker for EE license exemption - MastraCloudAuth is exempt */
472
+ isMastraCloudAuth = true;
473
+ /**
474
+ * Cookie header for handleCallback PKCE validation.
475
+ * Set via setCallbackCookieHeader() before handleCallback() is called.
476
+ * @internal
477
+ */
478
+ _lastCallbackCookieHeader = null;
479
+ constructor(options) {
480
+ super({ name: options?.name ?? "cloud" });
481
+ this.client = new MastraCloudAuth({
482
+ projectId: options.projectId,
483
+ cloudBaseUrl: options.cloudBaseUrl,
484
+ callbackUrl: options.callbackUrl,
485
+ isProduction: options.isProduction
486
+ });
487
+ this.registerOptions(options);
488
+ }
489
+ /**
490
+ * Set cookie header for handleCallback PKCE validation.
491
+ * Must be called before handleCallback() to pass cookie header.
492
+ *
493
+ * @param cookieHeader - Cookie header from original request
494
+ */
495
+ setCallbackCookieHeader(cookieHeader) {
496
+ this._lastCallbackCookieHeader = cookieHeader;
497
+ }
498
+ // ============================================================================
499
+ // MastraAuthProvider Implementation
500
+ // ============================================================================
501
+ /**
502
+ * Authenticate a bearer token or session cookie.
503
+ *
504
+ * Checks session cookie first, falls back to bearer token for API clients.
505
+ *
506
+ * @param token - Bearer token (from Authorization header)
507
+ * @param request - Hono or raw Request
508
+ * @returns Authenticated user with role, or null if invalid
509
+ */
510
+ async authenticateToken(token, request) {
511
+ try {
512
+ const rawRequest = "raw" in request ? request.raw : request;
513
+ const cookieHeader = rawRequest.headers.get("cookie");
514
+ const sessionToken = parseSessionCookie(cookieHeader);
515
+ if (sessionToken) {
516
+ const { user, role } = await this.client.verifyToken(sessionToken);
517
+ return { ...user, role };
518
+ }
519
+ if (token) {
520
+ const { user, role } = await this.client.verifyToken(token);
521
+ return { ...user, role };
522
+ }
523
+ return null;
524
+ } catch {
525
+ return null;
526
+ }
527
+ }
528
+ /**
529
+ * Authorize a user for access.
530
+ *
531
+ * Simple validation - detailed permission checking happens in server
532
+ * middleware via checkRoutePermission(), not authorizeUser().
533
+ *
534
+ * @param user - Authenticated user
535
+ * @returns True if user has valid id
536
+ */
537
+ authorizeUser(user) {
538
+ return !!user?.id;
539
+ }
540
+ // ============================================================================
541
+ // ISSOProvider Implementation
542
+ // ============================================================================
543
+ /**
544
+ * Cached login result for getLoginCookies() to retrieve cookies.
545
+ * @internal
546
+ */
547
+ _lastLoginResult = null;
548
+ /**
549
+ * Get URL to redirect user to for SSO login.
550
+ *
551
+ * @param redirectUri - Callback URL after authentication
552
+ * @param state - State parameter (format: uuid|encodedPostLoginRedirect)
553
+ * @returns Full authorization URL
554
+ */
555
+ getLoginUrl(redirectUri, state) {
556
+ let postLoginRedirect = "/";
557
+ if (state && state.includes("|")) {
558
+ const parts = state.split("|", 2);
559
+ const encodedRedirect = parts[1];
560
+ if (encodedRedirect) {
561
+ try {
562
+ postLoginRedirect = decodeURIComponent(encodedRedirect);
563
+ } catch {
564
+ postLoginRedirect = "/";
565
+ }
566
+ }
567
+ }
568
+ const redirectUrl = new URL(redirectUri);
569
+ const origin = redirectUrl.origin;
570
+ const result = this.client.getLoginUrl({
571
+ returnTo: postLoginRedirect,
572
+ requestOrigin: origin
573
+ });
574
+ this._lastLoginResult = result;
575
+ return result.url;
576
+ }
577
+ /**
578
+ * Get cookies to set during login redirect (PKCE verifier).
579
+ * Must be called after getLoginUrl() in same request.
580
+ *
581
+ * @returns Array of Set-Cookie header values
582
+ */
583
+ getLoginCookies() {
584
+ const cookies = this._lastLoginResult?.cookies;
585
+ this._lastLoginResult = null;
586
+ return cookies;
587
+ }
588
+ /**
589
+ * Handle OAuth callback, exchange code for tokens and user.
590
+ *
591
+ * @param code - Authorization code from callback
592
+ * @param state - State parameter for CSRF validation
593
+ * @returns User, tokens, and session cookies
594
+ */
595
+ async handleCallback(code, state) {
596
+ const cookieHeader = this._lastCallbackCookieHeader;
597
+ this._lastCallbackCookieHeader = null;
598
+ const result = await this.client.handleCallback({
599
+ code,
600
+ state,
601
+ cookieHeader
602
+ });
603
+ const sessionCookie = this.client.setSessionCookie(result.accessToken);
604
+ return {
605
+ user: result.user,
606
+ // Already has role from handleCallback
607
+ tokens: {
608
+ accessToken: result.accessToken
609
+ },
610
+ cookies: [...result.cookies, sessionCookie]
611
+ };
612
+ }
613
+ /**
614
+ * Get configuration for rendering login button in UI.
615
+ *
616
+ * @returns Login button configuration
617
+ */
618
+ getLoginButtonConfig() {
619
+ return {
620
+ provider: "mastra",
621
+ text: "Sign in with Mastra Cloud"
622
+ };
623
+ }
624
+ /**
625
+ * Get logout URL for client-side redirect.
626
+ * Requires the request to extract the session token for id_token_hint.
627
+ *
628
+ * @param redirectUri - URL to redirect to after logout
629
+ * @param request - Request to extract session token from
630
+ * @returns Logout URL with redirect and token parameters, or null if no session
631
+ */
632
+ getLogoutUrl(redirectUri, request) {
633
+ const sessionToken = request ? this.getSessionIdFromRequest(request) : null;
634
+ if (!sessionToken) {
635
+ return null;
636
+ }
637
+ return this.client.getLogoutUrl(redirectUri, sessionToken);
638
+ }
639
+ // ============================================================================
640
+ // ISessionProvider Implementation
641
+ // ============================================================================
642
+ /**
643
+ * Create a new session for a user.
644
+ *
645
+ * For Cloud auth, sessions are created via handleCallback.
646
+ * This method builds a Session object for interface compatibility.
647
+ *
648
+ * @param userId - User to create session for
649
+ * @param metadata - Optional metadata (accessToken can be passed here)
650
+ * @returns Session object
651
+ */
652
+ async createSession(userId, metadata) {
653
+ const now = /* @__PURE__ */ new Date();
654
+ const expiresAt = new Date(now.getTime() + 24 * 60 * 60 * 1e3);
655
+ return {
656
+ id: metadata?.accessToken ?? crypto.randomUUID(),
657
+ userId,
658
+ createdAt: now,
659
+ expiresAt,
660
+ metadata
661
+ };
662
+ }
663
+ /**
664
+ * Validate a session and return it if valid.
665
+ *
666
+ * @param sessionId - Session token to validate
667
+ * @returns Session object or null if invalid/expired
668
+ */
669
+ async validateSession(sessionId) {
670
+ const session = await this.client.validateSession(sessionId);
671
+ if (!session) return null;
672
+ return {
673
+ id: sessionId,
674
+ userId: session.userId,
675
+ createdAt: new Date(session.createdAt),
676
+ expiresAt: new Date(session.expiresAt)
677
+ };
678
+ }
679
+ /**
680
+ * Destroy a session (logout).
681
+ *
682
+ * @param sessionId - Session token to destroy
683
+ */
684
+ async destroySession(sessionId) {
685
+ await this.client.destroySession(sessionId);
686
+ }
687
+ /**
688
+ * Refresh a session, extending its expiry.
689
+ * Cloud handles refresh internally, so just validate.
690
+ *
691
+ * @param sessionId - Session token to refresh
692
+ * @returns Session object or null if invalid
693
+ */
694
+ async refreshSession(sessionId) {
695
+ return this.validateSession(sessionId);
696
+ }
697
+ /**
698
+ * Extract session ID from an incoming request.
699
+ *
700
+ * @param request - Incoming HTTP request
701
+ * @returns Session token or null if not present
702
+ */
703
+ getSessionIdFromRequest(request) {
704
+ return parseSessionCookie(request.headers.get("cookie"));
705
+ }
706
+ /**
707
+ * Create response headers to set session cookie.
708
+ *
709
+ * @param session - Session to encode (id is the access token)
710
+ * @returns Headers object with Set-Cookie
711
+ */
712
+ getSessionHeaders(session) {
713
+ return { "Set-Cookie": this.client.setSessionCookie(session.id) };
714
+ }
715
+ /**
716
+ * Create response headers to clear session (for logout).
717
+ *
718
+ * @returns Headers object to clear session cookie
719
+ */
720
+ getClearSessionHeaders() {
721
+ return { "Set-Cookie": this.client.clearSessionCookie() };
722
+ }
723
+ // ============================================================================
724
+ // IUserProvider Implementation
725
+ // ============================================================================
726
+ /**
727
+ * Get current user from request (session cookie).
728
+ *
729
+ * @param request - Incoming HTTP request
730
+ * @returns User with role or null if not authenticated
731
+ */
732
+ async getCurrentUser(request) {
733
+ const sessionToken = this.getSessionIdFromRequest(request);
734
+ if (!sessionToken) return null;
735
+ try {
736
+ const { user, role } = await this.client.verifyToken(sessionToken);
737
+ return { ...user, role };
738
+ } catch {
739
+ return null;
740
+ }
741
+ }
742
+ /**
743
+ * Get user by ID.
744
+ * Cloud API doesn't have a /users/:id endpoint.
745
+ *
746
+ * @returns Always null (not supported)
747
+ */
748
+ async getUser(_userId) {
749
+ return null;
750
+ }
751
+ };
752
+ var MastraRBACCloud = class {
753
+ options;
754
+ /**
755
+ * Expose roleMapping for middleware access.
756
+ * This allows the authorization middleware to resolve permissions
757
+ * without needing to call the async methods.
758
+ */
759
+ get roleMapping() {
760
+ return this.options.roleMapping;
761
+ }
762
+ /**
763
+ * Create a new Mastra Cloud RBAC provider.
764
+ *
765
+ * @param options - RBAC configuration options
766
+ */
767
+ constructor(options) {
768
+ this.options = options;
769
+ }
770
+ /**
771
+ * Get all roles for a user.
772
+ *
773
+ * Returns the user's role as a single-element array, or empty array if no role.
774
+ * Cloud uses a single-role model (role is attached via verifyToken()).
775
+ *
776
+ * @param user - Cloud user to get roles for
777
+ * @returns Array containing user's role, or empty array
778
+ */
779
+ async getRoles(user) {
780
+ return user.role ? [user.role] : [];
781
+ }
782
+ /**
783
+ * Check if a user has a specific role.
784
+ *
785
+ * @param user - Cloud user to check
786
+ * @param role - Role name to check for
787
+ * @returns True if user has the role
788
+ */
789
+ async hasRole(user, role) {
790
+ const roles = await this.getRoles(user);
791
+ return roles.includes(role);
792
+ }
793
+ /**
794
+ * Get all permissions for a user by mapping their role.
795
+ *
796
+ * Uses the configured roleMapping to translate the user's role
797
+ * into Mastra permission strings.
798
+ *
799
+ * If the user has no role, the _default permissions from the
800
+ * role mapping are applied.
801
+ *
802
+ * @param user - Cloud user to get permissions for
803
+ * @returns Array of permission strings
804
+ */
805
+ async getPermissions(user) {
806
+ const roles = await this.getRoles(user);
807
+ if (roles.length === 0) {
808
+ return this.options.roleMapping["_default"] ?? [];
809
+ }
810
+ return ee.resolvePermissionsFromMapping(roles, this.options.roleMapping);
811
+ }
812
+ /**
813
+ * Check if a user has a specific permission.
814
+ *
815
+ * Uses wildcard matching to check if the user's permissions
816
+ * grant access to the required permission.
817
+ *
818
+ * @param user - Cloud user to check
819
+ * @param permission - Permission to check for (e.g., 'agents:read')
820
+ * @returns True if user has the permission
821
+ */
822
+ async hasPermission(user, permission) {
823
+ const permissions = await this.getPermissions(user);
824
+ return permissions.some((p) => ee.matchesPermission(p, permission));
825
+ }
826
+ /**
827
+ * Check if a user has ALL of the specified permissions.
828
+ *
829
+ * @param user - Cloud user to check
830
+ * @param permissions - Array of permissions to check for
831
+ * @returns True if user has all permissions
832
+ */
833
+ async hasAllPermissions(user, permissions) {
834
+ const userPermissions = await this.getPermissions(user);
835
+ return permissions.every((required) => userPermissions.some((p) => ee.matchesPermission(p, required)));
836
+ }
837
+ /**
838
+ * Check if a user has ANY of the specified permissions.
839
+ *
840
+ * @param user - Cloud user to check
841
+ * @param permissions - Array of permissions to check for
842
+ * @returns True if user has at least one permission
843
+ */
844
+ async hasAnyPermission(user, permissions) {
845
+ const userPermissions = await this.getPermissions(user);
846
+ return permissions.some((required) => userPermissions.some((p) => ee.matchesPermission(p, required)));
847
+ }
848
+ };
849
+
850
+ exports.AuthError = AuthError;
851
+ exports.MastraCloudAuth = MastraCloudAuth;
852
+ exports.MastraCloudAuthProvider = MastraCloudAuthProvider;
853
+ exports.MastraRBACCloud = MastraRBACCloud;
854
+ //# sourceMappingURL=index.cjs.map
855
+ //# sourceMappingURL=index.cjs.map