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