@mastra/auth-auth0 1.0.1 → 1.1.1-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,50 @@
1
1
  # @mastra/auth-auth0
2
2
 
3
+ ## 1.1.1-alpha.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Security remediation for the 2026-06-17 "easy-day-js" supply-chain incident. Patch bump to publish clean versions and move the `latest` dist-tag forward, superseding the compromised versions that declared the malicious `easy-day-js` dependency. ([#18056](https://github.com/mastra-ai/mastra/pull/18056))
8
+
9
+ - Updated dependencies [[`77a2351`](https://github.com/mastra-ai/mastra/commit/77a2351ee79296e360bce822cb3391f7cfd6489d)]:
10
+ - @mastra/core@1.43.1-alpha.0
11
+
12
+ ## 1.1.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Added full Studio authentication support for Auth0 users. ([#16658](https://github.com/mastra-ai/mastra/pull/16658))
17
+
18
+ **What's new:**
19
+ - **Studio SSO login** — your internal team can now sign in to Mastra Studio using their Auth0 accounts via OAuth 2.0/OIDC
20
+ - **JWT validation** — API requests with Auth0-issued JWTs are automatically validated
21
+ - **Session persistence** — Studio sessions are maintained with encrypted cookies (no need to log in repeatedly)
22
+ - **Secure logout** — proper RP-Initiated Logout support via Auth0's `/v2/logout` endpoint
23
+
24
+ **Setup:**
25
+ 1. Create a Regular Web Application in your Auth0 Dashboard
26
+ 2. Configure the auth provider with your Auth0 credentials
27
+
28
+ ```typescript
29
+ import { MastraAuthAuth0 } from '@mastra/auth-auth0';
30
+
31
+ const auth = new MastraAuthAuth0({
32
+ domain: 'your-tenant.auth0.com',
33
+ audience: 'https://your-api',
34
+ // For Studio SSO login:
35
+ clientId: process.env.AUTH0_CLIENT_ID,
36
+ clientSecret: process.env.AUTH0_CLIENT_SECRET,
37
+ session: { cookiePassword: process.env.AUTH0_COOKIE_PASSWORD },
38
+ });
39
+ ```
40
+
41
+ **Note:** This release includes updates to `@mastra/core` (ISSOProvider interface now supports async getLoginUrl) and `@mastra/server` (handles async login URLs). All three packages should be updated together.
42
+
43
+ ### Patch Changes
44
+
45
+ - Updated dependencies [[`de66bb0`](https://github.com/mastra-ai/mastra/commit/de66bb040570444c702ce4d8e1e228a5de2949cb), [`67bf8e2`](https://github.com/mastra-ai/mastra/commit/67bf8e206dfe583954d96015cf0d09f7ac50e45f), [`8216d05`](https://github.com/mastra-ai/mastra/commit/8216d0528d866eb9a07f5d4c87ea3bb1e1139b45), [`d18b23c`](https://github.com/mastra-ai/mastra/commit/d18b23c5e29dfc381e73e3c51fcf6c779afd1823), [`5eb94eb`](https://github.com/mastra-ai/mastra/commit/5eb94ebcf66d4e28c9e26d5821ac93379bab20a0), [`1fa3e12`](https://github.com/mastra-ai/mastra/commit/1fa3e123582b63cfe49de4ee52dc6a065e8d956a), [`f9ee2ac`](https://github.com/mastra-ai/mastra/commit/f9ee2ac661af584e61bc063ac208c9035cd752ef), [`c853d53`](https://github.com/mastra-ai/mastra/commit/c853d535d2df84ab89db1adb4c28900c54c9a2d2), [`d8df1f8`](https://github.com/mastra-ai/mastra/commit/d8df1f8e947e1966c9d4e54713df56d0d0d65226), [`9192ddb`](https://github.com/mastra-ai/mastra/commit/9192ddbced8949113b30de444cbe763f075b59f5), [`ae96523`](https://github.com/mastra-ai/mastra/commit/ae965231f562d9766b0c90c49a69fc68acaa031c), [`17d5a92`](https://github.com/mastra-ai/mastra/commit/17d5a9211aa293b4d4418de3de70dc0394d58101), [`5573693`](https://github.com/mastra-ai/mastra/commit/5573693b589822250e20dfe6cf66e9ff3bc96da8), [`ec4da8a`](https://github.com/mastra-ai/mastra/commit/ec4da8a09e0d2ab452c6ee2c786042ea826b77e5), [`adc44e1`](https://github.com/mastra-ai/mastra/commit/adc44e13c7e570b91e86b20ea7556e61d819db31), [`ed346c0`](https://github.com/mastra-ai/mastra/commit/ed346c0bee2d8496690a4e538bfba1e46894660f), [`c9ce1b2`](https://github.com/mastra-ai/mastra/commit/c9ce1b28d10871110648f9d7b6d76e880b9fa999), [`3ef01fd`](https://github.com/mastra-ai/mastra/commit/3ef01fd130b53d5bd4f828beb174e516a2eb1158), [`245a9a3`](https://github.com/mastra-ai/mastra/commit/245a9a315705fce17ddd980f78a92504b6615c4a), [`dc0b611`](https://github.com/mastra-ai/mastra/commit/dc0b6119b769bd00ee2c5df9259fb376fe63077a), [`38b5de8`](https://github.com/mastra-ai/mastra/commit/38b5de8e5d1d41a69522addf53d96f4b3a1d5bf0), [`dc0b611`](https://github.com/mastra-ai/mastra/commit/dc0b6119b769bd00ee2c5df9259fb376fe63077a), [`dd6a66e`](https://github.com/mastra-ai/mastra/commit/dd6a66ea0b32e0dea8059aec6b35d151e2c87dc4), [`d785c59`](https://github.com/mastra-ai/mastra/commit/d785c593b67fcb4cdc4fab9fdbde5f3b7665efc0), [`1fa3e12`](https://github.com/mastra-ai/mastra/commit/1fa3e123582b63cfe49de4ee52dc6a065e8d956a), [`8b984f4`](https://github.com/mastra-ai/mastra/commit/8b984f4361c202270ceb69257185c4756c9a7c56), [`bf08402`](https://github.com/mastra-ai/mastra/commit/bf084022374fa5d06ca70ed67a86dd64e379071b), [`81fe587`](https://github.com/mastra-ai/mastra/commit/81fe587275035715c1720ddf3fee0505cf053036), [`1fa3e12`](https://github.com/mastra-ai/mastra/commit/1fa3e123582b63cfe49de4ee52dc6a065e8d956a), [`403c438`](https://github.com/mastra-ai/mastra/commit/403c438e417278989ce247233d2c465b8d902cdd), [`f8ba195`](https://github.com/mastra-ai/mastra/commit/f8ba1954e27ee2b20586cc6cd9cf13c002c232f2)]:
46
+ - @mastra/core@1.43.0
47
+
3
48
  ## 1.0.1
4
49
 
5
50
  ### Patch Changes
package/LICENSE.md CHANGED
@@ -1,3 +1,18 @@
1
+ Portions of this software are licensed as follows:
2
+
3
+ - All content that resides under any directory named "ee/" within this
4
+ repository, including but not limited to:
5
+ - `packages/core/src/auth/ee/`
6
+ - `packages/server/src/server/auth/ee/`
7
+ is licensed under the license defined in `ee/LICENSE`.
8
+
9
+ - All third-party components incorporated into the Mastra Software are
10
+ licensed under the original license provided by the owner of the
11
+ applicable component.
12
+
13
+ - Content outside of the above-mentioned directories or restrictions is
14
+ available under the "Apache License 2.0" as defined below.
15
+
1
16
  # Apache License 2.0
2
17
 
3
18
  Copyright (c) 2025 Kepler Software, Inc.
package/dist/index.cjs CHANGED
@@ -1,164 +1,135 @@
1
1
  'use strict';
2
2
 
3
+ var server = require('@mastra/core/server');
3
4
  var jose = require('jose');
4
5
 
5
- // ../../packages/core/dist/chunk-X2WMFSPB.js
6
- var RegisteredLogger = {
7
- LLM: "LLM"};
8
- var LogLevel = {
9
- DEBUG: "debug",
10
- INFO: "info",
11
- WARN: "warn",
12
- ERROR: "error"};
13
- var MastraLogger = class {
14
- name;
15
- level;
16
- transports;
17
- constructor(options = {}) {
18
- this.name = options.name || "Mastra";
19
- this.level = options.level || LogLevel.ERROR;
20
- this.transports = new Map(Object.entries(options.transports || {}));
21
- }
22
- getTransports() {
23
- return this.transports;
24
- }
25
- trackException(_error) {
26
- }
27
- async listLogs(transportId, params) {
28
- if (!transportId || !this.transports.has(transportId)) {
29
- return { logs: [], total: 0, page: params?.page ?? 1, perPage: params?.perPage ?? 100, hasMore: false };
30
- }
31
- return this.transports.get(transportId).listLogs(params) ?? {
32
- logs: [],
33
- total: 0,
34
- page: params?.page ?? 1,
35
- perPage: params?.perPage ?? 100,
36
- hasMore: false
37
- };
38
- }
39
- async listLogsByRunId({
40
- transportId,
41
- runId,
42
- fromDate,
43
- toDate,
44
- logLevel,
45
- filters,
46
- page,
47
- perPage
48
- }) {
49
- if (!transportId || !this.transports.has(transportId) || !runId) {
50
- return { logs: [], total: 0, page: page ?? 1, perPage: perPage ?? 100, hasMore: false };
51
- }
52
- return this.transports.get(transportId).listLogsByRunId({ runId, fromDate, toDate, logLevel, filters, page, perPage }) ?? {
53
- logs: [],
54
- total: 0,
55
- page: page ?? 1,
56
- perPage: perPage ?? 100,
57
- hasMore: false
58
- };
59
- }
60
- };
61
- var ConsoleLogger = class extends MastraLogger {
62
- constructor(options = {}) {
63
- super(options);
64
- }
65
- debug(message, ...args) {
66
- if (this.level === LogLevel.DEBUG) {
67
- console.info(message, ...args);
68
- }
69
- }
70
- info(message, ...args) {
71
- if (this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
72
- console.info(message, ...args);
73
- }
6
+ // src/index.ts
7
+ var DEFAULT_COOKIE_NAME = "auth0_session";
8
+ var DEFAULT_COOKIE_MAX_AGE = 86400;
9
+ var DEFAULT_SCOPES = ["openid", "profile", "email"];
10
+ var SALT_LENGTH = 16;
11
+ var IV_LENGTH = 12;
12
+ async function deriveKey(password, salt, usage) {
13
+ const encoder = new TextEncoder();
14
+ const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, [
15
+ "deriveBits",
16
+ "deriveKey"
17
+ ]);
18
+ return crypto.subtle.deriveKey(
19
+ { name: "PBKDF2", salt, iterations: 1e5, hash: "SHA-256" },
20
+ keyMaterial,
21
+ { name: "AES-GCM", length: 256 },
22
+ false,
23
+ [usage]
24
+ );
25
+ }
26
+ async function encryptSession(data, password) {
27
+ const encoder = new TextEncoder();
28
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
29
+ const key = await deriveKey(password, salt, "encrypt");
30
+ const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
31
+ const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(JSON.stringify(data)));
32
+ const combined = new Uint8Array(salt.length + iv.length + new Uint8Array(encrypted).length);
33
+ combined.set(salt);
34
+ combined.set(iv, salt.length);
35
+ combined.set(new Uint8Array(encrypted), salt.length + iv.length);
36
+ return btoa(String.fromCharCode(...combined));
37
+ }
38
+ async function decryptSession(encrypted, password) {
39
+ const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
40
+ if (combined.length < SALT_LENGTH + IV_LENGTH + 1) {
41
+ throw new Error("Invalid encrypted session data");
74
42
  }
75
- warn(message, ...args) {
76
- if (this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
77
- console.info(message, ...args);
78
- }
79
- }
80
- error(message, ...args) {
81
- if (this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
82
- console.error(message, ...args);
83
- }
43
+ const salt = combined.slice(0, SALT_LENGTH);
44
+ const iv = combined.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
45
+ const data = combined.slice(SALT_LENGTH + IV_LENGTH);
46
+ const key = await deriveKey(password, salt, "decrypt");
47
+ const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data);
48
+ return JSON.parse(new TextDecoder().decode(decrypted));
49
+ }
50
+ var STATE_TOKEN_EXPIRY_MS = 10 * 60 * 1e3;
51
+ function createStateToken(originalState, redirectUri, secret) {
52
+ const payload = {
53
+ s: originalState,
54
+ r: redirectUri,
55
+ e: Date.now() + STATE_TOKEN_EXPIRY_MS
56
+ };
57
+ const payloadB64 = btoa(JSON.stringify(payload));
58
+ const signature = hmacSign(payloadB64, secret);
59
+ return `${payloadB64}.${signature}`;
60
+ }
61
+ function verifyStateToken(stateToken, secret) {
62
+ const parts = stateToken.split(".");
63
+ if (parts.length !== 2) {
64
+ throw new Error("Invalid state token format");
84
65
  }
85
- async listLogs(_transportId, _params) {
86
- return { logs: [], total: 0, page: _params?.page ?? 1, perPage: _params?.perPage ?? 100, hasMore: false };
66
+ const [payloadB64, signature] = parts;
67
+ const expectedSig = hmacSign(payloadB64, secret);
68
+ if (!timingSafeEqual(signature, expectedSig)) {
69
+ throw new Error("Invalid or tampered state token");
87
70
  }
88
- async listLogsByRunId(_args) {
89
- return { logs: [], total: 0, page: _args.page ?? 1, perPage: _args.perPage ?? 100, hasMore: false };
90
- }
91
- };
92
-
93
- // ../../packages/core/dist/chunk-WCAFTXGK.js
94
- var MastraBase = class {
95
- component = RegisteredLogger.LLM;
96
- logger;
97
- name;
98
- #rawConfig;
99
- constructor({
100
- component,
101
- name,
102
- rawConfig
103
- }) {
104
- this.component = component || RegisteredLogger.LLM;
105
- this.name = name;
106
- this.#rawConfig = rawConfig;
107
- this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
71
+ let payload;
72
+ try {
73
+ payload = JSON.parse(atob(payloadB64));
74
+ } catch {
75
+ throw new Error("Invalid state token payload");
108
76
  }
109
- /**
110
- * Returns the raw storage configuration this primitive was created from,
111
- * or undefined if it was created from code.
112
- */
113
- toRawConfig() {
114
- return this.#rawConfig;
115
- }
116
- /**
117
- * Sets the raw storage configuration for this primitive.
118
- * @internal
119
- */
120
- __setRawConfig(rawConfig) {
121
- this.#rawConfig = rawConfig;
77
+ if (payload.e < Date.now()) {
78
+ throw new Error("State token has expired");
122
79
  }
123
- /**
124
- * Set the logger for the agent
125
- * @param logger
126
- */
127
- __setLogger(logger) {
128
- this.logger = logger;
129
- if (this.component !== RegisteredLogger.LLM) {
130
- this.logger.debug(`Logger updated [component=${this.component}] [name=${this.name}]`);
131
- }
80
+ return {
81
+ originalState: payload.s,
82
+ redirectUri: payload.r
83
+ };
84
+ }
85
+ function hmacSign(data, secret) {
86
+ const encoder = new TextEncoder();
87
+ const keyBytes = encoder.encode(secret);
88
+ const dataBytes = encoder.encode(data);
89
+ const combined = new Uint8Array(keyBytes.length + dataBytes.length + keyBytes.length);
90
+ combined.set(keyBytes);
91
+ combined.set(dataBytes, keyBytes.length);
92
+ combined.set(keyBytes, keyBytes.length + dataBytes.length);
93
+ let h1 = 2166136261;
94
+ let h2 = 16777619;
95
+ for (let i = 0; i < combined.length; i++) {
96
+ h1 ^= combined[i];
97
+ h1 = Math.imul(h1, 16777619);
98
+ h2 ^= combined[i];
99
+ h2 = Math.imul(h2, 2246822507);
132
100
  }
133
- };
134
-
135
- // ../../packages/core/dist/server/index.js
136
- var MastraAuthProvider = class extends MastraBase {
137
- protected;
138
- public;
139
- constructor(options) {
140
- super({ component: "AUTH", name: options?.name });
141
- if (options?.authorizeUser) {
142
- this.authorizeUser = options.authorizeUser.bind(this);
143
- }
144
- this.protected = options?.protected;
145
- this.public = options?.public;
101
+ const sigBytes = new Uint8Array(8);
102
+ const view = new DataView(sigBytes.buffer);
103
+ view.setUint32(0, h1 >>> 0);
104
+ view.setUint32(4, h2 >>> 0);
105
+ return btoa(String.fromCharCode(...sigBytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
106
+ }
107
+ function timingSafeEqual(a, b) {
108
+ if (a.length !== b.length) {
109
+ return false;
146
110
  }
147
- registerOptions(opts) {
148
- if (opts?.authorizeUser) {
149
- this.authorizeUser = opts.authorizeUser.bind(this);
150
- }
151
- if (opts?.protected) {
152
- this.protected = opts.protected;
153
- }
154
- if (opts?.public) {
155
- this.public = opts.public;
156
- }
111
+ let result = 0;
112
+ for (let i = 0; i < a.length; i++) {
113
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
157
114
  }
158
- };
159
- var MastraAuthAuth0 = class extends MastraAuthProvider {
115
+ return result === 0;
116
+ }
117
+ function escapeRegex(str) {
118
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
119
+ }
120
+ var MastraAuthAuth0 = class extends server.MastraAuthProvider {
160
121
  domain;
161
122
  audience;
123
+ // SSO fields
124
+ clientId;
125
+ clientSecret;
126
+ _redirectUri;
127
+ scopes;
128
+ cookieName;
129
+ cookieMaxAge;
130
+ cookiePassword;
131
+ secureCookies;
132
+ ssoEnabled;
162
133
  constructor(options) {
163
134
  super({ name: options?.name ?? "auth0" });
164
135
  const domain = options?.domain ?? process.env.AUTH0_DOMAIN;
@@ -170,9 +141,43 @@ var MastraAuthAuth0 = class extends MastraAuthProvider {
170
141
  }
171
142
  this.domain = domain;
172
143
  this.audience = audience;
144
+ const clientId = options?.clientId ?? process.env.AUTH0_CLIENT_ID;
145
+ const clientSecret = options?.clientSecret ?? process.env.AUTH0_CLIENT_SECRET;
146
+ const redirectUri = options?.redirectUri ?? process.env.AUTH0_REDIRECT_URI;
147
+ const cookiePassword = options?.session?.cookiePassword ?? process.env.AUTH0_COOKIE_PASSWORD ?? crypto.randomUUID() + crypto.randomUUID();
148
+ this.clientId = clientId ?? null;
149
+ this.clientSecret = clientSecret ?? null;
150
+ this._redirectUri = redirectUri ?? null;
151
+ this.scopes = options?.scopes ?? DEFAULT_SCOPES;
152
+ this.cookieName = options?.session?.cookieName ?? DEFAULT_COOKIE_NAME;
153
+ this.cookieMaxAge = options?.session?.cookieMaxAge ?? DEFAULT_COOKIE_MAX_AGE;
154
+ this.cookiePassword = cookiePassword;
155
+ this.secureCookies = options?.session?.secureCookies ?? process.env.NODE_ENV === "production";
156
+ this.ssoEnabled = !!(clientId && clientSecret);
157
+ if (this.ssoEnabled) {
158
+ if (cookiePassword.length < 32) {
159
+ throw new Error(
160
+ "Cookie password must be at least 32 characters for SSO. Set AUTH0_COOKIE_PASSWORD environment variable."
161
+ );
162
+ }
163
+ if (!options?.session?.cookiePassword && !process.env.AUTH0_COOKIE_PASSWORD) {
164
+ console.warn(
165
+ "[MastraAuthAuth0] No cookie password set \u2014 using auto-generated value. Sessions will not survive restarts. Set AUTH0_COOKIE_PASSWORD for production use."
166
+ );
167
+ }
168
+ this._attachSSOProvider();
169
+ this._attachSessionProvider();
170
+ }
173
171
  this.registerOptions(options);
174
172
  }
175
- async authenticateToken(token) {
173
+ // ============================================================================
174
+ // MastraAuthProvider Implementation
175
+ // ============================================================================
176
+ async authenticateToken(token, request) {
177
+ if (this.ssoEnabled && request) {
178
+ const sessionUser = await this.getUserFromSessionCookie(request);
179
+ if (sessionUser) return sessionUser;
180
+ }
176
181
  if (!token || typeof token !== "string") {
177
182
  return null;
178
183
  }
@@ -189,12 +194,249 @@ var MastraAuthAuth0 = class extends MastraAuthProvider {
189
194
  }
190
195
  }
191
196
  async authorizeUser(user) {
192
- if (!user || !user.sub) return false;
197
+ if (!user || !(user.sub || user.id)) return false;
193
198
  if (user.exp && user.exp * 1e3 < Date.now()) {
194
199
  return false;
195
200
  }
196
201
  return true;
197
202
  }
203
+ // ============================================================================
204
+ // IUserProvider Implementation
205
+ // ============================================================================
206
+ /**
207
+ * Extract the bearer token from the request's Authorization header.
208
+ */
209
+ extractToken(request) {
210
+ const authHeader = request.headers.get("Authorization");
211
+ if (authHeader) {
212
+ const token = authHeader.replace(/^Bearer\s+/i, "").trim();
213
+ if (token) return token;
214
+ }
215
+ return null;
216
+ }
217
+ async getCurrentUser(request) {
218
+ if (this.ssoEnabled) {
219
+ const sessionUser = await this.getUserFromSessionCookie(request);
220
+ if (sessionUser) return sessionUser;
221
+ }
222
+ const token = this.extractToken(request);
223
+ if (!token) return null;
224
+ try {
225
+ const payload = await this.authenticateToken(token);
226
+ if (!payload?.sub) return null;
227
+ return {
228
+ id: payload.sub,
229
+ email: payload.email ?? void 0,
230
+ name: payload.name ?? void 0,
231
+ avatarUrl: payload.picture ?? void 0
232
+ };
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+ async getUser(userId) {
238
+ return {
239
+ id: userId
240
+ };
241
+ }
242
+ getUserProfileUrl(user) {
243
+ return `/user/${user.id}`;
244
+ }
245
+ // ============================================================================
246
+ // Helper Methods
247
+ // ============================================================================
248
+ /**
249
+ * Check if SSO is enabled (OAuth credentials are configured).
250
+ */
251
+ isSSOEnabled() {
252
+ return this.ssoEnabled;
253
+ }
254
+ /**
255
+ * Build consistent cookie attribute string for set/clear operations.
256
+ */
257
+ cookieFlags(maxAge) {
258
+ const flags = `Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`;
259
+ return this.secureCookies ? `${flags}; Secure` : flags;
260
+ }
261
+ /**
262
+ * Extract user from the encrypted SSO session cookie.
263
+ */
264
+ async getUserFromSessionCookie(request) {
265
+ const cookie = "header" in request && typeof request.header === "function" ? request.header("cookie") : request.headers?.get("cookie");
266
+ if (!cookie) return null;
267
+ const match = cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegex(this.cookieName)}=([^;]+)`));
268
+ if (!match?.[1]) return null;
269
+ try {
270
+ const sessionData = await decryptSession(decodeURIComponent(match[1]), this.cookiePassword);
271
+ if (sessionData.expiresAt < Date.now()) {
272
+ return null;
273
+ }
274
+ return sessionData.user;
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ // ============================================================================
280
+ // Dynamic ISSOProvider attachment (only when OAuth is configured)
281
+ // ============================================================================
282
+ /**
283
+ * Dynamically attach ISSOProvider methods to this instance.
284
+ * This ensures duck-typing detection only finds these methods when SSO is configured.
285
+ */
286
+ _attachSSOProvider() {
287
+ const self = this;
288
+ this.getLoginUrl = function(redirectUri, state) {
289
+ const actualRedirectUri = redirectUri ?? self._redirectUri;
290
+ if (!actualRedirectUri) {
291
+ throw new Error("Redirect URI is required for SSO. Set AUTH0_REDIRECT_URI or pass redirectUri option.");
292
+ }
293
+ const signedState = createStateToken(state, actualRedirectUri, self.cookiePassword);
294
+ const params = new URLSearchParams({
295
+ client_id: self.clientId,
296
+ response_type: "code",
297
+ scope: self.scopes.join(" "),
298
+ redirect_uri: actualRedirectUri,
299
+ state: signedState
300
+ });
301
+ return `https://${self.domain}/authorize?${params.toString()}`;
302
+ };
303
+ this.handleCallback = async function(code, signedState) {
304
+ const { redirectUri } = verifyStateToken(signedState, self.cookiePassword);
305
+ const tokenResponse = await fetch(`https://${self.domain}/oauth/token`, {
306
+ method: "POST",
307
+ headers: { "Content-Type": "application/json" },
308
+ body: JSON.stringify({
309
+ grant_type: "authorization_code",
310
+ client_id: self.clientId,
311
+ client_secret: self.clientSecret,
312
+ code,
313
+ redirect_uri: redirectUri
314
+ }),
315
+ signal: AbortSignal.timeout(1e4)
316
+ // 10 second timeout
317
+ });
318
+ if (!tokenResponse.ok) {
319
+ const error = await tokenResponse.text();
320
+ throw new Error(`Token exchange failed: ${error}`);
321
+ }
322
+ const tokens = await tokenResponse.json();
323
+ let user;
324
+ if (tokens.id_token) {
325
+ try {
326
+ const JWKS = jose.createRemoteJWKSet(new URL(`https://${self.domain}/.well-known/jwks.json`));
327
+ const { payload } = await jose.jwtVerify(tokens.id_token, JWKS, {
328
+ issuer: `https://${self.domain}/`,
329
+ audience: self.clientId
330
+ // Validate token was issued for this client
331
+ });
332
+ user = {
333
+ id: payload.sub,
334
+ email: payload.email ?? void 0,
335
+ name: payload.name ?? void 0,
336
+ avatarUrl: payload.picture ?? void 0
337
+ };
338
+ } catch {
339
+ user = await self._fetchUserInfo(tokens.access_token);
340
+ }
341
+ } else {
342
+ user = await self._fetchUserInfo(tokens.access_token);
343
+ }
344
+ const sessionData = {
345
+ user,
346
+ expiresAt: Date.now() + self.cookieMaxAge * 1e3
347
+ };
348
+ const encryptedSession = await encryptSession(sessionData, self.cookiePassword);
349
+ const cookieValue = `${self.cookieName}=${encodeURIComponent(encryptedSession)}; ${self.cookieFlags(self.cookieMaxAge)}`;
350
+ return {
351
+ user,
352
+ tokens: {
353
+ accessToken: tokens.access_token,
354
+ refreshToken: tokens.refresh_token,
355
+ idToken: tokens.id_token,
356
+ expiresAt: new Date(Date.now() + tokens.expires_in * 1e3)
357
+ },
358
+ cookies: [cookieValue]
359
+ };
360
+ };
361
+ this.getLoginButtonConfig = function() {
362
+ return {
363
+ provider: "auth0",
364
+ text: "Sign in with Auth0",
365
+ description: "Sign in using your Auth0 account"
366
+ };
367
+ };
368
+ this.getLoginCookies = function(_state) {
369
+ return [];
370
+ };
371
+ this.getLogoutUrl = async function(redirectUri, _request) {
372
+ const params = new URLSearchParams({
373
+ client_id: self.clientId,
374
+ returnTo: redirectUri
375
+ });
376
+ return `https://${self.domain}/v2/logout?${params.toString()}`;
377
+ };
378
+ }
379
+ /**
380
+ * Fetch user info from Auth0's /userinfo endpoint.
381
+ */
382
+ async _fetchUserInfo(accessToken) {
383
+ const userInfoResponse = await fetch(`https://${this.domain}/userinfo`, {
384
+ headers: { Authorization: `Bearer ${accessToken}` },
385
+ signal: AbortSignal.timeout(1e4)
386
+ // 10 second timeout
387
+ });
388
+ if (!userInfoResponse.ok) {
389
+ throw new Error("Failed to fetch user info from Auth0");
390
+ }
391
+ const userInfo = await userInfoResponse.json();
392
+ return {
393
+ id: userInfo.sub,
394
+ email: userInfo.email,
395
+ name: userInfo.name,
396
+ avatarUrl: userInfo.picture
397
+ };
398
+ }
399
+ // ============================================================================
400
+ // Dynamic ISessionProvider attachment (only when OAuth is configured)
401
+ // ============================================================================
402
+ /**
403
+ * Dynamically attach ISessionProvider methods to this instance.
404
+ */
405
+ _attachSessionProvider() {
406
+ const self = this;
407
+ this.createSession = async function(userId, metadata) {
408
+ const now = /* @__PURE__ */ new Date();
409
+ return {
410
+ id: crypto.randomUUID(),
411
+ userId,
412
+ createdAt: now,
413
+ expiresAt: new Date(now.getTime() + self.cookieMaxAge * 1e3),
414
+ metadata
415
+ };
416
+ };
417
+ this.validateSession = async function(_sessionId) {
418
+ return null;
419
+ };
420
+ this.destroySession = async function(_sessionId) {
421
+ };
422
+ this.refreshSession = async function(_sessionId) {
423
+ return null;
424
+ };
425
+ this.getSessionIdFromRequest = function(request) {
426
+ const cookie = request.headers.get("Cookie");
427
+ if (!cookie) return null;
428
+ const match = cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegex(self.cookieName)}=([^;]+)`));
429
+ return match?.[1] ? decodeURIComponent(match[1]) : null;
430
+ };
431
+ this.getSessionHeaders = function(_session) {
432
+ return {};
433
+ };
434
+ this.getClearSessionHeaders = function() {
435
+ return {
436
+ "Set-Cookie": `${self.cookieName}=; ${self.cookieFlags(0)}`
437
+ };
438
+ };
439
+ }
198
440
  };
199
441
 
200
442
  exports.MastraAuthAuth0 = MastraAuthAuth0;