@n24q02m/hull 0.0.0-stage → 0.4.0-beta.10
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/LICENSE +201 -0
- package/README.md +166 -2
- package/build/auth/credential-form.d.ts +163 -0
- package/build/auth/credential-form.d.ts.map +1 -0
- package/build/auth/credential-form.js +1869 -0
- package/build/auth/credential-form.js.map +1 -0
- package/build/auth/delegated-oauth-app.d.ts +90 -0
- package/build/auth/delegated-oauth-app.d.ts.map +1 -0
- package/build/auth/delegated-oauth-app.js +881 -0
- package/build/auth/delegated-oauth-app.js.map +1 -0
- package/build/auth/index.d.ts +12 -0
- package/build/auth/index.d.ts.map +1 -0
- package/build/auth/index.js +12 -0
- package/build/auth/index.js.map +1 -0
- package/build/auth/local-oauth-app.d.ts +152 -0
- package/build/auth/local-oauth-app.d.ts.map +1 -0
- package/build/auth/local-oauth-app.js +975 -0
- package/build/auth/local-oauth-app.js.map +1 -0
- package/build/auth/relay-login.d.ts +46 -0
- package/build/auth/relay-login.d.ts.map +1 -0
- package/build/auth/relay-login.js +197 -0
- package/build/auth/relay-login.js.map +1 -0
- package/build/auth/router.d.ts +18 -0
- package/build/auth/router.d.ts.map +1 -0
- package/build/auth/router.js +62 -0
- package/build/auth/router.js.map +1 -0
- package/build/auth/session-store.d.ts +49 -0
- package/build/auth/session-store.d.ts.map +1 -0
- package/build/auth/session-store.js +94 -0
- package/build/auth/session-store.js.map +1 -0
- package/build/auth/stable-sub.d.ts +27 -0
- package/build/auth/stable-sub.d.ts.map +1 -0
- package/build/auth/stable-sub.js +38 -0
- package/build/auth/stable-sub.js.map +1 -0
- package/build/auth/well-known.d.ts +4 -0
- package/build/auth/well-known.d.ts.map +1 -0
- package/build/auth/well-known.js +26 -0
- package/build/auth/well-known.js.map +1 -0
- package/build/cli/build-cli.d.ts +97 -0
- package/build/cli/build-cli.d.ts.map +1 -0
- package/build/cli/build-cli.js +309 -0
- package/build/cli/build-cli.js.map +1 -0
- package/build/cli/index.d.ts +2 -0
- package/build/cli/index.d.ts.map +1 -0
- package/build/cli/index.js +2 -0
- package/build/cli/index.js.map +1 -0
- package/build/crypto/aes.d.ts +8 -0
- package/build/crypto/aes.d.ts.map +1 -0
- package/build/crypto/aes.js +27 -0
- package/build/crypto/aes.js.map +1 -0
- package/build/crypto/ecdh.d.ts +5 -0
- package/build/crypto/ecdh.d.ts.map +1 -0
- package/build/crypto/ecdh.js +16 -0
- package/build/crypto/ecdh.js.map +1 -0
- package/build/crypto/index.d.ts +4 -0
- package/build/crypto/index.d.ts.map +1 -0
- package/build/crypto/index.js +4 -0
- package/build/crypto/index.js.map +1 -0
- package/build/crypto/kdf.d.ts +15 -0
- package/build/crypto/kdf.d.ts.map +1 -0
- package/build/crypto/kdf.js +26 -0
- package/build/crypto/kdf.js.map +1 -0
- package/build/index.d.ts +19 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +28 -0
- package/build/index.js.map +1 -0
- package/build/lifecycle/lock.d.ts +68 -0
- package/build/lifecycle/lock.d.ts.map +1 -0
- package/build/lifecycle/lock.js +173 -0
- package/build/lifecycle/lock.js.map +1 -0
- package/build/oauth/cache.d.ts +22 -0
- package/build/oauth/cache.d.ts.map +1 -0
- package/build/oauth/cache.js +24 -0
- package/build/oauth/cache.js.map +1 -0
- package/build/oauth/index.d.ts +6 -0
- package/build/oauth/index.d.ts.map +1 -0
- package/build/oauth/index.js +6 -0
- package/build/oauth/index.js.map +1 -0
- package/build/oauth/jwt-issuer.d.ts +87 -0
- package/build/oauth/jwt-issuer.d.ts.map +1 -0
- package/build/oauth/jwt-issuer.js +205 -0
- package/build/oauth/jwt-issuer.js.map +1 -0
- package/build/oauth/pkce.d.ts +10 -0
- package/build/oauth/pkce.d.ts.map +1 -0
- package/build/oauth/pkce.js +34 -0
- package/build/oauth/pkce.js.map +1 -0
- package/build/oauth/provider.d.ts +52 -0
- package/build/oauth/provider.d.ts.map +1 -0
- package/build/oauth/provider.js +98 -0
- package/build/oauth/provider.js.map +1 -0
- package/build/oauth/user-store.d.ts +25 -0
- package/build/oauth/user-store.d.ts.map +1 -0
- package/build/oauth/user-store.js +85 -0
- package/build/oauth/user-store.js.map +1 -0
- package/build/relay/browser.d.ts +15 -0
- package/build/relay/browser.d.ts.map +1 -0
- package/build/relay/browser.js +121 -0
- package/build/relay/browser.js.map +1 -0
- package/build/relay/client.d.ts +36 -0
- package/build/relay/client.d.ts.map +1 -0
- package/build/relay/client.js +135 -0
- package/build/relay/client.js.map +1 -0
- package/build/relay/session.d.ts +12 -0
- package/build/relay/session.d.ts.map +1 -0
- package/build/relay/session.js +55 -0
- package/build/relay/session.js.map +1 -0
- package/build/relay/tool-helpers.d.ts +85 -0
- package/build/relay/tool-helpers.d.ts.map +1 -0
- package/build/relay/tool-helpers.js +103 -0
- package/build/relay/tool-helpers.js.map +1 -0
- package/build/relay/wordlist.d.ts +2 -0
- package/build/relay/wordlist.d.ts.map +1 -0
- package/build/relay/wordlist.js +7782 -0
- package/build/relay/wordlist.js.map +1 -0
- package/build/schema/types.d.ts +67 -0
- package/build/schema/types.d.ts.map +1 -0
- package/build/schema/types.js +2 -0
- package/build/schema/types.js.map +1 -0
- package/build/storage/atomic-write.d.ts +8 -0
- package/build/storage/atomic-write.d.ts.map +1 -0
- package/build/storage/atomic-write.js +26 -0
- package/build/storage/atomic-write.js.map +1 -0
- package/build/storage/backends.d.ts +74 -0
- package/build/storage/backends.d.ts.map +1 -0
- package/build/storage/backends.js +193 -0
- package/build/storage/backends.js.map +1 -0
- package/build/storage/config-file.d.ts +35 -0
- package/build/storage/config-file.d.ts.map +1 -0
- package/build/storage/config-file.js +259 -0
- package/build/storage/config-file.js.map +1 -0
- package/build/storage/credential-store.d.ts +71 -0
- package/build/storage/credential-store.d.ts.map +1 -0
- package/build/storage/credential-store.js +91 -0
- package/build/storage/credential-store.js.map +1 -0
- package/build/storage/data-dir.d.ts +58 -0
- package/build/storage/data-dir.d.ts.map +1 -0
- package/build/storage/data-dir.js +79 -0
- package/build/storage/data-dir.js.map +1 -0
- package/build/storage/encryption.d.ts +9 -0
- package/build/storage/encryption.d.ts.map +1 -0
- package/build/storage/encryption.js +27 -0
- package/build/storage/encryption.js.map +1 -0
- package/build/storage/home-dir.d.ts +11 -0
- package/build/storage/home-dir.d.ts.map +1 -0
- package/build/storage/home-dir.js +18 -0
- package/build/storage/home-dir.js.map +1 -0
- package/build/storage/index.d.ts +12 -0
- package/build/storage/index.d.ts.map +1 -0
- package/build/storage/index.js +15 -0
- package/build/storage/index.js.map +1 -0
- package/build/storage/machine-id.d.ts +4 -0
- package/build/storage/machine-id.d.ts.map +1 -0
- package/build/storage/machine-id.js +73 -0
- package/build/storage/machine-id.js.map +1 -0
- package/build/storage/mode.d.ts +23 -0
- package/build/storage/mode.d.ts.map +1 -0
- package/build/storage/mode.js +46 -0
- package/build/storage/mode.js.map +1 -0
- package/build/storage/per-plugin-store.d.ts +65 -0
- package/build/storage/per-plugin-store.d.ts.map +1 -0
- package/build/storage/per-plugin-store.js +194 -0
- package/build/storage/per-plugin-store.js.map +1 -0
- package/build/storage/resolver.d.ts +7 -0
- package/build/storage/resolver.d.ts.map +1 -0
- package/build/storage/resolver.js +38 -0
- package/build/storage/resolver.js.map +1 -0
- package/build/storage/session-lock.d.ts +19 -0
- package/build/storage/session-lock.d.ts.map +1 -0
- package/build/storage/session-lock.js +105 -0
- package/build/storage/session-lock.js.map +1 -0
- package/build/token-auth/authenticator.d.ts +70 -0
- package/build/token-auth/authenticator.d.ts.map +1 -0
- package/build/token-auth/authenticator.js +146 -0
- package/build/token-auth/authenticator.js.map +1 -0
- package/build/token-auth/cli.d.ts +38 -0
- package/build/token-auth/cli.d.ts.map +1 -0
- package/build/token-auth/cli.js +192 -0
- package/build/token-auth/cli.js.map +1 -0
- package/build/token-auth/context.d.ts +30 -0
- package/build/token-auth/context.d.ts.map +1 -0
- package/build/token-auth/context.js +32 -0
- package/build/token-auth/context.js.map +1 -0
- package/build/token-auth/http-guard.d.ts +26 -0
- package/build/token-auth/http-guard.d.ts.map +1 -0
- package/build/token-auth/http-guard.js +51 -0
- package/build/token-auth/http-guard.js.map +1 -0
- package/build/token-auth/index.d.ts +14 -0
- package/build/token-auth/index.d.ts.map +1 -0
- package/build/token-auth/index.js +14 -0
- package/build/token-auth/index.js.map +1 -0
- package/build/token-auth/limiter.d.ts +20 -0
- package/build/token-auth/limiter.d.ts.map +1 -0
- package/build/token-auth/limiter.js +41 -0
- package/build/token-auth/limiter.js.map +1 -0
- package/build/token-auth/loopback.d.ts +17 -0
- package/build/token-auth/loopback.d.ts.map +1 -0
- package/build/token-auth/loopback.js +62 -0
- package/build/token-auth/loopback.js.map +1 -0
- package/build/token-auth/py-compat.d.ts +24 -0
- package/build/token-auth/py-compat.d.ts.map +1 -0
- package/build/token-auth/py-compat.js +76 -0
- package/build/token-auth/py-compat.js.map +1 -0
- package/build/token-auth/tokens.d.ts +36 -0
- package/build/token-auth/tokens.d.ts.map +1 -0
- package/build/token-auth/tokens.js +115 -0
- package/build/token-auth/tokens.js.map +1 -0
- package/build/token-auth/users.d.ts +62 -0
- package/build/token-auth/users.d.ts.map +1 -0
- package/build/token-auth/users.js +174 -0
- package/build/token-auth/users.js.map +1 -0
- package/build/transport/cache.d.ts +19 -0
- package/build/transport/cache.d.ts.map +1 -0
- package/build/transport/cache.js +76 -0
- package/build/transport/cache.js.map +1 -0
- package/build/transport/index.d.ts +5 -0
- package/build/transport/index.d.ts.map +1 -0
- package/build/transport/index.js +4 -0
- package/build/transport/index.js.map +1 -0
- package/build/transport/local-server.d.ts +173 -0
- package/build/transport/local-server.d.ts.map +1 -0
- package/build/transport/local-server.js +457 -0
- package/build/transport/local-server.js.map +1 -0
- package/build/transport/oauth-middleware.d.ts +45 -0
- package/build/transport/oauth-middleware.d.ts.map +1 -0
- package/build/transport/oauth-middleware.js +86 -0
- package/build/transport/oauth-middleware.js.map +1 -0
- package/build/transport/streamable-http.d.ts +54 -0
- package/build/transport/streamable-http.d.ts.map +1 -0
- package/build/transport/streamable-http.js +57 -0
- package/build/transport/streamable-http.js.map +1 -0
- package/package.json +107 -4
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Issuer and JWKS generation for TypeScript MCP servers.
|
|
3
|
+
* Uses jose (industry-standard, zero-dep, Web Crypto compatible).
|
|
4
|
+
*
|
|
5
|
+
* Two signing modes, selected at construction:
|
|
6
|
+
*
|
|
7
|
+
* - LOCAL single-user (credentialSecret unset): RSA-2048, RS256, kid="key-1",
|
|
8
|
+
* keys generated on first run and persisted to disk so they survive restarts
|
|
9
|
+
* on a real machine. Behavior is unchanged from the pre-stability-fix code.
|
|
10
|
+
*
|
|
11
|
+
* - HTTP multi-user (credentialSecret set): Ed25519, EdDSA, signing key DERIVED
|
|
12
|
+
* deterministically from CREDENTIAL_SECRET via HKDF-SHA256. No disk I/O. Every
|
|
13
|
+
* container replica converges on the same key without a shared volume or
|
|
14
|
+
* external secret store, so OAuth tokens survive container recreation
|
|
15
|
+
* (Watchtower :latest redeploys). kid is the base64url SHA-256 thumbprint of
|
|
16
|
+
* the raw public key.
|
|
17
|
+
*
|
|
18
|
+
* The two modes are different deployments that never exchange tokens
|
|
19
|
+
* (iss/aud are server-scoped); the per-mode algorithm split is permanent and
|
|
20
|
+
* intentional. Each process runs exactly one algorithm; the verify path uses
|
|
21
|
+
* that single alg, never a {RS256, EdDSA} union.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash, createPrivateKey } from 'node:crypto';
|
|
24
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import * as jose from 'jose';
|
|
28
|
+
import { deriveJwtSigningSeed } from '../crypto/kdf.js';
|
|
29
|
+
const DEFAULT_KEYS_DIR = join(homedir(), '.hull', 'jwt-keys');
|
|
30
|
+
// RFC 8410 PKCS8 DER prefix for an Ed25519 private key. Prepended to the
|
|
31
|
+
// 32-byte HKDF seed to build an importable PKCS8 key (jose 6.2.3 cannot import
|
|
32
|
+
// a raw seed via importJWK d-only).
|
|
33
|
+
const ED25519_PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
34
|
+
export class JWTIssuer {
|
|
35
|
+
constructor(serverName, keysDir = DEFAULT_KEYS_DIR, credentialSecret = null) {
|
|
36
|
+
this.kid = 'key-1';
|
|
37
|
+
this.privateKey = null;
|
|
38
|
+
this.publicKey = null;
|
|
39
|
+
this.okpX = null;
|
|
40
|
+
this._initialized = false;
|
|
41
|
+
this.serverName = serverName;
|
|
42
|
+
this.keysDir = keysDir;
|
|
43
|
+
this.credentialSecret = credentialSecret;
|
|
44
|
+
this.alg = credentialSecret ? 'EdDSA' : 'RS256';
|
|
45
|
+
this.privateKeyPath = join(this.keysDir, `${serverName}_private.pem`);
|
|
46
|
+
this.publicKeyPath = join(this.keysDir, `${serverName}_public.pem`);
|
|
47
|
+
}
|
|
48
|
+
/** Must call before using issuer — loads/generates (RSA) or derives (EdDSA) keys. */
|
|
49
|
+
async init() {
|
|
50
|
+
if (this._initialized)
|
|
51
|
+
return;
|
|
52
|
+
if (this.credentialSecret) {
|
|
53
|
+
await this.deriveEddsaKeys(this.credentialSecret);
|
|
54
|
+
// Don't name the source env var in the message: only serverName + the
|
|
55
|
+
// public kid thumbprint are logged (never the secret), and the literal
|
|
56
|
+
// "CREDENTIAL_SECRET" token trips the SAST logger-credential heuristic
|
|
57
|
+
// for no real benefit (parity with core-py jwt_issuer.py).
|
|
58
|
+
console.error(`JWTIssuer[${this.serverName}]: HTTP multi-user mode, EdDSA signing key derived (kid=${this.kid})`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
await this.loadOrGenerateRsaKeys();
|
|
62
|
+
console.error(`JWTIssuer[${this.serverName}]: local single-user mode, RS256 key on disk (${this.keysDir})`);
|
|
63
|
+
}
|
|
64
|
+
this._initialized = true;
|
|
65
|
+
}
|
|
66
|
+
async deriveEddsaKeys(credentialSecret) {
|
|
67
|
+
const seed = deriveJwtSigningSeed(credentialSecret, this.serverName);
|
|
68
|
+
const der = Buffer.concat([ED25519_PKCS8_PREFIX, seed]);
|
|
69
|
+
const pem = `-----BEGIN PRIVATE KEY-----\n${der.toString('base64')}\n-----END PRIVATE KEY-----\n`;
|
|
70
|
+
this.privateKey = await jose.importPKCS8(pem, 'EdDSA', { extractable: false });
|
|
71
|
+
// Recover the public JWK x via node:crypto (jose has no seed->public path).
|
|
72
|
+
const jwk = createPrivateKey(pem).export({ format: 'jwk' });
|
|
73
|
+
this.okpX = jwk.x;
|
|
74
|
+
this.publicKey = (await jose.importJWK({ kty: 'OKP', crv: 'Ed25519', x: jwk.x }, 'EdDSA'));
|
|
75
|
+
this.kid = createHash('sha256').update(Buffer.from(jwk.x, 'base64url')).digest('base64url').slice(0, 16);
|
|
76
|
+
}
|
|
77
|
+
async loadOrGenerateRsaKeys() {
|
|
78
|
+
mkdirSync(this.keysDir, { recursive: true, mode: 0o700 });
|
|
79
|
+
if (process.platform !== 'win32') {
|
|
80
|
+
chmodSync(this.keysDir, 0o700);
|
|
81
|
+
}
|
|
82
|
+
if (existsSync(this.privateKeyPath) && existsSync(this.publicKeyPath)) {
|
|
83
|
+
const privatePem = readFileSync(this.privateKeyPath, 'utf-8');
|
|
84
|
+
const publicPem = readFileSync(this.publicKeyPath, 'utf-8');
|
|
85
|
+
this.privateKey = await jose.importPKCS8(privatePem, 'RS256', { extractable: false });
|
|
86
|
+
this.publicKey = await jose.importSPKI(publicPem, 'RS256', { extractable: true });
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const { publicKey, privateKey } = await jose.generateKeyPair('RS256', {
|
|
90
|
+
modulusLength: 2048,
|
|
91
|
+
extractable: true
|
|
92
|
+
});
|
|
93
|
+
const privatePem = await jose.exportPKCS8(privateKey);
|
|
94
|
+
const publicPem = await jose.exportSPKI(publicKey);
|
|
95
|
+
writeFileSync(this.privateKeyPath, privatePem, { mode: 0o600 });
|
|
96
|
+
writeFileSync(this.publicKeyPath, publicPem, { mode: 0o644 });
|
|
97
|
+
// Re-import with extractable: false for in-memory storage
|
|
98
|
+
this.privateKey = await jose.importPKCS8(privatePem, 'RS256', { extractable: false });
|
|
99
|
+
this.publicKey = await jose.importSPKI(publicPem, 'RS256', { extractable: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Return JWKS payload for /.well-known/jwks.json. Always a `keys` array
|
|
104
|
+
* (multi-key-aware for future rotation). RSA mode emits an RSA JWK; EdDSA
|
|
105
|
+
* mode emits an OKP JWK.
|
|
106
|
+
*/
|
|
107
|
+
async getJwks() {
|
|
108
|
+
if (!this.publicKey)
|
|
109
|
+
throw new Error('JWTIssuer not initialized');
|
|
110
|
+
if (this.alg === 'EdDSA') {
|
|
111
|
+
return {
|
|
112
|
+
keys: [{ kty: 'OKP', crv: 'Ed25519', x: this.okpX, use: 'sig', alg: 'EdDSA', kid: this.kid }]
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const jwk = await jose.exportJWK(this.publicKey);
|
|
116
|
+
jwk.kid = this.kid;
|
|
117
|
+
jwk.use = 'sig';
|
|
118
|
+
jwk.alg = 'RS256';
|
|
119
|
+
return { keys: [jwk] };
|
|
120
|
+
}
|
|
121
|
+
/** Issue a JWT access token (typ="access") signed with the active alg. */
|
|
122
|
+
async issueAccessToken(sub, expiresInSeconds = 3600) {
|
|
123
|
+
if (!this.privateKey)
|
|
124
|
+
throw new Error('JWTIssuer not initialized');
|
|
125
|
+
return new jose.SignJWT({ sub, typ: 'access' })
|
|
126
|
+
.setProtectedHeader({ alg: this.alg, kid: this.kid })
|
|
127
|
+
.setIssuer(this.serverName)
|
|
128
|
+
.setAudience(this.serverName)
|
|
129
|
+
.setIssuedAt()
|
|
130
|
+
.setExpirationTime(`${expiresInSeconds}s`)
|
|
131
|
+
.sign(this.privateKey);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Issue a JWT refresh token (typ="refresh") signed with the active alg.
|
|
135
|
+
*
|
|
136
|
+
* Defaults to a 1-year (31536000s) lifetime so long-running MCP clients can
|
|
137
|
+
* mint fresh access tokens without forcing the user back through the browser
|
|
138
|
+
* PKCE flow. The access token stays short-lived (1h); the refresh token is the
|
|
139
|
+
* renewal credential and is rotated on every use (the /token refresh handler
|
|
140
|
+
* issues a new refresh token each time), so the security control is rotation,
|
|
141
|
+
* not a short TTL. A short refresh TTL was the residual re-auth driver: a
|
|
142
|
+
* self-hosted server a user touches only intermittently (less than once a
|
|
143
|
+
* month) would silently expire its refresh token between sessions, forcing a
|
|
144
|
+
* fresh browser OAuth tab on the next use. With a 1-year floor, only a
|
|
145
|
+
* genuinely long idle gap re-prompts. Same key / iss / aud as access tokens;
|
|
146
|
+
* the typ claim is the only thing distinguishing them, and verifyAccessToken
|
|
147
|
+
* rejects typ="refresh" so a refresh token can never be used as an access
|
|
148
|
+
* token at the /mcp resource.
|
|
149
|
+
*/
|
|
150
|
+
async issueRefreshToken(sub, expiresInSeconds = 31536000) {
|
|
151
|
+
if (!this.privateKey)
|
|
152
|
+
throw new Error('JWTIssuer not initialized');
|
|
153
|
+
return new jose.SignJWT({ sub, typ: 'refresh' })
|
|
154
|
+
.setProtectedHeader({ alg: this.alg, kid: this.kid })
|
|
155
|
+
.setIssuer(this.serverName)
|
|
156
|
+
.setAudience(this.serverName)
|
|
157
|
+
.setIssuedAt()
|
|
158
|
+
.setExpirationTime(`${expiresInSeconds}s`)
|
|
159
|
+
.sign(this.privateKey);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Verify a JWT access token and return its payload. Throws on failure (bad
|
|
163
|
+
* signature, wrong issuer/audience, expired) — the same jose errors existing
|
|
164
|
+
* callers already catch. The verify path uses the single active algorithm,
|
|
165
|
+
* never a union. Additionally rejects tokens whose typ claim is "refresh"
|
|
166
|
+
* (throwing jose.errors.JWTClaimValidationFailed) so a refresh token cannot
|
|
167
|
+
* be replayed as an access token. Tokens with typ="access" OR a missing typ
|
|
168
|
+
* claim are accepted (the latter keeps already-issued pre-refresh-support
|
|
169
|
+
* tokens valid).
|
|
170
|
+
*/
|
|
171
|
+
async verifyAccessToken(token) {
|
|
172
|
+
if (!this.publicKey)
|
|
173
|
+
throw new Error('JWTIssuer not initialized');
|
|
174
|
+
const { payload } = await jose.jwtVerify(token, this.publicKey, {
|
|
175
|
+
issuer: this.serverName,
|
|
176
|
+
audience: this.serverName,
|
|
177
|
+
algorithms: [this.alg]
|
|
178
|
+
});
|
|
179
|
+
if (payload.typ === 'refresh') {
|
|
180
|
+
throw new jose.errors.JWTClaimValidationFailed('Refresh token cannot be used as an access token', payload, 'typ');
|
|
181
|
+
}
|
|
182
|
+
return payload;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Verify a JWT refresh token and return its payload. Same key / audience /
|
|
186
|
+
* issuer checks as verifyAccessToken and throws the same jose errors on
|
|
187
|
+
* failure; uses the single active algorithm. Additionally asserts
|
|
188
|
+
* typ=="refresh" (throwing jose.errors.JWTClaimValidationFailed otherwise) so
|
|
189
|
+
* an access token can never be exchanged at the refresh grant.
|
|
190
|
+
*/
|
|
191
|
+
async verifyRefreshToken(token) {
|
|
192
|
+
if (!this.publicKey)
|
|
193
|
+
throw new Error('JWTIssuer not initialized');
|
|
194
|
+
const { payload } = await jose.jwtVerify(token, this.publicKey, {
|
|
195
|
+
issuer: this.serverName,
|
|
196
|
+
audience: this.serverName,
|
|
197
|
+
algorithms: [this.alg]
|
|
198
|
+
});
|
|
199
|
+
if (payload.typ !== 'refresh') {
|
|
200
|
+
throw new jose.errors.JWTClaimValidationFailed('Token is not a refresh token', payload, 'typ');
|
|
201
|
+
}
|
|
202
|
+
return payload;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=jwt-issuer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt-issuer.js","sourceRoot":"","sources":["../../src/oauth/jwt-issuer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify PKCE code challenge.
|
|
3
|
+
* Throws an error if verification fails.
|
|
4
|
+
*
|
|
5
|
+
* @param codeVerifier The PKCE code verifier.
|
|
6
|
+
* @param codeChallenge The stored code challenge.
|
|
7
|
+
* @param method The code challenge method (S256 or plain).
|
|
8
|
+
*/
|
|
9
|
+
export declare function verifyPKCE(codeVerifier: string, codeChallenge: string, method: string): void;
|
|
10
|
+
//# sourceMappingURL=pkce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAqB5F"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* Verify PKCE code challenge.
|
|
4
|
+
* Throws an error if verification fails.
|
|
5
|
+
*
|
|
6
|
+
* @param codeVerifier The PKCE code verifier.
|
|
7
|
+
* @param codeChallenge The stored code challenge.
|
|
8
|
+
* @param method The code challenge method (S256 or plain).
|
|
9
|
+
*/
|
|
10
|
+
export function verifyPKCE(codeVerifier, codeChallenge, method) {
|
|
11
|
+
if (method === 'S256') {
|
|
12
|
+
const digest = createHash('sha256').update(codeVerifier).digest('base64url');
|
|
13
|
+
const expected = Buffer.from(digest);
|
|
14
|
+
const actual = Buffer.from(codeChallenge);
|
|
15
|
+
const isLengthEqual = expected.length === actual.length;
|
|
16
|
+
const compareActual = isLengthEqual ? actual : expected;
|
|
17
|
+
if (!timingSafeEqual(expected, compareActual) || !isLengthEqual) {
|
|
18
|
+
throw new Error('invalid_grant: PKCE verification failed');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (method === 'plain') {
|
|
22
|
+
const expected = Buffer.from(codeVerifier);
|
|
23
|
+
const actual = Buffer.from(codeChallenge);
|
|
24
|
+
const isLengthEqual = expected.length === actual.length;
|
|
25
|
+
const compareActual = isLengthEqual ? actual : expected;
|
|
26
|
+
if (!timingSafeEqual(expected, compareActual) || !isLengthEqual) {
|
|
27
|
+
throw new Error('invalid_grant: PKCE plain verification failed');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
throw new Error('unsupported_challenge_method');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/oauth/pkce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAEzD;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,aAAqB,EAAE,MAAc;IACpF,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;QAC5E,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;QACpC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACzC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAA;QACvD,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;QACvD,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;QAC5D,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QAC9B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAC1C,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACzC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,CAAA;QACvD,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAA;QACvD,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,aAAa,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YAChE,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;IACjD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Provider logic for TypeScript MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Implements the "MCP Server as Authorization Server" pattern.
|
|
5
|
+
* The MCP Server issues JWTs, processes PKCE verification,
|
|
6
|
+
* and uses the Relay Server purely as a UI consent transport.
|
|
7
|
+
*/
|
|
8
|
+
import type { RelayConfigSchema } from '../schema/types.js';
|
|
9
|
+
import { type IOAuthSessionCache } from './cache.js';
|
|
10
|
+
import type { JWTIssuer } from './jwt-issuer.js';
|
|
11
|
+
export { InMemoryAuthCache, type IOAuthSessionCache, type PreAuthSession } from './cache.js';
|
|
12
|
+
export interface OAuthProviderOptions {
|
|
13
|
+
serverName: string;
|
|
14
|
+
relayBaseUrl: string;
|
|
15
|
+
relaySchema: RelayConfigSchema;
|
|
16
|
+
jwtIssuer: JWTIssuer;
|
|
17
|
+
cache?: IOAuthSessionCache;
|
|
18
|
+
}
|
|
19
|
+
export declare class OAuthProvider {
|
|
20
|
+
private serverName;
|
|
21
|
+
private relayBaseUrl;
|
|
22
|
+
private relaySchema;
|
|
23
|
+
private jwtIssuer;
|
|
24
|
+
private cache;
|
|
25
|
+
constructor(options: OAuthProviderOptions);
|
|
26
|
+
/**
|
|
27
|
+
* Create a relay session and return the URL to redirect the user to.
|
|
28
|
+
*/
|
|
29
|
+
createAuthorizeRedirect(clientId: string, redirectUri: string, state: string, codeChallenge: string, codeChallengeMethod?: string): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Exchange the authorization code (relay session ID) for an access_token.
|
|
32
|
+
* PKCE verification is performed.
|
|
33
|
+
*
|
|
34
|
+
* @param code The authorization code from the client.
|
|
35
|
+
* @param codeVerifier The PKCE code verifier.
|
|
36
|
+
* @param userIdExtractor Function to derive a unique user_id from the credentials.
|
|
37
|
+
* @returns Tuple of [access_token, credentials]
|
|
38
|
+
*/
|
|
39
|
+
exchangeCode(code: string, codeVerifier: string, userIdExtractor: (credentials: Record<string, string>) => string): Promise<{
|
|
40
|
+
accessToken: string;
|
|
41
|
+
credentials: Record<string, string>;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Reconstruct relay session from pre-auth session.
|
|
45
|
+
*/
|
|
46
|
+
private reconstructRelaySession;
|
|
47
|
+
/**
|
|
48
|
+
* Get RFC 8414 Authorization Server Metadata.
|
|
49
|
+
*/
|
|
50
|
+
getMetadata(baseUrl: string): Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAqB,KAAK,kBAAkB,EAAuB,MAAM,YAAY,CAAA;AAC5F,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAGhD,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACpB,MAAM,YAAY,CAAA;AAEnB,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,iBAAiB,CAAA;IAC9B,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,CAAC,EAAE,kBAAkB,CAAA;CAC3B;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,KAAK,CAAoB;IAEjC,YAAY,OAAO,EAAE,oBAAoB,EAMxC;IAED;;OAEG;IACG,uBAAuB,CAC3B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,mBAAmB,SAAS,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB;IAED;;;;;;;;OAQG;IACG,YAAY,CAChB,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,GAC/D,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC,CAoBvE;IAED;;OAEG;YACW,uBAAuB;IAwBrC;;OAEG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWpD;CACF"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Provider logic for TypeScript MCP servers.
|
|
3
|
+
*
|
|
4
|
+
* Implements the "MCP Server as Authorization Server" pattern.
|
|
5
|
+
* The MCP Server issues JWTs, processes PKCE verification,
|
|
6
|
+
* and uses the Relay Server purely as a UI consent transport.
|
|
7
|
+
*/
|
|
8
|
+
import { createSession, pollForResult } from '../relay/client.js';
|
|
9
|
+
import { InMemoryAuthCache } from './cache.js';
|
|
10
|
+
import { verifyPKCE } from './pkce.js';
|
|
11
|
+
export { InMemoryAuthCache } from './cache.js';
|
|
12
|
+
export class OAuthProvider {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.serverName = options.serverName;
|
|
15
|
+
this.relayBaseUrl = options.relayBaseUrl;
|
|
16
|
+
this.relaySchema = options.relaySchema;
|
|
17
|
+
this.jwtIssuer = options.jwtIssuer;
|
|
18
|
+
this.cache = options.cache ?? new InMemoryAuthCache();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a relay session and return the URL to redirect the user to.
|
|
22
|
+
*/
|
|
23
|
+
async createAuthorizeRedirect(clientId, redirectUri, state, codeChallenge, codeChallengeMethod = 'S256') {
|
|
24
|
+
const session = await createSession(this.relayBaseUrl, this.serverName, this.relaySchema);
|
|
25
|
+
// Export private key for later credential retrieval
|
|
26
|
+
const privateJwk = await crypto.subtle.exportKey('jwk', session.keyPair.privateKey);
|
|
27
|
+
const preAuth = {
|
|
28
|
+
sessionId: session.sessionId,
|
|
29
|
+
clientId,
|
|
30
|
+
redirectUri,
|
|
31
|
+
state,
|
|
32
|
+
codeChallenge,
|
|
33
|
+
codeChallengeMethod,
|
|
34
|
+
keyPairJwk: privateJwk,
|
|
35
|
+
passphrase: session.passphrase,
|
|
36
|
+
expiresAt: Math.floor(Date.now() / 1000) + 600 // 10 minutes
|
|
37
|
+
};
|
|
38
|
+
this.cache.save(preAuth);
|
|
39
|
+
return session.relayUrl;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Exchange the authorization code (relay session ID) for an access_token.
|
|
43
|
+
* PKCE verification is performed.
|
|
44
|
+
*
|
|
45
|
+
* @param code The authorization code from the client.
|
|
46
|
+
* @param codeVerifier The PKCE code verifier.
|
|
47
|
+
* @param userIdExtractor Function to derive a unique user_id from the credentials.
|
|
48
|
+
* @returns Tuple of [access_token, credentials]
|
|
49
|
+
*/
|
|
50
|
+
async exchangeCode(code, codeVerifier, userIdExtractor) {
|
|
51
|
+
const preAuth = this.cache.getAndDelete(code);
|
|
52
|
+
if (!preAuth)
|
|
53
|
+
throw new Error('invalid_grant: Expired or invalid code');
|
|
54
|
+
// Verify PKCE
|
|
55
|
+
verifyPKCE(codeVerifier, preAuth.codeChallenge, preAuth.codeChallengeMethod);
|
|
56
|
+
// Reconstruct relay session
|
|
57
|
+
const reconstructedSession = await this.reconstructRelaySession(preAuth);
|
|
58
|
+
// Poll for credentials (short timeout — form already submitted)
|
|
59
|
+
const credentials = await pollForResult(this.relayBaseUrl, reconstructedSession, 1000, 10_000);
|
|
60
|
+
// Extract unique user_id
|
|
61
|
+
const userId = userIdExtractor(credentials);
|
|
62
|
+
if (!userId)
|
|
63
|
+
throw new Error('server_error: Unable to extract user_id from credentials');
|
|
64
|
+
// Issue access token
|
|
65
|
+
const accessToken = await this.jwtIssuer.issueAccessToken(userId);
|
|
66
|
+
return { accessToken, credentials };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Reconstruct relay session from pre-auth session.
|
|
70
|
+
*/
|
|
71
|
+
async reconstructRelaySession(preAuth) {
|
|
72
|
+
// Reconstruct key pair to decrypt relay credentials
|
|
73
|
+
const privateKey = await crypto.subtle.importKey('jwk', preAuth.keyPairJwk, { name: 'ECDH', namedCurve: 'P-256' }, true, ['deriveKey', 'deriveBits']);
|
|
74
|
+
const publicKey = await crypto.subtle.importKey('jwk', { ...preAuth.keyPairJwk, d: undefined }, { name: 'ECDH', namedCurve: 'P-256' }, true, []);
|
|
75
|
+
return {
|
|
76
|
+
sessionId: preAuth.sessionId,
|
|
77
|
+
keyPair: { privateKey, publicKey },
|
|
78
|
+
passphrase: preAuth.passphrase,
|
|
79
|
+
relayUrl: ''
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get RFC 8414 Authorization Server Metadata.
|
|
84
|
+
*/
|
|
85
|
+
getMetadata(baseUrl) {
|
|
86
|
+
return {
|
|
87
|
+
issuer: baseUrl,
|
|
88
|
+
authorization_endpoint: `${baseUrl}/authorize`,
|
|
89
|
+
token_endpoint: `${baseUrl}/token`,
|
|
90
|
+
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
91
|
+
response_types_supported: ['code'],
|
|
92
|
+
grant_types_supported: ['authorization_code'],
|
|
93
|
+
code_challenge_methods_supported: ['S256', 'plain'],
|
|
94
|
+
token_endpoint_auth_methods_supported: ['none']
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAqB,MAAM,oBAAoB,CAAA;AAEpF,OAAO,EAAE,iBAAiB,EAAgD,MAAM,YAAY,CAAA;AAE5F,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAEtC,OAAO,EACL,iBAAiB,EAGlB,MAAM,YAAY,CAAA;AAUnB,MAAM,OAAO,aAAa;IAOxB,YAAY,OAA6B;QACvC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAA;QACpC,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,CAAA;QACxC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAA;QACtC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;QAClC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,iBAAiB,EAAE,CAAA;IACvD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAC3B,QAAgB,EAChB,WAAmB,EACnB,KAAa,EACb,aAAqB,EACrB,mBAAmB,GAAG,MAAM;QAE5B,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,WAAW,CAAC,CAAA;QAEzF,oDAAoD;QACpD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAA;QAEnF,MAAM,OAAO,GAAmB;YAC9B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,QAAQ;YACR,WAAW;YACX,KAAK;YACL,aAAa;YACb,mBAAmB;YACnB,UAAU,EAAE,UAAU;YACtB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC,aAAa;SAC7D,CAAA;QACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACxB,OAAO,OAAO,CAAC,QAAQ,CAAA;IACzB,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,YAAY,CAChB,IAAY,EACZ,YAAoB,EACpB,eAAgE;QAEhE,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAA;QAC7C,IAAI,CAAC,OAAO;YAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;QAEvE,cAAc;QACd,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;QAE5E,4BAA4B;QAC5B,MAAM,oBAAoB,GAAG,MAAM,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,CAAA;QAExE,gEAAgE;QAChE,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,YAAY,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;QAE9F,yBAAyB;QACzB,MAAM,MAAM,GAAG,eAAe,CAAC,WAAW,CAAC,CAAA;QAC3C,IAAI,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAA;QAExF,qBAAqB;QACrB,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAA;QACjE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,CAAA;IACrC,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,uBAAuB,CAAC,OAAuB;QAC3D,oDAAoD;QACpD,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAC9C,KAAK,EACL,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,EACrC,IAAI,EACJ,CAAC,WAAW,EAAE,YAAY,CAAC,CAC5B,CAAA;QACD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAC7C,KAAK,EACL,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,EACvC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,EACrC,IAAI,EACJ,EAAE,CACH,CAAA;QACD,OAAO;YACL,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,OAAO,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE;YAClC,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,QAAQ,EAAE,EAAE;SACb,CAAA;IACH,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,OAAe;QACzB,OAAO;YACL,MAAM,EAAE,OAAO;YACf,sBAAsB,EAAE,GAAG,OAAO,YAAY;YAC9C,cAAc,EAAE,GAAG,OAAO,QAAQ;YAClC,QAAQ,EAAE,GAAG,OAAO,wBAAwB;YAC5C,wBAAwB,EAAE,CAAC,MAAM,CAAC;YAClC,qBAAqB,EAAE,CAAC,oBAAoB,CAAC;YAC7C,gCAAgC,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YACnD,qCAAqC,EAAE,CAAC,MAAM,CAAC;SAChD,CAAA;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user encrypted credential store for MCP HTTP modes.
|
|
3
|
+
* Uses better-sqlite3 for persistence and Node.js crypto for AES-256-GCM at-rest encryption.
|
|
4
|
+
*/
|
|
5
|
+
export interface IUserCredentialStore {
|
|
6
|
+
saveCredentials(userId: string, config: Record<string, string>): void;
|
|
7
|
+
getCredentials(userId: string): Record<string, string> | null;
|
|
8
|
+
deleteCredentials(userId: string): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class SqliteUserStore implements IUserCredentialStore {
|
|
11
|
+
private db;
|
|
12
|
+
private masterKey;
|
|
13
|
+
/**
|
|
14
|
+
* @param dbPath Path to the SQLite DB file.
|
|
15
|
+
* @param masterKey 32-byte AES key for at-rest encryption.
|
|
16
|
+
*/
|
|
17
|
+
constructor(dbPath: string, masterKey: Buffer);
|
|
18
|
+
private encrypt;
|
|
19
|
+
private decrypt;
|
|
20
|
+
saveCredentials(userId: string, config: Record<string, string>): void;
|
|
21
|
+
getCredentials(userId: string): Record<string, string> | null;
|
|
22
|
+
deleteCredentials(userId: string): void;
|
|
23
|
+
close(): void;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=user-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-store.d.ts","sourceRoot":"","sources":["../../src/oauth/user-store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;IACrE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;IAC7D,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CACxC;AAED,qBAAa,eAAgB,YAAW,oBAAoB;IAC1D,OAAO,CAAC,EAAE,CAAmB;IAC7B,OAAO,CAAC,SAAS,CAAQ;IAEzB;;;OAGG;IACH,YAAY,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAwB5C;IAED,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,OAAO;IASf,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAapE;IAED,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAY5D;IAED,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAEtC;IAED,KAAK,IAAI,IAAI,CAEZ;CACF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user encrypted credential store for MCP HTTP modes.
|
|
3
|
+
* Uses better-sqlite3 for persistence and Node.js crypto for AES-256-GCM at-rest encryption.
|
|
4
|
+
*/
|
|
5
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
6
|
+
import { chmodSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { dirname } from 'node:path';
|
|
8
|
+
import Database from 'better-sqlite3';
|
|
9
|
+
export class SqliteUserStore {
|
|
10
|
+
/**
|
|
11
|
+
* @param dbPath Path to the SQLite DB file.
|
|
12
|
+
* @param masterKey 32-byte AES key for at-rest encryption.
|
|
13
|
+
*/
|
|
14
|
+
constructor(dbPath, masterKey) {
|
|
15
|
+
if (masterKey.length !== 32)
|
|
16
|
+
throw new Error('masterKey must be 32 bytes');
|
|
17
|
+
this.masterKey = masterKey;
|
|
18
|
+
// Ensure directory exists with strict permissions (owner-only access).
|
|
19
|
+
// 0o700 = owner read+write+exec only; group/other denied. This is the
|
|
20
|
+
// secure default for a credential store directory.
|
|
21
|
+
mkdirSync(dirname(dbPath), { recursive: true, mode: 0o700 });
|
|
22
|
+
if (process.platform !== 'win32') {
|
|
23
|
+
chmodSync(dirname(dbPath), 0o700);
|
|
24
|
+
}
|
|
25
|
+
this.db = new Database(dbPath);
|
|
26
|
+
this.db.pragma('journal_mode = WAL');
|
|
27
|
+
this.db
|
|
28
|
+
.prepare(`
|
|
29
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
30
|
+
user_id TEXT PRIMARY KEY,
|
|
31
|
+
encrypted_config BLOB NOT NULL,
|
|
32
|
+
created_at INTEGER NOT NULL,
|
|
33
|
+
updated_at INTEGER NOT NULL
|
|
34
|
+
)
|
|
35
|
+
`)
|
|
36
|
+
.run();
|
|
37
|
+
}
|
|
38
|
+
encrypt(plaintext) {
|
|
39
|
+
const iv = randomBytes(12);
|
|
40
|
+
const cipher = createCipheriv('aes-256-gcm', this.masterKey, iv, { authTagLength: 16 });
|
|
41
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
|
|
42
|
+
const tag = cipher.getAuthTag();
|
|
43
|
+
// Layout: [12 iv] [16 tag] [... ciphertext]
|
|
44
|
+
return Buffer.concat([iv, tag, encrypted]);
|
|
45
|
+
}
|
|
46
|
+
decrypt(payload) {
|
|
47
|
+
const iv = payload.subarray(0, 12);
|
|
48
|
+
const tag = payload.subarray(12, 28);
|
|
49
|
+
const ciphertext = payload.subarray(28);
|
|
50
|
+
const decipher = createDecipheriv('aes-256-gcm', this.masterKey, iv, { authTagLength: 16 });
|
|
51
|
+
decipher.setAuthTag(tag);
|
|
52
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf-8');
|
|
53
|
+
}
|
|
54
|
+
saveCredentials(userId, config) {
|
|
55
|
+
const now = Math.floor(Date.now() / 1000);
|
|
56
|
+
const encrypted = this.encrypt(JSON.stringify(config));
|
|
57
|
+
this.db
|
|
58
|
+
.prepare(`
|
|
59
|
+
INSERT INTO users (user_id, encrypted_config, created_at, updated_at)
|
|
60
|
+
VALUES (?, ?, ?, ?)
|
|
61
|
+
ON CONFLICT(user_id) DO UPDATE SET
|
|
62
|
+
encrypted_config = excluded.encrypted_config,
|
|
63
|
+
updated_at = excluded.updated_at
|
|
64
|
+
`)
|
|
65
|
+
.run(userId, encrypted, now, now);
|
|
66
|
+
}
|
|
67
|
+
getCredentials(userId) {
|
|
68
|
+
const row = this.db.prepare('SELECT encrypted_config FROM users WHERE user_id = ?').get(userId);
|
|
69
|
+
if (!row)
|
|
70
|
+
return null;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(this.decrypt(Buffer.from(row.encrypted_config)));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
deleteCredentials(userId) {
|
|
79
|
+
this.db.prepare('DELETE FROM users WHERE user_id = ?').run(userId);
|
|
80
|
+
}
|
|
81
|
+
close() {
|
|
82
|
+
this.db.close();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=user-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-store.js","sourceRoot":"","sources":["../../src/oauth/user-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform browser opening with WSL detection.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Try to open URL in default browser. Returns true if likely succeeded.
|
|
6
|
+
*
|
|
7
|
+
* Detection order:
|
|
8
|
+
* 1. win32: powershell.exe -EncodedCommand
|
|
9
|
+
* 2. darwin: `open` command
|
|
10
|
+
* 3. linux: check WSL then `xdg-open`
|
|
11
|
+
*
|
|
12
|
+
* Never throws. Returns false on failure.
|
|
13
|
+
*/
|
|
14
|
+
export declare function tryOpenBrowser(url: string): Promise<boolean>;
|
|
15
|
+
//# sourceMappingURL=browser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/relay/browser.ts"],"names":[],"mappings":"AAAA;;GAEG;AA2EH;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA6ClE"}
|