@remit/backend 0.0.143 → 0.0.145
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/dist/dev-server/content-handler.d.ts +38 -0
- package/dist/dev-server/content-handler.js +46 -0
- package/dist/src/api.d.ts +5 -0
- package/dist/src/api.js +73 -0
- package/dist/src/auth.d.ts +24 -0
- package/dist/src/auth.js +81 -0
- package/dist/src/config/api-config.d.ts +20 -0
- package/dist/src/config/api-config.js +1 -0
- package/dist/src/config/from-env.d.ts +2 -0
- package/dist/src/config/from-env.js +65 -0
- package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
- package/dist/src/config/msoauth-secrets-manager.js +28 -0
- package/dist/src/config/msoauth.d.ts +19 -0
- package/dist/src/config/msoauth.js +6 -0
- package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
- package/dist/src/derive/autoMoved.js +49 -0
- package/dist/src/derive/contentSignature.d.ts +40 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +23 -77
- package/dist/src/derive/contentUrl.d.ts +33 -0
- package/dist/src/derive/contentUrl.js +27 -0
- package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
- package/dist/src/derive/deriveMuted.js +7 -0
- package/dist/src/derive/enrichThreadRows.d.ts +118 -0
- package/dist/src/derive/enrichThreadRows.js +151 -0
- package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
- package/dist/src/derive/filterThreadCriteria.js +24 -0
- package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
- package/dist/src/derive/pendingMoveCounts.js +53 -0
- package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
- package/dist/src/derive/senderTrust.js +19 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/error.js +85 -0
- package/dist/src/handlers/account-guards.d.ts +36 -0
- package/dist/src/handlers/account-guards.js +107 -0
- package/dist/src/handlers/account-oauth.d.ts +15 -0
- package/dist/src/handlers/account-oauth.js +334 -0
- package/dist/src/handlers/account-overrides.d.ts +53 -0
- package/dist/src/handlers/account-overrides.js +233 -0
- package/dist/src/handlers/account-signature.d.ts +28 -0
- package/dist/src/handlers/account-signature.js +85 -0
- package/dist/src/handlers/account.d.ts +7 -0
- package/dist/src/handlers/account.js +337 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +133 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +417 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +184 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +204 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +323 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +164 -0
- package/dist/src/handlers/ensure-account-config.d.ts +9 -0
- package/dist/src/handlers/ensure-account-config.js +27 -0
- package/dist/src/handlers/filter.d.ts +3 -0
- package/dist/src/handlers/filter.js +88 -0
- package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
- package/dist/src/handlers/folder-role-appointments.js +168 -0
- package/dist/src/handlers/folder-role-labels.d.ts +26 -0
- package/dist/src/handlers/folder-role-labels.js +40 -0
- package/dist/src/handlers/folder-role.d.ts +9 -0
- package/dist/src/handlers/folder-role.js +37 -0
- package/dist/src/handlers/index.d.ts +88 -0
- package/dist/src/handlers/index.js +62 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +107 -0
- package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
- package/dist/src/handlers/mailbox-bindings.js +61 -0
- package/dist/src/handlers/mailbox.d.ts +23 -0
- package/dist/src/handlers/mailbox.js +211 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +117 -0
- package/dist/src/handlers/message.d.ts +82 -0
- package/dist/src/handlers/message.js +595 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +103 -0
- package/dist/src/handlers/outbox-attachment.d.ts +25 -0
- package/dist/src/handlers/outbox-attachment.js +77 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +136 -0
- package/dist/src/handlers/sync-progress.d.ts +46 -0
- package/dist/src/handlers/sync-progress.js +63 -0
- package/dist/src/handlers/sync.d.ts +28 -0
- package/dist/src/handlers/sync.js +73 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +159 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +228 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +406 -0
- package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
- package/dist/src/handlers/vip-suggestions.js +16 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/index.js +53 -0
- package/{src/json.ts → dist/src/json.d.ts} +1 -4
- package/dist/src/json.js +10 -0
- package/dist/src/jwt-auth.d.ts +15 -0
- package/dist/src/jwt-auth.js +45 -0
- package/dist/src/lambda.d.ts +13 -0
- package/dist/src/lambda.js +33 -0
- package/dist/src/request.d.ts +9 -0
- package/dist/src/request.js +17 -0
- package/dist/src/response.d.ts +44 -0
- package/dist/src/response.js +80 -0
- package/dist/src/serve.d.ts +31 -0
- package/dist/src/serve.js +117 -0
- package/dist/src/service/account-deletion.d.ts +20 -0
- package/dist/src/service/account-deletion.js +33 -0
- package/dist/src/service/compose-sqlite.d.ts +3 -0
- package/dist/src/service/compose-sqlite.js +13 -0
- package/dist/src/service/create-remit-client.d.ts +109 -0
- package/dist/src/service/create-remit-client.js +188 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +42 -0
- package/dist/src/service/export-identity.d.ts +4 -0
- package/dist/src/service/export-identity.js +33 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +37 -0
- package/dist/src/service/organize.d.ts +181 -0
- package/dist/src/service/organize.js +367 -0
- package/dist/src/service/queues.d.ts +12 -0
- package/dist/src/service/queues.js +1 -0
- package/dist/src/service/refusal.d.ts +4 -0
- package/dist/src/service/refusal.js +20 -0
- package/dist/src/service/sender-category.d.ts +51 -0
- package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
- package/dist/src/service/shared-deps.d.ts +6 -0
- package/dist/src/service/shared-deps.js +24 -0
- package/dist/src/service/trigger-sync.d.ts +57 -0
- package/dist/src/service/trigger-sync.js +31 -0
- package/dist/src/types.d.ts +72 -0
- package/dist/src/types.js +1 -0
- package/package.json +125 -98
- package/.env.test +0 -4
- package/dev-server/content-auth.test.ts +0 -113
- package/dev-server/content-auth.ts +0 -54
- package/dev-server/content-handler.test.ts +0 -105
- package/dev-server/content-handler.ts +0 -79
- package/dev-server/content-path.test.ts +0 -37
- package/dev-server/content-path.ts +0 -27
- package/dev-server/cors.test.ts +0 -48
- package/dev-server/cors.ts +0 -25
- package/dev-server/keep-alive.test.ts +0 -24
- package/dev-server/lambda-helpers.ts +0 -69
- package/dev-server/log-output.test.ts +0 -173
- package/dev-server/oauth-callback-route.test.ts +0 -66
- package/dev-server/relational-health.test.ts +0 -83
- package/dev-server/relational-health.ts +0 -39
- package/dev-server/server.ts +0 -384
- package/dev-server/sync-age.test.ts +0 -91
- package/dev-server/sync-age.ts +0 -55
- package/dev-server/upload-handler.ts +0 -132
- package/dev-server/upload-route.test.ts +0 -196
- package/scripts/backfill-classification.ts +0 -91
- package/scripts/backfill-list-id.ts +0 -83
- package/scripts/config-save.ts +0 -128
- package/src/auth.test.ts +0 -12
- package/src/auth.ts +0 -92
- package/src/config/msoauth.test.ts +0 -48
- package/src/config/msoauth.ts +0 -48
- package/src/data-backend.test.ts +0 -23
- package/src/data-backend.ts +0 -14
- package/src/derive/autoMoved.test.ts +0 -104
- package/src/derive/contentSignature.test.ts +0 -161
- package/src/derive/contentUrl.test.ts +0 -172
- package/src/derive/contentUrl.ts +0 -86
- package/src/derive/deriveMuted.test.ts +0 -34
- package/src/derive/enrichThreadRows.test.ts +0 -360
- package/src/derive/enrichThreadRows.ts +0 -234
- package/src/derive/filterThreadCriteria.test.ts +0 -126
- package/src/derive/filterThreadCriteria.ts +0 -51
- package/src/derive/senderTrust.test.ts +0 -67
- package/src/error.test.ts +0 -201
- package/src/error.ts +0 -123
- package/src/handlers/account-guards.test.ts +0 -67
- package/src/handlers/account-guards.ts +0 -169
- package/src/handlers/account-oauth-callback.test.ts +0 -155
- package/src/handlers/account-oauth-consent.test.ts +0 -336
- package/src/handlers/account-oauth-start.test.ts +0 -191
- package/src/handlers/account-oauth.test.ts +0 -439
- package/src/handlers/account-oauth.ts +0 -635
- package/src/handlers/account-overrides.test.ts +0 -88
- package/src/handlers/account-overrides.ts +0 -323
- package/src/handlers/account-ownership.ts +0 -25
- package/src/handlers/account-signature.ts +0 -117
- package/src/handlers/account-synced-services.test.ts +0 -245
- package/src/handlers/account.ts +0 -552
- package/src/handlers/address.test.ts +0 -381
- package/src/handlers/address.ts +0 -200
- package/src/handlers/calendar-event.test.ts +0 -685
- package/src/handlers/calendar-event.ts +0 -738
- package/src/handlers/calendar-feed-validators.test.ts +0 -228
- package/src/handlers/calendar-feed.test.ts +0 -439
- package/src/handlers/calendar-feed.ts +0 -291
- package/src/handlers/calendar-sqlite-fixture.ts +0 -36
- package/src/handlers/calendar-suggestion.test.ts +0 -1269
- package/src/handlers/calendar-suggestion.ts +0 -425
- package/src/handlers/calendar.test.ts +0 -1080
- package/src/handlers/calendar.ts +0 -517
- package/src/handlers/config.test.ts +0 -245
- package/src/handlers/config.ts +0 -332
- package/src/handlers/ensure-account-config.ts +0 -30
- package/src/handlers/filter.test.ts +0 -644
- package/src/handlers/filter.ts +0 -518
- package/src/handlers/folder-role-appointments.test.ts +0 -438
- package/src/handlers/folder-role-appointments.ts +0 -249
- package/src/handlers/folder-role-labels.ts +0 -63
- package/src/handlers/folder-role.test.ts +0 -209
- package/src/handlers/folder-role.ts +0 -80
- package/src/handlers/index.ts +0 -83
- package/src/handlers/label.test.ts +0 -112
- package/src/handlers/label.ts +0 -210
- package/src/handlers/mailbox-bindings.ts +0 -111
- package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
- package/src/handlers/mailbox-settled-target.test.ts +0 -251
- package/src/handlers/mailbox.test.ts +0 -280
- package/src/handlers/mailbox.ts +0 -581
- package/src/handlers/me.test.ts +0 -81
- package/src/handlers/me.ts +0 -203
- package/src/handlers/message-read-backfill.test.ts +0 -103
- package/src/handlers/message.test.ts +0 -131
- package/src/handlers/message.ts +0 -1131
- package/src/handlers/organize.test.ts +0 -277
- package/src/handlers/organize.ts +0 -197
- package/src/handlers/outbox-attachment.test.ts +0 -765
- package/src/handlers/outbox-attachment.ts +0 -126
- package/src/handlers/outbox-list.test.ts +0 -179
- package/src/handlers/outbox.test.ts +0 -772
- package/src/handlers/outbox.ts +0 -258
- package/src/handlers/search.test.ts +0 -161
- package/src/handlers/search.ts +0 -105
- package/src/handlers/sync-progress.test.ts +0 -205
- package/src/handlers/sync-progress.ts +0 -99
- package/src/handlers/sync.test.ts +0 -168
- package/src/handlers/sync.ts +0 -118
- package/src/handlers/system-update.test.ts +0 -497
- package/src/handlers/system-update.ts +0 -230
- package/src/handlers/thread.test.ts +0 -396
- package/src/handlers/thread.ts +0 -427
- package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
- package/src/handlers/unified-threads.test.ts +0 -785
- package/src/handlers/unified-threads.ts +0 -669
- package/src/index.test.ts +0 -83
- package/src/index.ts +0 -228
- package/src/jwt-auth.test.ts +0 -152
- package/src/jwt-auth.ts +0 -132
- package/src/log-redaction.test.ts +0 -144
- package/src/openapi-spec.test.ts +0 -118
- package/src/request-context.test.ts +0 -61
- package/src/request-context.ts +0 -33
- package/src/request-pipeline.test.ts +0 -88
- package/src/request.ts +0 -10
- package/src/response.test.ts +0 -130
- package/src/response.ts +0 -144
- package/src/service/compose-sqlite.ts +0 -98
- package/src/service/config-import.ts +0 -39
- package/src/service/create-remit-client.ts +0 -522
- package/src/service/data-client.test.ts +0 -97
- package/src/service/data-client.ts +0 -58
- package/src/service/export-identity.ts +0 -39
- package/src/service/filter.ts +0 -40
- package/src/service/fire-and-forget.test.ts +0 -126
- package/src/service/fire-and-forget.ts +0 -79
- package/src/service/localhost-env-config.test.ts +0 -36
- package/src/service/organize-queue.ts +0 -98
- package/src/service/organize.test.ts +0 -1451
- package/src/service/organize.ts +0 -1025
- package/src/service/read-path-filter-config.test.ts +0 -243
- package/src/service/semantic-capability.test.ts +0 -78
- package/src/service/semantic-capability.ts +0 -95
- package/src/service/sender-category.test.ts +0 -235
- package/src/service/sqs.ts +0 -4
- package/src/service/trigger-sync.test.ts +0 -205
- package/src/service/trigger-sync.ts +0 -100
- package/src/stdout-capture-fixture.ts +0 -40
- package/src/types.ts +0 -271
- package/tsconfig.json +0 -7
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { parseContentStorageKey } from "@remit/storage-service";
|
|
2
|
+
export { parseContentStorageKey };
|
|
3
|
+
export interface ContentResult {
|
|
4
|
+
status: number;
|
|
5
|
+
headers: Record<string, string>;
|
|
6
|
+
body: string | Buffer;
|
|
7
|
+
}
|
|
8
|
+
export interface ServeContentDeps {
|
|
9
|
+
/**
|
|
10
|
+
* Reads the stored bytes, or returns null when the object is genuinely
|
|
11
|
+
* missing (ENOENT / NoSuchKey). Any other read error must throw so the route
|
|
12
|
+
* 500s — a permission/infra failure is never masked as a missing body.
|
|
13
|
+
*/
|
|
14
|
+
readObject(fullPath: string): Promise<Buffer | null>;
|
|
15
|
+
/** Resolves the message's mailbox + uid so the cue can issue one FETCH. */
|
|
16
|
+
lookupMessage(messageId: string): Promise<{
|
|
17
|
+
mailboxId: string;
|
|
18
|
+
uid: number;
|
|
19
|
+
} | null>;
|
|
20
|
+
requestBodySync(input: {
|
|
21
|
+
accountId: string;
|
|
22
|
+
mailboxId: string;
|
|
23
|
+
messageId: string;
|
|
24
|
+
uid?: number;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Local stand-in for the CloudFront `/content/*` behavior. Body-fetch outcomes:
|
|
29
|
+
*
|
|
30
|
+
* - Object present → 200 with the (decompressed) bytes.
|
|
31
|
+
* - Object missing → 202 + `Retry-After: 1`, and re-arm the SYNC_MESSAGE_BODY
|
|
32
|
+
* cue for that message so a retry succeeds once the worker stores the body.
|
|
33
|
+
* - Any other read failure throws, so the route 500s loudly.
|
|
34
|
+
*/
|
|
35
|
+
export declare const serveContent: (deps: ServeContentDeps, args: {
|
|
36
|
+
fullPath: string;
|
|
37
|
+
storageKey: string;
|
|
38
|
+
}) => Promise<ContentResult>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { gunzipSync } from "node:zlib";
|
|
2
|
+
import { parseContentStorageKey } from "@remit/storage-service";
|
|
3
|
+
// Re-exported for existing same-package consumers (server.ts,
|
|
4
|
+
// content-handler.test.ts). The key-shape parser itself lives in
|
|
5
|
+
// `@remit/storage-service` next to the `build*Key` functions it
|
|
6
|
+
// inverts, so non-backend consumers (e.g. the content-selfheal worker) can
|
|
7
|
+
// depend on it without pulling in the rest of `@remit/backend`.
|
|
8
|
+
export { parseContentStorageKey };
|
|
9
|
+
/**
|
|
10
|
+
* Local stand-in for the CloudFront `/content/*` behavior. Body-fetch outcomes:
|
|
11
|
+
*
|
|
12
|
+
* - Object present → 200 with the (decompressed) bytes.
|
|
13
|
+
* - Object missing → 202 + `Retry-After: 1`, and re-arm the SYNC_MESSAGE_BODY
|
|
14
|
+
* cue for that message so a retry succeeds once the worker stores the body.
|
|
15
|
+
* - Any other read failure throws, so the route 500s loudly.
|
|
16
|
+
*/
|
|
17
|
+
export const serveContent = async (deps, args) => {
|
|
18
|
+
const raw = await deps.readObject(args.fullPath);
|
|
19
|
+
if (raw) {
|
|
20
|
+
// The filesystem/S3 backends gzip every part; decompress here since there
|
|
21
|
+
// is no CloudFront to auto-decode locally.
|
|
22
|
+
const buffer = raw[0] === 0x1f && raw[1] === 0x8b ? gunzipSync(raw) : raw;
|
|
23
|
+
return {
|
|
24
|
+
status: 200,
|
|
25
|
+
headers: { "content-type": "application/octet-stream" },
|
|
26
|
+
body: buffer,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const ids = parseContentStorageKey(args.storageKey);
|
|
30
|
+
if (ids) {
|
|
31
|
+
const message = await deps.lookupMessage(ids.messageId);
|
|
32
|
+
if (message) {
|
|
33
|
+
await deps.requestBodySync({
|
|
34
|
+
accountId: ids.accountId,
|
|
35
|
+
mailboxId: message.mailboxId,
|
|
36
|
+
messageId: ids.messageId,
|
|
37
|
+
uid: message.uid,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
status: 202,
|
|
43
|
+
headers: { "Retry-After": "1" },
|
|
44
|
+
body: "body not yet synced",
|
|
45
|
+
};
|
|
46
|
+
};
|
package/dist/src/api.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import spec from "@remit/api-openapi-spec/openapi.json" with { type: "json" };
|
|
2
|
+
import { redactCalendarFeedPath } from "@remit/calendar-service";
|
|
3
|
+
import { logger } from "@remit/logger";
|
|
4
|
+
import { OpenAPIBackend, } from "openapi-backend";
|
|
5
|
+
import { defaultErrorCode } from "./error.js";
|
|
6
|
+
import { postResponseHandler } from "./response.js";
|
|
7
|
+
const isDocument = (value) => typeof value === "object" &&
|
|
8
|
+
value !== null &&
|
|
9
|
+
"openapi" in value &&
|
|
10
|
+
typeof value.openapi === "string" &&
|
|
11
|
+
"paths" in value;
|
|
12
|
+
// Imported, not read from disk, so no host has to place openapi.json next to the
|
|
13
|
+
// entry point — a Worker has no filesystem to read it from.
|
|
14
|
+
if (!isDocument(spec)) {
|
|
15
|
+
throw new Error("@remit/api-openapi-spec is not an OpenAPI document");
|
|
16
|
+
}
|
|
17
|
+
// Typed as a Document so importers do not see the JSON literal type.
|
|
18
|
+
export const OpenAPISpec = spec;
|
|
19
|
+
const api = new OpenAPIBackend({
|
|
20
|
+
definition: OpenAPISpec,
|
|
21
|
+
quick: true,
|
|
22
|
+
// Coerce query/path params to their schema types. API Gateway delivers every
|
|
23
|
+
// query param as a string; handlers cast them to number/boolean (e.g. `limit`,
|
|
24
|
+
// `hasAttachment`) and forward them downstream. Without coercion a string
|
|
25
|
+
// `limit` reaches ElectroDB's `.go({ limit })` and DynamoDB rejects the
|
|
26
|
+
// numeric Limit field ("STRING_VALUE cannot be converted to Integer"), 500ing
|
|
27
|
+
// /addresses/search.
|
|
28
|
+
coerceTypes: true,
|
|
29
|
+
});
|
|
30
|
+
api.register("postResponseHandler", postResponseHandler);
|
|
31
|
+
/**
|
|
32
|
+
* The validator's findings, narrowed to the three fields `RequestValidationDetail`
|
|
33
|
+
* declares. Copied field by field rather than passed through: an AJV error also
|
|
34
|
+
* carries `params`, and under `verbose` the rejected `data` itself — which for a
|
|
35
|
+
* refused request body is whatever the caller sent, a password among the
|
|
36
|
+
* possibilities. Naming the fields is what keeps that off the wire.
|
|
37
|
+
*/
|
|
38
|
+
const validationDetails = (errors) => (errors ?? []).map((error) => ({
|
|
39
|
+
path: error.instancePath,
|
|
40
|
+
rule: error.keyword,
|
|
41
|
+
message: error.message ?? "refused",
|
|
42
|
+
}));
|
|
43
|
+
// The errors and the operation, and nothing that came off the wire. A request
|
|
44
|
+
// that fails validation is logged at error level, and the request it carries
|
|
45
|
+
// holds the caller's headers and cookies — an Authorization bearer among them,
|
|
46
|
+
// and for a calendar feed the token in the path is itself the credential
|
|
47
|
+
// (issue #1065). The path is redacted the same way the request scope redacts it.
|
|
48
|
+
api.register("validationFail", (c) => {
|
|
49
|
+
logger.error({
|
|
50
|
+
errors: c.validation.errors,
|
|
51
|
+
path: redactCalendarFeedPath(c.request.path),
|
|
52
|
+
operation: c.operation?.operationId,
|
|
53
|
+
}, "Validation failed");
|
|
54
|
+
return {
|
|
55
|
+
statusCode: 400,
|
|
56
|
+
body: {
|
|
57
|
+
code: defaultErrorCode(400),
|
|
58
|
+
message: "Invalid request",
|
|
59
|
+
errors: validationDetails(c.validation.errors),
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
api.register("notFound", () => ({
|
|
64
|
+
statusCode: 404,
|
|
65
|
+
body: { code: defaultErrorCode(404), message: "Not found" },
|
|
66
|
+
}));
|
|
67
|
+
api.register("notImplemented", () => ({
|
|
68
|
+
statusCode: 501,
|
|
69
|
+
body: { code: defaultErrorCode(501), message: "Not implemented" },
|
|
70
|
+
}));
|
|
71
|
+
api.init();
|
|
72
|
+
export { setLogger } from "@remit/logger";
|
|
73
|
+
export { api };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { APIGatewayProxyEvent } from "aws-lambda";
|
|
2
|
+
import type { HandlerIdentity } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The LOCAL_ACCOUNT_CONFIG_ID / LOCAL_COGNITO_SUB overrides bypass authentication
|
|
5
|
+
* for local dev and fixtures: with them set, an unauthenticated request is served
|
|
6
|
+
* as the pinned account. They must never reach a deployed environment. Refuse to
|
|
7
|
+
* start (throw at module load) if either is present outside a local NODE_ENV — the
|
|
8
|
+
* same `development`/`test` signal the data layer already uses to decide local vs
|
|
9
|
+
* deployed. Fail loud rather than silently ignore, so a leaked env var is caught at
|
|
10
|
+
* boot, not exploited at runtime. In a local NODE_ENV the bypass is read here
|
|
11
|
+
* once, so a bypass set without LOCAL_EMAIL also fails at boot.
|
|
12
|
+
*/
|
|
13
|
+
export declare const assertLocalBypassNotInDeployedEnv: (env: NodeJS.ProcessEnv) => void;
|
|
14
|
+
export declare const userFromClaims: (sub: string, email: string | undefined) => HandlerIdentity;
|
|
15
|
+
export declare const localIdentityFromEnv: (env: NodeJS.ProcessEnv) => HandlerIdentity;
|
|
16
|
+
/**
|
|
17
|
+
* The caller of an API Gateway event. Production reads the Cognito `sub` and
|
|
18
|
+
* `email` claims (verified by the authorizer, or by `jwt-auth.ts` on
|
|
19
|
+
* self-host) and derives the accountConfigId via UUIDv5. Local development
|
|
20
|
+
* honours `LOCAL_ACCOUNT_CONFIG_ID` (a pinned seeded config) and
|
|
21
|
+
* `LOCAL_COGNITO_SUB` (a pinned sub, derived like production), read once at
|
|
22
|
+
* boot into `localIdentity`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const getIdentityFromEvent: (event: APIGatewayProxyEvent, localIdentity: HandlerIdentity) => HandlerIdentity;
|
package/dist/src/auth.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { deriveAccountConfigId } from "@remit/data-ports/id";
|
|
2
|
+
const LOCAL_BYPASS_VARS = [
|
|
3
|
+
"LOCAL_ACCOUNT_CONFIG_ID",
|
|
4
|
+
"LOCAL_COGNITO_SUB",
|
|
5
|
+
];
|
|
6
|
+
const isLocalEnv = (env) => env.NODE_ENV === "development" || env.NODE_ENV === "test";
|
|
7
|
+
/**
|
|
8
|
+
* The LOCAL_ACCOUNT_CONFIG_ID / LOCAL_COGNITO_SUB overrides bypass authentication
|
|
9
|
+
* for local dev and fixtures: with them set, an unauthenticated request is served
|
|
10
|
+
* as the pinned account. They must never reach a deployed environment. Refuse to
|
|
11
|
+
* start (throw at module load) if either is present outside a local NODE_ENV — the
|
|
12
|
+
* same `development`/`test` signal the data layer already uses to decide local vs
|
|
13
|
+
* deployed. Fail loud rather than silently ignore, so a leaked env var is caught at
|
|
14
|
+
* boot, not exploited at runtime. In a local NODE_ENV the bypass is read here
|
|
15
|
+
* once, so a bypass set without LOCAL_EMAIL also fails at boot.
|
|
16
|
+
*/
|
|
17
|
+
export const assertLocalBypassNotInDeployedEnv = (env) => {
|
|
18
|
+
if (isLocalEnv(env)) {
|
|
19
|
+
localIdentityFromEnv(env);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const leaked = LOCAL_BYPASS_VARS.filter((name) => {
|
|
23
|
+
const value = env[name];
|
|
24
|
+
return typeof value === "string" && value.length > 0;
|
|
25
|
+
});
|
|
26
|
+
if (leaked.length === 0)
|
|
27
|
+
return;
|
|
28
|
+
throw new Error(`Refusing to start: local auth bypass ${leaked.join(", ")} is set while NODE_ENV=${env.NODE_ENV ?? "(unset)"}. These overrides bypass authentication and must never be present in a deployed environment.`);
|
|
29
|
+
};
|
|
30
|
+
const readClaim = (event, name) => {
|
|
31
|
+
const value = event.requestContext?.authorizer?.claims?.[name];
|
|
32
|
+
if (typeof value === "string" && value.length > 0)
|
|
33
|
+
return value;
|
|
34
|
+
return undefined;
|
|
35
|
+
};
|
|
36
|
+
const ANONYMOUS = { kind: "anonymous" };
|
|
37
|
+
export const userFromClaims = (sub, email) => {
|
|
38
|
+
if (!email)
|
|
39
|
+
return ANONYMOUS;
|
|
40
|
+
return {
|
|
41
|
+
kind: "user",
|
|
42
|
+
accountConfigId: deriveAccountConfigId(sub),
|
|
43
|
+
sub,
|
|
44
|
+
email,
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
const pinnedLocalUser = (env) => {
|
|
48
|
+
const localAccountConfigId = env.LOCAL_ACCOUNT_CONFIG_ID;
|
|
49
|
+
if (localAccountConfigId) {
|
|
50
|
+
return { accountConfigId: localAccountConfigId, sub: localAccountConfigId };
|
|
51
|
+
}
|
|
52
|
+
const localSub = env.LOCAL_COGNITO_SUB;
|
|
53
|
+
if (localSub) {
|
|
54
|
+
return { accountConfigId: deriveAccountConfigId(localSub), sub: localSub };
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
};
|
|
58
|
+
export const localIdentityFromEnv = (env) => {
|
|
59
|
+
const pinned = pinnedLocalUser(env);
|
|
60
|
+
if (!pinned)
|
|
61
|
+
return ANONYMOUS;
|
|
62
|
+
const email = env.LOCAL_EMAIL;
|
|
63
|
+
if (!email) {
|
|
64
|
+
throw new Error("Refusing to start: the local auth bypass (LOCAL_ACCOUNT_CONFIG_ID or LOCAL_COGNITO_SUB) is set without LOCAL_EMAIL. The bypass serves a signed-in user, and every user carries an email; set LOCAL_EMAIL to the address that user signs in with.");
|
|
65
|
+
}
|
|
66
|
+
return { kind: "user", ...pinned, email };
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The caller of an API Gateway event. Production reads the Cognito `sub` and
|
|
70
|
+
* `email` claims (verified by the authorizer, or by `jwt-auth.ts` on
|
|
71
|
+
* self-host) and derives the accountConfigId via UUIDv5. Local development
|
|
72
|
+
* honours `LOCAL_ACCOUNT_CONFIG_ID` (a pinned seeded config) and
|
|
73
|
+
* `LOCAL_COGNITO_SUB` (a pinned sub, derived like production), read once at
|
|
74
|
+
* boot into `localIdentity`.
|
|
75
|
+
*/
|
|
76
|
+
export const getIdentityFromEvent = (event, localIdentity) => {
|
|
77
|
+
const sub = readClaim(event, "sub");
|
|
78
|
+
if (!sub)
|
|
79
|
+
return localIdentity;
|
|
80
|
+
return userFromClaims(sub, readClaim(event, "email"));
|
|
81
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MsOAuthConfig } from "./msoauth.js";
|
|
2
|
+
export type AuthConfig = {
|
|
3
|
+
mode: "BetterAuthJwt";
|
|
4
|
+
contentSigningSecret: string;
|
|
5
|
+
} | {
|
|
6
|
+
mode: "GatewayAuthorizer";
|
|
7
|
+
};
|
|
8
|
+
export type ResponseValidation = "Log" | "Skip";
|
|
9
|
+
export type SelfUpdate = "Enabled" | "Disabled";
|
|
10
|
+
export interface ApiConfig {
|
|
11
|
+
auth: AuthConfig;
|
|
12
|
+
corsAllowedOrigins: readonly string[];
|
|
13
|
+
publicOrigin: string;
|
|
14
|
+
contentDeliveryDomain: string;
|
|
15
|
+
msOAuth: MsOAuthConfig;
|
|
16
|
+
selfUpdate: SelfUpdate;
|
|
17
|
+
updateControlDir: string;
|
|
18
|
+
commit: string;
|
|
19
|
+
responseValidation: ResponseValidation;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { msOAuthCredentialsFromSecret } from "./msoauth-secrets-manager.js";
|
|
2
|
+
const DEFAULT_UPDATE_CONTROL_DIR = "/data/control";
|
|
3
|
+
const optional = (env, key) => env[key] || undefined;
|
|
4
|
+
const required = (env, key, reason) => {
|
|
5
|
+
const value = optional(env, key);
|
|
6
|
+
if (!value)
|
|
7
|
+
throw new Error(`Refusing to start: ${key} is not set. ${reason}`);
|
|
8
|
+
return value;
|
|
9
|
+
};
|
|
10
|
+
const commaList = (raw) => (raw ?? "")
|
|
11
|
+
.split(",")
|
|
12
|
+
.map((origin) => origin.trim())
|
|
13
|
+
.filter((origin) => origin.length > 0);
|
|
14
|
+
const authFromEnv = (env) => env.DATA_BACKEND === "sqlite"
|
|
15
|
+
? {
|
|
16
|
+
mode: "BetterAuthJwt",
|
|
17
|
+
contentSigningSecret: required(env, "BETTER_AUTH_SECRET", "The self-host SQL backend signs content URLs with it."),
|
|
18
|
+
}
|
|
19
|
+
: { mode: "GatewayAuthorizer" };
|
|
20
|
+
const contentDeliveryDomainFromEnv = (env, auth, publicOrigin) => {
|
|
21
|
+
if (auth.mode === "BetterAuthJwt") {
|
|
22
|
+
return optional(env, "CONTENT_DELIVERY_DOMAIN") ?? publicOrigin;
|
|
23
|
+
}
|
|
24
|
+
return required(env, "CONTENT_DELIVERY_DOMAIN", "BodyPartResponse.contentUrl is built from the CloudFront distribution domain (#299); wire it via the API Lambda env (see infra/bin/infra.ts).");
|
|
25
|
+
};
|
|
26
|
+
const msOAuthCredentialsFromEnv = (env) => {
|
|
27
|
+
const clientId = optional(env, "MSOAUTH_CLIENT_ID");
|
|
28
|
+
const clientSecret = optional(env, "MSOAUTH_CLIENT_SECRET");
|
|
29
|
+
if (clientId && clientSecret) {
|
|
30
|
+
return async () => ({ clientId, clientSecret });
|
|
31
|
+
}
|
|
32
|
+
const secretArn = optional(env, "MSOAUTH_SECRET_ARN");
|
|
33
|
+
if (secretArn)
|
|
34
|
+
return msOAuthCredentialsFromSecret(secretArn);
|
|
35
|
+
return undefined;
|
|
36
|
+
};
|
|
37
|
+
const msOAuthFromEnv = (env) => {
|
|
38
|
+
const credentials = msOAuthCredentialsFromEnv(env);
|
|
39
|
+
if (!credentials)
|
|
40
|
+
return { mode: "Disabled" };
|
|
41
|
+
const tokenEndpoint = optional(env, "MSOAUTH_TOKEN_ENDPOINT");
|
|
42
|
+
return {
|
|
43
|
+
mode: "Enabled",
|
|
44
|
+
redirectUri: required(env, "MSOAUTH_REDIRECT_URI", "It is derived from PUBLIC_ORIGIN — set PUBLIC_ORIGIN in the deploy env file (deploy/vps/.env) and restart, or set MSOAUTH_REDIRECT_URI directly."),
|
|
45
|
+
overrides: tokenEndpoint ? { tokenEndpoint } : {},
|
|
46
|
+
credentials,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export const configFromEnv = (env) => {
|
|
50
|
+
const publicOrigin = required(env, "PUBLIC_ORIGIN", "It is the origin the web client is served from.");
|
|
51
|
+
const auth = authFromEnv(env);
|
|
52
|
+
return {
|
|
53
|
+
auth,
|
|
54
|
+
corsAllowedOrigins: commaList(env.CORS_ALLOWED_ORIGINS),
|
|
55
|
+
publicOrigin,
|
|
56
|
+
contentDeliveryDomain: contentDeliveryDomainFromEnv(env, auth, publicOrigin),
|
|
57
|
+
msOAuth: msOAuthFromEnv(env),
|
|
58
|
+
selfUpdate: optional(env, "REMIT_UPDATE_MANIFEST_URL")
|
|
59
|
+
? "Enabled"
|
|
60
|
+
: "Disabled",
|
|
61
|
+
updateControlDir: optional(env, "REMIT_UPDATE_CONTROL_DIR") ?? DEFAULT_UPDATE_CONTROL_DIR,
|
|
62
|
+
commit: optional(env, "REMIT_WORKER_SHA") ?? optional(env, "GITHUB_SHA") ?? "dev",
|
|
63
|
+
responseValidation: env.STAGE_NAME === "dev" ? "Log" : "Skip",
|
|
64
|
+
};
|
|
65
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { GetSecretValueCommand, SecretsManagerClient, } from "@aws-sdk/client-secrets-manager";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const MsOAuthSecret = z.object({
|
|
4
|
+
clientId: z.string().min(1),
|
|
5
|
+
clientSecret: z.string().min(1),
|
|
6
|
+
});
|
|
7
|
+
let client = null;
|
|
8
|
+
const secretsManager = () => {
|
|
9
|
+
if (!client)
|
|
10
|
+
client = new SecretsManagerClient({});
|
|
11
|
+
return client;
|
|
12
|
+
};
|
|
13
|
+
const parseSecret = (raw) => {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export const msOAuthCredentialsFromSecret = (secretArn) => async () => {
|
|
22
|
+
const result = await secretsManager().send(new GetSecretValueCommand({ SecretId: secretArn }));
|
|
23
|
+
const parsed = MsOAuthSecret.safeParse(parseSecret(result.SecretString ?? ""));
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
throw new Error("The Microsoft OAuth secret named by MSOAUTH_SECRET_ARN does not hold a JSON object with a non-empty clientId and clientSecret");
|
|
26
|
+
}
|
|
27
|
+
return parsed.data;
|
|
28
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { OAuthProviderConfig } from "@remit/mail-oauth-service";
|
|
2
|
+
export interface MsOAuthCredentials {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MsOAuthEnabled {
|
|
7
|
+
mode: "Enabled";
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
overrides: Partial<Pick<OAuthProviderConfig, "tokenEndpoint">>;
|
|
10
|
+
credentials: () => Promise<MsOAuthCredentials>;
|
|
11
|
+
}
|
|
12
|
+
export type MsOAuthConfig = {
|
|
13
|
+
mode: "Disabled";
|
|
14
|
+
} | MsOAuthEnabled;
|
|
15
|
+
export interface LoadedMsOAuth {
|
|
16
|
+
config: MsOAuthEnabled;
|
|
17
|
+
credentials: MsOAuthCredentials;
|
|
18
|
+
}
|
|
19
|
+
export declare const loadMsOAuth: (config: MsOAuthConfig) => Promise<LoadedMsOAuth>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const loadMsOAuth = async (config) => {
|
|
2
|
+
if (config.mode === "Disabled") {
|
|
3
|
+
throw new Error("No Microsoft OAuth credentials are configured on this instance: set MSOAUTH_CLIENT_ID and MSOAUTH_CLIENT_SECRET, or MSOAUTH_SECRET_ARN");
|
|
4
|
+
}
|
|
5
|
+
return { config, credentials: await config.credentials() };
|
|
6
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AutoMovedInfo } from "@remit/api-openapi-types";
|
|
2
2
|
import type { MessageItem } from "@remit/data-ports";
|
|
3
|
-
import { PlacementConfidence } from "@remit/domain-enums";
|
|
4
|
-
|
|
5
3
|
/**
|
|
6
4
|
* Project a Message's internal auto-move state to the public `AutoMovedInfo`
|
|
7
5
|
* shape. Present only for a real, in-effect auto-move; absent otherwise, so a
|
|
@@ -27,30 +25,4 @@ import { PlacementConfidence } from "@remit/domain-enums";
|
|
|
27
25
|
* The client derives everything else (whether the move is still in effect, the
|
|
28
26
|
* undo target) from these fields plus current placement.
|
|
29
27
|
*/
|
|
30
|
-
export const deriveAutoMoved
|
|
31
|
-
message: Pick<
|
|
32
|
-
MessageItem,
|
|
33
|
-
"movedByRemit" | "placementVerdict" | "filterMove"
|
|
34
|
-
>,
|
|
35
|
-
): AutoMovedInfo | undefined => {
|
|
36
|
-
if (message.movedByRemit !== true) return undefined;
|
|
37
|
-
|
|
38
|
-
const filterMove = message.filterMove;
|
|
39
|
-
if (filterMove) {
|
|
40
|
-
return {
|
|
41
|
-
fromMailboxId: filterMove.sourceMailboxId,
|
|
42
|
-
destinationMailboxId: filterMove.destinationMailboxId,
|
|
43
|
-
filterId: filterMove.filterId,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const verdict = message.placementVerdict;
|
|
48
|
-
if (!verdict) return undefined;
|
|
49
|
-
if (verdict.confidence !== PlacementConfidence.Confident) return undefined;
|
|
50
|
-
if (verdict.dryRun === true) return undefined;
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
action: verdict.action,
|
|
54
|
-
fromPlacement: verdict.fromPlacement,
|
|
55
|
-
};
|
|
56
|
-
};
|
|
28
|
+
export declare const deriveAutoMoved: (message: Pick<MessageItem, "movedByRemit" | "placementVerdict" | "filterMove">) => AutoMovedInfo | undefined;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { PlacementConfidence } from "@remit/domain-enums";
|
|
2
|
+
/**
|
|
3
|
+
* Project a Message's internal auto-move state to the public `AutoMovedInfo`
|
|
4
|
+
* shape. Present only for a real, in-effect auto-move; absent otherwise, so a
|
|
5
|
+
* message synced before either mechanism existed (no `filterMove`, no
|
|
6
|
+
* `placementVerdict`) projects nothing and renders no badge.
|
|
7
|
+
*
|
|
8
|
+
* Two mechanisms move mail and both raise `movedByRemit`:
|
|
9
|
+
*
|
|
10
|
+
* 1. A standing filter (RFC 034) records a `filterMove` marker naming the
|
|
11
|
+
* source folder, the destination folder, and the filter. It has no
|
|
12
|
+
* classifier direction/role, so the projection carries mailbox ids
|
|
13
|
+
* (`fromMailboxId`/`destinationMailboxId`) and the `filterId` for the
|
|
14
|
+
* Settings link — an arbitrary destination the Inbox/Junk-shaped `action`
|
|
15
|
+
* cannot name.
|
|
16
|
+
* 2. The Tier 0 classifier records a `placementVerdict`. Its `action`
|
|
17
|
+
* (MoveToInbox/MoveToJunk) and `fromPlacement` (inbox/junk/other) are the
|
|
18
|
+
* only fields that cross to the wire; confidence, dryRun, decidedAt and
|
|
19
|
+
* reasons stay internal.
|
|
20
|
+
*
|
|
21
|
+
* The filter marker outranks the verdict check: a matched filter's move is
|
|
22
|
+
* exclusive and outranks the classifier's placement move at index time (RFC 034
|
|
23
|
+
* Decision 3.1), so a message carrying both was ultimately moved by the filter.
|
|
24
|
+
* The client derives everything else (whether the move is still in effect, the
|
|
25
|
+
* undo target) from these fields plus current placement.
|
|
26
|
+
*/
|
|
27
|
+
export const deriveAutoMoved = (message) => {
|
|
28
|
+
if (message.movedByRemit !== true)
|
|
29
|
+
return undefined;
|
|
30
|
+
const filterMove = message.filterMove;
|
|
31
|
+
if (filterMove) {
|
|
32
|
+
return {
|
|
33
|
+
fromMailboxId: filterMove.sourceMailboxId,
|
|
34
|
+
destinationMailboxId: filterMove.destinationMailboxId,
|
|
35
|
+
filterId: filterMove.filterId,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const verdict = message.placementVerdict;
|
|
39
|
+
if (!verdict)
|
|
40
|
+
return undefined;
|
|
41
|
+
if (verdict.confidence !== PlacementConfidence.Confident)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (verdict.dryRun === true)
|
|
44
|
+
return undefined;
|
|
45
|
+
return {
|
|
46
|
+
action: verdict.action,
|
|
47
|
+
fromPlacement: verdict.fromPlacement,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type SignedPathFailure } from "@remit/storage-service/signed-path";
|
|
2
|
+
import type { ApiConfig } from "../config/api-config.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default validity window for a signed content URL. Long enough to cover a
|
|
5
|
+
* viewing session (react-query caches describeMessage for 30 min and inline
|
|
6
|
+
* images may load late), short enough to bound the replay window if a URL
|
|
7
|
+
* leaks.
|
|
8
|
+
*/
|
|
9
|
+
export declare const CONTENT_URL_TTL_SECONDS = 3600;
|
|
10
|
+
export interface ContentSignature {
|
|
11
|
+
exp: number;
|
|
12
|
+
sig: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A function that signs a decoded, account-scoped storage path and returns the
|
|
16
|
+
* `exp`/`sig` query params to append to its content URL.
|
|
17
|
+
*/
|
|
18
|
+
export type ContentSigner = (relativePath: string) => ContentSignature;
|
|
19
|
+
export declare const createContentSigner: (masterSecret: string, ttlSeconds?: number) => ContentSigner;
|
|
20
|
+
/**
|
|
21
|
+
* Build the content-URL signer for the current environment, or `undefined` when
|
|
22
|
+
* signing does not apply. Signing is enforced on the self-host SQL backend,
|
|
23
|
+
* which serves `/content/*` straight from the backend container; on AWS the
|
|
24
|
+
* Lambda@Edge JWT verifier guards `/content/*` and the URL stays unsigned so
|
|
25
|
+
* CloudFront/S3 behaviour is unchanged.
|
|
26
|
+
*/
|
|
27
|
+
export declare const getContentSigner: (config: ApiConfig) => ContentSigner | undefined;
|
|
28
|
+
export type ContentSignatureFailure = SignedPathFailure;
|
|
29
|
+
export type ContentSignatureResult = {
|
|
30
|
+
valid: true;
|
|
31
|
+
} | {
|
|
32
|
+
valid: false;
|
|
33
|
+
reason: ContentSignatureFailure;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Verify a signed content request. Recomputes the HMAC over the requested path
|
|
37
|
+
* and the supplied expiry and constant-time compares it against the presented
|
|
38
|
+
* signature. Pure so the decision can be unit-tested without a live server.
|
|
39
|
+
*/
|
|
40
|
+
export declare const verifyContentSignature: (relativePath: string, expRaw: string | undefined, sig: string | undefined, masterSecret: string, nowSeconds: number) => ContentSignatureResult;
|