@n24q02m/hull 0.0.0-stage → 0.4.0-beta.9
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 @@
|
|
|
1
|
+
{"version":3,"file":"users.d.ts","sourceRoot":"","sources":["../../src/token-auth/users.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAUH,6EAA6E;AAC7E,qBAAa,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,EAGzD;CACF;AAED,wEAAwE;AACxE,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,YAAW;IAExB,YAAY,OAAO,EAAE,MAAM,EAG1B;CACF;AAED,MAAM,WAAW,eAAe;IAC9B,sDAAsD;IACtD,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;CACjC;AAED,yFAAyF;AACzF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CACzC;AAqED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAiB9D;AAED,6EAA6E;AAC7E,wBAAsB,eAAe,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAKrH;AA2BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAMtE"}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* users.toml: the multi-user token table (auth mode `multi`).
|
|
3
|
+
*
|
|
4
|
+
* Same schema and validation order as core-py `hull_core.auth.users`:
|
|
5
|
+
*
|
|
6
|
+
* [users.<uid>]
|
|
7
|
+
* token_hash = "scrypt$..." # mint with `hull token hash`
|
|
8
|
+
* enabled = true
|
|
9
|
+
* namespace = "<uid-or-shared>" # data isolation root
|
|
10
|
+
* allowed_roots = ["/data/alice"] # filesystem roots tools may touch
|
|
11
|
+
* [users.<uid>.limits]
|
|
12
|
+
* rpm = 60 # optional per-minute request cap
|
|
13
|
+
*
|
|
14
|
+
* Admin is host-side only: edit the file, restart the server. There is no
|
|
15
|
+
* admin API and no self-service.
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
18
|
+
import { basename, dirname, join, resolve, sep } from 'node:path';
|
|
19
|
+
import { parse, TomlError } from 'smol-toml';
|
|
20
|
+
import { pyRepr } from './py-compat.js';
|
|
21
|
+
import { isValidTokenHash, verifyToken } from './tokens.js';
|
|
22
|
+
/** Raised when users.toml is missing, malformed, or semantically invalid. */
|
|
23
|
+
export class UsersError extends Error {
|
|
24
|
+
constructor(message, options) {
|
|
25
|
+
super(message, options);
|
|
26
|
+
this.name = 'UsersError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Raised by `ensurePathAllowed` (core-py raises `PermissionError`). */
|
|
30
|
+
export class PathNotAllowedError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.code = 'EACCES';
|
|
34
|
+
this.name = 'PathNotAllowedError';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function isTable(value) {
|
|
38
|
+
// TOML tables parse to plain (null-prototype) objects; arrays and
|
|
39
|
+
// TomlDate values are excluded by their tags.
|
|
40
|
+
return Object.prototype.toString.call(value) === '[object Object]';
|
|
41
|
+
}
|
|
42
|
+
function readTomlText(path) {
|
|
43
|
+
let isFile = false;
|
|
44
|
+
try {
|
|
45
|
+
isFile = statSync(path).isFile();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
isFile = false;
|
|
49
|
+
}
|
|
50
|
+
if (!isFile)
|
|
51
|
+
throw new UsersError(`users file not found: ${path}`);
|
|
52
|
+
const bytes = readFileSync(path);
|
|
53
|
+
try {
|
|
54
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new UsersError(`users file is not valid UTF-8: ${path}`, { cause: err });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function field(entry, key, fallback) {
|
|
61
|
+
return Object.hasOwn(entry, key) ? entry[key] : fallback;
|
|
62
|
+
}
|
|
63
|
+
function parseUser(uid, entry) {
|
|
64
|
+
const who = `user ${pyRepr(uid)}`;
|
|
65
|
+
if (!isTable(entry))
|
|
66
|
+
throw new UsersError(`${who}: expected a table`);
|
|
67
|
+
const tokenHash = field(entry, 'token_hash', undefined);
|
|
68
|
+
if (typeof tokenHash !== 'string' || !isValidTokenHash(tokenHash)) {
|
|
69
|
+
throw new UsersError(`${who}: token_hash missing or not a scrypt encoding (mint one with \`hull token hash\`)`);
|
|
70
|
+
}
|
|
71
|
+
const namespace = field(entry, 'namespace', undefined);
|
|
72
|
+
if (typeof namespace !== 'string' || namespace.length === 0) {
|
|
73
|
+
throw new UsersError(`${who}: namespace must be a non-empty string`);
|
|
74
|
+
}
|
|
75
|
+
const enabled = field(entry, 'enabled', true);
|
|
76
|
+
if (typeof enabled !== 'boolean')
|
|
77
|
+
throw new UsersError(`${who}: enabled must be a boolean`);
|
|
78
|
+
const roots = field(entry, 'allowed_roots', []);
|
|
79
|
+
if (!Array.isArray(roots) || !roots.every((r) => typeof r === 'string')) {
|
|
80
|
+
throw new UsersError(`${who}: allowed_roots must be a list of strings`);
|
|
81
|
+
}
|
|
82
|
+
const limits = field(entry, 'limits', {});
|
|
83
|
+
if (!isTable(limits))
|
|
84
|
+
throw new UsersError(`${who}: limits must be a table`);
|
|
85
|
+
const rpmRaw = field(limits, 'rpm', undefined);
|
|
86
|
+
let rpm = null;
|
|
87
|
+
if (rpmRaw !== undefined) {
|
|
88
|
+
if (typeof rpmRaw !== 'bigint' || rpmRaw < 1n) {
|
|
89
|
+
throw new UsersError(`${who}: limits.rpm must be a positive integer`);
|
|
90
|
+
}
|
|
91
|
+
rpm = Number(rpmRaw);
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
uid,
|
|
95
|
+
tokenHash,
|
|
96
|
+
enabled,
|
|
97
|
+
namespace,
|
|
98
|
+
allowedRoots: Object.freeze([...roots]),
|
|
99
|
+
limits: Object.freeze({ rpm })
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Parse and validate users.toml into a `uid -> TokenUser` map.
|
|
104
|
+
*
|
|
105
|
+
* Synchronous on purpose: it runs once at server build time, and each
|
|
106
|
+
* `token_hash` is checked with one full scrypt derivation (like core-py
|
|
107
|
+
* `is_valid_encoding`) so an unusable row fails the boot, not a request.
|
|
108
|
+
*/
|
|
109
|
+
export function loadUsers(path) {
|
|
110
|
+
const text = readTomlText(path);
|
|
111
|
+
let raw;
|
|
112
|
+
try {
|
|
113
|
+
raw = parse(text, { integersAsBigInt: true });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const reason = err instanceof TomlError ? err.message : String(err);
|
|
117
|
+
throw new UsersError(`users file is not valid TOML: ${path}: ${reason}`, { cause: err });
|
|
118
|
+
}
|
|
119
|
+
const table = field(raw, 'users', undefined);
|
|
120
|
+
if (!isTable(table) || Object.keys(table).length === 0) {
|
|
121
|
+
throw new UsersError('users file must contain a non-empty [users] table');
|
|
122
|
+
}
|
|
123
|
+
const users = new Map();
|
|
124
|
+
for (const [uid, entry] of Object.entries(table))
|
|
125
|
+
users.set(uid, parseUser(uid, entry));
|
|
126
|
+
return users;
|
|
127
|
+
}
|
|
128
|
+
/** Return the first user whose `tokenHash` verifies `token`, else `null`. */
|
|
129
|
+
export async function findUserByToken(users, token) {
|
|
130
|
+
for (const user of users.values()) {
|
|
131
|
+
if (await verifyToken(token, user.tokenHash))
|
|
132
|
+
return user;
|
|
133
|
+
}
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
// Python `Path.resolve()` (non-strict): absolutize, follow symlinks as far
|
|
137
|
+
// as the path exists, keep the non-existent tail lexically.
|
|
138
|
+
function resolveLoose(target) {
|
|
139
|
+
const absolute = resolve(target);
|
|
140
|
+
try {
|
|
141
|
+
return realpathSync.native(absolute);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
const parent = dirname(absolute);
|
|
145
|
+
if (parent === absolute)
|
|
146
|
+
return absolute;
|
|
147
|
+
return join(resolveLoose(parent), basename(absolute));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function comparable(p) {
|
|
151
|
+
return process.platform === 'win32' ? p.toLowerCase() : p;
|
|
152
|
+
}
|
|
153
|
+
function isWithin(resolved, root) {
|
|
154
|
+
const target = comparable(resolved);
|
|
155
|
+
const base = comparable(root);
|
|
156
|
+
if (target === base)
|
|
157
|
+
return true;
|
|
158
|
+
const prefix = base.endsWith(sep) ? base : base + sep;
|
|
159
|
+
return target.startsWith(prefix);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Enforce the caller's `allowedRoots`. An empty list grants nothing. Both
|
|
163
|
+
* sides are resolved first, so a symlinked escape needs its resolved target
|
|
164
|
+
* to sit under a root too. Throws `PathNotAllowedError` on denial.
|
|
165
|
+
*/
|
|
166
|
+
export function ensurePathAllowed(scope, path) {
|
|
167
|
+
const resolved = resolveLoose(path);
|
|
168
|
+
for (const root of scope.allowedRoots) {
|
|
169
|
+
if (isWithin(resolved, resolveLoose(root)))
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
throw new PathNotAllowedError(`path ${resolved} is outside allowed roots for user ${pyRepr(scope.uid)}`);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"users.js","sourceRoot":"","sources":["../../src/token-auth/users.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools-list cache with version-aware key (D10) — TS parity with core-py.
|
|
3
|
+
*
|
|
4
|
+
* Cache filename: ``<server>-<port>-<srvVersion>-<coreVersion>.tools.json``.
|
|
5
|
+
* A mismatch on either ``srvVersion`` (the upstream MCP server's version) or
|
|
6
|
+
* ``coreVersion`` (mcp-core itself) invalidates the cache, so an upgrade on
|
|
7
|
+
* either side never serves a stale tool surface.
|
|
8
|
+
*
|
|
9
|
+
* ``persistToolsCache`` writes via ``.tmp`` + ``rename`` (Windows-safe) and
|
|
10
|
+
* suppresses all filesystem errors with a debug log. This is the D10
|
|
11
|
+
* root-cause fix for ``Failed to persist capabilities cache`` hard-failures
|
|
12
|
+
* (crg #384).
|
|
13
|
+
*/
|
|
14
|
+
export declare function cacheDir(): string;
|
|
15
|
+
export declare function cacheFilename(serverName: string, port: number, srvVersion: string, coreVersion: string): string;
|
|
16
|
+
export declare function atomicWrite(path: string, content: string): void;
|
|
17
|
+
export declare function persistToolsCache(serverName: string, port: number, srvVersion: string, coreVersion: string, tools: unknown[]): void;
|
|
18
|
+
export declare function loadToolsCache(serverName: string, port: number, srvVersion: string, coreVersion: string): unknown[] | null;
|
|
19
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/transport/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAUH,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAK/G;AAOD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAgB/D;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,OAAO,EAAE,GACf,IAAI,CASN;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,OAAO,EAAE,GAAG,IAAI,CAUlB"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools-list cache with version-aware key (D10) — TS parity with core-py.
|
|
3
|
+
*
|
|
4
|
+
* Cache filename: ``<server>-<port>-<srvVersion>-<coreVersion>.tools.json``.
|
|
5
|
+
* A mismatch on either ``srvVersion`` (the upstream MCP server's version) or
|
|
6
|
+
* ``coreVersion`` (mcp-core itself) invalidates the cache, so an upgrade on
|
|
7
|
+
* either side never serves a stale tool surface.
|
|
8
|
+
*
|
|
9
|
+
* ``persistToolsCache`` writes via ``.tmp`` + ``rename`` (Windows-safe) and
|
|
10
|
+
* suppresses all filesystem errors with a debug log. This is the D10
|
|
11
|
+
* root-cause fix for ``Failed to persist capabilities cache`` hard-failures
|
|
12
|
+
* (crg #384).
|
|
13
|
+
*/
|
|
14
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
16
|
+
import { dirname, join } from 'node:path';
|
|
17
|
+
// Self-namespace import lets ``vi.spyOn(cacheModule, 'cacheDir' | 'atomicWrite')``
|
|
18
|
+
// intercept internal calls (ESM bindings make plain references unmockable).
|
|
19
|
+
import * as self from './cache.js';
|
|
20
|
+
export function cacheDir() {
|
|
21
|
+
return join(homedir(), '.config', 'mcp', 'cache');
|
|
22
|
+
}
|
|
23
|
+
export function cacheFilename(serverName, port, srvVersion, coreVersion) {
|
|
24
|
+
const safeName = serverName.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
|
|
25
|
+
const safeSrvVersion = srvVersion.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
|
|
26
|
+
const safeCoreVersion = coreVersion.replace(/[^a-zA-Z0-9.-]/g, '_').replace(/\.\./g, '__');
|
|
27
|
+
return `${safeName}-${port}-${safeSrvVersion}-${safeCoreVersion}.tools.json`;
|
|
28
|
+
}
|
|
29
|
+
// Cache directories already chmod-tightened in this process. The directory is
|
|
30
|
+
// still re-created if missing (OS cleanup), but chmod runs once per directory
|
|
31
|
+
// so a pre-existing overly-permissive directory is tightened without paying
|
|
32
|
+
// the syscall on every hot-path write (TOCTOU hardening).
|
|
33
|
+
const initDirs = new Set();
|
|
34
|
+
export function atomicWrite(path, content) {
|
|
35
|
+
const dir = dirname(path);
|
|
36
|
+
if (dir) {
|
|
37
|
+
if (!existsSync(dir)) {
|
|
38
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
39
|
+
}
|
|
40
|
+
if (!initDirs.has(dir)) {
|
|
41
|
+
if (process.platform !== 'win32') {
|
|
42
|
+
chmodSync(dir, 0o700);
|
|
43
|
+
}
|
|
44
|
+
initDirs.add(dir);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const tmp = `${path}.tmp`;
|
|
48
|
+
writeFileSync(tmp, content, { encoding: 'utf-8', mode: 0o600 });
|
|
49
|
+
renameSync(tmp, path);
|
|
50
|
+
}
|
|
51
|
+
export function persistToolsCache(serverName, port, srvVersion, coreVersion, tools) {
|
|
52
|
+
const path = join(self.cacheDir(), cacheFilename(serverName, port, srvVersion, coreVersion));
|
|
53
|
+
const payload = JSON.stringify({ tools, srv_version: srvVersion, core_version: coreVersion });
|
|
54
|
+
try {
|
|
55
|
+
self.atomicWrite(path, payload);
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Suppress per D10 — log only.
|
|
59
|
+
console.debug('Failed to persist capabilities cache for %s: %s', serverName, err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function loadToolsCache(serverName, port, srvVersion, coreVersion) {
|
|
63
|
+
const path = join(self.cacheDir(), cacheFilename(serverName, port, srvVersion, coreVersion));
|
|
64
|
+
if (!existsSync(path))
|
|
65
|
+
return null;
|
|
66
|
+
try {
|
|
67
|
+
const payload = JSON.parse(readFileSync(path, 'utf-8'));
|
|
68
|
+
if (payload.srv_version !== srvVersion || payload.core_version !== coreVersion)
|
|
69
|
+
return null;
|
|
70
|
+
return Array.isArray(payload.tools) ? payload.tools : null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/transport/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { HttpAuthConfig } from '../token-auth/authenticator.js';
|
|
2
|
+
export { type HttpRoute, type HttpServerHandle, type JWTClaims, type RunHttpServerOptions, runHttpServer } from './local-server.js';
|
|
3
|
+
export { type AuthenticatedRequest, OAuthMiddleware, type OAuthMiddlewareOptions } from './oauth-middleware.js';
|
|
4
|
+
export { StreamableHTTPServer, type StreamableHTTPServerOptions } from './streamable-http.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AACpE,OAAO,EACL,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,oBAAoB,EACzB,aAAa,EACd,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,oBAAoB,EACzB,eAAe,EACf,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EACjC,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transport/index.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,aAAa,EACd,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAEL,eAAe,EAEhB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,oBAAoB,EAErB,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local MCP server entry point combining OAuth 2.1 AS + Streamable HTTP transport.
|
|
3
|
+
*
|
|
4
|
+
* Composes:
|
|
5
|
+
* 1. OAuth 2.1 AS (credential form + token exchange) -- serves /authorize,
|
|
6
|
+
* /token, /otp, /setup-status, /.well-known/*
|
|
7
|
+
* 2. MCP Streamable HTTP transport -- serves /mcp with optional Bearer auth
|
|
8
|
+
* 3. /health endpoint -- liveness probe
|
|
9
|
+
*
|
|
10
|
+
* For servers without credential input (e.g. godot) ``relaySchema`` may be
|
|
11
|
+
* omitted: only /mcp (unauthenticated) and /health are served.
|
|
12
|
+
*
|
|
13
|
+
* This is a TypeScript port of ``core-py``'s ``local_server.py``. Route layout,
|
|
14
|
+
* Bearer enforcement, and lifecycle semantics are kept identical.
|
|
15
|
+
*/
|
|
16
|
+
import { type IncomingMessage, type ServerResponse } from 'node:http';
|
|
17
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import type { JWTPayload } from 'jose';
|
|
19
|
+
import type { RelayConfigSchema } from '../auth/credential-form.js';
|
|
20
|
+
import { type DelegatedOAuthAppOptions } from '../auth/delegated-oauth-app.js';
|
|
21
|
+
import { type CredentialsCallback, type StepCallback } from '../auth/local-oauth-app.js';
|
|
22
|
+
import type { JWTIssuer } from '../oauth/jwt-issuer.js';
|
|
23
|
+
import { type HttpAuthConfig } from '../token-auth/authenticator.js';
|
|
24
|
+
/** Decoded JWT claims returned by JWTIssuer.verifyAccessToken. */
|
|
25
|
+
export type JWTClaims = JWTPayload & {
|
|
26
|
+
anonymous?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* A route a consumer registers on the same port ``runHttpServer`` serves.
|
|
30
|
+
*
|
|
31
|
+
* The built-in ``/mcp`` and ``/health`` routes are matched FIRST and cannot be
|
|
32
|
+
* overridden -- a consumer cannot accidentally shadow the MCP transport or the
|
|
33
|
+
* liveness probe. Everything else is tried against ``extraRoutes`` before the
|
|
34
|
+
* OAuth app, which is a catch-all for the remaining paths.
|
|
35
|
+
*/
|
|
36
|
+
export interface HttpRoute {
|
|
37
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
38
|
+
path: string;
|
|
39
|
+
handler: (req: IncomingMessage, res: ServerResponse) => Promise<void> | void;
|
|
40
|
+
}
|
|
41
|
+
export interface RunHttpServerOptions {
|
|
42
|
+
/** Identifier used for JWT iss/aud and credential storage. */
|
|
43
|
+
serverName: string;
|
|
44
|
+
/** If undefined, server has NO auth (e.g., godot). */
|
|
45
|
+
relaySchema?: RelayConfigSchema;
|
|
46
|
+
/** Optional pre-created issuer, primarily for isolated tests and custom key storage. */
|
|
47
|
+
jwtIssuer?: JWTIssuer;
|
|
48
|
+
/**
|
|
49
|
+
* Mutually exclusive with `relaySchema`. When set, the OAuth app is the
|
|
50
|
+
* delegated provider (upstream redirect or device_code) instead of the local
|
|
51
|
+
* credential form. The `serverName` and `jwtIssuer` are supplied by this
|
|
52
|
+
* function; callers provide only `flow`, `upstream`, and `onTokenReceived`.
|
|
53
|
+
*/
|
|
54
|
+
delegatedOAuth?: Omit<DelegatedOAuthAppOptions, 'serverName' | 'jwtIssuer'>;
|
|
55
|
+
/** 0 = auto-find a free port. Default: 0. */
|
|
56
|
+
port?: number;
|
|
57
|
+
/** Host to bind. Default '127.0.0.1'. */
|
|
58
|
+
host?: string;
|
|
59
|
+
/** Optional callback invoked with credentials after POST /authorize. */
|
|
60
|
+
onCredentialsSaved?: CredentialsCallback;
|
|
61
|
+
/** Optional callback invoked with step data after POST /otp. */
|
|
62
|
+
onStepSubmitted?: StepCallback;
|
|
63
|
+
/**
|
|
64
|
+
* Called after server ready so callers can wire background tasks (e.g.
|
|
65
|
+
* GDrive device code poll) to the form's ``/setup-status`` endpoint.
|
|
66
|
+
*
|
|
67
|
+
* Accepts either arity for backward compatibility:
|
|
68
|
+
* - Legacy 1-arg: ``hook(markComplete)`` -- success-only (older consumers).
|
|
69
|
+
* - New 2-arg: ``hook(markComplete, markFailed)`` -- surfaces upstream
|
|
70
|
+
* errors (``invalid_grant`` / ``expired_token`` / ``access_denied``)
|
|
71
|
+
* to the browser form so it stops polling and shows the error.
|
|
72
|
+
*
|
|
73
|
+
* Prefer the 2-arg form for new code. Arity is detected via
|
|
74
|
+
* ``Function.prototype.length``.
|
|
75
|
+
*/
|
|
76
|
+
setupCompleteHook?: ((markComplete: (key?: string) => void) => void) | ((markComplete: (key?: string) => void, markFailed: (key?: string, error?: string) => void) => void);
|
|
77
|
+
/**
|
|
78
|
+
* Optional renderer used in place of the default credential form on GET
|
|
79
|
+
* /authorize. Passed through to ``createLocalOAuthApp``.
|
|
80
|
+
*/
|
|
81
|
+
customCredentialFormHtml?: (schema: RelayConfigSchema, options: {
|
|
82
|
+
submitUrl: string;
|
|
83
|
+
prefill?: Record<string, string>;
|
|
84
|
+
}) => string;
|
|
85
|
+
/** Forwarded to the local OAuth app; see LocalOAuthAppOptions.stableSubEnabled. */
|
|
86
|
+
stableSubEnabled?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Optional middleware invoked after JWT verification and before the MCP
|
|
89
|
+
* transport handles the request. Called with verified claims and a ``next``
|
|
90
|
+
* function that invokes the MCP transport. Consumers use this to wrap the
|
|
91
|
+
* request in AsyncLocalStorage (e.g., for per-user token lookup).
|
|
92
|
+
*/
|
|
93
|
+
authScope?: (claims: JWTClaims, next: () => Promise<void>) => Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* When `true`, skip Bearer token validation on the MCP endpoint and treat
|
|
96
|
+
* the caller as anonymous. Intended for deployments behind an external
|
|
97
|
+
* auth boundary (reverse proxy, API gateway like agentgateway/Zitadel) that
|
|
98
|
+
* already enforces authentication. The deployer is responsible for ensuring
|
|
99
|
+
* the network in front of this server is locked down — anyone who can
|
|
100
|
+
* reach `/mcp` directly will get tool access.
|
|
101
|
+
*
|
|
102
|
+
* Wire via env var: `authDisabled: process.env.MCP_AUTH_DISABLE === '1'`.
|
|
103
|
+
*
|
|
104
|
+
* When set, `authScope` (if provided) receives anonymous claims
|
|
105
|
+
* `{ sub: 'anonymous', anonymous: true }`.
|
|
106
|
+
*/
|
|
107
|
+
authDisabled?: boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Whether to auto-open the setup URL in the user's browser when the stored
|
|
110
|
+
* config is not yet complete. Default ``true`` -- first-run setup for the
|
|
111
|
+
* servers built on this function depends on it.
|
|
112
|
+
*
|
|
113
|
+
* Set ``false`` when the consumer already hands the setup URL to the user by
|
|
114
|
+
* another route (a tool-call result, a log line, its own UI). Otherwise there
|
|
115
|
+
* are two entry points into the SAME temporary server, so the user starts two
|
|
116
|
+
* consent flows: whichever finishes first resolves the flow and closes the
|
|
117
|
+
* server, and the other tab's redirect then lands on a dead port
|
|
118
|
+
* (ERR_CONNECTION_REFUSED). The user is left with one "Setup complete" page
|
|
119
|
+
* and one connection error, unable to tell which one counted.
|
|
120
|
+
*/
|
|
121
|
+
openBrowser?: boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Extra HTTP routes served on the same port, matched by exact pathname and
|
|
124
|
+
* method. Tried after ``/mcp`` and ``/health`` (which always win) and before
|
|
125
|
+
* the OAuth app, whose handler is a catch-all for every other path -- so a
|
|
126
|
+
* route registered here is the only way for a consumer to own an endpoint
|
|
127
|
+
* inside this process. Used by servers that need their own OAuth callback,
|
|
128
|
+
* e.g. adding a second upstream account to an already-authenticated subject.
|
|
129
|
+
*/
|
|
130
|
+
extraRoutes?: HttpRoute[];
|
|
131
|
+
/**
|
|
132
|
+
* Token-table HTTP auth, the core-py `[server] auth` modes:
|
|
133
|
+
*
|
|
134
|
+
* - `{ mode: 'no-auth' }` -- no Authorization header; every request runs as
|
|
135
|
+
* `localAuthContext()`. Refuses (throws `ServerConfigError`) any `host`
|
|
136
|
+
* that is not loopback.
|
|
137
|
+
* - `{ mode: 'token', tokenHash, rpm? }` -- one shared bearer token checked
|
|
138
|
+
* against a `scrypt$...` hash; caller `uid` is `"shared"`.
|
|
139
|
+
* - `{ mode: 'multi', usersFile?, users? }` -- users.toml table (default
|
|
140
|
+
* `~/.hull/users.toml`); 401 wrong token, 403 disabled user, 429 over the
|
|
141
|
+
* user's `limits.rpm`.
|
|
142
|
+
*
|
|
143
|
+
* The guard runs before routing for every path except `/health` (so `/mcp`
|
|
144
|
+
* and `extraRoutes` both see the caller via `currentUser()`), and rejects
|
|
145
|
+
* with core-py's JSON body and `WWW-Authenticate: Bearer` on 401. The config
|
|
146
|
+
* is validated before the port is bound. Mutually exclusive with the
|
|
147
|
+
* OAuth/relay options (`relaySchema`, `delegatedOAuth`, `jwtIssuer`,
|
|
148
|
+
* `authDisabled`, `authScope`).
|
|
149
|
+
*/
|
|
150
|
+
auth?: HttpAuthConfig;
|
|
151
|
+
}
|
|
152
|
+
export interface HttpServerHandle {
|
|
153
|
+
/** Actual TCP port bound. Non-zero even when ``options.port`` was 0. */
|
|
154
|
+
port: number;
|
|
155
|
+
/** Host bound. */
|
|
156
|
+
host: string;
|
|
157
|
+
/** Cleanly close transport + http server. */
|
|
158
|
+
close: () => Promise<void>;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Start an HTTP server with optional local OAuth AS + MCP Streamable HTTP transport.
|
|
162
|
+
*
|
|
163
|
+
* Behavior:
|
|
164
|
+
* - If ``relaySchema`` is provided, serves OAuth routes (/authorize, /token,
|
|
165
|
+
* /otp, /setup-status, /.well-known/*) AND /mcp with Bearer auth.
|
|
166
|
+
* - If ``relaySchema`` is undefined (e.g., godot), serves ONLY /mcp without
|
|
167
|
+
* auth (plus /health).
|
|
168
|
+
* - Binds to ``host:port``. Port 0 auto-assigns via the OS.
|
|
169
|
+
* - Returns a handle for lifecycle management; the server runs in the
|
|
170
|
+
* background until ``close()`` is called.
|
|
171
|
+
*/
|
|
172
|
+
export declare function runHttpServer(serverFactory: () => McpServer, options: RunHttpServerOptions): Promise<HttpServerHandle>;
|
|
173
|
+
//# sourceMappingURL=local-server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-server.d.ts","sourceRoot":"","sources":["../../src/transport/local-server.ts"],"names":[],"mappings":"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"}
|