fastmcp 4.9.0 → 4.9.2
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/README.md +12 -36
- package/dist/FastMCP.cjs +3 -3
- package/dist/FastMCP.d.cts +4 -3
- package/dist/FastMCP.d.ts +4 -3
- package/dist/FastMCP.js +2 -2
- package/dist/{OAuthProvider-vyjR-9Z7.d.cts → OAuthProvider-BNqoBTYA.d.cts} +23 -6
- package/dist/{OAuthProvider-vyjR-9Z7.d.ts → OAuthProvider-BNqoBTYA.d.ts} +23 -6
- package/dist/auth/index.cjs +2 -2
- package/dist/auth/index.d.cts +22 -2
- package/dist/auth/index.d.ts +22 -2
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-IX3HKAX4.cjs → chunk-DZYT6QAH.cjs} +380 -80
- package/dist/chunk-DZYT6QAH.cjs.map +1 -0
- package/dist/{chunk-MJCWKCHZ.cjs → chunk-ENLYR3QM.cjs} +28 -17
- package/dist/chunk-ENLYR3QM.cjs.map +1 -0
- package/dist/{chunk-OJG4XYXA.js → chunk-GM44BOI7.js} +378 -78
- package/dist/chunk-GM44BOI7.js.map +1 -0
- package/dist/{chunk-2PFUZL7I.js → chunk-JIPRWP6F.js} +28 -17
- package/dist/chunk-JIPRWP6F.js.map +1 -0
- package/dist/examples/custom-routes.cjs +3 -3
- package/dist/examples/custom-routes.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-2PFUZL7I.js.map +0 -1
- package/dist/chunk-IX3HKAX4.cjs.map +0 -1
- package/dist/chunk-MJCWKCHZ.cjs.map +0 -1
- package/dist/chunk-OJG4XYXA.js.map +0 -1
|
@@ -37,6 +37,237 @@ function requireScopes(...requiredScopes) {
|
|
|
37
37
|
var _crypto = require('crypto');
|
|
38
38
|
var _zod = require('zod');
|
|
39
39
|
|
|
40
|
+
// src/auth/OAuthProxyStateStore.ts
|
|
41
|
+
|
|
42
|
+
var STORAGE_KEY_PREFIX = {
|
|
43
|
+
client: "client:",
|
|
44
|
+
code: "code:",
|
|
45
|
+
transaction: "transaction:"
|
|
46
|
+
};
|
|
47
|
+
var storedDateSchema = _zod.z.union([_zod.z.date(), _zod.z.string()]).transform((value, context) => {
|
|
48
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
49
|
+
if (Number.isNaN(date.getTime())) {
|
|
50
|
+
context.addIssue({
|
|
51
|
+
code: "custom",
|
|
52
|
+
message: "Invalid stored date"
|
|
53
|
+
});
|
|
54
|
+
return _zod.z.NEVER;
|
|
55
|
+
}
|
|
56
|
+
return date;
|
|
57
|
+
});
|
|
58
|
+
var dcrClientMetadataSchema = _zod.z.object({
|
|
59
|
+
client_name: _zod.z.string().optional(),
|
|
60
|
+
client_uri: _zod.z.string().optional(),
|
|
61
|
+
contacts: _zod.z.array(_zod.z.string()).optional(),
|
|
62
|
+
jwks: _zod.z.record(_zod.z.string(), _zod.z.unknown()).optional(),
|
|
63
|
+
jwks_uri: _zod.z.string().optional(),
|
|
64
|
+
logo_uri: _zod.z.string().optional(),
|
|
65
|
+
policy_uri: _zod.z.string().optional(),
|
|
66
|
+
scope: _zod.z.string().optional(),
|
|
67
|
+
software_id: _zod.z.string().optional(),
|
|
68
|
+
software_version: _zod.z.string().optional(),
|
|
69
|
+
tos_uri: _zod.z.string().optional()
|
|
70
|
+
});
|
|
71
|
+
var proxyDcrClientStorageSchema = _zod.z.object({
|
|
72
|
+
callbackUrl: _zod.z.string(),
|
|
73
|
+
clientId: _zod.z.string(),
|
|
74
|
+
clientSecret: _zod.z.string().optional(),
|
|
75
|
+
metadata: dcrClientMetadataSchema.optional(),
|
|
76
|
+
redirectUris: _zod.z.array(_zod.z.string()),
|
|
77
|
+
registeredAt: storedDateSchema
|
|
78
|
+
});
|
|
79
|
+
var upstreamTokenSetStorageSchema = _zod.z.object({
|
|
80
|
+
accessToken: _zod.z.string(),
|
|
81
|
+
expiresIn: _zod.z.number(),
|
|
82
|
+
idToken: _zod.z.string().optional(),
|
|
83
|
+
issuedAt: storedDateSchema,
|
|
84
|
+
refreshExpiresIn: _zod.z.number().optional(),
|
|
85
|
+
refreshToken: _zod.z.string().optional(),
|
|
86
|
+
scope: _zod.z.array(_zod.z.string()),
|
|
87
|
+
tokenType: _zod.z.string()
|
|
88
|
+
});
|
|
89
|
+
var clientCodeStorageSchema = _zod.z.object({
|
|
90
|
+
clientId: _zod.z.string(),
|
|
91
|
+
code: _zod.z.string(),
|
|
92
|
+
codeChallenge: _zod.z.string(),
|
|
93
|
+
codeChallengeMethod: _zod.z.string(),
|
|
94
|
+
createdAt: storedDateSchema,
|
|
95
|
+
expiresAt: storedDateSchema,
|
|
96
|
+
transactionId: _zod.z.string(),
|
|
97
|
+
upstreamTokens: upstreamTokenSetStorageSchema,
|
|
98
|
+
used: _zod.z.boolean().optional()
|
|
99
|
+
});
|
|
100
|
+
var spentClientCodeStorageSchema = _zod.z.object({
|
|
101
|
+
expiresAt: storedDateSchema,
|
|
102
|
+
used: _zod.z.literal(true)
|
|
103
|
+
});
|
|
104
|
+
var oauthTransactionStorageSchema = _zod.z.object({
|
|
105
|
+
clientCallbackUrl: _zod.z.string(),
|
|
106
|
+
clientCodeChallenge: _zod.z.string(),
|
|
107
|
+
clientCodeChallengeMethod: _zod.z.string(),
|
|
108
|
+
clientId: _zod.z.string(),
|
|
109
|
+
consentGiven: _zod.z.boolean().optional(),
|
|
110
|
+
createdAt: storedDateSchema,
|
|
111
|
+
expiresAt: storedDateSchema,
|
|
112
|
+
id: _zod.z.string(),
|
|
113
|
+
metadata: _zod.z.record(_zod.z.string(), _zod.z.unknown()).optional(),
|
|
114
|
+
proxyCodeChallenge: _zod.z.string(),
|
|
115
|
+
proxyCodeVerifier: _zod.z.string(),
|
|
116
|
+
scope: _zod.z.array(_zod.z.string()),
|
|
117
|
+
state: _zod.z.string()
|
|
118
|
+
});
|
|
119
|
+
var OAuthProxyStateStore = class {
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
constructor(config) {
|
|
123
|
+
this.registeredClientsByClientId = config.registeredClientsByClientId;
|
|
124
|
+
this.tokenStorage = config.tokenStorage;
|
|
125
|
+
}
|
|
126
|
+
cacheRegisteredClient(client) {
|
|
127
|
+
this.registeredClientsByClientId.set(client.clientId, client);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Atomically consume an authorization code. At most one caller — across all
|
|
131
|
+
* processes sharing the storage — can receive a given code, which is what
|
|
132
|
+
* makes single use enforceable (RFC 6749 §4.1.2).
|
|
133
|
+
*
|
|
134
|
+
* Spent codes are taken too, so the caller must write the tombstone back
|
|
135
|
+
* with `markClientCodeSpent` to keep reporting the precise error on the
|
|
136
|
+
* attempt after this one.
|
|
137
|
+
*/
|
|
138
|
+
async consumeClientCode(code) {
|
|
139
|
+
const stored = await this.takeFromStorage(
|
|
140
|
+
`${STORAGE_KEY_PREFIX.code}${code}`
|
|
141
|
+
);
|
|
142
|
+
const spent = spentClientCodeStorageSchema.safeParse(stored);
|
|
143
|
+
if (spent.success) {
|
|
144
|
+
return this.isExpired(spent.data.expiresAt) ? null : { expiresAt: spent.data.expiresAt, status: "spent" };
|
|
145
|
+
}
|
|
146
|
+
const parsed = clientCodeStorageSchema.safeParse(stored);
|
|
147
|
+
if (!parsed.success || this.isExpired(parsed.data.expiresAt)) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
clientCode: parsed.data,
|
|
152
|
+
expiresAt: parsed.data.expiresAt,
|
|
153
|
+
status: "active"
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Atomically consume a transaction, so a callback can only be redeemed once
|
|
158
|
+
* no matter which instance it lands on.
|
|
159
|
+
*/
|
|
160
|
+
async consumeTransaction(transactionId) {
|
|
161
|
+
const stored = await this.takeFromStorage(
|
|
162
|
+
`${STORAGE_KEY_PREFIX.transaction}${transactionId}`
|
|
163
|
+
);
|
|
164
|
+
const parsed = oauthTransactionStorageSchema.safeParse(stored);
|
|
165
|
+
if (!parsed.success || this.isExpired(parsed.data.expiresAt)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
return parsed.data;
|
|
169
|
+
}
|
|
170
|
+
async deleteTransaction(transactionId) {
|
|
171
|
+
await this.tokenStorage.delete(
|
|
172
|
+
`${STORAGE_KEY_PREFIX.transaction}${transactionId}`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
async getRegisteredClientByClientId(clientId) {
|
|
176
|
+
const cached = this.registeredClientsByClientId.get(clientId);
|
|
177
|
+
if (cached) {
|
|
178
|
+
return cached;
|
|
179
|
+
}
|
|
180
|
+
const stored = await this.tokenStorage.get(
|
|
181
|
+
`${STORAGE_KEY_PREFIX.client}${clientId}`
|
|
182
|
+
);
|
|
183
|
+
const parsed = proxyDcrClientStorageSchema.safeParse(stored);
|
|
184
|
+
if (!parsed.success) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
this.cacheRegisteredClient(parsed.data);
|
|
188
|
+
return parsed.data;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Read a transaction without consuming it. Used by the consent screen, which
|
|
192
|
+
* has to hand the same transaction back to the upstream redirect.
|
|
193
|
+
*/
|
|
194
|
+
async getTransaction(transactionId) {
|
|
195
|
+
const stored = await this.tokenStorage.get(
|
|
196
|
+
`${STORAGE_KEY_PREFIX.transaction}${transactionId}`
|
|
197
|
+
);
|
|
198
|
+
const parsed = oauthTransactionStorageSchema.safeParse(stored);
|
|
199
|
+
if (!parsed.success) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
if (this.isExpired(parsed.data.expiresAt)) {
|
|
203
|
+
await this.deleteTransaction(transactionId);
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
return parsed.data;
|
|
207
|
+
}
|
|
208
|
+
async isTransactionCallbackRegistered(transaction) {
|
|
209
|
+
const registeredClient = await this.getRegisteredClientByClientId(
|
|
210
|
+
transaction.clientId
|
|
211
|
+
);
|
|
212
|
+
return _nullishCoalesce(_optionalChain([registeredClient, 'optionalAccess', _ => _.redirectUris, 'access', _2 => _2.includes, 'call', _3 => _3(transaction.clientCallbackUrl)]), () => ( false));
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Record that an authorization code has been redeemed, so a later attempt
|
|
216
|
+
* gets "already used" rather than looking like an unknown code. Keyed and
|
|
217
|
+
* expiring exactly like the code it replaces, so it cannot outlive it.
|
|
218
|
+
*/
|
|
219
|
+
async markClientCodeSpent(code, expiresAt) {
|
|
220
|
+
await this.tokenStorage.save(
|
|
221
|
+
`${STORAGE_KEY_PREFIX.code}${code}`,
|
|
222
|
+
{ expiresAt, used: true },
|
|
223
|
+
this.getTtlSeconds(expiresAt)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
async saveClientCode(clientCode) {
|
|
227
|
+
await this.tokenStorage.save(
|
|
228
|
+
`${STORAGE_KEY_PREFIX.code}${clientCode.code}`,
|
|
229
|
+
clientCode,
|
|
230
|
+
this.getTtlSeconds(clientCode.expiresAt)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
async saveRegisteredClient(client) {
|
|
234
|
+
await this.tokenStorage.save(
|
|
235
|
+
`${STORAGE_KEY_PREFIX.client}${client.clientId}`,
|
|
236
|
+
client
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
async saveTransaction(transaction) {
|
|
240
|
+
await this.tokenStorage.save(
|
|
241
|
+
`${STORAGE_KEY_PREFIX.transaction}${transaction.id}`,
|
|
242
|
+
transaction,
|
|
243
|
+
this.getTtlSeconds(transaction.expiresAt)
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
getTtlSeconds(expiresAt) {
|
|
247
|
+
const millisecondsUntilExpiry = expiresAt.getTime() - Date.now();
|
|
248
|
+
return Math.max(Math.ceil(millisecondsUntilExpiry / 1e3), 1);
|
|
249
|
+
}
|
|
250
|
+
isExpired(expiresAt) {
|
|
251
|
+
return expiresAt.getTime() < Date.now();
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Prefer the storage's atomic take. Falling back to get + delete leaves a
|
|
255
|
+
* window in which two processes can both observe the same value, so
|
|
256
|
+
* multi-process deployments should implement `TokenStorage.take`.
|
|
257
|
+
*/
|
|
258
|
+
async takeFromStorage(key) {
|
|
259
|
+
if (this.tokenStorage.take) {
|
|
260
|
+
return this.tokenStorage.take(key);
|
|
261
|
+
}
|
|
262
|
+
const stored = await this.tokenStorage.get(key);
|
|
263
|
+
if (stored === null) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
await this.tokenStorage.delete(key);
|
|
267
|
+
return stored;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
40
271
|
// src/auth/types.ts
|
|
41
272
|
var DEFAULT_ACCESS_TOKEN_TTL = 3600;
|
|
42
273
|
var DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = 31536e3;
|
|
@@ -134,7 +365,7 @@ var ClaimsExtractor = (_class = class {
|
|
|
134
365
|
if (this.PROTECTED_CLAIMS.has(key)) {
|
|
135
366
|
continue;
|
|
136
367
|
}
|
|
137
|
-
if (_optionalChain([this, 'access',
|
|
368
|
+
if (_optionalChain([this, 'access', _4 => _4.config, 'access', _5 => _5.blockedClaims, 'optionalAccess', _6 => _6.includes, 'call', _7 => _7(key)])) {
|
|
138
369
|
continue;
|
|
139
370
|
}
|
|
140
371
|
if (this.config.allowedClaims && !this.config.allowedClaims.includes(key)) {
|
|
@@ -743,20 +974,7 @@ var EncryptedTokenStorage = (_class2 = class {
|
|
|
743
974
|
await this.backend.delete(key);
|
|
744
975
|
}
|
|
745
976
|
async get(key) {
|
|
746
|
-
|
|
747
|
-
if (!encrypted) {
|
|
748
|
-
return null;
|
|
749
|
-
}
|
|
750
|
-
try {
|
|
751
|
-
const decrypted = await this.decrypt(
|
|
752
|
-
encrypted,
|
|
753
|
-
this.encryptionKey
|
|
754
|
-
);
|
|
755
|
-
return JSON.parse(decrypted);
|
|
756
|
-
} catch (error) {
|
|
757
|
-
console.error("Failed to decrypt value:", error);
|
|
758
|
-
return null;
|
|
759
|
-
}
|
|
977
|
+
return this.decryptStored(await this.backend.get(key));
|
|
760
978
|
}
|
|
761
979
|
async save(key, value, ttl) {
|
|
762
980
|
const encrypted = await this.encrypt(
|
|
@@ -765,6 +983,21 @@ var EncryptedTokenStorage = (_class2 = class {
|
|
|
765
983
|
);
|
|
766
984
|
await this.backend.save(key, encrypted, ttl);
|
|
767
985
|
}
|
|
986
|
+
/**
|
|
987
|
+
* Delegates to the backend so that the atomicity of the underlying store is
|
|
988
|
+
* preserved; falls back to get + delete when the backend cannot do better.
|
|
989
|
+
*/
|
|
990
|
+
async take(key) {
|
|
991
|
+
if (this.backend.take) {
|
|
992
|
+
return this.decryptStored(await this.backend.take(key));
|
|
993
|
+
}
|
|
994
|
+
const value = await this.get(key);
|
|
995
|
+
if (value === null) {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
await this.backend.delete(key);
|
|
999
|
+
return value;
|
|
1000
|
+
}
|
|
768
1001
|
async decrypt(ciphertext, key) {
|
|
769
1002
|
const parts = ciphertext.split(":");
|
|
770
1003
|
if (parts.length !== 3) {
|
|
@@ -783,6 +1016,21 @@ var EncryptedTokenStorage = (_class2 = class {
|
|
|
783
1016
|
decrypted += decipher.final("utf8");
|
|
784
1017
|
return decrypted;
|
|
785
1018
|
}
|
|
1019
|
+
async decryptStored(encrypted) {
|
|
1020
|
+
if (!encrypted) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
const decrypted = await this.decrypt(
|
|
1025
|
+
encrypted,
|
|
1026
|
+
this.encryptionKey
|
|
1027
|
+
);
|
|
1028
|
+
return JSON.parse(decrypted);
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
console.error("Failed to decrypt value:", error);
|
|
1031
|
+
return null;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
786
1034
|
async encrypt(plaintext, key) {
|
|
787
1035
|
const iv = _crypto.randomBytes.call(void 0, 16);
|
|
788
1036
|
const cipher = _crypto.createCipheriv.call(void 0, this.algorithm, key, iv, {
|
|
@@ -852,6 +1100,18 @@ var MemoryTokenStorage = (_class3 = class {
|
|
|
852
1100
|
size() {
|
|
853
1101
|
return this.store.size;
|
|
854
1102
|
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Atomic by construction: the read and the delete happen in a single
|
|
1105
|
+
* synchronous block, so no other task can observe the value in between.
|
|
1106
|
+
*/
|
|
1107
|
+
async take(key) {
|
|
1108
|
+
const entry = this.store.get(key);
|
|
1109
|
+
this.store.delete(key);
|
|
1110
|
+
if (!entry || entry.expiresAt < Date.now()) {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
return entry.value;
|
|
1114
|
+
}
|
|
855
1115
|
}, _class3);
|
|
856
1116
|
|
|
857
1117
|
// src/auth/OAuthProxy.ts
|
|
@@ -867,17 +1127,19 @@ var RESERVED_AUTHORIZATION_PARAMS = /* @__PURE__ */ new Set([
|
|
|
867
1127
|
var OAuthProxy = (_class4 = class {
|
|
868
1128
|
__init5() {this.claimsExtractor = null}
|
|
869
1129
|
__init6() {this.cleanupInterval = null}
|
|
870
|
-
__init7() {this.clientCodes = /* @__PURE__ */ new Map()}
|
|
871
1130
|
|
|
872
1131
|
|
|
873
1132
|
|
|
874
|
-
/**
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
1133
|
+
/**
|
|
1134
|
+
* Keyed by proxy-issued client_id for authorize/token-exchange lookups and
|
|
1135
|
+
* for the defence-in-depth callback checks. A registration never changes
|
|
1136
|
+
* after it is written, so caching it locally cannot go stale; it is also
|
|
1137
|
+
* persisted, so another instance can hydrate it.
|
|
1138
|
+
*/
|
|
1139
|
+
__init7() {this.registeredClientsByClientId = /* @__PURE__ */ new Map()}
|
|
1140
|
+
|
|
878
1141
|
|
|
879
|
-
|
|
880
|
-
constructor(config) {;_class4.prototype.__init5.call(this);_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);_class4.prototype.__init10.call(this);
|
|
1142
|
+
constructor(config) {;_class4.prototype.__init5.call(this);_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);
|
|
881
1143
|
this.config = {
|
|
882
1144
|
authorizationCodeTtl: DEFAULT_AUTHORIZATION_CODE_TTL,
|
|
883
1145
|
consentRequired: true,
|
|
@@ -895,6 +1157,10 @@ var OAuthProxy = (_class4 = class {
|
|
|
895
1157
|
storage = new EncryptedTokenStorage(storage, encryptionKey);
|
|
896
1158
|
}
|
|
897
1159
|
this.tokenStorage = storage;
|
|
1160
|
+
this.stateStore = new OAuthProxyStateStore({
|
|
1161
|
+
registeredClientsByClientId: this.registeredClientsByClientId,
|
|
1162
|
+
tokenStorage: this.tokenStorage
|
|
1163
|
+
});
|
|
898
1164
|
this.consentManager = new ConsentManager(
|
|
899
1165
|
config.consentSigningKey || this.generateSigningKey()
|
|
900
1166
|
);
|
|
@@ -928,9 +1194,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
928
1194
|
"Only 'code' response type is supported"
|
|
929
1195
|
);
|
|
930
1196
|
}
|
|
931
|
-
const registeredClient = this.
|
|
932
|
-
params.client_id
|
|
933
|
-
);
|
|
1197
|
+
const registeredClient = await this.stateStore.getRegisteredClientByClientId(params.client_id);
|
|
934
1198
|
if (!registeredClient) {
|
|
935
1199
|
throw new OAuthProxyError("invalid_client", "Unknown client_id");
|
|
936
1200
|
}
|
|
@@ -963,9 +1227,6 @@ var OAuthProxy = (_class4 = class {
|
|
|
963
1227
|
clearInterval(this.cleanupInterval);
|
|
964
1228
|
this.cleanupInterval = null;
|
|
965
1229
|
}
|
|
966
|
-
this.transactions.clear();
|
|
967
|
-
this.clientCodes.clear();
|
|
968
|
-
this.registeredClients.clear();
|
|
969
1230
|
this.registeredClientsByClientId.clear();
|
|
970
1231
|
}
|
|
971
1232
|
/**
|
|
@@ -978,16 +1239,25 @@ var OAuthProxy = (_class4 = class {
|
|
|
978
1239
|
"Only authorization_code grant type is supported"
|
|
979
1240
|
);
|
|
980
1241
|
}
|
|
981
|
-
|
|
1242
|
+
const registeredClient = await this.stateStore.getRegisteredClientByClientId(request.client_id);
|
|
1243
|
+
if (!registeredClient) {
|
|
982
1244
|
throw new OAuthProxyError("invalid_client", "Unknown client_id");
|
|
983
1245
|
}
|
|
984
|
-
const
|
|
985
|
-
if (!
|
|
1246
|
+
const consumed = await this.stateStore.consumeClientCode(request.code);
|
|
1247
|
+
if (!consumed) {
|
|
986
1248
|
throw new OAuthProxyError(
|
|
987
1249
|
"invalid_grant",
|
|
988
1250
|
"Invalid or expired authorization code"
|
|
989
1251
|
);
|
|
990
1252
|
}
|
|
1253
|
+
await this.stateStore.markClientCodeSpent(request.code, consumed.expiresAt);
|
|
1254
|
+
if (consumed.status === "spent") {
|
|
1255
|
+
throw new OAuthProxyError(
|
|
1256
|
+
"invalid_grant",
|
|
1257
|
+
"Authorization code already used"
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
const clientCode = consumed.clientCode;
|
|
991
1261
|
if (clientCode.clientId !== request.client_id) {
|
|
992
1262
|
throw new OAuthProxyError("invalid_client", "Client ID mismatch");
|
|
993
1263
|
}
|
|
@@ -1007,14 +1277,6 @@ var OAuthProxy = (_class4 = class {
|
|
|
1007
1277
|
throw new OAuthProxyError("invalid_grant", "Invalid PKCE verifier");
|
|
1008
1278
|
}
|
|
1009
1279
|
}
|
|
1010
|
-
if (clientCode.used) {
|
|
1011
|
-
throw new OAuthProxyError(
|
|
1012
|
-
"invalid_grant",
|
|
1013
|
-
"Authorization code already used"
|
|
1014
|
-
);
|
|
1015
|
-
}
|
|
1016
|
-
clientCode.used = true;
|
|
1017
|
-
this.clientCodes.set(request.code, clientCode);
|
|
1018
1280
|
if (this.config.enableTokenSwap && this.jwtIssuer) {
|
|
1019
1281
|
return await this.issueSwappedTokens(
|
|
1020
1282
|
clientCode.clientId,
|
|
@@ -1090,23 +1352,21 @@ var OAuthProxy = (_class4 = class {
|
|
|
1090
1352
|
"Missing code or state parameter"
|
|
1091
1353
|
);
|
|
1092
1354
|
}
|
|
1093
|
-
const transaction = this.
|
|
1355
|
+
const transaction = await this.stateStore.consumeTransaction(state);
|
|
1094
1356
|
if (!transaction) {
|
|
1095
1357
|
throw new OAuthProxyError("invalid_request", "Invalid or expired state");
|
|
1096
1358
|
}
|
|
1097
|
-
if (!this.
|
|
1098
|
-
this.transactions.delete(state);
|
|
1359
|
+
if (!await this.stateStore.isTransactionCallbackRegistered(transaction)) {
|
|
1099
1360
|
throw new OAuthProxyError(
|
|
1100
1361
|
"invalid_request",
|
|
1101
1362
|
"Transaction callback URL is not registered"
|
|
1102
1363
|
);
|
|
1103
1364
|
}
|
|
1104
1365
|
const upstreamTokens = await this.exchangeUpstreamCode(code, transaction);
|
|
1105
|
-
const clientCode = this.generateAuthorizationCode(
|
|
1366
|
+
const clientCode = await this.generateAuthorizationCode(
|
|
1106
1367
|
transaction,
|
|
1107
1368
|
upstreamTokens
|
|
1108
1369
|
);
|
|
1109
|
-
this.transactions.delete(state);
|
|
1110
1370
|
const redirectUrl = new URL(transaction.clientCallbackUrl);
|
|
1111
1371
|
redirectUrl.searchParams.set("code", clientCode);
|
|
1112
1372
|
redirectUrl.searchParams.set("state", transaction.state);
|
|
@@ -1124,10 +1384,16 @@ var OAuthProxy = (_class4 = class {
|
|
|
1124
1384
|
const formData = await request.formData();
|
|
1125
1385
|
const transactionId = formData.get("transaction_id");
|
|
1126
1386
|
const action = formData.get("action");
|
|
1127
|
-
if (!transactionId) {
|
|
1387
|
+
if (typeof transactionId !== "string" || !transactionId) {
|
|
1128
1388
|
throw new OAuthProxyError("invalid_request", "Missing transaction_id");
|
|
1129
1389
|
}
|
|
1130
|
-
|
|
1390
|
+
if (action !== "approve" && action !== "deny") {
|
|
1391
|
+
throw new OAuthProxyError(
|
|
1392
|
+
"invalid_request",
|
|
1393
|
+
"action must be 'approve' or 'deny'"
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
const transaction = await this.stateStore.getTransaction(transactionId);
|
|
1131
1397
|
if (!transaction) {
|
|
1132
1398
|
throw new OAuthProxyError(
|
|
1133
1399
|
"invalid_request",
|
|
@@ -1135,8 +1401,8 @@ var OAuthProxy = (_class4 = class {
|
|
|
1135
1401
|
);
|
|
1136
1402
|
}
|
|
1137
1403
|
if (action === "deny") {
|
|
1138
|
-
this.
|
|
1139
|
-
if (!this.
|
|
1404
|
+
await this.stateStore.deleteTransaction(transactionId);
|
|
1405
|
+
if (!await this.stateStore.isTransactionCallbackRegistered(transaction)) {
|
|
1140
1406
|
throw new OAuthProxyError(
|
|
1141
1407
|
"invalid_request",
|
|
1142
1408
|
"Transaction callback URL is not registered"
|
|
@@ -1156,9 +1422,12 @@ var OAuthProxy = (_class4 = class {
|
|
|
1156
1422
|
status: 302
|
|
1157
1423
|
});
|
|
1158
1424
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1425
|
+
const approvedTransaction = {
|
|
1426
|
+
...transaction,
|
|
1427
|
+
consentGiven: true
|
|
1428
|
+
};
|
|
1429
|
+
await this.stateStore.saveTransaction(approvedTransaction);
|
|
1430
|
+
return this.redirectToUpstream(approvedTransaction);
|
|
1162
1431
|
}
|
|
1163
1432
|
/**
|
|
1164
1433
|
* Load upstream tokens from a FastMCP JWT
|
|
@@ -1168,7 +1437,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1168
1437
|
return null;
|
|
1169
1438
|
}
|
|
1170
1439
|
const result = await this.jwtIssuer.verify(fastmcpToken);
|
|
1171
|
-
if (!result.valid || !_optionalChain([result, 'access',
|
|
1440
|
+
if (!result.valid || !_optionalChain([result, 'access', _8 => _8.claims, 'optionalAccess', _9 => _9.jti])) {
|
|
1172
1441
|
return null;
|
|
1173
1442
|
}
|
|
1174
1443
|
const mapping = await this.tokenStorage.get(
|
|
@@ -1222,10 +1491,8 @@ var OAuthProxy = (_class4 = class {
|
|
|
1222
1491
|
redirectUris: request.redirect_uris,
|
|
1223
1492
|
registeredAt: /* @__PURE__ */ new Date()
|
|
1224
1493
|
};
|
|
1225
|
-
this.
|
|
1226
|
-
|
|
1227
|
-
this.registeredClients.set(uri, client);
|
|
1228
|
-
}
|
|
1494
|
+
this.stateStore.cacheRegisteredClient(client);
|
|
1495
|
+
await this.stateStore.saveRegisteredClient(client);
|
|
1229
1496
|
const response = {
|
|
1230
1497
|
client_id: proxyClientId,
|
|
1231
1498
|
client_id_issued_at: Math.floor(Date.now() / 1e3),
|
|
@@ -1269,21 +1536,15 @@ var OAuthProxy = (_class4 = class {
|
|
|
1269
1536
|
}
|
|
1270
1537
|
}
|
|
1271
1538
|
/**
|
|
1272
|
-
*
|
|
1539
|
+
* Periodic maintenance hook. Expiry is enforced by the token storage.
|
|
1273
1540
|
*/
|
|
1274
1541
|
cleanup() {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
for (const [code, clientCode] of this.clientCodes.entries()) {
|
|
1282
|
-
if (clientCode.expiresAt.getTime() < now) {
|
|
1283
|
-
this.clientCodes.delete(code);
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
void this.tokenStorage.cleanup();
|
|
1542
|
+
void this.tokenStorage.cleanup().catch((error) => {
|
|
1543
|
+
console.error(
|
|
1544
|
+
"[FastMCP] OAuth proxy token storage cleanup failed:",
|
|
1545
|
+
error instanceof Error ? error.message : String(error)
|
|
1546
|
+
);
|
|
1547
|
+
});
|
|
1287
1548
|
}
|
|
1288
1549
|
/**
|
|
1289
1550
|
* Create a new OAuth transaction
|
|
@@ -1306,7 +1567,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1306
1567
|
scope: params.scope ? params.scope.split(" ") : this.config.scopes || [],
|
|
1307
1568
|
state: params.state || this.generateId()
|
|
1308
1569
|
};
|
|
1309
|
-
this.
|
|
1570
|
+
await this.stateStore.saveTransaction(transaction);
|
|
1310
1571
|
return transaction;
|
|
1311
1572
|
}
|
|
1312
1573
|
/**
|
|
@@ -1367,7 +1628,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1367
1628
|
throw new Error("JWT issuer not initialized");
|
|
1368
1629
|
}
|
|
1369
1630
|
const result = await this.jwtIssuer.verify(token);
|
|
1370
|
-
if (!result.valid || !_optionalChain([result, 'access',
|
|
1631
|
+
if (!result.valid || !_optionalChain([result, 'access', _10 => _10.claims, 'optionalAccess', _11 => _11.jti])) {
|
|
1371
1632
|
throw new Error("Failed to extract JTI from token");
|
|
1372
1633
|
}
|
|
1373
1634
|
return result.claims.jti;
|
|
@@ -1406,7 +1667,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1406
1667
|
/**
|
|
1407
1668
|
* Generate authorization code for client
|
|
1408
1669
|
*/
|
|
1409
|
-
generateAuthorizationCode(transaction, upstreamTokens) {
|
|
1670
|
+
async generateAuthorizationCode(transaction, upstreamTokens) {
|
|
1410
1671
|
const code = this.generateId();
|
|
1411
1672
|
const clientCode = {
|
|
1412
1673
|
clientId: transaction.clientId,
|
|
@@ -1420,7 +1681,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1420
1681
|
transactionId: transaction.id,
|
|
1421
1682
|
upstreamTokens
|
|
1422
1683
|
};
|
|
1423
|
-
this.
|
|
1684
|
+
await this.stateStore.saveClientCode(clientCode);
|
|
1424
1685
|
return code;
|
|
1425
1686
|
}
|
|
1426
1687
|
/**
|
|
@@ -1514,7 +1775,7 @@ var OAuthProxy = (_class4 = class {
|
|
|
1514
1775
|
"Invalid or expired refresh token"
|
|
1515
1776
|
);
|
|
1516
1777
|
}
|
|
1517
|
-
const jti = _optionalChain([verifyResult, 'access',
|
|
1778
|
+
const jti = _optionalChain([verifyResult, 'access', _12 => _12.claims, 'optionalAccess', _13 => _13.jti]);
|
|
1518
1779
|
if (!jti) {
|
|
1519
1780
|
throw new OAuthProxyError("invalid_grant", "Refresh token missing JTI");
|
|
1520
1781
|
}
|
|
@@ -1916,7 +2177,7 @@ var AuthProvider = class {
|
|
|
1916
2177
|
if (!request) {
|
|
1917
2178
|
return void 0;
|
|
1918
2179
|
}
|
|
1919
|
-
const authHeader = _optionalChain([request, 'access',
|
|
2180
|
+
const authHeader = _optionalChain([request, 'access', _14 => _14.headers, 'optionalAccess', _15 => _15.authorization]);
|
|
1920
2181
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
1921
2182
|
return void 0;
|
|
1922
2183
|
}
|
|
@@ -2105,13 +2366,22 @@ var OAuthProvider = class extends AuthProvider {
|
|
|
2105
2366
|
};
|
|
2106
2367
|
|
|
2107
2368
|
// src/auth/utils/diskStore.ts
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
|
|
2372
|
+
|
|
2373
|
+
|
|
2374
|
+
|
|
2375
|
+
|
|
2376
|
+
|
|
2377
|
+
|
|
2108
2378
|
var _promises = require('fs/promises');
|
|
2109
2379
|
var _path = require('path');
|
|
2110
2380
|
var DiskStore = (_class5 = class {
|
|
2111
|
-
|
|
2381
|
+
__init8() {this.cleanupInterval = null}
|
|
2112
2382
|
|
|
2113
2383
|
|
|
2114
|
-
constructor(options) {;_class5.prototype.
|
|
2384
|
+
constructor(options) {;_class5.prototype.__init8.call(this);
|
|
2115
2385
|
this.directory = options.directory;
|
|
2116
2386
|
this.fileExtension = options.fileExtension || ".json";
|
|
2117
2387
|
void this.ensureDirectory();
|
|
@@ -2224,6 +2494,36 @@ var DiskStore = (_class5 = class {
|
|
|
2224
2494
|
return 0;
|
|
2225
2495
|
}
|
|
2226
2496
|
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Atomically retrieve a value and delete it.
|
|
2499
|
+
*
|
|
2500
|
+
* rename() is atomic on POSIX filesystems, so when several processes race
|
|
2501
|
+
* for the same key exactly one rename succeeds and the losers see ENOENT.
|
|
2502
|
+
* That is what makes single-use authorization codes safe when more than one
|
|
2503
|
+
* process shares the directory.
|
|
2504
|
+
*/
|
|
2505
|
+
async take(key) {
|
|
2506
|
+
const filePath = this.getFilePath(key);
|
|
2507
|
+
const claimPath = `${filePath}.${_crypto.randomUUID.call(void 0, )}.claim${this.fileExtension}`;
|
|
2508
|
+
try {
|
|
2509
|
+
await _promises.rename.call(void 0, filePath, claimPath);
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
if (error.code !== "ENOENT") {
|
|
2512
|
+
console.error(`Failed to take key ${key}:`, error);
|
|
2513
|
+
}
|
|
2514
|
+
return null;
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
const content = await _promises.readFile.call(void 0, claimPath, "utf-8");
|
|
2518
|
+
const entry = JSON.parse(content);
|
|
2519
|
+
return entry.expiresAt < Date.now() ? null : entry.value;
|
|
2520
|
+
} catch (error) {
|
|
2521
|
+
console.error(`Failed to read key ${key}:`, error);
|
|
2522
|
+
return null;
|
|
2523
|
+
} finally {
|
|
2524
|
+
await _promises.rm.call(void 0, claimPath, { force: true });
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2227
2527
|
/**
|
|
2228
2528
|
* Ensure storage directory exists
|
|
2229
2529
|
*/
|
|
@@ -2254,9 +2554,9 @@ var DiskStore = (_class5 = class {
|
|
|
2254
2554
|
var JWKSVerifier = (_class6 = class {
|
|
2255
2555
|
|
|
2256
2556
|
|
|
2257
|
-
|
|
2557
|
+
__init9() {this.joseLoaded = false}
|
|
2258
2558
|
|
|
2259
|
-
constructor(config) {;_class6.prototype.
|
|
2559
|
+
constructor(config) {;_class6.prototype.__init9.call(this);
|
|
2260
2560
|
this.config = {
|
|
2261
2561
|
cacheDuration: 36e5,
|
|
2262
2562
|
// 1 hour
|
|
@@ -2414,4 +2714,4 @@ Original error: ${error.message}`
|
|
|
2414
2714
|
|
|
2415
2715
|
|
|
2416
2716
|
exports.getAuthSession = getAuthSession; exports.requireAll = requireAll; exports.requireAny = requireAny; exports.requireAuth = requireAuth; exports.requireRole = requireRole; exports.requireScopes = requireScopes; exports.DEFAULT_ACCESS_TOKEN_TTL = DEFAULT_ACCESS_TOKEN_TTL; exports.DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH = DEFAULT_ACCESS_TOKEN_TTL_NO_REFRESH; exports.DEFAULT_REFRESH_TOKEN_TTL = DEFAULT_REFRESH_TOKEN_TTL; exports.DEFAULT_AUTHORIZATION_CODE_TTL = DEFAULT_AUTHORIZATION_CODE_TTL; exports.DEFAULT_TRANSACTION_TTL = DEFAULT_TRANSACTION_TTL; exports.ConsentManager = ConsentManager; exports.JWTIssuer = JWTIssuer; exports.PKCEUtils = PKCEUtils; exports.EncryptedTokenStorage = EncryptedTokenStorage; exports.MemoryTokenStorage = MemoryTokenStorage; exports.OAuthProxy = OAuthProxy; exports.OAuthProxyError = OAuthProxyError; exports.AuthProvider = AuthProvider; exports.AzureProvider = AzureProvider; exports.GitHubProvider = GitHubProvider; exports.GoogleProvider = GoogleProvider; exports.OAuthProvider = OAuthProvider; exports.DiskStore = DiskStore; exports.JWKSVerifier = JWKSVerifier;
|
|
2417
|
-
//# sourceMappingURL=chunk-
|
|
2717
|
+
//# sourceMappingURL=chunk-DZYT6QAH.cjs.map
|