@decodo/mcp-server 1.2.6 → 1.3.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/build/auth/credential.js +4 -1
- package/build/auth/credential.js.map +1 -1
- package/build/clients/hosts.js +10 -0
- package/build/clients/hosts.js.map +1 -0
- package/build/clients/scraper-api-client.js +15 -5
- package/build/clients/scraper-api-client.js.map +1 -1
- package/build/index.js +2 -2
- package/build/index.js.map +1 -1
- package/build/oauth/backend.js +3 -0
- package/build/oauth/backend.js.map +1 -0
- package/build/oauth/backends/decodo/index.js +71 -0
- package/build/oauth/backends/decodo/index.js.map +1 -0
- package/build/oauth/backends/decodo/request-jwt.js +62 -0
- package/build/oauth/backends/decodo/request-jwt.js.map +1 -0
- package/build/oauth/backends/decodo/token-exchange.js +88 -0
- package/build/oauth/backends/decodo/token-exchange.js.map +1 -0
- package/build/oauth/backends/mock/dashboard.js +84 -0
- package/build/oauth/backends/mock/dashboard.js.map +1 -0
- package/build/oauth/backends/mock/index.js +42 -0
- package/build/oauth/backends/mock/index.js.map +1 -0
- package/build/oauth/backends/mock/subscription-api.js +130 -0
- package/build/oauth/backends/mock/subscription-api.js.map +1 -0
- package/build/oauth/bounded-body.js +29 -0
- package/build/oauth/bounded-body.js.map +1 -0
- package/build/oauth/clients-store.js +142 -0
- package/build/oauth/clients-store.js.map +1 -0
- package/build/oauth/config.js +77 -0
- package/build/oauth/config.js.map +1 -0
- package/build/oauth/cookies.js +25 -0
- package/build/oauth/cookies.js.map +1 -0
- package/build/oauth/index.js +46 -0
- package/build/oauth/index.js.map +1 -0
- package/build/oauth/provider.js +172 -0
- package/build/oauth/provider.js.map +1 -0
- package/build/oauth/redirect-uris.js +17 -0
- package/build/oauth/redirect-uris.js.map +1 -0
- package/build/oauth/router.js +68 -0
- package/build/oauth/router.js.map +1 -0
- package/build/oauth/sealer.js +55 -0
- package/build/oauth/sealer.js.map +1 -0
- package/build/oauth/token-exchange.js +17 -0
- package/build/oauth/token-exchange.js.map +1 -0
- package/build/oauth/ttl-store.js +47 -0
- package/build/oauth/ttl-store.js.map +1 -0
- package/build/server/app.js +77 -0
- package/build/server/app.js.map +1 -0
- package/build/server/revoked-credentials.js +25 -0
- package/build/server/revoked-credentials.js.map +1 -0
- package/build/server/sapi-base-server.js +2 -2
- package/build/server/sapi-base-server.js.map +1 -1
- package/build/server/sapi-http-server.js +2 -2
- package/build/server/sapi-http-server.js.map +1 -1
- package/build/server/trust-proxy.js +19 -0
- package/build/server/trust-proxy.js.map +1 -0
- package/build/server.js +6 -44
- package/build/server.js.map +1 -1
- package/package.json +3 -9
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.mockSubscriptionApiRouter = exports.MockSubscriptionApi = exports.MockRequestError = void 0;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const express_1 = __importDefault(require("express"));
|
|
9
|
+
const token_exchange_1 = require("../../token-exchange");
|
|
10
|
+
const ttl_store_1 = require("../../ttl-store");
|
|
11
|
+
const request_jwt_1 = require("../decodo/request-jwt");
|
|
12
|
+
const PENDING_TTL_MS = request_jwt_1.REQUEST_JWT.LIFETIME_SECONDS * 1000;
|
|
13
|
+
const CODE_TTL_MS = 60000;
|
|
14
|
+
const CODE_BYTES = 32;
|
|
15
|
+
class MockRequestError extends Error {
|
|
16
|
+
}
|
|
17
|
+
exports.MockRequestError = MockRequestError;
|
|
18
|
+
class MockSubscriptionApi {
|
|
19
|
+
constructor(publicKey, registeredClient) {
|
|
20
|
+
this.publicKey = publicKey;
|
|
21
|
+
this.registeredClient = registeredClient;
|
|
22
|
+
this.pending = new ttl_store_1.TtlStore(PENDING_TTL_MS);
|
|
23
|
+
this.codes = new ttl_store_1.TtlStore(CODE_TTL_MS);
|
|
24
|
+
}
|
|
25
|
+
validateRequest({ clientId, request }) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
const decoded = (0, request_jwt_1.verifyRequestJwt)(request, this.publicKey);
|
|
28
|
+
if (!decoded) {
|
|
29
|
+
throw new MockRequestError('request JWT signature or format is invalid');
|
|
30
|
+
}
|
|
31
|
+
const { header, claims } = decoded;
|
|
32
|
+
const now = Math.floor(Date.now() / 1000);
|
|
33
|
+
const failures = [
|
|
34
|
+
header.kid !== this.registeredClient.kid && 'kid is not registered for this client',
|
|
35
|
+
claims.aud !== request_jwt_1.REQUEST_JWT.AUDIENCE && 'aud must be subscription-api',
|
|
36
|
+
claims.client_id !== clientId && 'client_id in the JWT differs from the query',
|
|
37
|
+
clientId !== this.registeredClient.clientId && 'unknown client_id',
|
|
38
|
+
claims.redirect_uri !== this.registeredClient.redirectUri && 'redirect_uri is not the registered one',
|
|
39
|
+
claims.code_challenge_method !== request_jwt_1.REQUEST_JWT.CODE_CHALLENGE_METHOD && 'code_challenge_method must be S256',
|
|
40
|
+
((_a = claims.code_challenge) === null || _a === void 0 ? void 0 : _a.length) !== 43 && 'code_challenge must be 43 characters',
|
|
41
|
+
!(((_b = claims.state) === null || _b === void 0 ? void 0 : _b.length) >= 16 && claims.state.length <= 255) && 'state must be 16 to 255 characters',
|
|
42
|
+
!(claims.exp > claims.iat && claims.exp - claims.iat <= request_jwt_1.REQUEST_JWT.LIFETIME_SECONDS) && 'exp must be within 120 s of iat',
|
|
43
|
+
claims.exp < now - 30 && 'request has expired',
|
|
44
|
+
].filter((failure) => typeof failure === 'string');
|
|
45
|
+
if (failures.length > 0) {
|
|
46
|
+
throw new MockRequestError(failures.join('; '));
|
|
47
|
+
}
|
|
48
|
+
const uuid = (0, node_crypto_1.randomUUID)();
|
|
49
|
+
this.pending.set(uuid, {
|
|
50
|
+
client_id: claims.client_id,
|
|
51
|
+
redirect_uri: claims.redirect_uri,
|
|
52
|
+
state: claims.state,
|
|
53
|
+
code_challenge: claims.code_challenge,
|
|
54
|
+
});
|
|
55
|
+
return { uuid, clientName: 'Decodo MCP (mock)' };
|
|
56
|
+
}
|
|
57
|
+
pendingRequest(uuid) {
|
|
58
|
+
const pending = this.pending.get(uuid);
|
|
59
|
+
return pending && Object.assign(Object.assign({}, pending), { uuid });
|
|
60
|
+
}
|
|
61
|
+
decide({ uuid, decision, scraperApiKey }) {
|
|
62
|
+
const pending = this.pending.take(uuid);
|
|
63
|
+
if (!pending) {
|
|
64
|
+
throw new MockRequestError('Exchange request can no longer be decided.');
|
|
65
|
+
}
|
|
66
|
+
if (decision !== 'approve') {
|
|
67
|
+
return { redirectUri: pending.redirect_uri, state: pending.state, error: 'access_denied' };
|
|
68
|
+
}
|
|
69
|
+
const code = (0, node_crypto_1.randomBytes)(CODE_BYTES).toString('base64url');
|
|
70
|
+
this.codes.set(code, Object.assign(Object.assign({}, pending), { scraperApiKey }));
|
|
71
|
+
return { redirectUri: pending.redirect_uri, state: pending.state, code };
|
|
72
|
+
}
|
|
73
|
+
exchange({ clientId, code, codeVerifier }) {
|
|
74
|
+
const issued = this.codes.take(code);
|
|
75
|
+
if (!issued) {
|
|
76
|
+
return { ok: false, status: 400, appCode: token_exchange_1.TOKEN_EXCHANGE_ERROR.INVALID_GRANT };
|
|
77
|
+
}
|
|
78
|
+
if (issued.client_id !== clientId) {
|
|
79
|
+
return { ok: false, status: 403, appCode: token_exchange_1.TOKEN_EXCHANGE_ERROR.INVALID_CLIENT };
|
|
80
|
+
}
|
|
81
|
+
if ((0, node_crypto_1.createHash)('sha256').update(codeVerifier).digest('base64url') !== issued.code_challenge) {
|
|
82
|
+
return { ok: false, status: 400, appCode: token_exchange_1.TOKEN_EXCHANGE_ERROR.INVALID_GRANT };
|
|
83
|
+
}
|
|
84
|
+
return { ok: true, sapiToken: issued.scraperApiKey };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.MockSubscriptionApi = MockSubscriptionApi;
|
|
88
|
+
const mockSubscriptionApiRouter = (api) => {
|
|
89
|
+
const router = express_1.default.Router();
|
|
90
|
+
router.use(express_1.default.json());
|
|
91
|
+
router.post('/token-exchange/request', (req, res) => {
|
|
92
|
+
var _a;
|
|
93
|
+
const { client_id: clientId, request } = (_a = req.body) !== null && _a !== void 0 ? _a : {};
|
|
94
|
+
if (typeof clientId !== 'string' || typeof request !== 'string') {
|
|
95
|
+
res.status(422).json({ status_code: 422, title: 'Unprocessable Entity', detail: 'client_id and request are required' });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const { uuid, clientName } = api.validateRequest({ clientId, request });
|
|
100
|
+
const expiresAt = new Date(Date.now() + PENDING_TTL_MS).toISOString();
|
|
101
|
+
res.status(201).json({ uuid, client_name: clientName, expires_at: expiresAt });
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const detail = error instanceof MockRequestError ? error.message : 'unexpected error';
|
|
105
|
+
res.status(400).json({ status_code: 400, title: 'Bad Request', detail });
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
router.post('/token-exchange/exchange', (req, res) => {
|
|
109
|
+
var _a;
|
|
110
|
+
const { client_id: clientId, code, code_verifier: codeVerifier } = (_a = req.body) !== null && _a !== void 0 ? _a : {};
|
|
111
|
+
if (typeof clientId !== 'string' || typeof code !== 'string' || typeof codeVerifier !== 'string') {
|
|
112
|
+
res.status(422).json({ status_code: 422, title: 'Unprocessable Entity', detail: 'client_id, code and code_verifier are required' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const result = api.exchange({ clientId, code, codeVerifier });
|
|
116
|
+
if (!result.ok) {
|
|
117
|
+
res.status(result.status).json({
|
|
118
|
+
status_code: result.status,
|
|
119
|
+
title: result.status === 403 ? 'Forbidden' : 'Bad Request',
|
|
120
|
+
detail: 'Exchange failed.',
|
|
121
|
+
app_code: result.appCode,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
res.status(200).json({ sapi_token: result.sapiToken, label: 'mock key' });
|
|
126
|
+
});
|
|
127
|
+
return router;
|
|
128
|
+
};
|
|
129
|
+
exports.mockSubscriptionApiRouter = mockSubscriptionApiRouter;
|
|
130
|
+
//# sourceMappingURL=subscription-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription-api.js","sourceRoot":"","sources":["../../../../src/oauth/backends/mock/subscription-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readBoundedText = void 0;
|
|
4
|
+
const readBoundedText = async (response, maxBytes) => {
|
|
5
|
+
if (Number(response.headers.get('content-length')) > maxBytes) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (!response.body) {
|
|
9
|
+
return '';
|
|
10
|
+
}
|
|
11
|
+
const reader = response.body.getReader();
|
|
12
|
+
const chunks = [];
|
|
13
|
+
let received = 0;
|
|
14
|
+
for (;;) {
|
|
15
|
+
const { done, value } = await reader.read();
|
|
16
|
+
if (done) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
received += value.byteLength;
|
|
20
|
+
if (received > maxBytes) {
|
|
21
|
+
await reader.cancel();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
chunks.push(value);
|
|
25
|
+
}
|
|
26
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
27
|
+
};
|
|
28
|
+
exports.readBoundedText = readBoundedText;
|
|
29
|
+
//# sourceMappingURL=bounded-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-body.js","sourceRoot":"","sources":["../../src/oauth/bounded-body.ts"],"names":[],"mappings":";;;AAAO,MAAM,eAAe,GAAG,KAAK,EAAE,QAAkB,EAAE,QAAgB,EAA+B,EAAE;IACzG,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,GAAG,QAAQ,EAAE,CAAC;QAC9D,OAAO;IACT,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;IACzC,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,SAAS,CAAC;QACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAE5C,IAAI,IAAI,EAAE,CAAC;YACT,MAAM;QACR,CAAC;QAED,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC;QAE7B,IAAI,QAAQ,GAAG,QAAQ,EAAE,CAAC;YACxB,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,OAAO;QACT,CAAC;QAED,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC,CAAC;AA/BW,QAAA,eAAe,mBA+B1B"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClientsStore = exports.assertClientMetadataBounded = exports.isClientIdMetadataUrl = exports.MAX_SCOPE_LENGTH = exports.MAX_REDIRECT_URI_LENGTH = exports.MAX_REDIRECT_URIS = exports.MAX_CLIENT_NAME_LENGTH = void 0;
|
|
4
|
+
const errors_js_1 = require("@modelcontextprotocol/sdk/server/auth/errors.js");
|
|
5
|
+
const auth_js_1 = require("@modelcontextprotocol/sdk/shared/auth.js");
|
|
6
|
+
const logger_1 = require("../logger");
|
|
7
|
+
const bounded_body_1 = require("./bounded-body");
|
|
8
|
+
const redirect_uris_1 = require("./redirect-uris");
|
|
9
|
+
const ttl_store_1 = require("./ttl-store");
|
|
10
|
+
const SEAL_PURPOSE = 'client';
|
|
11
|
+
const PUBLIC_CLIENT = 'none';
|
|
12
|
+
exports.MAX_CLIENT_NAME_LENGTH = 128;
|
|
13
|
+
exports.MAX_REDIRECT_URIS = 10;
|
|
14
|
+
exports.MAX_REDIRECT_URI_LENGTH = 1024;
|
|
15
|
+
exports.MAX_SCOPE_LENGTH = 256;
|
|
16
|
+
const MAX_METADATA_DOCUMENT_BYTES = 64 * 1024;
|
|
17
|
+
const METADATA_DOCUMENT_TTL_MS = 60 * 60 * 1000;
|
|
18
|
+
const METADATA_FETCH_TIMEOUT_MS = 5000;
|
|
19
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
20
|
+
const IP_LITERAL = /^(\d{1,3}\.){3}\d{1,3}$|^\[/;
|
|
21
|
+
const isClientIdMetadataUrl = (clientId) => {
|
|
22
|
+
if (!URL.canParse(clientId)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
const url = new URL(clientId);
|
|
26
|
+
return (url.protocol === 'https:' &&
|
|
27
|
+
url.pathname !== '/' &&
|
|
28
|
+
!url.hash &&
|
|
29
|
+
!url.username &&
|
|
30
|
+
!LOOPBACK_HOSTS.has(url.hostname) &&
|
|
31
|
+
!IP_LITERAL.test(url.hostname));
|
|
32
|
+
};
|
|
33
|
+
exports.isClientIdMetadataUrl = isClientIdMetadataUrl;
|
|
34
|
+
const assertClientMetadataBounded = (client) => {
|
|
35
|
+
if (client.redirect_uris.length === 0) {
|
|
36
|
+
throw new errors_js_1.InvalidClientMetadataError('redirect_uris must contain at least one uri');
|
|
37
|
+
}
|
|
38
|
+
if (client.redirect_uris.length > exports.MAX_REDIRECT_URIS) {
|
|
39
|
+
throw new errors_js_1.InvalidClientMetadataError(`redirect_uris must contain at most ${exports.MAX_REDIRECT_URIS} uris`);
|
|
40
|
+
}
|
|
41
|
+
if (client.redirect_uris.some(uri => uri.length > exports.MAX_REDIRECT_URI_LENGTH)) {
|
|
42
|
+
throw new errors_js_1.InvalidClientMetadataError(`each redirect_uri must be at most ${exports.MAX_REDIRECT_URI_LENGTH} characters`);
|
|
43
|
+
}
|
|
44
|
+
if (client.client_name && client.client_name.length > exports.MAX_CLIENT_NAME_LENGTH) {
|
|
45
|
+
throw new errors_js_1.InvalidClientMetadataError(`client_name must be at most ${exports.MAX_CLIENT_NAME_LENGTH} characters`);
|
|
46
|
+
}
|
|
47
|
+
if (client.scope && client.scope.length > exports.MAX_SCOPE_LENGTH) {
|
|
48
|
+
throw new errors_js_1.InvalidClientMetadataError(`scope must be at most ${exports.MAX_SCOPE_LENGTH} characters`);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
exports.assertClientMetadataBounded = assertClientMetadataBounded;
|
|
52
|
+
class ClientsStore {
|
|
53
|
+
constructor({ sealer, allowedRedirectUris, fetch = globalThis.fetch, }) {
|
|
54
|
+
this.metadataDocuments = new ttl_store_1.TtlStore(METADATA_DOCUMENT_TTL_MS);
|
|
55
|
+
this.sealer = sealer;
|
|
56
|
+
this.allowedRedirectUris = allowedRedirectUris;
|
|
57
|
+
this.fetch = fetch;
|
|
58
|
+
}
|
|
59
|
+
registerClient(client) {
|
|
60
|
+
if (client.token_endpoint_auth_method !== PUBLIC_CLIENT) {
|
|
61
|
+
throw new errors_js_1.InvalidClientMetadataError(`only public clients are supported (token_endpoint_auth_method "${PUBLIC_CLIENT}")`);
|
|
62
|
+
}
|
|
63
|
+
(0, exports.assertClientMetadataBounded)(client);
|
|
64
|
+
this.assertRedirectUrisAllowed(client.redirect_uris);
|
|
65
|
+
const sealed = {
|
|
66
|
+
client_name: client.client_name,
|
|
67
|
+
redirect_uris: client.redirect_uris,
|
|
68
|
+
grant_types: client.grant_types,
|
|
69
|
+
response_types: client.response_types,
|
|
70
|
+
scope: client.scope,
|
|
71
|
+
token_endpoint_auth_method: PUBLIC_CLIENT,
|
|
72
|
+
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
73
|
+
};
|
|
74
|
+
return Object.assign(Object.assign(Object.assign({}, client), sealed), { client_id: this.sealer.seal(SEAL_PURPOSE, sealed), client_secret: undefined, client_secret_expires_at: undefined });
|
|
75
|
+
}
|
|
76
|
+
async getClient(clientId) {
|
|
77
|
+
var _a;
|
|
78
|
+
if ((0, exports.isClientIdMetadataUrl)(clientId)) {
|
|
79
|
+
return (_a = this.metadataDocuments.get(clientId)) !== null && _a !== void 0 ? _a : (await this.fetchMetadataDocument(clientId));
|
|
80
|
+
}
|
|
81
|
+
const sealed = this.sealer.unseal(SEAL_PURPOSE, clientId);
|
|
82
|
+
return sealed && Object.assign(Object.assign({}, sealed), { client_id: clientId });
|
|
83
|
+
}
|
|
84
|
+
assertRedirectUrisAllowed(redirectUris) {
|
|
85
|
+
const disallowed = (0, redirect_uris_1.disallowedRedirectUris)(redirectUris, this.allowedRedirectUris);
|
|
86
|
+
if (disallowed.length > 0) {
|
|
87
|
+
throw new errors_js_1.InvalidClientMetadataError(`redirect_uri not allowed: ${disallowed.join(', ')}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async fetchMetadataDocument(clientId) {
|
|
91
|
+
try {
|
|
92
|
+
const response = await this.fetch(clientId, {
|
|
93
|
+
headers: { Accept: 'application/json' },
|
|
94
|
+
redirect: 'error',
|
|
95
|
+
signal: AbortSignal.timeout(METADATA_FETCH_TIMEOUT_MS),
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.fetch_failed', { clientId, status: response.status });
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const text = await (0, bounded_body_1.readBoundedText)(response, MAX_METADATA_DOCUMENT_BYTES);
|
|
102
|
+
if (text === undefined) {
|
|
103
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.too_large', { clientId, maxBytes: MAX_METADATA_DOCUMENT_BYTES });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const document = JSON.parse(text);
|
|
107
|
+
if (document.client_id !== clientId) {
|
|
108
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.client_id_mismatch', { clientId });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const parsed = auth_js_1.OAuthClientMetadataSchema.safeParse(document);
|
|
112
|
+
if (!parsed.success) {
|
|
113
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.invalid', { clientId, issues: parsed.error.message });
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
(0, exports.assertClientMetadataBounded)(parsed.data);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.invalid', { clientId, issues: error instanceof Error ? error.message : String(error) });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const disallowed = (0, redirect_uris_1.disallowedRedirectUris)(parsed.data.redirect_uris, this.allowedRedirectUris);
|
|
124
|
+
if (disallowed.length > 0) {
|
|
125
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.redirect_uri_not_allowed', { clientId, disallowed });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const client = Object.assign(Object.assign({}, parsed.data), { client_id: clientId, token_endpoint_auth_method: PUBLIC_CLIENT });
|
|
129
|
+
this.metadataDocuments.set(clientId, client);
|
|
130
|
+
return client;
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
(0, logger_1.log)('warn', 'oauth.client_metadata.fetch_failed', {
|
|
134
|
+
clientId,
|
|
135
|
+
error: error instanceof Error ? error.message : String(error),
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
exports.ClientsStore = ClientsStore;
|
|
142
|
+
//# sourceMappingURL=clients-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clients-store.js","sourceRoot":"","sources":["../../src/oauth/clients-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.oauthConfigFromEnv = exports.MCP_PATH = exports.CALLBACK_PATH = exports.CLAUDE_REDIRECT_URIS = exports.DEFAULT_TOKEN_EXCHANGE_CLIENT_ID = exports.DEFAULT_TOKEN_EXCHANGE_BASE_URL = exports.DEFAULT_DASHBOARD_AUTHORIZE_URL = exports.OAUTH_BACKEND = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const logger_1 = require("../logger");
|
|
6
|
+
const sealer_1 = require("./sealer");
|
|
7
|
+
exports.OAUTH_BACKEND = {
|
|
8
|
+
DECODO: 'decodo',
|
|
9
|
+
MOCK: 'mock',
|
|
10
|
+
};
|
|
11
|
+
exports.DEFAULT_DASHBOARD_AUTHORIZE_URL = 'https://dashboard.decodo.com/scraper/token-exchange/decision';
|
|
12
|
+
exports.DEFAULT_TOKEN_EXCHANGE_BASE_URL = 'https://dashboard.decodo.com/subscription-api/v1/api/scraper/apikey/token-exchange';
|
|
13
|
+
exports.DEFAULT_TOKEN_EXCHANGE_CLIENT_ID = 'scrapper-mcp';
|
|
14
|
+
exports.CLAUDE_REDIRECT_URIS = [
|
|
15
|
+
'https://claude.ai/api/mcp/auth_callback',
|
|
16
|
+
'https://claude.com/api/mcp/auth_callback',
|
|
17
|
+
];
|
|
18
|
+
exports.CALLBACK_PATH = '/oauth/callback';
|
|
19
|
+
exports.MCP_PATH = '/mcp';
|
|
20
|
+
const urlFromEnv = (env, key, fallback) => {
|
|
21
|
+
var _a;
|
|
22
|
+
const value = ((_a = env[key]) === null || _a === void 0 ? void 0 : _a.trim()) || fallback;
|
|
23
|
+
if (!URL.canParse(value)) {
|
|
24
|
+
throw new Error(`${key} must be an absolute URL, got "${value}"`);
|
|
25
|
+
}
|
|
26
|
+
return new URL(value);
|
|
27
|
+
};
|
|
28
|
+
const listFromEnv = (env, key) => {
|
|
29
|
+
var _a;
|
|
30
|
+
return ((_a = env[key]) !== null && _a !== void 0 ? _a : '')
|
|
31
|
+
.split(',')
|
|
32
|
+
.map(value => value.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
};
|
|
35
|
+
const privateKeyFromEnv = (env) => {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
const file = (_a = env.TOKEN_EXCHANGE_PRIVATE_KEY_FILE) === null || _a === void 0 ? void 0 : _a.trim();
|
|
38
|
+
if (file) {
|
|
39
|
+
try {
|
|
40
|
+
return (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error(`TOKEN_EXCHANGE_PRIVATE_KEY_FILE cannot be read (${file}): ${error.message}`, { cause: error });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return ((_b = env.TOKEN_EXCHANGE_PRIVATE_KEY) !== null && _b !== void 0 ? _b : '').replace(/\\n/g, '\n').trim();
|
|
47
|
+
};
|
|
48
|
+
const stateSecretFromEnv = (env) => {
|
|
49
|
+
var _a;
|
|
50
|
+
const secret = (_a = env.OAUTH_STATE_SECRET) === null || _a === void 0 ? void 0 : _a.trim();
|
|
51
|
+
if (secret) {
|
|
52
|
+
return secret;
|
|
53
|
+
}
|
|
54
|
+
(0, logger_1.log)('warn', 'oauth.state_secret_missing', {
|
|
55
|
+
message: 'OAUTH_STATE_SECRET is not set; using a random one, so sign-in only works with a single replica',
|
|
56
|
+
});
|
|
57
|
+
return sealer_1.Sealer.randomSecret();
|
|
58
|
+
};
|
|
59
|
+
const oauthConfigFromEnv = (env, port) => {
|
|
60
|
+
var _a, _b, _c;
|
|
61
|
+
return ({
|
|
62
|
+
publicUrl: urlFromEnv(env, 'PUBLIC_URL', `http://localhost:${port}`),
|
|
63
|
+
backend: env.OAUTH_BACKEND === exports.OAUTH_BACKEND.MOCK ? exports.OAUTH_BACKEND.MOCK : exports.OAUTH_BACKEND.DECODO,
|
|
64
|
+
stateSecret: stateSecretFromEnv(env),
|
|
65
|
+
dashboardAuthorizeUrl: urlFromEnv(env, 'DASHBOARD_AUTHORIZE_URL', exports.DEFAULT_DASHBOARD_AUTHORIZE_URL),
|
|
66
|
+
tokenExchange: {
|
|
67
|
+
clientId: ((_a = env.TOKEN_EXCHANGE_CLIENT_ID) === null || _a === void 0 ? void 0 : _a.trim()) || exports.DEFAULT_TOKEN_EXCHANGE_CLIENT_ID,
|
|
68
|
+
kid: ((_b = env.TOKEN_EXCHANGE_KID) === null || _b === void 0 ? void 0 : _b.trim()) || '',
|
|
69
|
+
privateKey: privateKeyFromEnv(env),
|
|
70
|
+
baseUrl: urlFromEnv(env, 'TOKEN_EXCHANGE_BASE_URL', exports.DEFAULT_TOKEN_EXCHANGE_BASE_URL),
|
|
71
|
+
},
|
|
72
|
+
allowedRedirectUris: [...exports.CLAUDE_REDIRECT_URIS, ...listFromEnv(env, 'OAUTH_ALLOWED_REDIRECT_URIS')],
|
|
73
|
+
mockScraperApiKey: ((_c = env.MOCK_SCRAPER_API_KEY) === null || _c === void 0 ? void 0 : _c.trim()) || '',
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
exports.oauthConfigFromEnv = oauthConfigFromEnv;
|
|
77
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/oauth/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cookieValue = void 0;
|
|
4
|
+
const cookieValue = (header, name) => {
|
|
5
|
+
if (!header) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
for (const part of header.split(';')) {
|
|
9
|
+
const separator = part.indexOf('=');
|
|
10
|
+
if (separator === -1) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
if (part.slice(0, separator).trim() === name) {
|
|
14
|
+
try {
|
|
15
|
+
return decodeURIComponent(part.slice(separator + 1).trim());
|
|
16
|
+
}
|
|
17
|
+
catch (_a) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
};
|
|
24
|
+
exports.cookieValue = cookieValue;
|
|
25
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/oauth/cookies.ts"],"names":[],"mappings":";;;AAAO,MAAM,WAAW,GAAG,CAAC,MAA0B,EAAE,IAAY,EAAsB,EAAE;IAC1F,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAEpC,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;YACrB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,OAAO,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YAC9D,CAAC;YAAC,WAAM,CAAC;gBACP,OAAO;YACT,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;AACT,CAAC,CAAC;AAtBW,QAAA,WAAW,eAsBtB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createOAuth = exports.oauthConfigFromEnv = exports.OAUTH_BACKEND = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
const router_js_1 = require("@modelcontextprotocol/sdk/server/auth/router.js");
|
|
9
|
+
const decodo_1 = require("./backends/decodo");
|
|
10
|
+
const mock_1 = require("./backends/mock");
|
|
11
|
+
const clients_store_1 = require("./clients-store");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
const provider_1 = require("./provider");
|
|
14
|
+
const router_1 = require("./router");
|
|
15
|
+
const sealer_1 = require("./sealer");
|
|
16
|
+
var config_2 = require("./config");
|
|
17
|
+
Object.defineProperty(exports, "OAUTH_BACKEND", { enumerable: true, get: function () { return config_2.OAUTH_BACKEND; } });
|
|
18
|
+
Object.defineProperty(exports, "oauthConfigFromEnv", { enumerable: true, get: function () { return config_2.oauthConfigFromEnv; } });
|
|
19
|
+
const BACKENDS = {
|
|
20
|
+
[config_1.OAUTH_BACKEND.DECODO]: decodo_1.createDecodoBackend,
|
|
21
|
+
[config_1.OAUTH_BACKEND.MOCK]: mock_1.createMockBackend,
|
|
22
|
+
};
|
|
23
|
+
const createOAuth = (config, { fetch, onTokenIssued } = {}) => {
|
|
24
|
+
const callbackUrl = new URL(config_1.CALLBACK_PATH, config.publicUrl);
|
|
25
|
+
const resourceServerUrl = new URL(config_1.MCP_PATH, config.publicUrl);
|
|
26
|
+
const sealer = new sealer_1.Sealer(config.stateSecret);
|
|
27
|
+
const backend = BACKENDS[config.backend](config, { callbackUrl, fetch });
|
|
28
|
+
const provider = new provider_1.DecodoOAuthProvider({
|
|
29
|
+
clientsStore: new clients_store_1.ClientsStore({ sealer, allowedRedirectUris: config.allowedRedirectUris, fetch }),
|
|
30
|
+
backend,
|
|
31
|
+
sealer,
|
|
32
|
+
callbackUrl,
|
|
33
|
+
onTokenIssued,
|
|
34
|
+
});
|
|
35
|
+
const router = express_1.default.Router();
|
|
36
|
+
if (backend.router) {
|
|
37
|
+
router.use(backend.router);
|
|
38
|
+
}
|
|
39
|
+
router.use((0, router_1.oauthRouter)({ provider, issuerUrl: config.publicUrl, resourceServerUrl }));
|
|
40
|
+
return {
|
|
41
|
+
router,
|
|
42
|
+
resourceMetadataUrl: (0, router_js_1.getOAuthProtectedResourceMetadataUrl)(resourceServerUrl),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
exports.createOAuth = createOAuth;
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/oauth/index.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;AAC9B,+EAAuG;AAEvG,8CAAwD;AACxD,0CAAoD;AACpD,mDAA+C;AAC/C,qCAAkE;AAElE,yCAAiD;AACjD,qCAAuC;AACvC,qCAAkC;AAElC,mCAA6D;AAApD,uGAAA,aAAa,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAG1C,MAAM,QAAQ,GAAkD;IAC9D,CAAC,sBAAa,CAAC,MAAM,CAAC,EAAE,4BAAmB;IAC3C,CAAC,sBAAa,CAAC,IAAI,CAAC,EAAE,wBAAiB;CACxC,CAAC;AASK,MAAM,WAAW,GAAG,CACzB,MAAmB,EACnB,EAAE,KAAK,EAAE,aAAa,KAA6E,EAAE,EAC9F,EAAE;IACT,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,sBAAa,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,iBAAQ,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IAEzE,MAAM,QAAQ,GAAG,IAAI,8BAAmB,CAAC;QACvC,YAAY,EAAE,IAAI,4BAAY,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,CAAC,mBAAmB,EAAE,KAAK,EAAE,CAAC;QAClG,OAAO;QACP,MAAM;QACN,WAAW;QACX,aAAa;KACd,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,iBAAO,CAAC,MAAM,EAAE,CAAC;IAEhC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;IAED,MAAM,CAAC,GAAG,CAAC,IAAA,oBAAW,EAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC;IAEtF,OAAO;QACL,MAAM;QACN,mBAAmB,EAAE,IAAA,gDAAoC,EAAC,iBAAiB,CAAC;KAC7E,CAAC;AACJ,CAAC,CAAC;AA7BW,QAAA,WAAW,eA6BtB"}
|