@argentic/chest-sdk 0.3.0 → 0.4.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.
- package/README.md +210 -44
- package/client/index.ts +4 -3
- package/client/src/api.ts +12 -0
- package/client/src/chest.ts +30 -6
- package/client/src/database.ts +6 -1
- package/client/src/events.ts +12 -104
- package/client/src/files.ts +9 -9
- package/client/src/members.ts +18 -13
- package/client/src/schedules.ts +87 -0
- package/client/src/signed.ts +166 -0
- package/client/src/testing.ts +130 -54
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/src/api.d.ts +1 -0
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +13 -0
- package/dist/src/api.js.map +1 -1
- package/dist/src/chest.d.ts +5 -0
- package/dist/src/chest.d.ts.map +1 -1
- package/dist/src/chest.js +13 -1
- package/dist/src/chest.js.map +1 -1
- package/dist/src/database.d.ts.map +1 -1
- package/dist/src/database.js +5 -1
- package/dist/src/database.js.map +1 -1
- package/dist/src/events.d.ts +3 -6
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +8 -102
- package/dist/src/events.js.map +1 -1
- package/dist/src/files.d.ts +1 -0
- package/dist/src/files.d.ts.map +1 -1
- package/dist/src/files.js +6 -7
- package/dist/src/files.js.map +1 -1
- package/dist/src/members.d.ts +1 -1
- package/dist/src/members.d.ts.map +1 -1
- package/dist/src/members.js +6 -5
- package/dist/src/members.js.map +1 -1
- package/dist/src/schedules.d.ts +15 -0
- package/dist/src/schedules.d.ts.map +1 -0
- package/dist/src/schedules.js +45 -0
- package/dist/src/schedules.js.map +1 -0
- package/dist/src/signed.d.ts +29 -0
- package/dist/src/signed.d.ts.map +1 -0
- package/dist/src/signed.js +139 -0
- package/dist/src/signed.js.map +1 -0
- package/dist/src/testing.d.ts +15 -5
- package/dist/src/testing.d.ts.map +1 -1
- package/dist/src/testing.js +122 -52
- package/dist/src/testing.js.map +1 -1
- package/package.json +14 -3
package/client/src/testing.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHash,
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
2
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
3
3
|
import type { AddressInfo } from "node:net";
|
|
4
4
|
import type { Alias, Provider } from "./ai.js";
|
|
@@ -6,21 +6,24 @@ import type { AiUnavailableReason } from "./errors.js";
|
|
|
6
6
|
import type { ChestEvent } from "./events.js";
|
|
7
7
|
import { groupIdPattern, memberIdPattern, type Member } from "./member.js";
|
|
8
8
|
import { forget } from "./members.js";
|
|
9
|
+
import { eventChannel, scheduleChannel, sign, signClaims, type Channel } from "./signed.js";
|
|
9
10
|
|
|
10
11
|
// For a tool's own tests, never imported by its production code: a member's
|
|
11
12
|
// assertion signed as the Chest signs it, and a Chest's API in the test's
|
|
12
|
-
// process that answers members, groups, files (stat, move, links
|
|
13
|
-
// it
|
|
13
|
+
// process that answers members, groups, files (stat, move, links and
|
|
14
|
+
// uploads, which it serves and takes itself as the team host would, on its
|
|
15
|
+
// own origin), badges, notifications, AI (chat,
|
|
14
16
|
// streamed or not, embeddings, models, usage: deterministic answers, no
|
|
15
17
|
// provider) and the acknowledgment of an erasure with the Chest's bounds,
|
|
16
|
-
// quotas and errors; and that delivers an event
|
|
17
|
-
// Chest signs
|
|
18
|
+
// quotas and errors; and that delivers an event or a run of a schedule to
|
|
19
|
+
// the tool, signed as the Chest signs them.
|
|
18
20
|
//
|
|
19
21
|
// import { fakeChest, withMember } from "@argentic/chest-sdk/testing";
|
|
20
22
|
// const chest = await fakeChest({ members: [camille], capabilities: ["members", "files", "notifications"] });
|
|
21
23
|
// const response = await app(withMember(new Request("http://tool/chest"), camille));
|
|
22
24
|
// assert.equal(chest.notifications[0]?.member, camille.id);
|
|
23
25
|
// assert.equal(await chest.emit({ type: "access.revoked", data: { id: camille.id } }, request => app(request)), 204);
|
|
26
|
+
// assert.equal(await chest.run("morning", request => app(request)), 204);
|
|
24
27
|
// await chest.close();
|
|
25
28
|
|
|
26
29
|
// A group as a fake Chest keeps it: its identifier, its name, and the
|
|
@@ -28,6 +31,10 @@ import { forget } from "./members.js";
|
|
|
28
31
|
export type FakeGroup = { id: string; name: string; members: string[] };
|
|
29
32
|
// A file as a fake Chest keeps it.
|
|
30
33
|
export type FakeFile = { data: Uint8Array; type: string; updated: string };
|
|
34
|
+
// Someone the tool had who no longer has it: a member of the Chest without
|
|
35
|
+
// access to it, a member who left ("former", by default), or one whose data
|
|
36
|
+
// was erased (their name gone).
|
|
37
|
+
export type FakeFormer = { id: string; name?: string; status?: "no_access" | "former" | "erased" };
|
|
31
38
|
// A notification as a fake Chest keeps it: the member it went to, its text
|
|
32
39
|
// cleaned as the Chest cleans it, its path (/chest when not said) and its
|
|
33
40
|
// key.
|
|
@@ -51,22 +58,24 @@ export type FakeAi = { models?: FakeAiModel[]; reply?: (request: Record<string,
|
|
|
51
58
|
// sent (null for a GET).
|
|
52
59
|
export type FakeAiCall = { path: string; body: unknown };
|
|
53
60
|
|
|
54
|
-
// What a fake Chest is given: the members who have the tool, those
|
|
55
|
-
//
|
|
61
|
+
// What a fake Chest is given: the members who have the tool, those it had
|
|
62
|
+
// who no longer have it (FakeFormer), its groups, the
|
|
56
63
|
// capabilities its version holds (a capability left out answers 403;
|
|
57
64
|
// members, files, notifications and ai by default, members.email to read the
|
|
58
65
|
// addresses), the events it receives (["member.*"] by default, [] to answer
|
|
59
66
|
// an acknowledgment 403), the files it keeps, its AI, and what the Chest is
|
|
60
|
-
// (the chest module: "Test organization", UTC and
|
|
67
|
+
// (the chest module: "Test organization", UTC, English and euros by default;
|
|
68
|
+
// the tool at https://<tool>-chest.chest.test, its public part at
|
|
69
|
+
// https://<tool>.chest.test).
|
|
61
70
|
export type FakeChestOptions = {
|
|
62
71
|
members?: Member[];
|
|
63
|
-
former?:
|
|
72
|
+
former?: FakeFormer[];
|
|
64
73
|
groups?: FakeGroup[];
|
|
65
74
|
capabilities?: string[];
|
|
66
75
|
receives?: string[];
|
|
67
76
|
files?: Record<string, { data: Uint8Array | string; type?: string }>;
|
|
68
77
|
ai?: FakeAi;
|
|
69
|
-
chest?: { organization?: string; timeZone?: string; language?: string };
|
|
78
|
+
chest?: { organization?: string; timeZone?: string; language?: string; currency?: string; teamUrl?: string; publicUrl?: string | null };
|
|
70
79
|
};
|
|
71
80
|
|
|
72
81
|
// An event for emit: its type and data; its id (a new evt_… by default) and
|
|
@@ -74,13 +83,20 @@ export type FakeChestOptions = {
|
|
|
74
83
|
// twice.
|
|
75
84
|
export type FakeEvent = { [K in ChestEvent["type"]]: { type: K; data: Extract<ChestEvent, { type: K }>["data"]; id?: string; occurredAt?: string } }[ChestEvent["type"]];
|
|
76
85
|
|
|
77
|
-
// A
|
|
78
|
-
//
|
|
86
|
+
// A run for run: its id (a new run_… by default: name it to deliver the same
|
|
87
|
+
// run twice), the time it stands for (now by default) and its attempt (1 by
|
|
88
|
+
// default).
|
|
89
|
+
export type FakeRun = { id?: string; scheduledAt?: string; attempt?: number };
|
|
90
|
+
|
|
91
|
+
// A fake Chest in the test's process: its address — its API, and the origin
|
|
92
|
+
// where it serves the links and takes the uploads it signs —, the token and
|
|
93
|
+
// the tool it set in the environment, what it keeps (members, groups and files a test
|
|
79
94
|
// changes or reads; the notifications the tool sent, in the order sent, a
|
|
80
95
|
// replaced one last; each member's badge; the erasures the tool
|
|
81
96
|
// acknowledged; its calls to AI, in order), emit, which delivers an event to the tool — POST
|
|
82
97
|
// /chest-events of its address, or a handler of Web Requests — and says the
|
|
83
|
-
// status it answered,
|
|
98
|
+
// status it answered, run, which delivers a run of a schedule the same way
|
|
99
|
+
// on /chest-schedules, and close, which stops it and restores the
|
|
84
100
|
// environment. Its members are those who have the tool: the others are
|
|
85
101
|
// skipped.
|
|
86
102
|
export type FakeChest = {
|
|
@@ -95,11 +111,10 @@ export type FakeChest = {
|
|
|
95
111
|
acknowledged: string[];
|
|
96
112
|
ai: FakeAiCall[];
|
|
97
113
|
emit(event: FakeEvent, to: string | ((request: Request) => Response | Promise<Response>)): Promise<number>;
|
|
114
|
+
run(name: string, to: string | ((request: Request) => Response | Promise<Response>), run?: FakeRun): Promise<number>;
|
|
98
115
|
close(): Promise<void>;
|
|
99
116
|
};
|
|
100
117
|
|
|
101
|
-
const encode = (value: unknown): string => Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
102
|
-
|
|
103
118
|
// What an assertion is signed with and says besides the member: the token
|
|
104
119
|
// (CHEST_TOKEN by default) and the tool (CHEST_TOOL by default) it is for,
|
|
105
120
|
// and when it is issued (now by default). The member is signed as given: a
|
|
@@ -116,27 +131,16 @@ export function signAssertion(member: Member, options: AssertionOptions = {}): s
|
|
|
116
131
|
if (!token || !tool) throw new Error("signAssertion needs a token and a tool: start a fakeChest, or name them");
|
|
117
132
|
if (!memberIdPattern.test(member.id) || !member.groups.every(g => groupIdPattern.test(g))) throw new Error("signAssertion needs identifiers of the Chest's shape (mbr_…, grp_…)");
|
|
118
133
|
const iat = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
119
|
-
|
|
134
|
+
// Under the label of the assertion's shape, as the Chest signs it and
|
|
135
|
+
// member() reads it.
|
|
136
|
+
return signClaims("Chest-Member v2", {
|
|
120
137
|
iss: `https://${tool}-chest.chest.test`, aud: tool, iat, exp: iat + 60, sub: member.id,
|
|
121
138
|
given_name: member.firstName, family_name: member.lastName, name: member.name, picture: member.photo ?? "", role: member.role ?? "",
|
|
122
139
|
admin: member.isAdmin, builder: member.isBuilder, groups: member.groups, time_zone: member.timeZone, ...(member.email === undefined ? {} : { email: member.email }),
|
|
123
140
|
language: member.language,
|
|
124
|
-
});
|
|
125
|
-
// The key as the Chest derives it, and member() reads it: HMAC-SHA256 of
|
|
126
|
-
// the label of the assertion's shape under the text of the token.
|
|
127
|
-
const key = createHmac("sha256", Buffer.from(token, "utf8")).update("Chest-Member v2").digest();
|
|
128
|
-
return body + "." + createHmac("sha256", key).update(body).digest("base64url");
|
|
141
|
+
}, token);
|
|
129
142
|
}
|
|
130
143
|
|
|
131
|
-
// signEvent is the Chest-Event value the Chest would send with that body:
|
|
132
|
-
// HS256 under the key the token derives for events, for the tool, naming the
|
|
133
|
-
// event and the digest of the body, valid 60 seconds.
|
|
134
|
-
function signEvent(id: string, body: string, options: { token: string; tool: string }): string {
|
|
135
|
-
const iat = Math.floor(Date.now() / 1000);
|
|
136
|
-
const signed = encode({ alg: "HS256", typ: "JWT" }) + "." + encode({ aud: options.tool, iat, exp: iat + 60, jti: id, digest: createHash("sha256").update(body).digest("base64url") });
|
|
137
|
-
const key = createHmac("sha256", Buffer.from(options.token, "utf8")).update("Chest-Event v1").digest();
|
|
138
|
-
return signed + "." + createHmac("sha256", key).update(signed).digest("base64url");
|
|
139
|
-
}
|
|
140
144
|
|
|
141
145
|
// withMember is the request carrying that member's assertion, signed with
|
|
142
146
|
// the options of signAssertion: a new Web Request, or the same Node request
|
|
@@ -156,6 +160,19 @@ export function withMember<R extends Request | IncomingMessage>(request: R, memb
|
|
|
156
160
|
const maxLimit = 500, defaultLimit = 100, maxLookup = 200, callsPerMinute = 600;
|
|
157
161
|
const maxObject = 32 << 20, maxObjects = 10000, maxTotal = 1 << 30;
|
|
158
162
|
const namePattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}(\/[A-Za-z0-9][A-Za-z0-9._-]{0,99}){0,7}$/u;
|
|
163
|
+
// Its links and uploads: their paths on the team host, their lives in
|
|
164
|
+
// seconds, the images it makes thumbnails of, the first bytes an upload of
|
|
165
|
+
// a type must start with, the endings of the names it chooses (chest/toolfiles).
|
|
166
|
+
const linkPath = "/_chest/files/", uploadPath = "/_chest/files/upload/", linkLife = 900, uploadLife = 900;
|
|
167
|
+
const mediaPattern = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,62}\/(\*|[a-z0-9][a-z0-9!#$&^_.+-]{0,62})$/u;
|
|
168
|
+
const thumbnailed = ["image/jpeg", "image/png", "image/gif", "image/webp"];
|
|
169
|
+
const starts = (...heads: string[]) => (data: Buffer): boolean => heads.some(head => data.subarray(0, head.length).equals(Buffer.from(head, "latin1")));
|
|
170
|
+
const signatures: Record<string, (data: Buffer) => boolean> = {
|
|
171
|
+
"image/jpeg": starts("\xff\xd8\xff"), "image/png": starts("\x89PNG\r\n\x1a\n"), "image/gif": starts("GIF87a", "GIF89a"),
|
|
172
|
+
"image/webp": data => data.subarray(0, 4).toString("latin1") === "RIFF" && data.subarray(8, 12).toString("latin1") === "WEBP", "application/pdf": starts("%PDF-"),
|
|
173
|
+
};
|
|
174
|
+
const extensions: Record<string, string> = { "image/jpeg": ".jpg", "image/png": ".png", "image/gif": ".gif", "image/webp": ".webp", "image/avif": ".avif", "image/heic": ".heic", "application/pdf": ".pdf", "text/plain": ".txt", "text/csv": ".csv", "application/json": ".json", "application/zip": ".zip", "video/mp4": ".mp4", "audio/mpeg": ".mp3" };
|
|
175
|
+
const newToken = (): string => randomBytes(18).toString("base64url") + "." + randomBytes(12).toString("base64url");
|
|
159
176
|
// Its notifications: recipients and badges a call, text, quotas.
|
|
160
177
|
const maxRecipients = 500, maxTitle = 80, maxText = 280, maxPath = 512, maxCount = 9999;
|
|
161
178
|
const recipientsPerHour = 1000, itemsPerDay = 100, badgesPerMinute = 600;
|
|
@@ -216,12 +233,12 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
216
233
|
const receives = options.receives ?? ["member.*"];
|
|
217
234
|
// The erasures the tool was told of, by emit: those it may acknowledge.
|
|
218
235
|
const erasures = new Set<string>();
|
|
219
|
-
const chest: FakeChest = { api: "", token, tool, members: [...(options.members ?? [])], groups: [...(options.groups ?? [])], files, notifications: [], badges: new Map(), acknowledged: [], ai: [], emit: async () => 0, close: async () => {} };
|
|
236
|
+
const chest: FakeChest = { api: "", token, tool, members: [...(options.members ?? [])], groups: [...(options.groups ?? [])], files, notifications: [], badges: new Map(), acknowledged: [], ai: [], emit: async () => 0, run: async () => 0, close: async () => {} };
|
|
220
237
|
const former = [...(options.former ?? [])];
|
|
221
238
|
let window = 0, calls = 0;
|
|
222
239
|
const shown = (m: Member) => ({ id: m.id, first_name: m.firstName, last_name: m.lastName, name: m.name, photo: m.photo, role: m.role, admin: m.isAdmin, builder: m.isBuilder, groups: m.groups, language: m.language, time_zone: m.timeZone, ...(email && m.email !== undefined ? { email: m.email } : {}) });
|
|
223
240
|
const key = (m: Member) => fold(m.name) + "\u0000" + m.id;
|
|
224
|
-
const described = (name: string, f: FakeFile) => ({ name, type: f.type, size: f.data.byteLength, updated: f.updated });
|
|
241
|
+
const described = (name: string, f: FakeFile) => ({ name, type: f.type, size: f.data.byteLength, sha256: createHash("sha256").update(f.data).digest("hex"), updated: f.updated });
|
|
225
242
|
|
|
226
243
|
async function members(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {
|
|
227
244
|
if (!capabilities.has("members")) return send(response, 403, { error: "capability_not_granted" });
|
|
@@ -252,7 +269,8 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
252
269
|
for (const id of new Set(ids as string[])) {
|
|
253
270
|
const m = chest.members.find(x => x.id === id), f = former.find(x => x.id === id);
|
|
254
271
|
if (m) answer.members.push(shown(m));
|
|
255
|
-
else if (f?.erased) answer.former.push({ id, status: "erased" });
|
|
272
|
+
else if (f?.status === "erased") answer.former.push({ id, status: "erased" });
|
|
273
|
+
else if (f?.status === "no_access") answer.former.push({ id, name: f.name ?? "Test member", status: "no_access" });
|
|
256
274
|
else if (f) answer.former.push({ id, ...(f.name ? { name: f.name } : {}), status: "former" });
|
|
257
275
|
else answer.unknown.push(id);
|
|
258
276
|
}
|
|
@@ -275,23 +293,30 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
275
293
|
const names = [...files.keys()].filter(n => n.startsWith(prefix) && n > after).sort();
|
|
276
294
|
return send(response, 200, { files: names.slice(0, 1000).map(n => described(n, files.get(n)!)), next: names.length > 1000 ? names[999] : null });
|
|
277
295
|
}
|
|
278
|
-
if (request.method === "POST" && url.pathname === "/files/url") {
|
|
279
|
-
const raw = await body(request, 4096);
|
|
280
|
-
let name: unknown;
|
|
281
|
-
try { name = (JSON.parse(raw?.toString() ?? "") as { name?: unknown }).name; } catch { name = undefined; }
|
|
282
|
-
if (typeof name !== "string") return send(response, 400, { error: "invalid_body" });
|
|
283
|
-
if (!files.has(name)) return send(response, 404, { error: "not_found" });
|
|
284
|
-
return send(response, 200, { url: `https://${tool}-chest.chest.test/_chest/files/${Buffer.from(name).toString("base64url")}.fake`, expires_in: 900 });
|
|
285
|
-
}
|
|
286
|
-
if (request.method === "POST" && (url.pathname === "/files/move" || url.pathname === "/files/upload-url")) {
|
|
296
|
+
if (request.method === "POST" && (url.pathname === "/files/url" || url.pathname === "/files/move" || url.pathname === "/files/upload-url")) {
|
|
287
297
|
const raw = await body(request, 4096);
|
|
288
298
|
let command: Record<string, unknown> = {};
|
|
289
299
|
try { command = JSON.parse(raw?.toString() ?? "") as Record<string, unknown>; } catch { command = {}; }
|
|
300
|
+
if (url.pathname === "/files/url") {
|
|
301
|
+
const { name, thumbnail, download } = command;
|
|
302
|
+
if (typeof name !== "string" || !(thumbnail === undefined || thumbnail === 256 || thumbnail === 1024) || !(download === undefined || typeof download === "boolean")) return send(response, 400, { error: "invalid_body" });
|
|
303
|
+
const object = files.get(name);
|
|
304
|
+
if (!object) return send(response, 404, { error: "not_found" });
|
|
305
|
+
if (thumbnail !== undefined && !thumbnailed.includes(object.type)) return send(response, 400, { error: "no_thumbnail" });
|
|
306
|
+
const token = newToken();
|
|
307
|
+
links.set(token, { name, object, until: Date.now() + linkLife * 1000, download: download === true });
|
|
308
|
+
return send(response, 200, { url: `${chest.api}${linkPath}${token}`, expires_in: linkLife });
|
|
309
|
+
}
|
|
290
310
|
if (url.pathname === "/files/upload-url") {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if (typeof
|
|
294
|
-
|
|
311
|
+
const { name, max_size: maxSize, types, expires_in: expiresIn } = command;
|
|
312
|
+
const folder = typeof name === "string" && name.endsWith("/");
|
|
313
|
+
if (typeof name !== "string" || !namePattern.test(folder ? name.slice(0, -1) : name)) return send(response, 400, { error: "invalid_name" });
|
|
314
|
+
if (!(types === undefined || (Array.isArray(types) && types.length <= 8 && types.every(t => typeof t === "string" && mediaPattern.test(t))))) return send(response, 400, { error: "invalid_type" });
|
|
315
|
+
if (!(maxSize === undefined || (typeof maxSize === "number" && Number.isSafeInteger(maxSize) && maxSize > 0)) || !(expiresIn === undefined || (typeof expiresIn === "number" && Number.isInteger(expiresIn) && expiresIn > 0 && expiresIn <= uploadLife))) return send(response, 400, { error: "invalid_body" });
|
|
316
|
+
if ((maxSize ?? 0) > maxObject) return send(response, 413, { error: "too_large" });
|
|
317
|
+
const token = newToken(), life = (expiresIn as number | undefined) ?? uploadLife;
|
|
318
|
+
uploads.set(token, { name, maxSize: (maxSize as number | undefined) ?? maxObject, types: (types as string[] | undefined) ?? [], until: Date.now() + life * 1000 });
|
|
319
|
+
return send(response, 200, { url: `${chest.api}${uploadPath}${token}`, method: "PUT", expires_in: life });
|
|
295
320
|
}
|
|
296
321
|
const from = command["from"], to = command["to"];
|
|
297
322
|
if (typeof from !== "string" || typeof to !== "string" || !namePattern.test(from) || !namePattern.test(to)) return send(response, 400, { error: "invalid_name" });
|
|
@@ -323,6 +348,39 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
323
348
|
send(response, 404, { error: "not_found" });
|
|
324
349
|
}
|
|
325
350
|
|
|
351
|
+
// The team host's part of the files, on the fake's own origin: a link it
|
|
352
|
+
// signed opens the content it was signed for, for its life, as long as
|
|
353
|
+
// the file is that content — a thumbnail is the image itself, a fake does
|
|
354
|
+
// not reduce it —; an upload token takes one file, once, within its life,
|
|
355
|
+
// of the types it names and up to its size, whose first bytes are those of
|
|
356
|
+
// its type, named by the Chest in a folder. Nobody's session is checked:
|
|
357
|
+
// the Chest's own front does that.
|
|
358
|
+
const links = new Map<string, { name: string; object: FakeFile; until: number; download: boolean }>();
|
|
359
|
+
const uploads = new Map<string, { name: string; maxSize: number; types: string[]; until: number }>();
|
|
360
|
+
async function front(request: IncomingMessage, response: ServerResponse, url: URL): Promise<void> {
|
|
361
|
+
if (request.method === "GET" && url.pathname.startsWith(linkPath) && !url.pathname.startsWith(uploadPath)) {
|
|
362
|
+
const link = links.get(url.pathname.slice(linkPath.length));
|
|
363
|
+
if (!link || link.until < Date.now() || files.get(link.name) !== link.object) return send(response, 404, { error: "not_found" });
|
|
364
|
+
const headers: Record<string, string> = { "Content-Type": link.object.type, "Content-Length": String(link.object.data.byteLength), "X-Content-Type-Options": "nosniff" };
|
|
365
|
+
if (link.download) headers["Content-Disposition"] = `attachment; filename="${link.name.split("/").pop()}"`;
|
|
366
|
+
return void response.writeHead(200, headers).end(link.object.data);
|
|
367
|
+
}
|
|
368
|
+
if (request.method !== "PUT" || !url.pathname.startsWith(uploadPath)) return send(response, 404, { error: "not_found" });
|
|
369
|
+
const token = url.pathname.slice(uploadPath.length), grant = uploads.get(token);
|
|
370
|
+
uploads.delete(token);
|
|
371
|
+
if (!grant || grant.until < Date.now()) return send(response, 403, { error: "invalid_token" });
|
|
372
|
+
const type = (request.headers["content-type"] ?? "").split(";")[0]!.trim().toLowerCase();
|
|
373
|
+
if (!mediaPattern.test(type) || type.endsWith("/*") || (grant.types.length > 0 && !grant.types.some(t => t === type || (t.endsWith("/*") && type.startsWith(t.slice(0, -1)))))) return send(response, 415, { error: "type_refused" });
|
|
374
|
+
const data = await body(request, grant.maxSize);
|
|
375
|
+
if (data === null) return send(response, 413, { error: "too_large" });
|
|
376
|
+
if (signatures[type] && !signatures[type]!(data)) return send(response, 400, { error: "type_mismatch" });
|
|
377
|
+
const name = grant.name.endsWith("/") ? grant.name + randomBytes(10).toString("hex") + (extensions[type] ?? "") : grant.name;
|
|
378
|
+
const total = [...files.entries()].reduce((sum, [n, f]) => n === name ? sum : sum + f.data.byteLength, 0);
|
|
379
|
+
if (total + data.length > maxTotal || (!files.has(name) && files.size >= maxObjects)) return send(response, 429, { error: "quota_exceeded" });
|
|
380
|
+
files.set(name, { data: new Uint8Array(data), type, updated: new Date().toISOString() });
|
|
381
|
+
send(response, 201, { name, type, size: data.length });
|
|
382
|
+
}
|
|
383
|
+
|
|
326
384
|
// The windows of the notification quotas, each from the first call it
|
|
327
385
|
// counts: the tool's recipients this hour, each member's items this day,
|
|
328
386
|
// the tool's badge writes this minute.
|
|
@@ -529,7 +587,8 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
529
587
|
|
|
530
588
|
const server = createServer((request, response) => {
|
|
531
589
|
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
532
|
-
const route = url.pathname.startsWith("/
|
|
590
|
+
const route = url.pathname.startsWith("/_chest/files/") ? front
|
|
591
|
+
: url.pathname.startsWith("/erasures/") ? erasuresRoute
|
|
533
592
|
: url.pathname.startsWith("/ai/") ? aiRoute
|
|
534
593
|
: url.pathname === "/members" || url.pathname.startsWith("/members/") || url.pathname === "/groups" ? members
|
|
535
594
|
: url.pathname === "/files" || url.pathname.startsWith("/files/") ? filesRoute
|
|
@@ -538,19 +597,36 @@ export async function fakeChest(options: FakeChestOptions = {}): Promise<FakeChe
|
|
|
538
597
|
route(request, response, url).catch(() => { if (!response.headersSent) send(response, 503, { error: "unavailable" }); else response.destroy(); });
|
|
539
598
|
});
|
|
540
599
|
await new Promise<void>(resolve => server.listen(0, "127.0.0.1", resolve));
|
|
541
|
-
const saved = Object.fromEntries(["CHEST_API", "CHEST_TOKEN", "CHEST_TOOL", "CHEST_ORGANIZATION", "CHEST_TIME_ZONE", "CHEST_LANGUAGE"].map(name => [name, process.env[name]]));
|
|
600
|
+
const saved = Object.fromEntries(["CHEST_API", "CHEST_TOKEN", "CHEST_TOOL", "CHEST_ORGANIZATION", "CHEST_TIME_ZONE", "CHEST_LANGUAGE", "CHEST_CURRENCY", "CHEST_TEAM_URL", "CHEST_PUBLIC_URL"].map(name => [name, process.env[name]]));
|
|
542
601
|
chest.api = "http://127.0.0.1:" + (server.address() as AddressInfo).port;
|
|
543
|
-
|
|
602
|
+
// Its links are on its own origin, the CHEST_API it sets: the files
|
|
603
|
+
// module takes them there (api.ts, chestLink), in this process or in a
|
|
604
|
+
// tool it starts with this environment.
|
|
605
|
+
const publicUrl = options.chest?.publicUrl === undefined ? `https://${tool}.chest.test` : options.chest.publicUrl;
|
|
606
|
+
Object.assign(process.env, { CHEST_API: chest.api, CHEST_TOKEN: token, CHEST_TOOL: tool, CHEST_ORGANIZATION: options.chest?.organization ?? "Test organization", CHEST_TIME_ZONE: options.chest?.timeZone ?? "UTC", CHEST_LANGUAGE: options.chest?.language ?? "en", CHEST_CURRENCY: options.chest?.currency ?? "EUR", CHEST_TEAM_URL: options.chest?.teamUrl ?? `https://${tool}-chest.chest.test` });
|
|
607
|
+
if (publicUrl === null) delete process.env["CHEST_PUBLIC_URL"];
|
|
608
|
+
else process.env["CHEST_PUBLIC_URL"] = publicUrl;
|
|
544
609
|
forget();
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const request = new Request(typeof to === "string" ? to.replace(/\/$/u, "") + "/chest-events" : "http://tool.test/chest-events", { method: "POST", headers: { "Content-Type": "application/json", "Chest-Event": signEvent(id, body, { token, tool }) }, body });
|
|
610
|
+
// post delivers what the Chest posts to the tool on a channel — at its
|
|
611
|
+
// address, or to a handler — and says the status it answered.
|
|
612
|
+
const post = async (path: string, channel: Channel, id: string, body: string, to: string | ((request: Request) => Response | Promise<Response>)): Promise<number> => {
|
|
613
|
+
const request = new Request((typeof to === "string" ? to.replace(/\/$/u, "") : "http://tool.test") + path, { method: "POST", headers: { "Content-Type": "application/json", [channel.header]: sign(channel, id, body, { token, tool }) }, body });
|
|
550
614
|
const answer = typeof to === "string" ? await fetch(request, { redirect: "manual" }) : await to(request);
|
|
551
615
|
await answer.body?.cancel();
|
|
552
616
|
return answer.status;
|
|
553
617
|
};
|
|
618
|
+
const newId = (prefix: string): string => prefix + Array.from(randomBytes(26), b => "abcdefghijklmnopqrstuvwxyz234567"[b & 31]).join("");
|
|
619
|
+
chest.emit = async (event, to) => {
|
|
620
|
+
const id = event.id ?? newId("evt_");
|
|
621
|
+
const body = JSON.stringify({ id, type: event.type, occurredAt: event.occurredAt ?? new Date().toISOString(), data: event.data });
|
|
622
|
+
if (event.type === "member.erased") erasures.add(event.data.erasure);
|
|
623
|
+
return post("/chest-events", eventChannel, id, body, to);
|
|
624
|
+
};
|
|
625
|
+
chest.run = async (name, to, run = {}) => {
|
|
626
|
+
const id = run.id ?? newId("run_");
|
|
627
|
+
const body = JSON.stringify({ id, name, scheduledAt: run.scheduledAt ?? new Date().toISOString(), attempt: run.attempt ?? 1 });
|
|
628
|
+
return post("/chest-schedules", scheduleChannel, id, body, to);
|
|
629
|
+
};
|
|
554
630
|
chest.close = async () => {
|
|
555
631
|
server.closeAllConnections();
|
|
556
632
|
await new Promise<void>(resolve => server.close(() => resolve()));
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,6 @@ export * as files from "./src/files.js";
|
|
|
6
6
|
export * as members from "./src/members.js";
|
|
7
7
|
export * as notifications from "./src/notifications.js";
|
|
8
8
|
export * as events from "./src/events.js";
|
|
9
|
+
export * as schedules from "./src/schedules.js";
|
|
9
10
|
export * as ai from "./src/ai.js";
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAOA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAOA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The package root: every published module a tool's code uses (tool contract
|
|
2
|
-
//
|
|
3
|
-
// /database, /files, /members, /notifications, /events, /ai, /errors), which
|
|
4
|
-
// pulls in nothing else. The files, members, notifications, events and ai
|
|
2
|
+
// 0.4). Each one is also its own subpath (@argentic/chest-sdk/member, /chest,
|
|
3
|
+
// /database, /files, /members, /notifications, /events, /schedules, /ai, /errors), which
|
|
4
|
+
// pulls in nothing else. The files, members, notifications, events, schedules and ai
|
|
5
5
|
// APIs are namespaces here, as their names (get, list, stat, move, notify,
|
|
6
6
|
// verify, chat…) are too plain to stand alone. @argentic/chest-sdk/testing is for a tool's tests only, and
|
|
7
7
|
// is not here.
|
|
@@ -13,5 +13,6 @@ export * as files from "./src/files.js";
|
|
|
13
13
|
export * as members from "./src/members.js";
|
|
14
14
|
export * as notifications from "./src/notifications.js";
|
|
15
15
|
export * as events from "./src/events.js";
|
|
16
|
+
export * as schedules from "./src/schedules.js";
|
|
16
17
|
export * as ai from "./src/ai.js";
|
|
17
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../client/index.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,yFAAyF;AACzF,qFAAqF;AACrF,2EAA2E;AAC3E,2GAA2G;AAC3G,eAAe;AACf,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AACxC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,aAAa,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,MAAM,MAAM,iBAAiB,CAAC;AAC1C,OAAO,KAAK,SAAS,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/src/api.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ChestError } from "./errors.js";
|
|
2
|
+
export declare function chestLink(url: unknown, path: string): string | undefined;
|
|
2
3
|
export declare function ask(capability: string, method: string, path: string, init?: {
|
|
3
4
|
body?: Uint8Array<ArrayBuffer> | string;
|
|
4
5
|
type?: string;
|
package/dist/src/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,UAAU,EAAqD,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,UAAU,EAAqD,MAAM,aAAa,CAAC;AAgBlH,wBAAgB,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKxE;AAcD,wBAAsB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAW7M;AAID,wBAAsB,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAqBjF;AAGD,wBAAsB,IAAI,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAM/D;AAGD,wBAAsB,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAazF"}
|
package/dist/src/api.js
CHANGED
|
@@ -6,6 +6,19 @@ import { CapabilityNotGranted, ChestError, QuotaExceeded, RateLimited, TooLarge,
|
|
|
6
6
|
// identity. Not a published module.
|
|
7
7
|
const maxAnswer = 4 << 20;
|
|
8
8
|
const deadline = 120000;
|
|
9
|
+
// chestLink reads a link to the team host the Chest answered, at path (its
|
|
10
|
+
// links, its uploads): the token it carries, or undefined for an address
|
|
11
|
+
// that is not one — https, or the origin of the Chest's API itself
|
|
12
|
+
// (CHEST_API), where only a fake Chest of a tool's tests serves its links:
|
|
13
|
+
// the address the tool already trusts for every call, never another.
|
|
14
|
+
export function chestLink(url, path) {
|
|
15
|
+
if (typeof url !== "string")
|
|
16
|
+
return undefined;
|
|
17
|
+
const found = /^(https:\/\/[A-Za-z0-9.-]{1,253}(?::[0-9]{1,5})?|http:\/\/127\.0\.0\.1:[0-9]{1,5})(\/_chest\/[a-z/]+\/)([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/u.exec(url);
|
|
18
|
+
if (!found || found[2] !== path || (found[1].startsWith("http:") && found[1] !== process.env["CHEST_API"]))
|
|
19
|
+
return undefined;
|
|
20
|
+
return found[3];
|
|
21
|
+
}
|
|
9
22
|
// base is the Chest's API as the launcher gives it; without, the version holds
|
|
10
23
|
// none of the capabilities that use it.
|
|
11
24
|
function base(capability) {
|
package/dist/src/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../client/src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/src/chest.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type Chest = {
|
|
|
4
4
|
};
|
|
5
5
|
readonly timeZone: string;
|
|
6
6
|
readonly language: string;
|
|
7
|
+
readonly currency: string;
|
|
8
|
+
readonly tool: {
|
|
9
|
+
readonly teamUrl: string;
|
|
10
|
+
readonly publicUrl: string | null;
|
|
11
|
+
};
|
|
7
12
|
today(at?: Date | number): string;
|
|
8
13
|
};
|
|
9
14
|
export declare const chest: Chest;
|
package/dist/src/chest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chest.d.ts","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chest.d.ts","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"AAoCA,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,YAAY,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC/E,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;CACnC,CAAC;AAqCF,eAAO,MAAM,KAAK,EAAE,KAuBnB,CAAC"}
|
package/dist/src/chest.js
CHANGED
|
@@ -2,8 +2,12 @@ import { ChestError } from "./errors.js";
|
|
|
2
2
|
import { languagePattern, timeZonePattern } from "./member.js";
|
|
3
3
|
// The shapes the Chest gives: the organization's (2 to 80 characters,
|
|
4
4
|
// counted as code points, without control characters), a zone's
|
|
5
|
-
// (timeZonePattern, and one this runtime knows), a language's
|
|
5
|
+
// (timeZonePattern, and one this runtime knows), a language's, a currency's,
|
|
6
|
+
// an origin's.
|
|
6
7
|
const organizationPattern = /^[^\u0000-\u001f\u007f-\u009f]{2,80}$/u;
|
|
8
|
+
// An ISO 4217 code; an https origin without a path, as the Chest gives them.
|
|
9
|
+
const currencyPattern = /^[A-Z]{3}$/u;
|
|
10
|
+
const originPattern = /^https:\/\/[a-z0-9]([a-z0-9.-]{0,251}[a-z0-9])?(:[0-9]{1,5})?$/u;
|
|
7
11
|
function read(name, valid) {
|
|
8
12
|
const value = process.env[name];
|
|
9
13
|
if (typeof value !== "string" || !valid(value))
|
|
@@ -39,6 +43,14 @@ export const chest = {
|
|
|
39
43
|
get language() {
|
|
40
44
|
return read("CHEST_LANGUAGE", value => languagePattern.test(value));
|
|
41
45
|
},
|
|
46
|
+
get currency() {
|
|
47
|
+
return read("CHEST_CURRENCY", value => currencyPattern.test(value));
|
|
48
|
+
},
|
|
49
|
+
get tool() {
|
|
50
|
+
const teamUrl = read("CHEST_TEAM_URL", value => originPattern.test(value));
|
|
51
|
+
const publicUrl = process.env["CHEST_PUBLIC_URL"] === undefined ? null : read("CHEST_PUBLIC_URL", value => originPattern.test(value));
|
|
52
|
+
return { teamUrl, publicUrl };
|
|
53
|
+
},
|
|
42
54
|
today(at = Date.now()) {
|
|
43
55
|
const instant = typeof at === "number" ? new Date(at) : at;
|
|
44
56
|
if (Number.isNaN(instant.getTime()))
|
package/dist/src/chest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chest.js","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"chest.js","sourceRoot":"","sources":["../../client/src/chest.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../client/src/database.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../client/src/database.ts"],"names":[],"mappings":"AAkBA,wBAAgB,WAAW,IAAI,MAAM,CAcpC"}
|
package/dist/src/database.js
CHANGED
|
@@ -7,9 +7,13 @@ import { CapabilityNotGranted } from "./errors.js";
|
|
|
7
7
|
// postgres (porsager) or pg; the SDK carries none. PGHOST, PGPORT, PGUSER,
|
|
8
8
|
// PGPASSWORD and PGDATABASE say the same for a client that reads them.
|
|
9
9
|
//
|
|
10
|
+
// Its user is the tool's role (t_<tool>), or, in the preview of a draft
|
|
11
|
+
// Perseus Code builds, the draft's (pb_<project>: its own empty database).
|
|
12
|
+
//
|
|
10
13
|
// Throws CapabilityNotGranted when the Chest gave no database: the version
|
|
11
14
|
// does not declare it, or a DATABASE_URL of the tool's own is not the
|
|
12
15
|
// Chest's. The value is a secret: never log it, never send it to a browser.
|
|
16
|
+
const role = /^(t_[a-z][a-z0-9_]{0,47}|pb_[a-z2-7]{26})$/u;
|
|
13
17
|
export function databaseUrl() {
|
|
14
18
|
const value = process.env["DATABASE_URL"];
|
|
15
19
|
if (typeof value !== "string" || value.length > 1024)
|
|
@@ -22,7 +26,7 @@ export function databaseUrl() {
|
|
|
22
26
|
throw new CapabilityNotGranted("database");
|
|
23
27
|
}
|
|
24
28
|
const port = Number(url.port);
|
|
25
|
-
if (url.protocol !== "postgres:" || url.hostname !== "127.0.0.1" || !Number.isInteger(port) || port < 1 || port > 65535 ||
|
|
29
|
+
if (url.protocol !== "postgres:" || url.hostname !== "127.0.0.1" || !Number.isInteger(port) || port < 1 || port > 65535 || !role.test(url.username) || url.pathname !== "/" + url.username || url.password === "" || url.search !== "?sslmode=disable" || url.hash !== "") {
|
|
26
30
|
throw new CapabilityNotGranted("database");
|
|
27
31
|
}
|
|
28
32
|
return value;
|
package/dist/src/database.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../client/src/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,2EAA2E;AAC3E,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,uEAAuE;AACvE,EAAE;AACF,2EAA2E;AAC3E,sEAAsE;AACtE,4EAA4E;AAC5E,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;QAAE,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjG,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../client/src/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,2EAA2E;AAC3E,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,uEAAuE;AACvE,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,EAAE;AACF,2EAA2E;AAC3E,sEAAsE;AACtE,4EAA4E;AAC5E,MAAM,IAAI,GAAG,6CAA6C,CAAC;AAE3D,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI;QAAE,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjG,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,GAAG,CAAC,QAAQ,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,KAAK,GAAG,GAAG,GAAG,CAAC,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,kBAAkB,IAAI,GAAG,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;QAC1Q,MAAM,IAAI,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/dist/src/events.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
|
-
|
|
2
|
+
import { type Seen } from "./signed.js";
|
|
3
|
+
export type MemberChange = "name" | "photo" | "role" | "groups" | "email" | "language" | "timeZone";
|
|
3
4
|
export type MemberUpdated = {
|
|
4
5
|
id: string;
|
|
5
6
|
type: "member.updated";
|
|
@@ -42,11 +43,7 @@ export type Handlers = {
|
|
|
42
43
|
type: K;
|
|
43
44
|
}>) => void | Promise<void>;
|
|
44
45
|
};
|
|
45
|
-
export type Seen
|
|
46
|
-
has(id: string): boolean | Promise<boolean>;
|
|
47
|
-
add(id: string): void | Promise<void>;
|
|
48
|
-
};
|
|
49
|
-
export declare function memorySeen(limit?: number): Seen;
|
|
46
|
+
export { memorySeen, type Seen } from "./signed.js";
|
|
50
47
|
export declare const erasureIdPattern: RegExp;
|
|
51
48
|
export declare function verify(request: IncomingMessage | Request): Promise<ChestEvent | null>;
|
|
52
49
|
export declare function handle(request: IncomingMessage | Request, handlers: Handlers, options?: {
|
package/dist/src/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../client/src/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../client/src/events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAKjD,OAAO,EAA6D,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AA0BnG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;AAEpG,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,YAAY,EAAE,CAAA;KAAE,CAAA;CAAE,CAAC;AAEtI,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE7G,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAG7G,MAAM,MAAM,YAAY,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,eAAe,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC;AAE9I,MAAM,MAAM,UAAU,GAAG,aAAa,GAAG,aAAa,GAAG,aAAa,GAAG,YAAY,CAAC;AACtF,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAGhD,MAAM,MAAM,QAAQ,GAAG;KAAG,CAAC,IAAI,cAAc,CAAC,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;CAAE,CAAC;AAIrH,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAGpD,eAAO,MAAM,gBAAgB,QAAwB,CAAC;AAsCtD,wBAAsB,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAG3F;AAWD,wBAAsB,MAAM,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,IAAI,CAAA;CAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAanI;AAQD,wBAAsB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASvE"}
|