@mastra/auth-studio 1.3.1-alpha.0 → 1.3.2-alpha.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @mastra/auth-studio
2
2
 
3
+ ## 1.3.2-alpha.0
4
+
5
+ ### Patch Changes
6
+
7
+ - `MastraAuthStudio` now automatically creates a personal organization for users who don't belong to one yet, and can check whether a user is an organization admin — matching the behavior already available in `MastraAuthWorkos` and `MastraAuthBetterAuth`. This lets hosts like a self-hosted MastraCode deployment authorize organization-level actions without users needing to manually set up an organization first. ([#19858](https://github.com/mastra-ai/mastra/pull/19858))
8
+
9
+ - `MastraAuthStudio.ensureOrganization` now dedupes concurrent bootstraps for the same user, so parallel tabs or requests for a brand-new sign-in no longer end up creating duplicate personal organizations. ([#19858](https://github.com/mastra-ai/mastra/pull/19858))
10
+
11
+ ## 1.3.1
12
+
13
+ ### Patch Changes
14
+
15
+ - Improved auth package builds by removing the direct core dependency from auth providers while preserving the existing public auth APIs. ([#17142](https://github.com/mastra-ai/mastra/pull/17142))
16
+
3
17
  ## 1.3.1-alpha.0
4
18
 
5
19
  ### Patch Changes
@@ -3,12 +3,9 @@
3
3
  *
4
4
  * @license Mastra Enterprise License - see ee/LICENSE
5
5
  */
6
- import type { FGACheckContext, IFGAProvider } from './interfaces/fga.js';
6
+ import type { ActorSignal, FGACheckContext, IFGAProvider } from './interfaces/fga.js';
7
7
  import type { MastraFGAPermissionInput } from './interfaces/permissions.generated.js';
8
- export type ActorSignal = true | {
9
- actorKind: 'system';
10
- sourceWorkflow?: string;
11
- };
8
+ export type { ActorSignal };
12
9
  export interface CheckFGAOptions {
13
10
  fgaProvider: IFGAProvider | undefined;
14
11
  user: any;
@@ -8,6 +8,34 @@
8
8
  * @license Mastra Enterprise License - see ee/LICENSE
9
9
  */
10
10
  import type { MastraFGAPermissionInput } from './permissions.generated.js';
11
+ /**
12
+ * Signals that a call is made by a trusted non-user actor rather than an
13
+ * authenticated end user.
14
+ *
15
+ * - `true` is the anonymous system shorthand.
16
+ * - The object form additionally names the acting agent (`agentId`) and carries
17
+ * the permission grants / scope a provider can enforce for least privilege.
18
+ */
19
+ export type ActorSignal = true | {
20
+ actorKind: 'system';
21
+ sourceWorkflow?: string;
22
+ /**
23
+ * Identity of the acting system agent. Unlike the check `resource` (the
24
+ * target), this names the actor itself so a provider can enforce
25
+ * per-agent least privilege.
26
+ */
27
+ agentId?: string;
28
+ /**
29
+ * Permission grants *claimed* for this actor — the actor analog of a
30
+ * user's resolved permissions. This is an untrusted, self-asserted hint:
31
+ * a provider enforcing real least privilege should resolve the actor's
32
+ * authoritative grants from a trusted source (e.g. a manifest or FGA,
33
+ * keyed by `agentId`) rather than trusting these values directly.
34
+ */
35
+ permissions?: MastraFGAPermissionInput[];
36
+ /** Additional provider-specific scope for the actor (e.g. tenant, environment). */
37
+ scope?: Record<string, string>;
38
+ };
11
39
  /**
12
40
  * Optional context for an authorization check.
13
41
  */
@@ -306,6 +334,25 @@ export interface IFGAProvider<TUser = unknown> {
306
334
  filterAccessible<T extends {
307
335
  id: string;
308
336
  }>(user: TUser, resources: T[], resourceType: string, permission: MastraFGAPermissionInput): Promise<T[]>;
337
+ /**
338
+ * Authorize a non-user (system / autonomous agent) actor.
339
+ *
340
+ * System actors bypass the user-centric `require()` path. Implement this
341
+ * method to opt in to provider-driven least privilege for those actors:
342
+ * decide whether the actor (identified by `actor.agentId` and constrained by
343
+ * `actor.permissions` / `actor.scope`) may perform `permission` on
344
+ * `resource`, and throw {@link FGADeniedError} to deny.
345
+ *
346
+ * When this method is not implemented, Mastra preserves the legacy
347
+ * trusted-actor bypass (allow after the tenant-scope check). Adding it is
348
+ * therefore fully backward compatible.
349
+ *
350
+ * @param actor - The system actor signal (`true` shorthand, or an object with
351
+ * `agentId`, `permissions`, `scope`).
352
+ * @param params - The resource and permission being attempted.
353
+ * @throws FGADeniedError if the actor may not perform the action.
354
+ */
355
+ requireActor?(actor: ActorSignal, params: FGACheckParams): Promise<void>;
309
356
  }
310
357
  /**
311
358
  * Extended FGA interface with write operations for managing resources and role assignments.
@@ -10,7 +10,7 @@
10
10
  * All known API resources.
11
11
  * Derived from SERVER_ROUTES paths in @mastra/server.
12
12
  */
13
- export declare const RESOURCES: readonly ["a2a", "agent-builder", "agents", "auth", "background-tasks", "channels", "datasets", "embedders", "experiments", "harness", "infrastructure", "logs", "mcp", "memory", "observability", "processor-providers", "processors", "schedules", "scores", "stored-agents", "stored-mcp-clients", "stored-prompt-blocks", "stored-scorers", "stored-skills", "stored-workspaces", "system", "tool-providers", "tools", "vector", "vectors", "workflows", "workspaces"];
13
+ export declare const RESOURCES: readonly ["a2a", "agent-builder", "agent-controller", "agents", "auth", "background-tasks", "channels", "datasets", "embedders", "experiments", "infrastructure", "logs", "mcp", "memory", "observability", "processor-providers", "processors", "schedules", "scores", "stored-agents", "stored-mcp-clients", "stored-prompt-blocks", "stored-scorers", "stored-skills", "stored-workspaces", "system", "tool-providers", "tools", "vector", "vectors", "workflows", "workspaces"];
14
14
  /**
15
15
  * Resource type union.
16
16
  */
@@ -54,6 +54,8 @@ export declare const PERMISSION_PATTERNS: {
54
54
  readonly 'a2a:*': "a2a:*";
55
55
  /** Full access to agent builder */
56
56
  readonly 'agent-builder:*': "agent-builder:*";
57
+ /** Full access to agent controller sessions */
58
+ readonly 'agent-controller:*': "agent-controller:*";
57
59
  /** Full access to agents */
58
60
  readonly 'agents:*': "agents:*";
59
61
  /** Full access to auth */
@@ -68,8 +70,6 @@ export declare const PERMISSION_PATTERNS: {
68
70
  readonly 'embedders:*': "embedders:*";
69
71
  /** Full access to experiments */
70
72
  readonly 'experiments:*': "experiments:*";
71
- /** Full access to harness sessions */
72
- readonly 'harness:*': "harness:*";
73
73
  /** Full access to infrastructure */
74
74
  readonly 'infrastructure:*': "infrastructure:*";
75
75
  /** Full access to logs */
@@ -124,6 +124,10 @@ export declare const PERMISSION_PATTERNS: {
124
124
  readonly 'agent-builder:read': "agent-builder:read";
125
125
  /** Create and modify agent builder */
126
126
  readonly 'agent-builder:write': "agent-builder:write";
127
+ /** Execute agent controller sessions */
128
+ readonly 'agent-controller:execute': "agent-controller:execute";
129
+ /** View agent controller sessions */
130
+ readonly 'agent-controller:read': "agent-controller:read";
127
131
  /** Create agents */
128
132
  readonly 'agents:create': "agents:create";
129
133
  /** Delete agents */
@@ -154,10 +158,6 @@ export declare const PERMISSION_PATTERNS: {
154
158
  readonly 'embedders:read': "embedders:read";
155
159
  /** View experiments */
156
160
  readonly 'experiments:read': "experiments:read";
157
- /** Execute harness sessions */
158
- readonly 'harness:execute': "harness:execute";
159
- /** View harness sessions */
160
- readonly 'harness:read': "harness:read";
161
161
  /** View infrastructure */
162
162
  readonly 'infrastructure:read': "infrastructure:read";
163
163
  /** View logs */
@@ -186,6 +186,8 @@ export declare const PERMISSION_PATTERNS: {
186
186
  readonly 'processors:execute': "processors:execute";
187
187
  /** View processors */
188
188
  readonly 'processors:read': "processors:read";
189
+ /** Delete schedules */
190
+ readonly 'schedules:delete': "schedules:delete";
189
191
  /** Execute schedules */
190
192
  readonly 'schedules:execute': "schedules:execute";
191
193
  /** View schedules */
@@ -303,7 +305,7 @@ export type PermissionPattern = keyof typeof PERMISSION_PATTERNS;
303
305
  /**
304
306
  * All valid resource:action permission combinations (excludes wildcards).
305
307
  */
306
- export declare const PERMISSIONS: readonly ["a2a:read", "a2a:write", "agent-builder:execute", "agent-builder:read", "agent-builder:write", "agents:create", "agents:delete", "agents:execute", "agents:read", "agents:write", "auth:read", "background-tasks:read", "channels:read", "channels:write", "datasets:delete", "datasets:execute", "datasets:read", "datasets:write", "embedders:read", "experiments:read", "harness:execute", "harness:read", "infrastructure:read", "logs:read", "mcp:execute", "mcp:read", "mcp:write", "memory:delete", "memory:execute", "memory:read", "memory:write", "observability:read", "observability:write", "processor-providers:read", "processors:execute", "processors:read", "schedules:execute", "schedules:read", "schedules:write", "scores:read", "scores:write", "stored-agents:delete", "stored-agents:publish", "stored-agents:read", "stored-agents:write", "stored-mcp-clients:delete", "stored-mcp-clients:publish", "stored-mcp-clients:read", "stored-mcp-clients:write", "stored-prompt-blocks:delete", "stored-prompt-blocks:publish", "stored-prompt-blocks:read", "stored-prompt-blocks:write", "stored-scorers:delete", "stored-scorers:publish", "stored-scorers:read", "stored-scorers:write", "stored-skills:delete", "stored-skills:publish", "stored-skills:read", "stored-skills:write", "stored-workspaces:delete", "stored-workspaces:read", "stored-workspaces:write", "system:read", "tool-providers:delete", "tool-providers:read", "tool-providers:write", "tools:execute", "tools:read", "vector:delete", "vector:execute", "vector:read", "vector:write", "vectors:read", "workflows:delete", "workflows:execute", "workflows:read", "workflows:write", "workspaces:delete", "workspaces:read", "workspaces:write"];
308
+ export declare const PERMISSIONS: readonly ["a2a:read", "a2a:write", "agent-builder:execute", "agent-builder:read", "agent-builder:write", "agent-controller:execute", "agent-controller:read", "agents:create", "agents:delete", "agents:execute", "agents:read", "agents:write", "auth:read", "background-tasks:read", "channels:read", "channels:write", "datasets:delete", "datasets:execute", "datasets:read", "datasets:write", "embedders:read", "experiments:read", "infrastructure:read", "logs:read", "mcp:execute", "mcp:read", "mcp:write", "memory:delete", "memory:execute", "memory:read", "memory:write", "observability:read", "observability:write", "processor-providers:read", "processors:execute", "processors:read", "schedules:delete", "schedules:execute", "schedules:read", "schedules:write", "scores:read", "scores:write", "stored-agents:delete", "stored-agents:publish", "stored-agents:read", "stored-agents:write", "stored-mcp-clients:delete", "stored-mcp-clients:publish", "stored-mcp-clients:read", "stored-mcp-clients:write", "stored-prompt-blocks:delete", "stored-prompt-blocks:publish", "stored-prompt-blocks:read", "stored-prompt-blocks:write", "stored-scorers:delete", "stored-scorers:publish", "stored-scorers:read", "stored-scorers:write", "stored-skills:delete", "stored-skills:publish", "stored-skills:read", "stored-skills:write", "stored-workspaces:delete", "stored-workspaces:read", "stored-workspaces:write", "system:read", "tool-providers:delete", "tool-providers:read", "tool-providers:write", "tools:execute", "tools:read", "vector:delete", "vector:execute", "vector:read", "vector:write", "vectors:read", "workflows:delete", "workflows:execute", "workflows:read", "workflows:write", "workspaces:delete", "workspaces:read", "workspaces:write"];
307
309
  /**
308
310
  * Specific permission type (e.g., 'agents:read', 'workflows:execute').
309
311
  */
@@ -325,6 +327,10 @@ export declare const MastraFGAPermissions: {
325
327
  readonly AGENT_BUILDER_READ: "agent-builder:read";
326
328
  /** Create and modify agent builder */
327
329
  readonly AGENT_BUILDER_WRITE: "agent-builder:write";
330
+ /** Execute agent controller sessions */
331
+ readonly AGENT_CONTROLLER_EXECUTE: "agent-controller:execute";
332
+ /** View agent controller sessions */
333
+ readonly AGENT_CONTROLLER_READ: "agent-controller:read";
328
334
  /** Create agents */
329
335
  readonly AGENTS_CREATE: "agents:create";
330
336
  /** Delete agents */
@@ -355,10 +361,6 @@ export declare const MastraFGAPermissions: {
355
361
  readonly EMBEDDERS_READ: "embedders:read";
356
362
  /** View experiments */
357
363
  readonly EXPERIMENTS_READ: "experiments:read";
358
- /** Execute harness sessions */
359
- readonly HARNESS_EXECUTE: "harness:execute";
360
- /** View harness sessions */
361
- readonly HARNESS_READ: "harness:read";
362
364
  /** View infrastructure */
363
365
  readonly INFRASTRUCTURE_READ: "infrastructure:read";
364
366
  /** View logs */
@@ -387,6 +389,8 @@ export declare const MastraFGAPermissions: {
387
389
  readonly PROCESSORS_EXECUTE: "processors:execute";
388
390
  /** View processors */
389
391
  readonly PROCESSORS_READ: "processors:read";
392
+ /** Delete schedules */
393
+ readonly SCHEDULES_DELETE: "schedules:delete";
390
394
  /** Execute schedules */
391
395
  readonly SCHEDULES_EXECUTE: "schedules:execute";
392
396
  /** View schedules */
@@ -270,6 +270,72 @@ export interface ICredentialsProvider<TUser extends User = User> {
270
270
  */
271
271
  isSignUpEnabled?(): boolean;
272
272
  }
273
+ /**
274
+ * Provider interface for organization membership management.
275
+ *
276
+ * Implement this interface to enable multi-tenant hosts (e.g. the Mastra Code
277
+ * web factory) to bootstrap a personal organization for new users and to
278
+ * authorize organization-level administrative mutations.
279
+ */
280
+ export interface IOrganizationsProvider {
281
+ /**
282
+ * Ensure the user belongs to an organization, bootstrapping a personal org
283
+ * on first use when they have none. Must be idempotent under
284
+ * concurrent/retried first logins.
285
+ *
286
+ * @param userId - Stable provider user id
287
+ * @returns The organization id, or `undefined` when the user genuinely
288
+ * stays no-org (bootstrap is best-effort)
289
+ */
290
+ ensureOrganization(userId: string): Promise<string | undefined>;
291
+ /**
292
+ * Whether the user holds an admin-equivalent role in the organization.
293
+ * Provider errors should resolve to `false` rather than throw.
294
+ *
295
+ * @param organizationId - Organization id
296
+ * @param userId - Stable provider user id
297
+ */
298
+ isOrganizationAdmin(organizationId: string, userId: string): Promise<boolean>;
299
+ }
300
+ /**
301
+ * Host-level context handed to {@link IAuthInit.init} once during server
302
+ * preparation. Storage-agnostic: hosts pass whatever database handle their
303
+ * storage backend exposes.
304
+ */
305
+ export interface AuthInitContext {
306
+ /**
307
+ * Database handle for providers that persist their own auth tables
308
+ * (e.g. better-auth). Shape is provider-defined; hosts pass their storage
309
+ * backend's auth database as-is.
310
+ */
311
+ database?: unknown;
312
+ /** Browser-facing origin (no trailing slash), e.g. `https://factory.acme.com`. */
313
+ publicUrl?: string;
314
+ /**
315
+ * Extra browser origins allowed to talk to the API (cross-origin SPA
316
+ * deploys). Providers that enforce their own origin allow-list (e.g.
317
+ * better-auth `trustedOrigins`) must honor these.
318
+ */
319
+ allowedOrigins?: string[];
320
+ }
321
+ /**
322
+ * Optional one-time initialization hook for auth providers. Hosts call
323
+ * `init()` once during preparation with host-level context (database, public
324
+ * origin) so providers can consume it without the deploy entry passing it
325
+ * twice. Providers should fail fast here for requirements only satisfiable at
326
+ * prepare time.
327
+ */
328
+ export interface IAuthInit {
329
+ init(ctx: AuthInitContext): Promise<void>;
330
+ }
331
+ /**
332
+ * Provider interface for handling raw auth HTTP requests. Providers that ship
333
+ * their own HTTP API surface (e.g. better-auth's `/api/auth/*` handler)
334
+ * implement this so hosts can mount it (typically under `/auth/api/*`).
335
+ */
336
+ export interface IAuthHttpHandler {
337
+ handleAuthRequest(request: Request): Promise<Response>;
338
+ }
273
339
  export * from './session/index.js';
274
340
  export * from './provider/index.js';
275
341
  export * from './types/index.js';
@@ -1,5 +1,5 @@
1
1
  import { MastraBase } from '../_types/@internal_core/dist/base/index.d.ts';
2
- import type { CredentialsResult, ISSOProvider, ISessionProvider, IUserProvider, Session, SSOCallbackResult, SSOLoginConfig, User } from '..';
2
+ import type { CredentialsResult, IAuthHttpHandler, IAuthInit, ICredentialsProvider, IOrganizationsProvider, ISSOProvider, ISessionProvider, IUserProvider, Session, SSOCallbackResult, SSOLoginConfig, User } from '..';
3
3
  import type { AuthorizeUserFn, MastraAuthConfig, MastraAuthRequest } from '../types/index.js';
4
4
  export interface MastraAuthProviderOptions<TUser = unknown> {
5
5
  name?: string;
@@ -14,7 +14,44 @@ export interface MastraAuthProviderOptions<TUser = unknown> {
14
14
  */
15
15
  public?: MastraAuthConfig['public'];
16
16
  }
17
- export declare abstract class MastraAuthProvider<TUser = unknown> extends MastraBase {
17
+ /**
18
+ * Structural description of the public surface of a `MastraAuthProvider`.
19
+ *
20
+ * Auth provider packages bundle their own copy of the `MastraAuthProvider`
21
+ * declaration, so provider class types cannot be compared nominally across
22
+ * package boundaries — `#private`/`protected` members would make otherwise
23
+ * identical copies mutually unassignable. Positions that accept user-supplied
24
+ * providers (e.g. `server.auth`, `CompositeAuth`) accept this interface
25
+ * instead of the class.
26
+ *
27
+ * Note: methods intentionally use method syntax (not property syntax) so they
28
+ * are checked bivariantly — providers with a narrower `TUser` must remain
29
+ * assignable to `IMastraAuthProvider<unknown>`.
30
+ */
31
+ export interface IMastraAuthProvider<TUser = unknown> {
32
+ name?: string;
33
+ /**
34
+ * Protected paths for the auth provider
35
+ */
36
+ protected?: MastraAuthConfig['protected'];
37
+ /**
38
+ * Public paths for the auth provider
39
+ */
40
+ public?: MastraAuthConfig['public'];
41
+ /**
42
+ * Authenticate a token and return the payload
43
+ */
44
+ authenticateToken(token: string, request: MastraAuthRequest): Promise<TUser | null>;
45
+ /**
46
+ * Authorize a user for a path and method
47
+ */
48
+ authorizeUser(user: TUser, request: MastraAuthRequest): Promise<boolean> | boolean;
49
+ /**
50
+ * Map an authenticated user to a memory resource id
51
+ */
52
+ mapUserToResourceId?(user: TUser): string | undefined | null;
53
+ }
54
+ export declare abstract class MastraAuthProvider<TUser = unknown> extends MastraBase implements IMastraAuthProvider<TUser> {
18
55
  protected?: MastraAuthConfig['protected'];
19
56
  public?: MastraAuthConfig['public'];
20
57
  mapUserToResourceId?(user: TUser): string | undefined | null;
@@ -35,11 +72,18 @@ export declare abstract class MastraAuthProvider<TUser = unknown> extends Mastra
35
72
  abstract authorizeUser(user: TUser, request: MastraAuthRequest): Promise<boolean> | boolean;
36
73
  protected registerOptions(opts?: MastraAuthProviderOptions<TUser>): void;
37
74
  }
75
+ export declare function isSSOProvider(p: unknown): p is ISSOProvider;
76
+ export declare function isSessionProvider(p: unknown): p is ISessionProvider;
77
+ export declare function isUserProvider(p: unknown): p is IUserProvider;
78
+ export declare function isCredentialsProvider(p: unknown): p is ICredentialsProvider;
79
+ export declare function isOrganizationsProvider(p: unknown): p is IOrganizationsProvider;
80
+ export declare function isAuthHttpHandler(p: unknown): p is IAuthHttpHandler;
81
+ export declare function hasAuthInit(p: unknown): p is IAuthInit;
38
82
  export declare class CompositeAuth extends MastraAuthProvider implements ISSOProvider<User>, ISessionProvider<Session>, IUserProvider<User> {
39
83
  private providers;
40
84
  private authenticatedProviderByObject;
41
85
  private authenticatedProviderByPrimitive;
42
- constructor(providers: MastraAuthProvider[]);
86
+ constructor(providers: IMastraAuthProvider[]);
43
87
  private findProvider;
44
88
  private rememberAuthenticatedProvider;
45
89
  private takeAuthenticatedProvider;
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // ../../packages/_internals/auth/dist/chunk-JCX4XB6F.js
3
+ // ../../packages/_internals/auth/dist/chunk-LZCWL5CT.js
4
4
  var RESOURCE_EXPANSIONS = {
5
5
  stored: [
6
6
  "stored-agents",
@@ -229,7 +229,7 @@ var MastraBase = class {
229
229
  }
230
230
  };
231
231
 
232
- // ../../packages/_internals/auth/dist/chunk-XG6GH2VJ.js
232
+ // ../../packages/_internals/auth/dist/chunk-NJOKT6V5.js
233
233
  var MastraAuthProvider = class extends MastraBase {
234
234
  protected;
235
235
  public;
@@ -272,6 +272,23 @@ var MastraAuthStudio = class extends MastraAuthProvider {
272
272
  organizationId;
273
273
  useProductionCookies;
274
274
  cookieDomain;
275
+ /**
276
+ * `userId → sealed session cookie` cache. The `IOrganizationsProvider`
277
+ * interface only hands us a `userId`, but the shared API's org endpoints are
278
+ * cookie-authenticated — so we remember the cookie last seen for a user
279
+ * inside `verifySessionCookie` and reuse it here. Kept small: bounded to
280
+ * the last 1000 users, LRU-evicted on insert.
281
+ */
282
+ userSessionCookies = /* @__PURE__ */ new Map();
283
+ maxCachedSessions = 1e3;
284
+ /**
285
+ * In-flight `ensureOrganization` promises keyed by userId. Concurrent calls
286
+ * for the same brand-new user (multiple tabs, parallel requests) would
287
+ * otherwise all see "no org" from `GET /auth/me` and each fire
288
+ * `POST /auth/orgs`, creating duplicate personal organizations. The first
289
+ * caller's promise is reused by every follower until it settles.
290
+ */
291
+ organizationBootstrapInFlight = /* @__PURE__ */ new Map();
275
292
  constructor(options) {
276
293
  super({ name: "mastra-studio", ...options });
277
294
  this.sharedApiUrl = options?.sharedApiUrl || process.env.MASTRA_SHARED_API_URL || "http://localhost:3010/v1";
@@ -510,8 +527,127 @@ var MastraAuthStudio = class extends MastraAuthProvider {
510
527
  return null;
511
528
  }
512
529
  // ---------------------------------------------------------------------------
530
+ // IOrganizationsProvider
531
+ // ---------------------------------------------------------------------------
532
+ /**
533
+ * Ensure the user belongs to an organization, bootstrapping a personal org
534
+ * on first use when they have none.
535
+ *
536
+ * Because the shared API's org endpoints are cookie-authenticated but this
537
+ * method only receives a userId, we look up the user's sealed session cookie
538
+ * from the {@link userSessionCookies} cache populated by
539
+ * {@link verifySessionCookie}. If we have never seen a cookie for this user
540
+ * (e.g. bearer-token flow, or the cache was evicted), we skip bootstrap and
541
+ * return `undefined` — the caller keeps the user in their current no-org
542
+ * state and the next authenticated request retries.
543
+ *
544
+ * Best-effort: any shared-API failure returns `undefined` rather than
545
+ * throwing, mirroring `MastraAuthWorkos.ensureOrganization`.
546
+ */
547
+ async ensureOrganization(userId) {
548
+ const inFlight = this.organizationBootstrapInFlight.get(userId);
549
+ if (inFlight) return inFlight;
550
+ const bootstrap = this.doEnsureOrganization(userId).finally(() => {
551
+ this.organizationBootstrapInFlight.delete(userId);
552
+ });
553
+ this.organizationBootstrapInFlight.set(userId, bootstrap);
554
+ return bootstrap;
555
+ }
556
+ async doEnsureOrganization(userId) {
557
+ const sessionCookie = this.userSessionCookies.get(userId);
558
+ if (!sessionCookie) {
559
+ this.logger.debug("ensureOrganization: no cached session cookie for user; skipping bootstrap", { userId });
560
+ return void 0;
561
+ }
562
+ try {
563
+ const me = await this.fetchMe(sessionCookie);
564
+ if (me?.organizationId) return me.organizationId;
565
+ if (me?.memberOrgIds && me.memberOrgIds.length > 0) return me.memberOrgIds[0];
566
+ const orgName = me?.user?.email ? `${me.user.email}'s org` : `Personal (${userId})`;
567
+ const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, {
568
+ method: "POST",
569
+ headers: {
570
+ "Content-Type": "application/json",
571
+ Cookie: `${COOKIE_NAME}=${sessionCookie}`
572
+ },
573
+ body: JSON.stringify({ name: orgName })
574
+ });
575
+ if (!res.ok) {
576
+ this.logger.warn("ensureOrganization: shared API POST /auth/orgs returned non-OK", {
577
+ status: res.status,
578
+ userId
579
+ });
580
+ return void 0;
581
+ }
582
+ const data = await res.json();
583
+ return data.organization?.id;
584
+ } catch (error) {
585
+ this.logger.error("ensureOrganization: fetch to shared API failed", {
586
+ userId,
587
+ error: error instanceof Error ? { message: error.message, stack: error.stack } : String(error)
588
+ });
589
+ return void 0;
590
+ }
591
+ }
592
+ /**
593
+ * Whether the user holds an admin-equivalent role in the organization.
594
+ *
595
+ * Fast path: if the org matches the user's currently-active session org, we
596
+ * read the role directly from `/auth/me`. Cross-org path: we call
597
+ * `/auth/orgs` (which returns per-membership roles) and look up the target
598
+ * org. Any shared-API failure resolves to `false`.
599
+ */
600
+ async isOrganizationAdmin(organizationId, userId) {
601
+ const sessionCookie = this.userSessionCookies.get(userId);
602
+ if (!sessionCookie) return false;
603
+ try {
604
+ const me = await this.fetchMe(sessionCookie);
605
+ if (me?.organizationId === organizationId) {
606
+ return isAdminRole(me.role);
607
+ }
608
+ const res = await fetch(`${this.sharedApiUrl}/auth/orgs`, {
609
+ headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` }
610
+ });
611
+ if (!res.ok) return false;
612
+ const data = await res.json();
613
+ const membership = data.organizations?.find((o) => o.id === organizationId);
614
+ return isAdminRole(membership?.role ?? void 0);
615
+ } catch {
616
+ return false;
617
+ }
618
+ }
619
+ // ---------------------------------------------------------------------------
513
620
  // Internal helpers
514
621
  // ---------------------------------------------------------------------------
622
+ /**
623
+ * Record the sealed session cookie last seen for a user so
624
+ * {@link ensureOrganization} / {@link isOrganizationAdmin} can act on their
625
+ * behalf. LRU-evicted at {@link maxCachedSessions} entries.
626
+ */
627
+ rememberUserSession(userId, sessionCookie) {
628
+ this.userSessionCookies.delete(userId);
629
+ this.userSessionCookies.set(userId, sessionCookie);
630
+ if (this.userSessionCookies.size > this.maxCachedSessions) {
631
+ const oldest = this.userSessionCookies.keys().next().value;
632
+ if (oldest !== void 0) this.userSessionCookies.delete(oldest);
633
+ }
634
+ }
635
+ /**
636
+ * Fetch the shared API's `/auth/me` and return the raw response body, or
637
+ * `null` on any non-OK / network error. Split out so `ensureOrganization`
638
+ * and `isOrganizationAdmin` can reuse it without duplicating the shape.
639
+ */
640
+ async fetchMe(sessionCookie) {
641
+ try {
642
+ const res = await fetch(`${this.sharedApiUrl}/auth/me`, {
643
+ headers: { Cookie: `${COOKIE_NAME}=${sessionCookie}` }
644
+ });
645
+ if (!res.ok) return null;
646
+ return await res.json();
647
+ } catch {
648
+ return null;
649
+ }
650
+ }
515
651
  /**
516
652
  * Forward a sealed session cookie to the shared API's /auth/me endpoint
517
653
  * to validate it and get user info.
@@ -532,6 +668,7 @@ var MastraAuthStudio = class extends MastraAuthProvider {
532
668
  return null;
533
669
  }
534
670
  const data = await res.json();
671
+ this.rememberUserSession(data.user.id, sessionCookie);
535
672
  return {
536
673
  id: data.user.id,
537
674
  email: data.user.email,
@@ -591,6 +728,9 @@ function parseCookie(cookieHeader, name) {
591
728
  const match = cookieHeader.match(new RegExp(`${name}=([^;]+)`));
592
729
  return match?.[1] ?? null;
593
730
  }
731
+ function isAdminRole(role) {
732
+ return role === "admin" || role === "owner";
733
+ }
594
734
  function parseCookieFromHeader(setCookieHeader, name) {
595
735
  const parts = setCookieHeader.split(";");
596
736
  if (parts.length === 0) return null;