@blen/fedreg-mcp-server 2.0.11 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/README.md +13 -2
- package/dist/accounts/app.d.ts +16 -0
- package/dist/accounts/app.d.ts.map +1 -0
- package/dist/accounts/app.js +352 -0
- package/dist/accounts/app.js.map +1 -0
- package/dist/accounts/config.d.ts +16 -0
- package/dist/accounts/config.d.ts.map +1 -0
- package/dist/accounts/config.js +50 -0
- package/dist/accounts/config.js.map +1 -0
- package/dist/accounts/crypto.d.ts +18 -0
- package/dist/accounts/crypto.d.ts.map +1 -0
- package/dist/accounts/crypto.js +43 -0
- package/dist/accounts/crypto.js.map +1 -0
- package/dist/accounts/portal.d.ts +8 -0
- package/dist/accounts/portal.d.ts.map +1 -0
- package/dist/accounts/portal.js +170 -0
- package/dist/accounts/portal.js.map +1 -0
- package/dist/accounts/provider.d.ts +2568 -0
- package/dist/accounts/provider.d.ts.map +1 -0
- package/dist/accounts/provider.js +107 -0
- package/dist/accounts/provider.js.map +1 -0
- package/dist/accounts/runtime.d.ts +16 -0
- package/dist/accounts/runtime.d.ts.map +1 -0
- package/dist/accounts/runtime.js +122 -0
- package/dist/accounts/runtime.js.map +1 -0
- package/dist/accounts/store.d.ts +25 -0
- package/dist/accounts/store.d.ts.map +1 -0
- package/dist/accounts/store.js +142 -0
- package/dist/accounts/store.js.map +1 -0
- package/dist/bin.js +13 -4
- package/dist/bin.js.map +1 -1
- package/dist/sdk/bindings.js +1 -1
- package/dist/sdk/bindings.js.map +1 -1
- package/dist/sdk/sources/corpus-loader.d.ts +3 -1
- package/dist/sdk/sources/corpus-loader.d.ts.map +1 -1
- package/dist/sdk/sources/corpus-loader.js +10 -2
- package/dist/sdk/sources/corpus-loader.js.map +1 -1
- package/dist/sdk/sources/source.d.ts +2 -5
- package/dist/sdk/sources/source.d.ts.map +1 -1
- package/dist/server/mcpServer.js +1 -1
- package/dist/server/mcpServer.js.map +1 -1
- package/dist/server/toolCatalog.d.ts +2 -1
- package/dist/server/toolCatalog.d.ts.map +1 -1
- package/dist/server/toolCatalog.js.map +1 -1
- package/dist/server/zodToJsonSchema.d.ts +1 -1
- package/dist/server/zodToJsonSchema.d.ts.map +1 -1
- package/dist/tools/describeSchema.d.ts +1 -1
- package/dist/tools/describeSchema.d.ts.map +1 -1
- package/dist/tools/describeSchema.js +1 -1
- package/dist/tools/describeSchema.js.map +1 -1
- package/dist/tools/execute.d.ts +4 -2
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/execute.js +5 -4
- package/dist/tools/execute.js.map +1 -1
- package/dist/tools/searchApi.d.ts +1 -1
- package/dist/tools/searchApi.d.ts.map +1 -1
- package/dist/tools/searchApi.js +1 -1
- package/dist/tools/searchApi.js.map +1 -1
- package/dist/util/httpClient.d.ts +9 -4
- package/dist/util/httpClient.d.ts.map +1 -1
- package/dist/util/httpClient.js +1 -1
- package/dist/util/httpClient.js.map +1 -1
- package/dist/web/assets/blen-logo-BKJFojyj.png +0 -0
- package/dist/web/assets/index--Hn7cjpj.js +299 -0
- package/dist/web/assets/index-bNtg7OeV.css +2 -0
- package/dist/web/index.html +14 -0
- package/package.json +35 -7
package/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,11 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
## [2.1.0] - 2026-10-13
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- add personal regulations.gov accounts with SAM.gov UX (#7) (auth)
|
|
20
25
|
## [2.0.11] - 2026-10-13
|
|
21
26
|
|
|
22
27
|
### Fixed
|
|
@@ -200,7 +205,8 @@ Initial public release.
|
|
|
200
205
|
→ notifications/initialized → tools/list → tools/call → sandbox → SDK
|
|
201
206
|
→ mocked upstream).
|
|
202
207
|
|
|
203
|
-
[Unreleased]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.0
|
|
208
|
+
[Unreleased]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.1.0...HEAD
|
|
209
|
+
[2.1.0]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.0.11...v2.1.0
|
|
204
210
|
[2.0.11]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.0.10...v2.0.11
|
|
205
211
|
[2.0.10]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.0.9...v2.0.10
|
|
206
212
|
[2.0.9]: https://github.com/blen-labs/fedreg-mcp-server/compare/v2.0.8...v2.0.9
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://github.com/blen-labs/fedreg-mcp-server/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@blen/fedreg-mcp-server)
|
|
5
5
|
[](./LICENSE)
|
|
6
|
-
[](https://nodejs.org/)
|
|
7
7
|
[](https://modelcontextprotocol.io/specification/2026-07-28)
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -22,6 +22,17 @@ Instead of bolting on dozens of rigid, narrow tools, it hands the model a small,
|
|
|
22
22
|
|
|
23
23
|
> Independent open-source project. It calls public U.S. government APIs and is not affiliated with or endorsed by the U.S. government.
|
|
24
24
|
|
|
25
|
+
## Personal accounts and regulations.gov keys
|
|
26
|
+
|
|
27
|
+
Hosted operators can enable `FEDREG_AUTH_PROVIDER=accounts` for Google sign-in,
|
|
28
|
+
a personal API-key page at `/account`, and OAuth/PKCE connections from MCP clients.
|
|
29
|
+
The interface matches the SAM.gov server: Google sign-in, a Connect key-entry step,
|
|
30
|
+
a separate Authorize screen, and account controls with the shared BLEN styling.
|
|
31
|
+
Keys are encrypted in PostgreSQL and selected from each caller's verified account.
|
|
32
|
+
Federal Register and eCFR still work without a saved key. Stdio retains its existing
|
|
33
|
+
`FEDREG_REGS_API_KEY` configuration. See [account setup](docs/accounts.md) for
|
|
34
|
+
required secrets, Google callback configuration, testing, and rollout steps.
|
|
35
|
+
|
|
25
36
|
## See it in action
|
|
26
37
|
|
|
27
38
|
Ask Claude:
|
|
@@ -118,7 +129,7 @@ Restart Claude Desktop, then just ask in plain language:
|
|
|
118
129
|
> *"What does 40 CFR Part 60 cover?"*
|
|
119
130
|
> *"Which proposed rules opened for public comment this week?"*
|
|
120
131
|
|
|
121
|
-
Requires **Node.js ≥ 20.
|
|
132
|
+
Requires **Node.js ≥ 20.19**. `npx` downloads and runs the server on demand; no global install needed. If the `execute` tool reports `SandboxUnavailable`, see [Sandbox runtimes](#sandbox-runtimes).
|
|
122
133
|
|
|
123
134
|
## How it works
|
|
124
135
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createMcpHandler } from '@modelcontextprotocol/server';
|
|
3
|
+
import type { CatalogDeps } from '../server/toolCatalog.js';
|
|
4
|
+
import type { AccountConfig } from './config.js';
|
|
5
|
+
import { type Store } from './store.js';
|
|
6
|
+
import { type Auth } from './provider.js';
|
|
7
|
+
/** MCP's rule for redirect URIs: HTTPS, or plain HTTP only to this computer. */
|
|
8
|
+
export declare function allowedRedirect(value: unknown): boolean;
|
|
9
|
+
export declare function createAccountApp(config: AccountConfig, store: Store, deps: CatalogDeps, fetcher?: typeof fetch): Promise<{
|
|
10
|
+
app: express.Express;
|
|
11
|
+
auth: Auth;
|
|
12
|
+
mcp: ReturnType<typeof createMcpHandler>;
|
|
13
|
+
close: () => Promise<void>;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function forwardResponse(response: Response, res: express.Response): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/accounts/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAA8B,KAAK,IAAI,EAAE,MAAM,eAAe,CAAC;AAkCtE,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,WAQ7C;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAoTrH;AACD,wBAAsB,eAAe,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,iBAO9E"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { createMcpHandler } from '@modelcontextprotocol/server';
|
|
4
|
+
import { toNodeHandler } from '@modelcontextprotocol/node';
|
|
5
|
+
import { buildMcpServer, MCP_PROTOCOL_VERSION } from '../server/mcpServer.js';
|
|
6
|
+
import { createAuth, initializeAuth } from './provider.js';
|
|
7
|
+
import { installPortal } from './portal.js';
|
|
8
|
+
import { digest } from './crypto.js';
|
|
9
|
+
import { log } from '../util/logger.js';
|
|
10
|
+
import { clientIp } from '../server/ipRateLimiter.js';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { resolve } from 'node:path';
|
|
14
|
+
const CROSS_ORIGIN_PATHS = /^\/(?:mcp|\.well-known\/(?:oauth-protected-resource|oauth-authorization-server|openid-configuration)(?:\/[\w-]+)*|api\/auth\/(?:oauth2\/(?:register|token|revoke)|\.well-known\/(?:oauth-authorization-server|openid-configuration)))$/;
|
|
15
|
+
const CORS_HEADERS = {
|
|
16
|
+
'Access-Control-Allow-Origin': '*',
|
|
17
|
+
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
18
|
+
'Access-Control-Expose-Headers': 'WWW-Authenticate, Mcp-Session-Id, Mcp-Protocol-Version',
|
|
19
|
+
'Access-Control-Max-Age': '600',
|
|
20
|
+
};
|
|
21
|
+
// Newer MCP clients add per-request headers (Mcp-Method, Mcp-Name, Mcp-Param-*), so preflights
|
|
22
|
+
// echo what was asked for; no credentials are allowed, so this grants nothing beyond the bearer.
|
|
23
|
+
const CORS_ALLOW_HEADERS = 'Authorization, Content-Type, Accept, Mcp-Protocol-Version, Mcp-Session-Id, Mcp-Method, Mcp-Name, Last-Event-ID';
|
|
24
|
+
/** Longest a token or revoke request waits for a lock connection, and then for the lock. */
|
|
25
|
+
const LOCK_WAIT_MS = 10_000;
|
|
26
|
+
const LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]']);
|
|
27
|
+
/** Redirect URIs in a registration body; malformed JSON is left for Better Auth to reject. */
|
|
28
|
+
function registeredRedirects(body) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(body.toString('utf8'))?.redirect_uris ?? [];
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** MCP's rule for redirect URIs: HTTPS, or plain HTTP only to this computer. */
|
|
37
|
+
export function allowedRedirect(value) {
|
|
38
|
+
if (typeof value !== 'string')
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
const url = new URL(value);
|
|
42
|
+
return !url.username && !url.password && !url.hash && (url.protocol === 'https:' || (url.protocol === 'http:' && LOOPBACK.has(url.hostname)));
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function createAccountApp(config, store, deps, fetcher = fetch) {
|
|
49
|
+
const app = express();
|
|
50
|
+
app.disable('x-powered-by');
|
|
51
|
+
// Railway terminates TLS at one trusted proxy hop.
|
|
52
|
+
if (config.production)
|
|
53
|
+
app.set('trust proxy', 1);
|
|
54
|
+
// With `trust proxy` 1, req.ip is the rightmost X-Forwarded-For entry. Railway rebuilds that
|
|
55
|
+
// header as "<client>, <edge>", so req.ip is its edge, shared by many users. The header the
|
|
56
|
+
// proxy overwrites with the client address (Railway: x-real-ip) is used instead when set.
|
|
57
|
+
const ipOf = (req) => config.clientIpHeader ? clientIp(req, config.clientIpHeader) : req.ip;
|
|
58
|
+
const auth = createAuth(config, store);
|
|
59
|
+
await initializeAuth(auth, config, store);
|
|
60
|
+
// Each waiting token request holds a connection, so size this for concurrent token requests.
|
|
61
|
+
const locks = new Pool({ ...store.pool.options, max: 10, connectionTimeoutMillis: LOCK_WAIT_MS });
|
|
62
|
+
locks.on('error', () => log.error('account.auth_lock_connection_error'));
|
|
63
|
+
/**
|
|
64
|
+
* The advisory lock for a /oauth2/token or /oauth2/revoke body: the owner of the presented
|
|
65
|
+
* refresh or access token, else the presented code or token itself (an unknown token
|
|
66
|
+
* changes nothing, and a code is single-use). Tokens are stored as digests of their value.
|
|
67
|
+
*/
|
|
68
|
+
const tokenLockId = async (body) => {
|
|
69
|
+
let fields = {};
|
|
70
|
+
const text = body?.toString('utf8').trim() ?? '';
|
|
71
|
+
try {
|
|
72
|
+
fields = text.startsWith('{') ? JSON.parse(text) : Object.fromEntries(new URLSearchParams(text));
|
|
73
|
+
}
|
|
74
|
+
catch { /* Better Auth rejects the malformed body; any lock id will do. */ }
|
|
75
|
+
const value = (name) => typeof fields[name] === 'string' ? fields[name] : undefined;
|
|
76
|
+
const token = value('refresh_token') ?? value('token');
|
|
77
|
+
let key = `code:${value('code') ?? ''}`;
|
|
78
|
+
if (token) {
|
|
79
|
+
const { adapter } = await auth.$context;
|
|
80
|
+
const where = [{ field: 'token', value: digest(token) }];
|
|
81
|
+
const row = await adapter.findOne({ model: 'oauthRefreshToken', where })
|
|
82
|
+
?? await adapter.findOne({ model: 'oauthAccessToken', where });
|
|
83
|
+
key = row?.userId ? `user:${row.userId}` : `token:${digest(token)}`;
|
|
84
|
+
}
|
|
85
|
+
// pg_advisory_lock takes a signed 64-bit key.
|
|
86
|
+
return BigInt.asIntN(64, BigInt(`0x${digest(key).slice(0, 16)}`)).toString();
|
|
87
|
+
};
|
|
88
|
+
const mcp = createMcpHandler(ctx => buildMcpServer(deps, { subject: ctx.authInfo?.extra?.userId }), { legacy: 'stateless', maxSubscriptions: 0, onerror: () => log.warn('account.mcp_handler_error') });
|
|
89
|
+
const resource = `${config.origin}/mcp`;
|
|
90
|
+
const foreignOrigin = (req) => !!req.headers.origin && req.headers.origin !== config.origin;
|
|
91
|
+
const metadataUrl = `${config.origin}/.well-known/oauth-protected-resource/mcp`;
|
|
92
|
+
const originHost = new URL(config.origin).host;
|
|
93
|
+
app.use((req, res, next) => {
|
|
94
|
+
res.set({
|
|
95
|
+
'Cache-Control': 'no-store',
|
|
96
|
+
'Referrer-Policy': 'strict-origin',
|
|
97
|
+
'X-Content-Type-Options': 'nosniff',
|
|
98
|
+
'X-Frame-Options': 'DENY',
|
|
99
|
+
'Content-Security-Policy': "default-src 'none'; style-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'",
|
|
100
|
+
});
|
|
101
|
+
if (config.production)
|
|
102
|
+
res.set('Strict-Transport-Security', 'max-age=31536000');
|
|
103
|
+
if (req.path !== '/health' && req.headers.host !== originHost)
|
|
104
|
+
return res.status(421).send('Invalid host');
|
|
105
|
+
// MCP clients, including browser-based ones and hosted connectors such as Claude, call
|
|
106
|
+
// discovery, registration, token and /mcp from other origins. These use bearer tokens or
|
|
107
|
+
// client credentials, never cookies, and the Host check above prevents DNS rebinding.
|
|
108
|
+
if (CROSS_ORIGIN_PATHS.test(req.path)) {
|
|
109
|
+
res.set(CORS_HEADERS);
|
|
110
|
+
if (req.method === 'OPTIONS')
|
|
111
|
+
return res
|
|
112
|
+
.status(204)
|
|
113
|
+
.set({
|
|
114
|
+
'Access-Control-Allow-Headers': req.headers['access-control-request-headers']?.slice(0, 1024) || CORS_ALLOW_HEADERS,
|
|
115
|
+
Vary: 'Access-Control-Request-Headers',
|
|
116
|
+
})
|
|
117
|
+
.end();
|
|
118
|
+
return next();
|
|
119
|
+
}
|
|
120
|
+
// Everything else is a cookie-authenticated browser surface and stays same-origin.
|
|
121
|
+
if (foreignOrigin(req)) {
|
|
122
|
+
log.warn('account.origin_rejected');
|
|
123
|
+
return res.status(403).send('Invalid origin');
|
|
124
|
+
}
|
|
125
|
+
next();
|
|
126
|
+
});
|
|
127
|
+
app.get('/health', async (_req, res) => {
|
|
128
|
+
try {
|
|
129
|
+
await store.pool.query('SELECT 1');
|
|
130
|
+
res.json({
|
|
131
|
+
status: 'ok',
|
|
132
|
+
protocolVersion: MCP_PROTOCOL_VERSION, sandbox: deps.sandbox.kind,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
res.status(503).json({ status: 'unavailable' });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const webRoot = fileURLToPath(new URL('../../dist/web/', import.meta.url));
|
|
140
|
+
app.use('/assets', express.static(resolve(webRoot, 'assets'), { immutable: true, maxAge: '1y', index: false }));
|
|
141
|
+
// Production reads the built page once; development rereads it so `pnpm dev:web` rebuilds show.
|
|
142
|
+
const loadIndex = () => readFile(resolve(webRoot, 'index.html'), 'utf8').catch(() => undefined);
|
|
143
|
+
let indexHtml;
|
|
144
|
+
app.get(['/', '/sign-in', '/connect', '/consent', '/account', '/privacy-policy', '/terms'], async (_req, res) => {
|
|
145
|
+
const html = config.production ? (indexHtml ??= await loadIndex()) : await loadIndex();
|
|
146
|
+
if (!html)
|
|
147
|
+
return res.status(503).send('The web interface is not built. Run pnpm build.');
|
|
148
|
+
res.set('Content-Security-Policy', "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'")
|
|
149
|
+
.type('html').send(html);
|
|
150
|
+
});
|
|
151
|
+
const tooMany = (res) => res.status(429).set('Retry-After', '60').send('Too many requests');
|
|
152
|
+
app.use(async (req, res, next) => {
|
|
153
|
+
// Hosted connectors (ChatGPT, Claude) send every user's OAuth and MCP traffic from a few
|
|
154
|
+
// shared egress IPs, so those endpoints are limited elsewhere: Better Auth limits each OAuth
|
|
155
|
+
// endpoint, and /mcp is limited per user after the token is checked (see below).
|
|
156
|
+
const platform = CROSS_ORIGIN_PATHS.test(req.path) &&
|
|
157
|
+
(req.path !== '/mcp' || !!req.headers.authorization?.startsWith('Bearer '));
|
|
158
|
+
if (!platform && !(await store.allow(`http:${ipOf(req)}`, 600, 60)))
|
|
159
|
+
return tooMany(res);
|
|
160
|
+
next();
|
|
161
|
+
});
|
|
162
|
+
const mcpNode = toNodeHandler(mcp, {
|
|
163
|
+
onerror: () => log.warn('account.mcp_http_error'),
|
|
164
|
+
});
|
|
165
|
+
// No body parser and no size cap, matching the base HTTP transport: large `execute` scripts
|
|
166
|
+
// must work in both modes. The SDK reads the body only after the token checks below pass.
|
|
167
|
+
app.all('/mcp', async (req, res) => {
|
|
168
|
+
const challenge = (status, error) => res
|
|
169
|
+
.status(status)
|
|
170
|
+
.set('WWW-Authenticate', `Bearer resource_metadata="${metadataUrl}", error="${error}", scope="fedreg:read"`)
|
|
171
|
+
.json({ error });
|
|
172
|
+
const authorization = req.headers.authorization;
|
|
173
|
+
if (!authorization?.startsWith('Bearer ') || authorization.length > 4096)
|
|
174
|
+
return challenge(401, 'invalid_token');
|
|
175
|
+
// A flood guard sized for a connector's shared IP; the per-user limit below does the real work.
|
|
176
|
+
if (!(await store.allow(`mcp-ip:${ipOf(req)}`, 6000, 60)))
|
|
177
|
+
return tooMany(res);
|
|
178
|
+
const value = authorization.slice(7);
|
|
179
|
+
// Opaque tokens are checked against their hashed database row on every request, so
|
|
180
|
+
// revocation, client disabling, user deletion and loss of email verification apply immediately.
|
|
181
|
+
const context = await auth.$context;
|
|
182
|
+
const token = await context.adapter.findOne({
|
|
183
|
+
model: 'oauthAccessToken',
|
|
184
|
+
where: [{ field: 'token', value: digest(value) }],
|
|
185
|
+
});
|
|
186
|
+
if (!token?.userId ||
|
|
187
|
+
token.revoked ||
|
|
188
|
+
!(new Date(token.expiresAt).getTime() > Date.now()) ||
|
|
189
|
+
!(token.resources ?? []).includes(resource))
|
|
190
|
+
return challenge(401, 'invalid_token');
|
|
191
|
+
const [client, user] = await Promise.all([
|
|
192
|
+
context.adapter.findOne({
|
|
193
|
+
model: 'oauthClient',
|
|
194
|
+
where: [{ field: 'clientId', value: token.clientId }],
|
|
195
|
+
}),
|
|
196
|
+
context.adapter.findOne({
|
|
197
|
+
model: 'user',
|
|
198
|
+
where: [{ field: 'id', value: token.userId }],
|
|
199
|
+
}),
|
|
200
|
+
]);
|
|
201
|
+
if (!client || client.disabled || !user?.emailVerified)
|
|
202
|
+
return challenge(401, 'invalid_token');
|
|
203
|
+
if (!(token.scopes ?? []).includes('fedreg:read'))
|
|
204
|
+
return challenge(403, 'insufficient_scope');
|
|
205
|
+
if (!(await store.allow(`mcp-user:${user.id}`, 600, 60)))
|
|
206
|
+
return tooMany(res);
|
|
207
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
208
|
+
if (!await store.allow(`mcp-day:${user.id}:${day}`, config.subjectDailyQuota ?? 10000, 86400))
|
|
209
|
+
return tooMany(res);
|
|
210
|
+
Object.assign(req, {
|
|
211
|
+
auth: {
|
|
212
|
+
token: value,
|
|
213
|
+
clientId: token.clientId,
|
|
214
|
+
scopes: token.scopes,
|
|
215
|
+
extra: { userId: user.id },
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
await mcpNode(req, res);
|
|
219
|
+
});
|
|
220
|
+
// Clients look for authorization-server metadata in different places: RFC 8414 path
|
|
221
|
+
// insertion for the /api/auth issuer (canonical, served by Better Auth), the issuer path with
|
|
222
|
+
// /.well-known appended, or the origin root when a client skips protected-resource discovery.
|
|
223
|
+
// Serve the same document at each. The openid-configuration URLs stay 404: this is not an
|
|
224
|
+
// OpenID provider, and clients parse documents found there with the stricter OIDC schema.
|
|
225
|
+
app.get(['/.well-known/oauth-authorization-server', '/api/auth/.well-known/oauth-authorization-server'], async (_req, res) => forwardResponse(await auth.handler(new Request(`${config.origin}/.well-known/oauth-authorization-server/api/auth`)), res));
|
|
226
|
+
app.use(['/api/auth', '/.well-known'], express.raw({ type: () => true, limit: '64kb' }), async (req, res) => {
|
|
227
|
+
const headers = new Headers();
|
|
228
|
+
for (const [name, value] of Object.entries(req.headers))
|
|
229
|
+
if (value !== undefined)
|
|
230
|
+
headers.set(name, Array.isArray(value) ? value.join(', ') : value);
|
|
231
|
+
const ip = ipOf(req);
|
|
232
|
+
if (ip)
|
|
233
|
+
headers.set('x-forwarded-for', ip);
|
|
234
|
+
else
|
|
235
|
+
headers.delete('x-forwarded-for');
|
|
236
|
+
const authUrl = new URL(req.originalUrl, config.origin);
|
|
237
|
+
// WHATWG Request normalizes dot segments and backslashes. Reject aliases
|
|
238
|
+
// before forwarding so Express guards and Better Auth see the same path.
|
|
239
|
+
if (authUrl.origin !== config.origin || authUrl.pathname !== req.originalUrl.split('?')[0]) {
|
|
240
|
+
return res.status(404).send('Not found');
|
|
241
|
+
}
|
|
242
|
+
let body = ['GET', 'HEAD'].includes(req.method) ? undefined : req.body;
|
|
243
|
+
// MCP requires every redirect URI to be HTTPS or localhost. Check registrations here, and
|
|
244
|
+
// authorization requests too, since Client ID Metadata Documents skip registration.
|
|
245
|
+
const redirects = req.path === '/oauth2/register' && Buffer.isBuffer(body)
|
|
246
|
+
? registeredRedirects(body)
|
|
247
|
+
: req.path === '/oauth2/authorize'
|
|
248
|
+
? [
|
|
249
|
+
(req.method === 'POST' && Buffer.isBuffer(body)
|
|
250
|
+
? new URLSearchParams(body.toString('utf8'))
|
|
251
|
+
: authUrl.searchParams).get('redirect_uri'),
|
|
252
|
+
].filter(Boolean)
|
|
253
|
+
: [];
|
|
254
|
+
if (!Array.isArray(redirects) || !redirects.every(allowedRedirect))
|
|
255
|
+
return res.status(400).json({
|
|
256
|
+
error: req.path === '/oauth2/register' ? 'invalid_redirect_uri' : 'invalid_request',
|
|
257
|
+
error_description: 'Redirect URIs must use HTTPS or a localhost address.',
|
|
258
|
+
});
|
|
259
|
+
// Clients that omit RFC 8707 `resource` (Gemini Enterprise may) would get tokens this
|
|
260
|
+
// server cannot accept. It protects one resource, so default to it; the code keeps it.
|
|
261
|
+
if (req.path === '/oauth2/authorize') {
|
|
262
|
+
if (req.method === 'GET' && !authUrl.searchParams.get('resource'))
|
|
263
|
+
authUrl.searchParams.set('resource', resource);
|
|
264
|
+
if (req.method === 'POST' &&
|
|
265
|
+
req.is('application/x-www-form-urlencoded') &&
|
|
266
|
+
Buffer.isBuffer(body)) {
|
|
267
|
+
const form = new URLSearchParams(body.toString('utf8'));
|
|
268
|
+
if (!form.get('resource'))
|
|
269
|
+
form.set('resource', resource);
|
|
270
|
+
body = Buffer.from(form.toString());
|
|
271
|
+
headers.delete('content-length');
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const request = new Request(authUrl, {
|
|
275
|
+
method: req.method,
|
|
276
|
+
headers,
|
|
277
|
+
body,
|
|
278
|
+
});
|
|
279
|
+
const execute = async () => {
|
|
280
|
+
const response = await auth.handler(request);
|
|
281
|
+
// Registration failures are otherwise invisible; log the outcome only, no request data.
|
|
282
|
+
if (req.path === '/oauth2/register')
|
|
283
|
+
log.info('client_registration', { status: response.status, crossOrigin: foreignOrigin(req) });
|
|
284
|
+
return forwardResponse(response, res);
|
|
285
|
+
};
|
|
286
|
+
if (req.path !== '/oauth2/token' && req.path !== '/oauth2/revoke')
|
|
287
|
+
return execute();
|
|
288
|
+
// Serialize token-family rotation and invalidation across replicas. Better Auth's
|
|
289
|
+
// current adapter performs those writes separately; overlapping requests must not
|
|
290
|
+
// interleave. A family is every refresh token of one (client, user), so refresh and revoke
|
|
291
|
+
// lock per user and a code exchange locks per code: unrelated users never queue together.
|
|
292
|
+
// A code exchange can still overlap a family wipe for the same user; at worst that new
|
|
293
|
+
// authorization is revoked and the user authorizes again.
|
|
294
|
+
const lockId = await tokenLockId(Buffer.isBuffer(body) ? body : undefined);
|
|
295
|
+
const lock = await locks.connect();
|
|
296
|
+
// A transaction-scoped lock is released by COMMIT or ROLLBACK, and a bounded wait keeps one
|
|
297
|
+
// stuck request from holding every lock connection and queueing all users' token requests.
|
|
298
|
+
let broken = false;
|
|
299
|
+
try {
|
|
300
|
+
await lock.query('BEGIN');
|
|
301
|
+
await lock.query(`SET LOCAL lock_timeout = '${LOCK_WAIT_MS}ms'`);
|
|
302
|
+
await lock.query('SELECT pg_advisory_xact_lock($1)', [lockId]);
|
|
303
|
+
await execute();
|
|
304
|
+
await lock.query('COMMIT');
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// A connection that cannot roll back may still hold the lock; discard it.
|
|
308
|
+
await lock.query('ROLLBACK').catch(() => { broken = true; });
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
lock.release(broken);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
installPortal(app, config, store, auth, fetcher, ipOf);
|
|
316
|
+
app.use((_req, res) => res.status(404).send('Not found'));
|
|
317
|
+
app.use((error, _req, res, _next) => {
|
|
318
|
+
if (error &&
|
|
319
|
+
typeof error === 'object' &&
|
|
320
|
+
'type' in error &&
|
|
321
|
+
['entity.too.large', 'parameters.too.many'].includes(String(error.type))) {
|
|
322
|
+
if (!res.headersSent)
|
|
323
|
+
res.status(413).send('Request too large');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
log.error('account.request_failed');
|
|
327
|
+
if (!res.headersSent)
|
|
328
|
+
res
|
|
329
|
+
.status(500)
|
|
330
|
+
.json({ error: 'Unable to complete this request. Please try again.' });
|
|
331
|
+
});
|
|
332
|
+
return {
|
|
333
|
+
app,
|
|
334
|
+
auth,
|
|
335
|
+
mcp,
|
|
336
|
+
close: async () => {
|
|
337
|
+
await mcp.close();
|
|
338
|
+
await locks.end();
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
export async function forwardResponse(response, res) {
|
|
343
|
+
res.status(response.status);
|
|
344
|
+
for (const [name, value] of response.headers)
|
|
345
|
+
if (name.toLowerCase() !== 'set-cookie')
|
|
346
|
+
res.setHeader(name, value);
|
|
347
|
+
const cookies = response.headers.getSetCookie();
|
|
348
|
+
if (cookies.length)
|
|
349
|
+
res.setHeader('Set-Cookie', cookies);
|
|
350
|
+
res.send(Buffer.from(await response.arrayBuffer()));
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/accounts/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Keyring } from './crypto.js';
|
|
2
|
+
export interface AccountConfig {
|
|
3
|
+
origin: string;
|
|
4
|
+
production: boolean;
|
|
5
|
+
databaseUrl: string;
|
|
6
|
+
authSecret: string;
|
|
7
|
+
googleClientId: string;
|
|
8
|
+
googleClientSecret: string;
|
|
9
|
+
keyring: Keyring;
|
|
10
|
+
subjectDailyQuota?: number;
|
|
11
|
+
/** Header the reverse proxy overwrites with the client address (Railway: x-real-ip). */
|
|
12
|
+
clientIpHeader?: string;
|
|
13
|
+
}
|
|
14
|
+
/** `env` is passed in by src/bin.ts, the only module that reads process.env. */
|
|
15
|
+
export declare function loadAccountConfig(env: Record<string, string | undefined>): AccountConfig;
|
|
16
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/accounts/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEtC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wFAAwF;IACxF,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,aAAa,CAoCxF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Keyring } from './crypto.js';
|
|
2
|
+
/** `env` is passed in by src/bin.ts, the only module that reads process.env. */
|
|
3
|
+
export function loadAccountConfig(env) {
|
|
4
|
+
const required = (name) => {
|
|
5
|
+
const value = env[name];
|
|
6
|
+
if (!value || value.includes('REPLACE_WITH_'))
|
|
7
|
+
throw new Error(`Missing configuration: ${name}`);
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const origin = new URL(required('FEDREG_PUBLIC_ORIGIN'));
|
|
11
|
+
const production = env.NODE_ENV !== 'development' && env.NODE_ENV !== 'test';
|
|
12
|
+
if (origin.pathname !== '/' || origin.search || origin.hash || origin.username || origin.password ||
|
|
13
|
+
(origin.protocol !== 'https:' && (production || origin.protocol !== 'http:' ||
|
|
14
|
+
!['localhost', '127.0.0.1', '[::1]'].includes(origin.hostname)))) {
|
|
15
|
+
throw new Error('FEDREG_PUBLIC_ORIGIN must be an HTTPS origin (HTTP loopback allowed in development)');
|
|
16
|
+
}
|
|
17
|
+
const authSecret = required('BETTER_AUTH_SECRET');
|
|
18
|
+
if (authSecret.length < 32)
|
|
19
|
+
throw new Error('BETTER_AUTH_SECRET must have at least 32 characters');
|
|
20
|
+
let encoded;
|
|
21
|
+
const encodedKeys = required('ENCRYPTION_KEYS_JSON');
|
|
22
|
+
try {
|
|
23
|
+
encoded = JSON.parse(encodedKeys);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error('ENCRYPTION_KEYS_JSON must be valid JSON');
|
|
27
|
+
}
|
|
28
|
+
if (!encoded || typeof encoded !== 'object' || Array.isArray(encoded))
|
|
29
|
+
throw new Error('Invalid encryption keyring');
|
|
30
|
+
const keys = Object.fromEntries(Object.entries(encoded).map(([id, value]) => {
|
|
31
|
+
if (typeof value !== 'string' || !/^[A-Za-z0-9+/]{43}=$/.test(value))
|
|
32
|
+
throw new Error('Invalid base64 encryption key');
|
|
33
|
+
return [id, Buffer.from(value, 'base64')];
|
|
34
|
+
}));
|
|
35
|
+
const subjectDailyQuota = Number(env.FEDREG_SUBJECT_DAILY_QUOTA ?? 10000);
|
|
36
|
+
if (!Number.isInteger(subjectDailyQuota) || subjectDailyQuota < 1)
|
|
37
|
+
throw new Error('FEDREG_SUBJECT_DAILY_QUOTA must be a positive integer');
|
|
38
|
+
const clientIpHeader = env.FEDREG_CLIENT_IP_HEADER?.trim().toLowerCase() || undefined;
|
|
39
|
+
if (clientIpHeader && !/^[a-z0-9-]+$/.test(clientIpHeader))
|
|
40
|
+
throw new Error('FEDREG_CLIENT_IP_HEADER must be a header name');
|
|
41
|
+
return {
|
|
42
|
+
subjectDailyQuota, clientIpHeader,
|
|
43
|
+
origin: origin.origin, production, authSecret,
|
|
44
|
+
databaseUrl: required('DATABASE_URL'),
|
|
45
|
+
googleClientId: required('GOOGLE_CLIENT_ID'),
|
|
46
|
+
googleClientSecret: required('GOOGLE_CLIENT_SECRET'),
|
|
47
|
+
keyring: new Keyring(required('ENCRYPTION_ACTIVE_KEY_ID'), keys),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/accounts/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Sealed {
|
|
2
|
+
v: 1;
|
|
3
|
+
kid: string;
|
|
4
|
+
iv: string;
|
|
5
|
+
tag: string;
|
|
6
|
+
data: string;
|
|
7
|
+
}
|
|
8
|
+
/** AEAD binds each ciphertext to its purpose and owner, preventing row swapping. */
|
|
9
|
+
export declare class Keyring {
|
|
10
|
+
readonly activeId: string;
|
|
11
|
+
private readonly keys;
|
|
12
|
+
constructor(activeId: string, keys: Record<string, Buffer>);
|
|
13
|
+
seal(value: string, context: string): Sealed;
|
|
14
|
+
open(value: Sealed, context: string): string;
|
|
15
|
+
}
|
|
16
|
+
export declare const digest: (value: string) => string;
|
|
17
|
+
export declare const randomToken: () => string;
|
|
18
|
+
//# sourceMappingURL=crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/accounts/crypto.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,CAAC,CAAC;IACL,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAID,oFAAoF;AACpF,qBAAa,OAAO;IAEhB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBADZ,QAAQ,EAAE,MAAM,EACR,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAM/C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAa5C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;CAY7C;AAED,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,WAAqD,CAAC;AAC1F,eAAO,MAAM,WAAW,cAA8C,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
const TAG_BYTES = 16;
|
|
3
|
+
/** AEAD binds each ciphertext to its purpose and owner, preventing row swapping. */
|
|
4
|
+
export class Keyring {
|
|
5
|
+
activeId;
|
|
6
|
+
keys;
|
|
7
|
+
constructor(activeId, keys) {
|
|
8
|
+
this.activeId = activeId;
|
|
9
|
+
this.keys = keys;
|
|
10
|
+
if (!Object.hasOwn(keys, activeId) || Object.values(keys).some((key) => key.length !== 32)) {
|
|
11
|
+
throw new Error('Encryption keys must be 32 bytes; active key must exist');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
seal(value, context) {
|
|
15
|
+
const iv = randomBytes(12);
|
|
16
|
+
const cipher = createCipheriv('aes-256-gcm', this.keys[this.activeId], iv, { authTagLength: TAG_BYTES });
|
|
17
|
+
cipher.setAAD(Buffer.from(JSON.stringify([1, this.activeId, context])));
|
|
18
|
+
const encrypted = Buffer.concat([cipher.update(value, 'utf8'), cipher.final()]);
|
|
19
|
+
return {
|
|
20
|
+
v: 1,
|
|
21
|
+
kid: this.activeId,
|
|
22
|
+
iv: iv.toString('base64'),
|
|
23
|
+
tag: cipher.getAuthTag().toString('base64'),
|
|
24
|
+
data: encrypted.toString('base64'),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
open(value, context) {
|
|
28
|
+
const key = Object.hasOwn(this.keys, value.kid) ? this.keys[value.kid] : undefined;
|
|
29
|
+
if (value.v !== 1 || !key)
|
|
30
|
+
throw new Error('Unsupported ciphertext');
|
|
31
|
+
// A fixed tag length: GCM otherwise accepts truncated tags as short as 4 bytes.
|
|
32
|
+
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(value.iv, 'base64'), { authTagLength: TAG_BYTES });
|
|
33
|
+
decipher.setAAD(Buffer.from(JSON.stringify([1, value.kid, context])));
|
|
34
|
+
decipher.setAuthTag(Buffer.from(value.tag, 'base64'));
|
|
35
|
+
return Buffer.concat([
|
|
36
|
+
decipher.update(Buffer.from(value.data, 'base64')),
|
|
37
|
+
decipher.final(),
|
|
38
|
+
]).toString('utf8');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
42
|
+
export const randomToken = () => randomBytes(32).toString('base64url');
|
|
43
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/accounts/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Express, type Request } from 'express';
|
|
2
|
+
import type { AccountConfig } from './config.js';
|
|
3
|
+
import { type Store } from './store.js';
|
|
4
|
+
import type { Auth } from './provider.js';
|
|
5
|
+
/** Keys go only to this fixed upstream; no URL, owner or user ID comes from the form. */
|
|
6
|
+
export declare function checkKey(key: unknown, fetcher?: typeof fetch): Promise<string | undefined>;
|
|
7
|
+
export declare function installPortal(app: Express, config: AccountConfig, store: Store, auth: Auth, fetcher: typeof fetch, ipOf: (req: Request) => string | undefined): void;
|
|
8
|
+
//# sourceMappingURL=portal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/accounts/portal.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,EAAE,KAAK,OAAO,EAAiB,MAAM,SAAS,CAAC;AAG7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C,yFAAyF;AACzF,wBAAsB,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAavG;AAED,wBAAgB,aAAa,CAC3B,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,KAAK,EACpF,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,QA8H3C"}
|