@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 +45 -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/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
|
-
//
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
var
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|