@mastra/auth-auth0 1.0.1-alpha.0 → 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.
- package/CHANGELOG.md +43 -0
- package/LICENSE.md +15 -0
- package/dist/index.cjs +391 -149
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +110 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +390 -148
- package/dist/index.js.map +1 -1
- package/package.json +14 -11
package/dist/index.js
CHANGED
|
@@ -1,162 +1,133 @@
|
|
|
1
|
+
import { MastraAuthProvider } from '@mastra/core/server';
|
|
1
2
|
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
2
3
|
|
|
3
|
-
//
|
|
4
|
-
var
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
runId,
|
|
40
|
-
fromDate,
|
|
41
|
-
toDate,
|
|
42
|
-
logLevel,
|
|
43
|
-
filters,
|
|
44
|
-
page,
|
|
45
|
-
perPage
|
|
46
|
-
}) {
|
|
47
|
-
if (!transportId || !this.transports.has(transportId) || !runId) {
|
|
48
|
-
return { logs: [], total: 0, page: page ?? 1, perPage: perPage ?? 100, hasMore: false };
|
|
49
|
-
}
|
|
50
|
-
return this.transports.get(transportId).listLogsByRunId({ runId, fromDate, toDate, logLevel, filters, page, perPage }) ?? {
|
|
51
|
-
logs: [],
|
|
52
|
-
total: 0,
|
|
53
|
-
page: page ?? 1,
|
|
54
|
-
perPage: perPage ?? 100,
|
|
55
|
-
hasMore: false
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
var ConsoleLogger = class extends MastraLogger {
|
|
60
|
-
constructor(options = {}) {
|
|
61
|
-
super(options);
|
|
62
|
-
}
|
|
63
|
-
debug(message, ...args) {
|
|
64
|
-
if (this.level === LogLevel.DEBUG) {
|
|
65
|
-
console.info(message, ...args);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
info(message, ...args) {
|
|
69
|
-
if (this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) {
|
|
70
|
-
console.info(message, ...args);
|
|
71
|
-
}
|
|
4
|
+
// src/index.ts
|
|
5
|
+
var DEFAULT_COOKIE_NAME = "auth0_session";
|
|
6
|
+
var DEFAULT_COOKIE_MAX_AGE = 86400;
|
|
7
|
+
var DEFAULT_SCOPES = ["openid", "profile", "email"];
|
|
8
|
+
var SALT_LENGTH = 16;
|
|
9
|
+
var IV_LENGTH = 12;
|
|
10
|
+
async function deriveKey(password, salt, usage) {
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
const keyMaterial = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, [
|
|
13
|
+
"deriveBits",
|
|
14
|
+
"deriveKey"
|
|
15
|
+
]);
|
|
16
|
+
return crypto.subtle.deriveKey(
|
|
17
|
+
{ name: "PBKDF2", salt, iterations: 1e5, hash: "SHA-256" },
|
|
18
|
+
keyMaterial,
|
|
19
|
+
{ name: "AES-GCM", length: 256 },
|
|
20
|
+
false,
|
|
21
|
+
[usage]
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
async function encryptSession(data, password) {
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
27
|
+
const key = await deriveKey(password, salt, "encrypt");
|
|
28
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
29
|
+
const encrypted = await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, encoder.encode(JSON.stringify(data)));
|
|
30
|
+
const combined = new Uint8Array(salt.length + iv.length + new Uint8Array(encrypted).length);
|
|
31
|
+
combined.set(salt);
|
|
32
|
+
combined.set(iv, salt.length);
|
|
33
|
+
combined.set(new Uint8Array(encrypted), salt.length + iv.length);
|
|
34
|
+
return btoa(String.fromCharCode(...combined));
|
|
35
|
+
}
|
|
36
|
+
async function decryptSession(encrypted, password) {
|
|
37
|
+
const combined = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
|
|
38
|
+
if (combined.length < SALT_LENGTH + IV_LENGTH + 1) {
|
|
39
|
+
throw new Error("Invalid encrypted session data");
|
|
72
40
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
41
|
+
const salt = combined.slice(0, SALT_LENGTH);
|
|
42
|
+
const iv = combined.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
|
|
43
|
+
const data = combined.slice(SALT_LENGTH + IV_LENGTH);
|
|
44
|
+
const key = await deriveKey(password, salt, "decrypt");
|
|
45
|
+
const decrypted = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, data);
|
|
46
|
+
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
47
|
+
}
|
|
48
|
+
var STATE_TOKEN_EXPIRY_MS = 10 * 60 * 1e3;
|
|
49
|
+
function createStateToken(originalState, redirectUri, secret) {
|
|
50
|
+
const payload = {
|
|
51
|
+
s: originalState,
|
|
52
|
+
r: redirectUri,
|
|
53
|
+
e: Date.now() + STATE_TOKEN_EXPIRY_MS
|
|
54
|
+
};
|
|
55
|
+
const payloadB64 = btoa(JSON.stringify(payload));
|
|
56
|
+
const signature = hmacSign(payloadB64, secret);
|
|
57
|
+
return `${payloadB64}.${signature}`;
|
|
58
|
+
}
|
|
59
|
+
function verifyStateToken(stateToken, secret) {
|
|
60
|
+
const parts = stateToken.split(".");
|
|
61
|
+
if (parts.length !== 2) {
|
|
62
|
+
throw new Error("Invalid state token format");
|
|
82
63
|
}
|
|
83
|
-
|
|
84
|
-
|
|
64
|
+
const [payloadB64, signature] = parts;
|
|
65
|
+
const expectedSig = hmacSign(payloadB64, secret);
|
|
66
|
+
if (!timingSafeEqual(signature, expectedSig)) {
|
|
67
|
+
throw new Error("Invalid or tampered state token");
|
|
85
68
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ../../packages/core/dist/chunk-WCAFTXGK.js
|
|
92
|
-
var MastraBase = class {
|
|
93
|
-
component = RegisteredLogger.LLM;
|
|
94
|
-
logger;
|
|
95
|
-
name;
|
|
96
|
-
#rawConfig;
|
|
97
|
-
constructor({
|
|
98
|
-
component,
|
|
99
|
-
name,
|
|
100
|
-
rawConfig
|
|
101
|
-
}) {
|
|
102
|
-
this.component = component || RegisteredLogger.LLM;
|
|
103
|
-
this.name = name;
|
|
104
|
-
this.#rawConfig = rawConfig;
|
|
105
|
-
this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
|
|
69
|
+
let payload;
|
|
70
|
+
try {
|
|
71
|
+
payload = JSON.parse(atob(payloadB64));
|
|
72
|
+
} catch {
|
|
73
|
+
throw new Error("Invalid state token payload");
|
|
106
74
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
* or undefined if it was created from code.
|
|
110
|
-
*/
|
|
111
|
-
toRawConfig() {
|
|
112
|
-
return this.#rawConfig;
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Sets the raw storage configuration for this primitive.
|
|
116
|
-
* @internal
|
|
117
|
-
*/
|
|
118
|
-
__setRawConfig(rawConfig) {
|
|
119
|
-
this.#rawConfig = rawConfig;
|
|
75
|
+
if (payload.e < Date.now()) {
|
|
76
|
+
throw new Error("State token has expired");
|
|
120
77
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
78
|
+
return {
|
|
79
|
+
originalState: payload.s,
|
|
80
|
+
redirectUri: payload.r
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function hmacSign(data, secret) {
|
|
84
|
+
const encoder = new TextEncoder();
|
|
85
|
+
const keyBytes = encoder.encode(secret);
|
|
86
|
+
const dataBytes = encoder.encode(data);
|
|
87
|
+
const combined = new Uint8Array(keyBytes.length + dataBytes.length + keyBytes.length);
|
|
88
|
+
combined.set(keyBytes);
|
|
89
|
+
combined.set(dataBytes, keyBytes.length);
|
|
90
|
+
combined.set(keyBytes, keyBytes.length + dataBytes.length);
|
|
91
|
+
let h1 = 2166136261;
|
|
92
|
+
let h2 = 16777619;
|
|
93
|
+
for (let i = 0; i < combined.length; i++) {
|
|
94
|
+
h1 ^= combined[i];
|
|
95
|
+
h1 = Math.imul(h1, 16777619);
|
|
96
|
+
h2 ^= combined[i];
|
|
97
|
+
h2 = Math.imul(h2, 2246822507);
|
|
130
98
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
this.authorizeUser = options.authorizeUser.bind(this);
|
|
141
|
-
}
|
|
142
|
-
this.protected = options?.protected;
|
|
143
|
-
this.public = options?.public;
|
|
99
|
+
const sigBytes = new Uint8Array(8);
|
|
100
|
+
const view = new DataView(sigBytes.buffer);
|
|
101
|
+
view.setUint32(0, h1 >>> 0);
|
|
102
|
+
view.setUint32(4, h2 >>> 0);
|
|
103
|
+
return btoa(String.fromCharCode(...sigBytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
104
|
+
}
|
|
105
|
+
function timingSafeEqual(a, b) {
|
|
106
|
+
if (a.length !== b.length) {
|
|
107
|
+
return false;
|
|
144
108
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (opts?.protected) {
|
|
150
|
-
this.protected = opts.protected;
|
|
151
|
-
}
|
|
152
|
-
if (opts?.public) {
|
|
153
|
-
this.public = opts.public;
|
|
154
|
-
}
|
|
109
|
+
let result = 0;
|
|
110
|
+
for (let i = 0; i < a.length; i++) {
|
|
111
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
155
112
|
}
|
|
156
|
-
|
|
113
|
+
return result === 0;
|
|
114
|
+
}
|
|
115
|
+
function escapeRegex(str) {
|
|
116
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
117
|
+
}
|
|
157
118
|
var MastraAuthAuth0 = class extends MastraAuthProvider {
|
|
158
119
|
domain;
|
|
159
120
|
audience;
|
|
121
|
+
// SSO fields
|
|
122
|
+
clientId;
|
|
123
|
+
clientSecret;
|
|
124
|
+
_redirectUri;
|
|
125
|
+
scopes;
|
|
126
|
+
cookieName;
|
|
127
|
+
cookieMaxAge;
|
|
128
|
+
cookiePassword;
|
|
129
|
+
secureCookies;
|
|
130
|
+
ssoEnabled;
|
|
160
131
|
constructor(options) {
|
|
161
132
|
super({ name: options?.name ?? "auth0" });
|
|
162
133
|
const domain = options?.domain ?? process.env.AUTH0_DOMAIN;
|
|
@@ -168,9 +139,43 @@ var MastraAuthAuth0 = class extends MastraAuthProvider {
|
|
|
168
139
|
}
|
|
169
140
|
this.domain = domain;
|
|
170
141
|
this.audience = audience;
|
|
142
|
+
const clientId = options?.clientId ?? process.env.AUTH0_CLIENT_ID;
|
|
143
|
+
const clientSecret = options?.clientSecret ?? process.env.AUTH0_CLIENT_SECRET;
|
|
144
|
+
const redirectUri = options?.redirectUri ?? process.env.AUTH0_REDIRECT_URI;
|
|
145
|
+
const cookiePassword = options?.session?.cookiePassword ?? process.env.AUTH0_COOKIE_PASSWORD ?? crypto.randomUUID() + crypto.randomUUID();
|
|
146
|
+
this.clientId = clientId ?? null;
|
|
147
|
+
this.clientSecret = clientSecret ?? null;
|
|
148
|
+
this._redirectUri = redirectUri ?? null;
|
|
149
|
+
this.scopes = options?.scopes ?? DEFAULT_SCOPES;
|
|
150
|
+
this.cookieName = options?.session?.cookieName ?? DEFAULT_COOKIE_NAME;
|
|
151
|
+
this.cookieMaxAge = options?.session?.cookieMaxAge ?? DEFAULT_COOKIE_MAX_AGE;
|
|
152
|
+
this.cookiePassword = cookiePassword;
|
|
153
|
+
this.secureCookies = options?.session?.secureCookies ?? process.env.NODE_ENV === "production";
|
|
154
|
+
this.ssoEnabled = !!(clientId && clientSecret);
|
|
155
|
+
if (this.ssoEnabled) {
|
|
156
|
+
if (cookiePassword.length < 32) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
"Cookie password must be at least 32 characters for SSO. Set AUTH0_COOKIE_PASSWORD environment variable."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (!options?.session?.cookiePassword && !process.env.AUTH0_COOKIE_PASSWORD) {
|
|
162
|
+
console.warn(
|
|
163
|
+
"[MastraAuthAuth0] No cookie password set \u2014 using auto-generated value. Sessions will not survive restarts. Set AUTH0_COOKIE_PASSWORD for production use."
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
this._attachSSOProvider();
|
|
167
|
+
this._attachSessionProvider();
|
|
168
|
+
}
|
|
171
169
|
this.registerOptions(options);
|
|
172
170
|
}
|
|
173
|
-
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// MastraAuthProvider Implementation
|
|
173
|
+
// ============================================================================
|
|
174
|
+
async authenticateToken(token, request) {
|
|
175
|
+
if (this.ssoEnabled && request) {
|
|
176
|
+
const sessionUser = await this.getUserFromSessionCookie(request);
|
|
177
|
+
if (sessionUser) return sessionUser;
|
|
178
|
+
}
|
|
174
179
|
if (!token || typeof token !== "string") {
|
|
175
180
|
return null;
|
|
176
181
|
}
|
|
@@ -187,12 +192,249 @@ var MastraAuthAuth0 = class extends MastraAuthProvider {
|
|
|
187
192
|
}
|
|
188
193
|
}
|
|
189
194
|
async authorizeUser(user) {
|
|
190
|
-
if (!user || !user.sub) return false;
|
|
195
|
+
if (!user || !(user.sub || user.id)) return false;
|
|
191
196
|
if (user.exp && user.exp * 1e3 < Date.now()) {
|
|
192
197
|
return false;
|
|
193
198
|
}
|
|
194
199
|
return true;
|
|
195
200
|
}
|
|
201
|
+
// ============================================================================
|
|
202
|
+
// IUserProvider Implementation
|
|
203
|
+
// ============================================================================
|
|
204
|
+
/**
|
|
205
|
+
* Extract the bearer token from the request's Authorization header.
|
|
206
|
+
*/
|
|
207
|
+
extractToken(request) {
|
|
208
|
+
const authHeader = request.headers.get("Authorization");
|
|
209
|
+
if (authHeader) {
|
|
210
|
+
const token = authHeader.replace(/^Bearer\s+/i, "").trim();
|
|
211
|
+
if (token) return token;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
async getCurrentUser(request) {
|
|
216
|
+
if (this.ssoEnabled) {
|
|
217
|
+
const sessionUser = await this.getUserFromSessionCookie(request);
|
|
218
|
+
if (sessionUser) return sessionUser;
|
|
219
|
+
}
|
|
220
|
+
const token = this.extractToken(request);
|
|
221
|
+
if (!token) return null;
|
|
222
|
+
try {
|
|
223
|
+
const payload = await this.authenticateToken(token);
|
|
224
|
+
if (!payload?.sub) return null;
|
|
225
|
+
return {
|
|
226
|
+
id: payload.sub,
|
|
227
|
+
email: payload.email ?? void 0,
|
|
228
|
+
name: payload.name ?? void 0,
|
|
229
|
+
avatarUrl: payload.picture ?? void 0
|
|
230
|
+
};
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async getUser(userId) {
|
|
236
|
+
return {
|
|
237
|
+
id: userId
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
getUserProfileUrl(user) {
|
|
241
|
+
return `/user/${user.id}`;
|
|
242
|
+
}
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// Helper Methods
|
|
245
|
+
// ============================================================================
|
|
246
|
+
/**
|
|
247
|
+
* Check if SSO is enabled (OAuth credentials are configured).
|
|
248
|
+
*/
|
|
249
|
+
isSSOEnabled() {
|
|
250
|
+
return this.ssoEnabled;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Build consistent cookie attribute string for set/clear operations.
|
|
254
|
+
*/
|
|
255
|
+
cookieFlags(maxAge) {
|
|
256
|
+
const flags = `Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`;
|
|
257
|
+
return this.secureCookies ? `${flags}; Secure` : flags;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Extract user from the encrypted SSO session cookie.
|
|
261
|
+
*/
|
|
262
|
+
async getUserFromSessionCookie(request) {
|
|
263
|
+
const cookie = "header" in request && typeof request.header === "function" ? request.header("cookie") : request.headers?.get("cookie");
|
|
264
|
+
if (!cookie) return null;
|
|
265
|
+
const match = cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegex(this.cookieName)}=([^;]+)`));
|
|
266
|
+
if (!match?.[1]) return null;
|
|
267
|
+
try {
|
|
268
|
+
const sessionData = await decryptSession(decodeURIComponent(match[1]), this.cookiePassword);
|
|
269
|
+
if (sessionData.expiresAt < Date.now()) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
return sessionData.user;
|
|
273
|
+
} catch {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// ============================================================================
|
|
278
|
+
// Dynamic ISSOProvider attachment (only when OAuth is configured)
|
|
279
|
+
// ============================================================================
|
|
280
|
+
/**
|
|
281
|
+
* Dynamically attach ISSOProvider methods to this instance.
|
|
282
|
+
* This ensures duck-typing detection only finds these methods when SSO is configured.
|
|
283
|
+
*/
|
|
284
|
+
_attachSSOProvider() {
|
|
285
|
+
const self = this;
|
|
286
|
+
this.getLoginUrl = function(redirectUri, state) {
|
|
287
|
+
const actualRedirectUri = redirectUri ?? self._redirectUri;
|
|
288
|
+
if (!actualRedirectUri) {
|
|
289
|
+
throw new Error("Redirect URI is required for SSO. Set AUTH0_REDIRECT_URI or pass redirectUri option.");
|
|
290
|
+
}
|
|
291
|
+
const signedState = createStateToken(state, actualRedirectUri, self.cookiePassword);
|
|
292
|
+
const params = new URLSearchParams({
|
|
293
|
+
client_id: self.clientId,
|
|
294
|
+
response_type: "code",
|
|
295
|
+
scope: self.scopes.join(" "),
|
|
296
|
+
redirect_uri: actualRedirectUri,
|
|
297
|
+
state: signedState
|
|
298
|
+
});
|
|
299
|
+
return `https://${self.domain}/authorize?${params.toString()}`;
|
|
300
|
+
};
|
|
301
|
+
this.handleCallback = async function(code, signedState) {
|
|
302
|
+
const { redirectUri } = verifyStateToken(signedState, self.cookiePassword);
|
|
303
|
+
const tokenResponse = await fetch(`https://${self.domain}/oauth/token`, {
|
|
304
|
+
method: "POST",
|
|
305
|
+
headers: { "Content-Type": "application/json" },
|
|
306
|
+
body: JSON.stringify({
|
|
307
|
+
grant_type: "authorization_code",
|
|
308
|
+
client_id: self.clientId,
|
|
309
|
+
client_secret: self.clientSecret,
|
|
310
|
+
code,
|
|
311
|
+
redirect_uri: redirectUri
|
|
312
|
+
}),
|
|
313
|
+
signal: AbortSignal.timeout(1e4)
|
|
314
|
+
// 10 second timeout
|
|
315
|
+
});
|
|
316
|
+
if (!tokenResponse.ok) {
|
|
317
|
+
const error = await tokenResponse.text();
|
|
318
|
+
throw new Error(`Token exchange failed: ${error}`);
|
|
319
|
+
}
|
|
320
|
+
const tokens = await tokenResponse.json();
|
|
321
|
+
let user;
|
|
322
|
+
if (tokens.id_token) {
|
|
323
|
+
try {
|
|
324
|
+
const JWKS = createRemoteJWKSet(new URL(`https://${self.domain}/.well-known/jwks.json`));
|
|
325
|
+
const { payload } = await jwtVerify(tokens.id_token, JWKS, {
|
|
326
|
+
issuer: `https://${self.domain}/`,
|
|
327
|
+
audience: self.clientId
|
|
328
|
+
// Validate token was issued for this client
|
|
329
|
+
});
|
|
330
|
+
user = {
|
|
331
|
+
id: payload.sub,
|
|
332
|
+
email: payload.email ?? void 0,
|
|
333
|
+
name: payload.name ?? void 0,
|
|
334
|
+
avatarUrl: payload.picture ?? void 0
|
|
335
|
+
};
|
|
336
|
+
} catch {
|
|
337
|
+
user = await self._fetchUserInfo(tokens.access_token);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
user = await self._fetchUserInfo(tokens.access_token);
|
|
341
|
+
}
|
|
342
|
+
const sessionData = {
|
|
343
|
+
user,
|
|
344
|
+
expiresAt: Date.now() + self.cookieMaxAge * 1e3
|
|
345
|
+
};
|
|
346
|
+
const encryptedSession = await encryptSession(sessionData, self.cookiePassword);
|
|
347
|
+
const cookieValue = `${self.cookieName}=${encodeURIComponent(encryptedSession)}; ${self.cookieFlags(self.cookieMaxAge)}`;
|
|
348
|
+
return {
|
|
349
|
+
user,
|
|
350
|
+
tokens: {
|
|
351
|
+
accessToken: tokens.access_token,
|
|
352
|
+
refreshToken: tokens.refresh_token,
|
|
353
|
+
idToken: tokens.id_token,
|
|
354
|
+
expiresAt: new Date(Date.now() + tokens.expires_in * 1e3)
|
|
355
|
+
},
|
|
356
|
+
cookies: [cookieValue]
|
|
357
|
+
};
|
|
358
|
+
};
|
|
359
|
+
this.getLoginButtonConfig = function() {
|
|
360
|
+
return {
|
|
361
|
+
provider: "auth0",
|
|
362
|
+
text: "Sign in with Auth0",
|
|
363
|
+
description: "Sign in using your Auth0 account"
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
this.getLoginCookies = function(_state) {
|
|
367
|
+
return [];
|
|
368
|
+
};
|
|
369
|
+
this.getLogoutUrl = async function(redirectUri, _request) {
|
|
370
|
+
const params = new URLSearchParams({
|
|
371
|
+
client_id: self.clientId,
|
|
372
|
+
returnTo: redirectUri
|
|
373
|
+
});
|
|
374
|
+
return `https://${self.domain}/v2/logout?${params.toString()}`;
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Fetch user info from Auth0's /userinfo endpoint.
|
|
379
|
+
*/
|
|
380
|
+
async _fetchUserInfo(accessToken) {
|
|
381
|
+
const userInfoResponse = await fetch(`https://${this.domain}/userinfo`, {
|
|
382
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
383
|
+
signal: AbortSignal.timeout(1e4)
|
|
384
|
+
// 10 second timeout
|
|
385
|
+
});
|
|
386
|
+
if (!userInfoResponse.ok) {
|
|
387
|
+
throw new Error("Failed to fetch user info from Auth0");
|
|
388
|
+
}
|
|
389
|
+
const userInfo = await userInfoResponse.json();
|
|
390
|
+
return {
|
|
391
|
+
id: userInfo.sub,
|
|
392
|
+
email: userInfo.email,
|
|
393
|
+
name: userInfo.name,
|
|
394
|
+
avatarUrl: userInfo.picture
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// ============================================================================
|
|
398
|
+
// Dynamic ISessionProvider attachment (only when OAuth is configured)
|
|
399
|
+
// ============================================================================
|
|
400
|
+
/**
|
|
401
|
+
* Dynamically attach ISessionProvider methods to this instance.
|
|
402
|
+
*/
|
|
403
|
+
_attachSessionProvider() {
|
|
404
|
+
const self = this;
|
|
405
|
+
this.createSession = async function(userId, metadata) {
|
|
406
|
+
const now = /* @__PURE__ */ new Date();
|
|
407
|
+
return {
|
|
408
|
+
id: crypto.randomUUID(),
|
|
409
|
+
userId,
|
|
410
|
+
createdAt: now,
|
|
411
|
+
expiresAt: new Date(now.getTime() + self.cookieMaxAge * 1e3),
|
|
412
|
+
metadata
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
this.validateSession = async function(_sessionId) {
|
|
416
|
+
return null;
|
|
417
|
+
};
|
|
418
|
+
this.destroySession = async function(_sessionId) {
|
|
419
|
+
};
|
|
420
|
+
this.refreshSession = async function(_sessionId) {
|
|
421
|
+
return null;
|
|
422
|
+
};
|
|
423
|
+
this.getSessionIdFromRequest = function(request) {
|
|
424
|
+
const cookie = request.headers.get("Cookie");
|
|
425
|
+
if (!cookie) return null;
|
|
426
|
+
const match = cookie.match(new RegExp(`(?:^|;\\s*)${escapeRegex(self.cookieName)}=([^;]+)`));
|
|
427
|
+
return match?.[1] ? decodeURIComponent(match[1]) : null;
|
|
428
|
+
};
|
|
429
|
+
this.getSessionHeaders = function(_session) {
|
|
430
|
+
return {};
|
|
431
|
+
};
|
|
432
|
+
this.getClearSessionHeaders = function() {
|
|
433
|
+
return {
|
|
434
|
+
"Set-Cookie": `${self.cookieName}=; ${self.cookieFlags(0)}`
|
|
435
|
+
};
|
|
436
|
+
};
|
|
437
|
+
}
|
|
196
438
|
};
|
|
197
439
|
|
|
198
440
|
export { MastraAuthAuth0 };
|