@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 +14 -0
- package/dist/_types/@internal_auth/dist/ee/fga-check.d.ts +2 -5
- package/dist/_types/@internal_auth/dist/ee/interfaces/fga.d.ts +47 -0
- package/dist/_types/@internal_auth/dist/ee/interfaces/permissions.generated.d.ts +16 -12
- package/dist/_types/@internal_auth/dist/index.d.ts +66 -0
- package/dist/_types/@internal_auth/dist/provider/index.d.ts +47 -3
- package/dist/index.cjs +142 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +57 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +142 -2
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
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
|
|
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", "
|
|
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", "
|
|
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
|
-
|
|
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:
|
|
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-
|
|
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-
|
|
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;
|