@nubisco/openbridge 1.4.1 → 1.5.1

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.
Files changed (44) hide show
  1. package/dist/__tests__/auth-origin.test.js +2 -4
  2. package/dist/__tests__/auth-origin.test.js.map +1 -1
  3. package/dist/__tests__/mcp.test.d.ts +2 -0
  4. package/dist/__tests__/mcp.test.d.ts.map +1 -0
  5. package/dist/__tests__/mcp.test.js +464 -0
  6. package/dist/__tests__/mcp.test.js.map +1 -0
  7. package/dist/access.d.ts +148 -0
  8. package/dist/access.d.ts.map +1 -0
  9. package/dist/access.js +401 -0
  10. package/dist/access.js.map +1 -0
  11. package/dist/auth.d.ts +13 -5
  12. package/dist/auth.d.ts.map +1 -1
  13. package/dist/auth.js +16 -10
  14. package/dist/auth.js.map +1 -1
  15. package/dist/index.js +8 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/mcp/index.d.ts +48 -0
  18. package/dist/mcp/index.d.ts.map +1 -0
  19. package/dist/mcp/index.js +173 -0
  20. package/dist/mcp/index.js.map +1 -0
  21. package/dist/mcp/oauth.d.ts +35 -0
  22. package/dist/mcp/oauth.d.ts.map +1 -0
  23. package/dist/mcp/oauth.js +319 -0
  24. package/dist/mcp/oauth.js.map +1 -0
  25. package/dist/mcp/tools.d.ts +39 -0
  26. package/dist/mcp/tools.d.ts.map +1 -0
  27. package/dist/mcp/tools.js +432 -0
  28. package/dist/mcp/tools.js.map +1 -0
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +67 -114
  31. package/dist/server.js.map +1 -1
  32. package/dist/services/bridge.d.ts +102 -0
  33. package/dist/services/bridge.d.ts.map +1 -0
  34. package/dist/services/bridge.js +191 -0
  35. package/dist/services/bridge.js.map +1 -0
  36. package/dist/token-cli.d.ts +10 -0
  37. package/dist/token-cli.d.ts.map +1 -0
  38. package/dist/token-cli.js +76 -0
  39. package/dist/token-cli.js.map +1 -0
  40. package/package.json +6 -5
  41. package/ui-dist/assets/{index-CL7JhxrS.css → index-9bWNU3bq.css} +2 -2
  42. package/ui-dist/assets/index-DIyesg4J.js +109 -0
  43. package/ui-dist/index.html +2 -2
  44. package/ui-dist/assets/index-Rvs_bO_V.js +0 -109
@@ -0,0 +1,319 @@
1
+ /**
2
+ * The OAuth surface for MCP connectors, after Tally's (tally/apps/api/src/routes/oauth.ts):
3
+ * discovery, registration, consent and token. Plus the dashboard's own routes
4
+ * for managing tokens and connections.
5
+ *
6
+ * The consent screen is a page of the dashboard (OAuthConsentView.vue), built
7
+ * from the design system. Every decision stays here: /oauth/context says
8
+ * whether a request is well formed and who would approve it, and the approval
9
+ * posts back to POST /oauth/authorize, which revalidates from scratch.
10
+ *
11
+ * Who may approve: the signed-in person when dashboard sign-in is on. When it
12
+ * is off, the dashboard is open to the network and so is this screen, exactly
13
+ * as anyone who can open the dashboard can already create a token in Settings.
14
+ * Either way the approval must come from the dashboard itself (see
15
+ * sameOrigin), so a web page elsewhere cannot approve on someone's behalf.
16
+ */
17
+ import { SCOPES } from '../access.js';
18
+ import { readSession } from '../auth.js';
19
+ import { publicOrigin } from './index.js';
20
+ const AUTHORIZE_FIELDS = [
21
+ 'client_id',
22
+ 'redirect_uri',
23
+ 'response_type',
24
+ 'scope',
25
+ 'state',
26
+ 'code_challenge',
27
+ 'code_challenge_method',
28
+ ];
29
+ /** Reads a field from a query string or a parsed form, as a string. */
30
+ function getter(source) {
31
+ const record = (source ?? {});
32
+ return (k) => {
33
+ const v = record[k];
34
+ return typeof v === 'string' ? v : '';
35
+ };
36
+ }
37
+ /**
38
+ * Whether a state-changing request came from this bridge's own pages.
39
+ *
40
+ * With sign-in off the dashboard has no cookie to protect it, and CORS here
41
+ * reflects any origin, so without this check any web page a person on the
42
+ * home network opens could mint a token and read it back. Browsers send
43
+ * Sec-Fetch-Site and Origin on every POST; a request with neither is not from
44
+ * a browser, and a non-browser caller cannot ride on anyone's visit.
45
+ */
46
+ export function sameOrigin(req) {
47
+ const site = req.headers['sec-fetch-site'];
48
+ if (typeof site === 'string' && site !== 'same-origin' && site !== 'none')
49
+ return false;
50
+ const origin = req.headers.origin;
51
+ if (typeof origin === 'string' && origin !== '') {
52
+ let host;
53
+ try {
54
+ host = new URL(origin).host;
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ const allowed = new Set([req.host]);
60
+ try {
61
+ allowed.add(new URL(publicOrigin(req)).host);
62
+ }
63
+ catch {
64
+ // A malformed OPENBRIDGE_PUBLIC_URL only narrows what is accepted.
65
+ }
66
+ if (!allowed.has(host))
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+ function validateAuthorize(store, get) {
72
+ const redirectUri = get('redirect_uri');
73
+ const client = store.client(get('client_id'), redirectUri);
74
+ if (!client)
75
+ return { ok: false, kind: 'refuse', message: 'Unknown client, or a redirect_uri it did not register' };
76
+ const fail = (error) => {
77
+ const to = new URL(redirectUri);
78
+ to.searchParams.set('error', error);
79
+ if (get('state'))
80
+ to.searchParams.set('state', get('state'));
81
+ return { ok: false, kind: 'redirect', to: to.href };
82
+ };
83
+ if (get('response_type') !== 'code')
84
+ return fail('unsupported_response_type');
85
+ if (!get('code_challenge') || get('code_challenge_method') !== 'S256')
86
+ return fail('invalid_request');
87
+ return {
88
+ ok: true,
89
+ p: {
90
+ clientId: client.id,
91
+ clientName: client.name,
92
+ redirectUri,
93
+ state: get('state'),
94
+ codeChallenge: get('code_challenge'),
95
+ },
96
+ };
97
+ }
98
+ /** Registration is unauthenticated by design (RFC 7591), so it is limited per address. */
99
+ const REGISTER_WINDOW_MS = 60 * 60 * 1000;
100
+ const REGISTER_LIMIT = 20;
101
+ const registrations = new Map();
102
+ function registrationAllowed(ip, now = Date.now()) {
103
+ const recent = (registrations.get(ip) ?? []).filter((t) => now - t < REGISTER_WINDOW_MS);
104
+ if (recent.length >= REGISTER_LIMIT) {
105
+ registrations.set(ip, recent);
106
+ return false;
107
+ }
108
+ recent.push(now);
109
+ registrations.set(ip, recent);
110
+ return true;
111
+ }
112
+ const LOCAL_OWNER = { id: 'local', email: null };
113
+ export async function registerOauthRoutes(app, deps) {
114
+ const { store, authConfig } = deps;
115
+ const signInOn = authConfig.enabled && !!authConfig.issuer;
116
+ /** The person acting, or null when sign-in is on and nobody is signed in. */
117
+ function ownerOf(req) {
118
+ if (!signInOn)
119
+ return LOCAL_OWNER;
120
+ const session = readSession(authConfig, req);
121
+ return session ? { id: session.userId, email: session.email } : null;
122
+ }
123
+ // Encapsulated so the form parser below applies to these routes only.
124
+ await app.register(async (scope) => {
125
+ // The token endpoint and the consent form both post
126
+ // application/x-www-form-urlencoded, which Fastify does not parse by default.
127
+ scope.addContentTypeParser('application/x-www-form-urlencoded', { parseAs: 'string' }, (_req, body, done) => {
128
+ done(null, Object.fromEntries(new URLSearchParams(body)));
129
+ });
130
+ // ── Discovery ──
131
+ //
132
+ // Both the plain and the path-inserted spellings: RFC 8414 inserts
133
+ // .well-known between host and path, so a client discovering /mcp asks for
134
+ // /.well-known/oauth-authorization-server/mcp too, and tries the OpenID
135
+ // spelling of both. Answering only the bare path broke claude.ai on Acta.
136
+ const asMeta = async (req) => {
137
+ const origin = publicOrigin(req);
138
+ return {
139
+ issuer: origin,
140
+ authorization_endpoint: `${origin}/oauth/authorize`,
141
+ token_endpoint: `${origin}/oauth/token`,
142
+ registration_endpoint: `${origin}/oauth/register`,
143
+ response_types_supported: ['code'],
144
+ grant_types_supported: ['authorization_code', 'refresh_token'],
145
+ code_challenge_methods_supported: ['S256'],
146
+ token_endpoint_auth_methods_supported: ['none'],
147
+ scopes_supported: [...SCOPES],
148
+ };
149
+ };
150
+ const prMeta = async (req) => {
151
+ const origin = publicOrigin(req);
152
+ return {
153
+ resource: `${origin}/mcp`,
154
+ authorization_servers: [origin],
155
+ bearer_methods_supported: ['header'],
156
+ scopes_supported: [...SCOPES],
157
+ };
158
+ };
159
+ for (const path of [
160
+ '/.well-known/oauth-authorization-server',
161
+ '/.well-known/oauth-authorization-server/*',
162
+ '/.well-known/openid-configuration',
163
+ '/.well-known/openid-configuration/*',
164
+ '/mcp/.well-known/oauth-authorization-server',
165
+ ]) {
166
+ scope.get(path, asMeta);
167
+ }
168
+ for (const path of [
169
+ '/.well-known/oauth-protected-resource',
170
+ '/.well-known/oauth-protected-resource/*',
171
+ '/mcp/.well-known/oauth-protected-resource',
172
+ ]) {
173
+ scope.get(path, prMeta);
174
+ }
175
+ // ── Registration ──
176
+ scope.post('/oauth/register', async (req, reply) => {
177
+ if (!registrationAllowed(req.ip)) {
178
+ return reply.status(429).send({ error: 'slow_down', error_description: 'Too many registrations. Try later.' });
179
+ }
180
+ const body = req.body;
181
+ if (typeof body !== 'object' || body === null || Array.isArray(body)) {
182
+ return reply.status(400).send({ error: 'invalid_client_metadata', error_description: 'Body must be JSON' });
183
+ }
184
+ const out = store.registerClient(body);
185
+ if (!out.ok) {
186
+ return reply
187
+ .status(out.error === 'temporarily_unavailable' ? 503 : 400)
188
+ .send({ error: out.error, error_description: out.description });
189
+ }
190
+ return reply.status(201).send({
191
+ client_id: out.id,
192
+ client_name: out.name,
193
+ redirect_uris: out.uris,
194
+ token_endpoint_auth_method: 'none',
195
+ grant_types: ['authorization_code', 'refresh_token'],
196
+ response_types: ['code'],
197
+ });
198
+ });
199
+ // ── Consent ──
200
+ /** Hands the request to the dashboard's consent screen, or reports a malformed one to the client. */
201
+ scope.get('/oauth/authorize', async (req, reply) => {
202
+ const get = getter(req.query);
203
+ const check = validateAuthorize(store, get);
204
+ if (!check.ok && check.kind === 'redirect')
205
+ return reply.redirect(check.to, 302);
206
+ const qs = new URLSearchParams();
207
+ for (const k of AUTHORIZE_FIELDS)
208
+ if (get(k))
209
+ qs.set(k, get(k));
210
+ return reply.redirect(`/oauth/consent?${qs.toString()}`, 302);
211
+ });
212
+ /** What the consent screen needs to render honestly. */
213
+ scope.get('/oauth/context', async (req) => {
214
+ const check = validateAuthorize(store, getter(req.query));
215
+ if (!check.ok) {
216
+ return { ok: false, reason: check.kind === 'refuse' ? check.message : 'That request cannot be approved' };
217
+ }
218
+ const owner = ownerOf(req);
219
+ return {
220
+ ok: true,
221
+ clientName: check.p.clientName,
222
+ redirectHost: new URL(check.p.redirectUri).host,
223
+ signInRequired: signInOn,
224
+ email: owner?.email ?? null,
225
+ signedIn: owner !== null,
226
+ };
227
+ });
228
+ /** The decision, a real form post so the browser follows the redirect back to the client. */
229
+ scope.post('/oauth/authorize', async (req, reply) => {
230
+ if (!sameOrigin(req))
231
+ return reply.status(403).type('text/plain').send('Approve from the OpenBridge dashboard');
232
+ const get = getter(req.body);
233
+ const check = validateAuthorize(store, get);
234
+ if (!check.ok) {
235
+ if (check.kind === 'redirect')
236
+ return reply.redirect(check.to, 302);
237
+ return reply.status(400).type('text/plain').send(check.message);
238
+ }
239
+ const owner = ownerOf(req);
240
+ if (!owner) {
241
+ const qs = new URLSearchParams();
242
+ for (const k of AUTHORIZE_FIELDS)
243
+ if (get(k))
244
+ qs.set(k, get(k));
245
+ return reply.redirect(`/oauth/consent?${qs.toString()}`, 302);
246
+ }
247
+ if (get('decision') !== 'approve') {
248
+ const to = new URL(check.p.redirectUri);
249
+ if (check.p.state)
250
+ to.searchParams.set('state', check.p.state);
251
+ to.searchParams.set('error', 'access_denied');
252
+ return reply.redirect(to.href, 302);
253
+ }
254
+ const access = get('access') === 'control' ? 'control' : 'read';
255
+ const code = store.issueCode({ ...check.p, access }, owner);
256
+ const to = new URL(check.p.redirectUri);
257
+ if (check.p.state)
258
+ to.searchParams.set('state', check.p.state);
259
+ to.searchParams.set('code', code);
260
+ return reply.redirect(to.href, 302);
261
+ });
262
+ // ── Token ──
263
+ scope.post('/oauth/token', async (req, reply) => {
264
+ const get = getter(req.body);
265
+ const grant = get('grant_type');
266
+ const out = grant === 'authorization_code'
267
+ ? store.exchangeCode(get)
268
+ : grant === 'refresh_token'
269
+ ? store.refresh(get)
270
+ : { ok: false, error: 'unsupported_grant_type' };
271
+ reply.header('Cache-Control', 'no-store');
272
+ if (!out.ok)
273
+ return reply.status(400).send({ error: out.error });
274
+ return out.pair;
275
+ });
276
+ });
277
+ // ── Dashboard: tokens and connections ──
278
+ //
279
+ // Under /api, so the session gate in auth.ts covers them when sign-in is on.
280
+ // Never reachable with a token: a bearer opens /mcp and nothing else, so a
281
+ // leaked token cannot mint another one or revoke the person's own.
282
+ app.get('/api/access', async (req) => ({
283
+ enabled: true,
284
+ endpoint: `${publicOrigin(req)}/mcp`,
285
+ tokens: store.listTokens(),
286
+ connections: store.listConnections(),
287
+ }));
288
+ app.post('/api/access/tokens', async (req, reply) => {
289
+ if (!sameOrigin(req))
290
+ return reply.status(403).send({ error: 'Create tokens from the OpenBridge dashboard' });
291
+ const owner = ownerOf(req);
292
+ if (!owner)
293
+ return reply.status(401).send({ error: 'unauthenticated' });
294
+ const { name, access } = (req.body ?? {});
295
+ if (typeof name !== 'string' || !name.trim())
296
+ return reply.status(400).send({ error: 'name is required' });
297
+ if (access !== 'read' && access !== 'control') {
298
+ return reply.status(400).send({ error: "access must be 'read' or 'control'" });
299
+ }
300
+ return store.createToken(name, access, owner);
301
+ });
302
+ app.delete('/api/access/tokens/:id', async (req, reply) => {
303
+ if (!sameOrigin(req))
304
+ return reply.status(403).send({ error: 'Revoke tokens from the OpenBridge dashboard' });
305
+ const { id } = req.params;
306
+ if (!store.revokeToken(id))
307
+ return reply.status(404).send({ error: 'token not found' });
308
+ return { ok: true };
309
+ });
310
+ app.delete('/api/access/connections/:clientId', async (req, reply) => {
311
+ if (!sameOrigin(req))
312
+ return reply.status(403).send({ error: 'Disconnect from the OpenBridge dashboard' });
313
+ const { clientId } = req.params;
314
+ if (!store.revokeConnection(clientId))
315
+ return reply.status(404).send({ error: 'connection not found' });
316
+ return { ok: true };
317
+ });
318
+ }
319
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The MCP tools. Each one is a record: name, description, zod schema, the
3
+ * scope it needs, and a handler that calls the same service functions as the
4
+ * REST routes (services/bridge.ts). The JSON Schema in tools/list is generated
5
+ * from the zod schema, never written by hand.
6
+ *
7
+ * Designed for an agent's context window, not as a mirror of the REST API:
8
+ * `overview` orients in one call, reads are compact unless asked for more, and
9
+ * history comes back with its summary already computed, so the model reports
10
+ * numbers the code worked out rather than doing arithmetic over raw points.
11
+ */
12
+ import { z } from 'zod';
13
+ import { type BridgeContext } from '../services/bridge.js';
14
+ import { type Credential, type Scope } from '../access.js';
15
+ export interface McpContext {
16
+ bridge: BridgeContext;
17
+ credential: Credential;
18
+ /** Public origin of this bridge, for url_templates and icons. */
19
+ origin: string;
20
+ /** Bridge name from config, for the overview. */
21
+ bridgeName: () => string | null;
22
+ }
23
+ export interface McpTool {
24
+ name: string;
25
+ description: string;
26
+ schema: z.ZodType;
27
+ scope: Scope;
28
+ write: boolean;
29
+ handler: (args: never, ctx: McpContext) => unknown | Promise<unknown>;
30
+ }
31
+ /** A refusal the model must read: becomes `isError: true`, not a protocol error. */
32
+ export declare class Refusal extends Error {
33
+ }
34
+ export declare function isRefusal(err: unknown): err is Error;
35
+ export declare function toolInputSchema(tool: McpTool): unknown;
36
+ /** For tests: forget every remembered op. */
37
+ export declare function clearReplays(): void;
38
+ export declare const MCP_TOOLS: McpTool[];
39
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAGvB,OAAO,EAYL,KAAK,aAAa,EAEnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAA6B,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAA;AAIrF,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,aAAa,CAAA;IACrB,UAAU,EAAE,UAAU,CAAA;IACtB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,UAAU,EAAE,MAAM,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAA;IACjB,KAAK,EAAE,KAAK,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACtE;AAED,oFAAoF;AACpF,qBAAa,OAAQ,SAAQ,KAAK;CAAG;AAErC,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,KAAK,CAEpD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAItD;AA+HD,6CAA6C;AAC7C,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAwFD,eAAO,MAAM,SAAS,EAAE,OAAO,EAuM9B,CAAA"}