@remit/backend 0.0.142 → 0.0.144
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 +7 -0
- package/dist/src/api.js +144 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +73 -0
- package/dist/src/config/msoauth.d.ts +30 -0
- package/dist/src/config/msoauth.js +30 -0
- package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
- package/dist/src/data-backend.js +12 -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 +41 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
- package/dist/src/derive/contentUrl.d.ts +46 -0
- package/dist/src/derive/contentUrl.js +46 -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 +14 -0
- package/dist/src/handlers/account-oauth.js +387 -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 +6 -0
- package/dist/src/handlers/account.js +350 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +141 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +425 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +192 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +212 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +330 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +176 -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 +103 -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 +40 -0
- package/dist/src/handlers/index.d.ts +2 -0
- package/dist/src/handlers/index.js +60 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +114 -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 +219 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +138 -0
- package/dist/src/handlers/message.d.ts +84 -0
- package/dist/src/handlers/message.js +607 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +112 -0
- package/dist/src/handlers/outbox-attachment.d.ts +23 -0
- package/dist/src/handlers/outbox-attachment.js +81 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +144 -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 +82 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +172 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +233 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +409 -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 +2 -0
- package/dist/src/index.js +37 -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 +18 -0
- package/dist/src/jwt-auth.js +97 -0
- package/dist/src/request-context.d.ts +9 -0
- package/dist/src/request-context.js +22 -0
- package/dist/src/request.d.ts +3 -0
- package/dist/src/request.js +7 -0
- package/dist/src/response.d.ts +27 -0
- package/dist/src/response.js +87 -0
- package/dist/src/service/account-deletion.d.ts +22 -0
- package/dist/src/service/account-deletion.js +39 -0
- package/dist/src/service/compose-sqlite.d.ts +2 -0
- package/dist/src/service/compose-sqlite.js +11 -0
- package/dist/src/service/create-remit-client.d.ts +95 -0
- package/dist/src/service/create-remit-client.js +207 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +44 -0
- package/dist/src/service/export-identity.d.ts +3 -0
- package/dist/src/service/export-identity.js +35 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +38 -0
- package/dist/src/service/organize.d.ts +184 -0
- package/dist/src/service/organize.js +384 -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/sqs.d.ts +1 -0
- package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
- package/dist/src/service/trigger-sync.d.ts +51 -0
- package/dist/src/service/trigger-sync.js +39 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.js +1 -0
- package/package.json +113 -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/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 -521
- 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/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
package/src/config/msoauth.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Typed reads for Microsoft OAuth (Entra) environment variables.
|
|
3
|
-
*
|
|
4
|
-
* `MSOAUTH_REDIRECT_URI` is derived from `PUBLIC_ORIGIN` by the compose file
|
|
5
|
-
* (deploy/vps/docker-compose.sqlite.yml) and must match the redirect URI
|
|
6
|
-
* registered on the Entra app registration.
|
|
7
|
-
*
|
|
8
|
-
* Credentials come either from `MSOAUTH_CLIENT_ID` / `MSOAUTH_CLIENT_SECRET`
|
|
9
|
-
* in the deploy env file, or from Secrets Manager via `MSOAUTH_SECRET_ARN`.
|
|
10
|
-
*
|
|
11
|
-
* The Microsoft authorization and token endpoints are fixed in
|
|
12
|
-
* `@remit/mail-oauth-service`; no authority URL is read from the environment.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
export interface MsOAuthConfig {
|
|
16
|
-
/** Secrets Manager ARN — an alternative to the client id/secret pair. */
|
|
17
|
-
readonly secretArn: string | undefined;
|
|
18
|
-
/** OAuth redirect URI registered in the Entra app. */
|
|
19
|
-
readonly redirectUri: string;
|
|
20
|
-
/** Client ID — set directly to bypass Secrets Manager. */
|
|
21
|
-
readonly clientId: string | undefined;
|
|
22
|
-
/** Client secret — set directly to bypass Secrets Manager. */
|
|
23
|
-
readonly clientSecret: string | undefined;
|
|
24
|
-
/** Token endpoint override — used for local stubbing (bypasses default OIDC discovery). */
|
|
25
|
-
readonly tokenEndpoint: string | undefined;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Read Microsoft OAuth configuration from the environment.
|
|
30
|
-
* Throws when `MSOAUTH_REDIRECT_URI` is absent.
|
|
31
|
-
*/
|
|
32
|
-
export const getMsOAuthConfig = (): MsOAuthConfig => {
|
|
33
|
-
const redirectUri = process.env.MSOAUTH_REDIRECT_URI;
|
|
34
|
-
|
|
35
|
-
if (!redirectUri) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
"MSOAUTH_REDIRECT_URI is not set. 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.",
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
secretArn: process.env.MSOAUTH_SECRET_ARN,
|
|
43
|
-
redirectUri,
|
|
44
|
-
clientId: process.env.MSOAUTH_CLIENT_ID,
|
|
45
|
-
clientSecret: process.env.MSOAUTH_CLIENT_SECRET,
|
|
46
|
-
tokenEndpoint: process.env.MSOAUTH_TOKEN_ENDPOINT,
|
|
47
|
-
};
|
|
48
|
-
};
|
package/src/data-backend.test.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, describe, it } from "node:test";
|
|
3
|
-
import { usesBetterAuthJwt } from "./data-backend.js";
|
|
4
|
-
|
|
5
|
-
describe("usesBetterAuthJwt", () => {
|
|
6
|
-
const ORIGINAL = process.env.DATA_BACKEND;
|
|
7
|
-
afterEach(() => {
|
|
8
|
-
if (ORIGINAL === undefined) delete process.env.DATA_BACKEND;
|
|
9
|
-
else process.env.DATA_BACKEND = ORIGINAL;
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("is true for the self-host SQL backend", () => {
|
|
13
|
-
process.env.DATA_BACKEND = "sqlite";
|
|
14
|
-
assert.equal(usesBetterAuthJwt(), true);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("is false for the AWS DynamoDB path and when unset", () => {
|
|
18
|
-
process.env.DATA_BACKEND = "dynamodb";
|
|
19
|
-
assert.equal(usesBetterAuthJwt(), false);
|
|
20
|
-
delete process.env.DATA_BACKEND;
|
|
21
|
-
assert.equal(usesBetterAuthJwt(), false);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type { MessageItem } from "@remit/data-ports";
|
|
4
|
-
import { PlacementAction, PlacementConfidence } from "@remit/domain-enums";
|
|
5
|
-
import { deriveAutoMoved } from "./autoMoved.js";
|
|
6
|
-
|
|
7
|
-
type AutoMovedInput = Pick<
|
|
8
|
-
MessageItem,
|
|
9
|
-
"movedByRemit" | "placementVerdict" | "filterMove"
|
|
10
|
-
>;
|
|
11
|
-
|
|
12
|
-
const confidentVerdict = {
|
|
13
|
-
action: PlacementAction.MoveToInbox,
|
|
14
|
-
confidence: PlacementConfidence.Confident,
|
|
15
|
-
fromPlacement: "junk",
|
|
16
|
-
reasons: ["provider=spam", "dmarc=pass"],
|
|
17
|
-
dryRun: false,
|
|
18
|
-
decidedAt: 1_700_000_000_000,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const filterMove = {
|
|
22
|
-
filterId: "flt-1",
|
|
23
|
-
sourceMailboxId: "mb-inbox",
|
|
24
|
-
destinationMailboxId: "mb-travel",
|
|
25
|
-
decidedAt: 1_700_000_000_000,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
describe("deriveAutoMoved", () => {
|
|
29
|
-
it("returns undefined when the message was not moved by Remit", () => {
|
|
30
|
-
const message: AutoMovedInput = {
|
|
31
|
-
movedByRemit: false,
|
|
32
|
-
placementVerdict: confidentVerdict,
|
|
33
|
-
};
|
|
34
|
-
assert.equal(deriveAutoMoved(message), undefined);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("projects a standing-filter move to its mailbox ids and filter id", () => {
|
|
38
|
-
const message: AutoMovedInput = { movedByRemit: true, filterMove };
|
|
39
|
-
assert.deepEqual(deriveAutoMoved(message), {
|
|
40
|
-
fromMailboxId: "mb-inbox",
|
|
41
|
-
destinationMailboxId: "mb-travel",
|
|
42
|
-
filterId: "flt-1",
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("never leaks the classifier action/fromPlacement for a filter move", () => {
|
|
47
|
-
const result = deriveAutoMoved({ movedByRemit: true, filterMove });
|
|
48
|
-
assert.equal(result?.action, undefined);
|
|
49
|
-
assert.equal(result?.fromPlacement, undefined);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it("a filter move outranks a co-present placement verdict", () => {
|
|
53
|
-
// Both a filter move and a classifier verdict can be recorded; the filter's
|
|
54
|
-
// exclusive move is what ultimately placed the message (RFC 034 Dec. 3.1).
|
|
55
|
-
const message: AutoMovedInput = {
|
|
56
|
-
movedByRemit: true,
|
|
57
|
-
filterMove,
|
|
58
|
-
placementVerdict: confidentVerdict,
|
|
59
|
-
};
|
|
60
|
-
assert.deepEqual(deriveAutoMoved(message), {
|
|
61
|
-
fromMailboxId: "mb-inbox",
|
|
62
|
-
destinationMailboxId: "mb-travel",
|
|
63
|
-
filterId: "flt-1",
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("projects a confident classifier move to action + fromPlacement", () => {
|
|
68
|
-
const message: AutoMovedInput = {
|
|
69
|
-
movedByRemit: true,
|
|
70
|
-
placementVerdict: confidentVerdict,
|
|
71
|
-
};
|
|
72
|
-
assert.deepEqual(deriveAutoMoved(message), {
|
|
73
|
-
action: PlacementAction.MoveToInbox,
|
|
74
|
-
fromPlacement: "junk",
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("drops an unsure classifier verdict", () => {
|
|
79
|
-
const message: AutoMovedInput = {
|
|
80
|
-
movedByRemit: true,
|
|
81
|
-
placementVerdict: {
|
|
82
|
-
...confidentVerdict,
|
|
83
|
-
confidence: PlacementConfidence.Unsure,
|
|
84
|
-
},
|
|
85
|
-
};
|
|
86
|
-
assert.equal(deriveAutoMoved(message), undefined);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("drops a dry-run classifier verdict", () => {
|
|
90
|
-
const message: AutoMovedInput = {
|
|
91
|
-
movedByRemit: true,
|
|
92
|
-
placementVerdict: { ...confidentVerdict, dryRun: true },
|
|
93
|
-
};
|
|
94
|
-
assert.equal(deriveAutoMoved(message), undefined);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("does not crash on legacy verdict-less, filter-less moved data", () => {
|
|
98
|
-
// A message moved before either the verdict or the filter marker existed:
|
|
99
|
-
// movedByRemit is set but nothing is derivable. Must return undefined, not
|
|
100
|
-
// throw (issue #223 back-compat).
|
|
101
|
-
const message: AutoMovedInput = { movedByRemit: true };
|
|
102
|
-
assert.equal(deriveAutoMoved(message), undefined);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, describe, it } from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
CONTENT_URL_TTL_SECONDS,
|
|
5
|
-
createContentSigner,
|
|
6
|
-
getContentSigner,
|
|
7
|
-
verifyContentSignature,
|
|
8
|
-
} from "./contentSignature.js";
|
|
9
|
-
|
|
10
|
-
const SECRET = "test-master-secret-at-least-32-chars-long";
|
|
11
|
-
const PATH_A = "accounts/cfg-alice/acc-alice/messages/msg-1/parts/1.2";
|
|
12
|
-
const PATH_B = "accounts/cfg-bob/acc-bob/messages/msg-9/parts/1.2";
|
|
13
|
-
|
|
14
|
-
const nowSeconds = () => Math.floor(Date.now() / 1000);
|
|
15
|
-
|
|
16
|
-
describe("content signature: sign + verify round trip", () => {
|
|
17
|
-
it("a freshly signed URL verifies", () => {
|
|
18
|
-
const { exp, sig } = createContentSigner(SECRET)(PATH_A);
|
|
19
|
-
const result = verifyContentSignature(
|
|
20
|
-
PATH_A,
|
|
21
|
-
String(exp),
|
|
22
|
-
sig,
|
|
23
|
-
SECRET,
|
|
24
|
-
nowSeconds(),
|
|
25
|
-
);
|
|
26
|
-
assert.deepEqual(result, { valid: true });
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("rejects a signature minted for a different account's path (cross-account IDOR defense)", () => {
|
|
30
|
-
const { exp, sig } = createContentSigner(SECRET)(PATH_A);
|
|
31
|
-
// Present account A's signature against account B's path.
|
|
32
|
-
const result = verifyContentSignature(
|
|
33
|
-
PATH_B,
|
|
34
|
-
String(exp),
|
|
35
|
-
sig,
|
|
36
|
-
SECRET,
|
|
37
|
-
nowSeconds(),
|
|
38
|
-
);
|
|
39
|
-
assert.deepEqual(result, { valid: false, reason: "bad-signature" });
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it("rejects a tampered signature", () => {
|
|
43
|
-
const { exp, sig } = createContentSigner(SECRET)(PATH_A);
|
|
44
|
-
const tampered = `${sig.slice(0, -1)}${sig.endsWith("A") ? "B" : "A"}`;
|
|
45
|
-
const result = verifyContentSignature(
|
|
46
|
-
PATH_A,
|
|
47
|
-
String(exp),
|
|
48
|
-
tampered,
|
|
49
|
-
SECRET,
|
|
50
|
-
nowSeconds(),
|
|
51
|
-
);
|
|
52
|
-
assert.equal(result.valid, false);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("rejects a valid signature verified with the wrong secret", () => {
|
|
56
|
-
const { exp, sig } = createContentSigner(SECRET)(PATH_A);
|
|
57
|
-
const result = verifyContentSignature(
|
|
58
|
-
PATH_A,
|
|
59
|
-
String(exp),
|
|
60
|
-
sig,
|
|
61
|
-
"a-different-master-secret-also-32-chars",
|
|
62
|
-
nowSeconds(),
|
|
63
|
-
);
|
|
64
|
-
assert.deepEqual(result, { valid: false, reason: "bad-signature" });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("rejects an expired signature", () => {
|
|
68
|
-
// Negative TTL mints a signature whose expiry is already in the past.
|
|
69
|
-
const { exp, sig } = createContentSigner(SECRET, -20)(PATH_A);
|
|
70
|
-
assert.ok(exp < nowSeconds());
|
|
71
|
-
const result = verifyContentSignature(
|
|
72
|
-
PATH_A,
|
|
73
|
-
String(exp),
|
|
74
|
-
sig,
|
|
75
|
-
SECRET,
|
|
76
|
-
nowSeconds(),
|
|
77
|
-
);
|
|
78
|
-
assert.deepEqual(result, { valid: false, reason: "expired" });
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("reports missing exp/sig distinctly from a bad signature", () => {
|
|
82
|
-
assert.deepEqual(
|
|
83
|
-
verifyContentSignature(
|
|
84
|
-
PATH_A,
|
|
85
|
-
undefined,
|
|
86
|
-
undefined,
|
|
87
|
-
SECRET,
|
|
88
|
-
nowSeconds(),
|
|
89
|
-
),
|
|
90
|
-
{ valid: false, reason: "missing" },
|
|
91
|
-
);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("rejects a non-integer exp as malformed", () => {
|
|
95
|
-
const { sig } = createContentSigner(SECRET)(PATH_A);
|
|
96
|
-
assert.deepEqual(
|
|
97
|
-
verifyContentSignature(PATH_A, "not-a-number", sig, SECRET, nowSeconds()),
|
|
98
|
-
{ valid: false, reason: "malformed" },
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
describe("getContentSigner", () => {
|
|
104
|
-
const ORIGINAL_BACKEND = process.env.DATA_BACKEND;
|
|
105
|
-
const ORIGINAL_SECRET = process.env.BETTER_AUTH_SECRET;
|
|
106
|
-
|
|
107
|
-
afterEach(() => {
|
|
108
|
-
if (ORIGINAL_BACKEND === undefined) delete process.env.DATA_BACKEND;
|
|
109
|
-
else process.env.DATA_BACKEND = ORIGINAL_BACKEND;
|
|
110
|
-
if (ORIGINAL_SECRET === undefined) delete process.env.BETTER_AUTH_SECRET;
|
|
111
|
-
else process.env.BETTER_AUTH_SECRET = ORIGINAL_SECRET;
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it("returns undefined on the AWS path (unsigned URLs)", () => {
|
|
115
|
-
delete process.env.DATA_BACKEND;
|
|
116
|
-
assert.equal(getContentSigner(), undefined);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("returns a working signer in SQLite mode", () => {
|
|
120
|
-
process.env.DATA_BACKEND = "sqlite";
|
|
121
|
-
process.env.BETTER_AUTH_SECRET = SECRET;
|
|
122
|
-
const signer = getContentSigner();
|
|
123
|
-
assert.ok(signer);
|
|
124
|
-
const { exp, sig } = signer(PATH_A);
|
|
125
|
-
assert.deepEqual(
|
|
126
|
-
verifyContentSignature(PATH_A, String(exp), sig, SECRET, nowSeconds()),
|
|
127
|
-
{ valid: true },
|
|
128
|
-
);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("throws in SQLite mode when the master secret is missing (fail loud)", () => {
|
|
132
|
-
process.env.DATA_BACKEND = "sqlite";
|
|
133
|
-
delete process.env.BETTER_AUTH_SECRET;
|
|
134
|
-
assert.throws(() => getContentSigner(), /BETTER_AUTH_SECRET/);
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
describe("the wire format is pinned, not merely self-consistent", () => {
|
|
139
|
-
/**
|
|
140
|
-
* Every other test here round-trips through this module, so a change to the
|
|
141
|
-
* canonical message or the key derivation passes them all while breaking every
|
|
142
|
-
* `/content` URL already in a browser. This vector was computed once, by hand,
|
|
143
|
-
* from the scheme as shipped: HMAC-SHA256 with a subkey derived from the
|
|
144
|
-
* master secret under `remit-content-url-signing-v1`, over `path\nexp`,
|
|
145
|
-
* base64url. If it fails, the format moved and in-flight URLs moved with it.
|
|
146
|
-
*/
|
|
147
|
-
it("still produces the signature it produced when the scheme shipped", () => {
|
|
148
|
-
const signer = createContentSigner(
|
|
149
|
-
"a-signing-secret-of-at-least-32-characters",
|
|
150
|
-
);
|
|
151
|
-
const originalNow = Date.now;
|
|
152
|
-
Date.now = () => 1_800_000_000_000 - CONTENT_URL_TTL_SECONDS * 1000;
|
|
153
|
-
try {
|
|
154
|
-
const { exp, sig } = signer("accounts/cfg1/acc1/messages/msg1/parts/1.2");
|
|
155
|
-
assert.equal(exp, 1_800_000_000);
|
|
156
|
-
assert.equal(sig, "eFKwOYf42a6DWwaZahIm7aVtWuqMOfhOAPoyxtpFybo");
|
|
157
|
-
} finally {
|
|
158
|
-
Date.now = originalNow;
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
});
|
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
3
|
-
import {
|
|
4
|
-
createContentSigner,
|
|
5
|
-
verifyContentSignature,
|
|
6
|
-
} from "./contentSignature.js";
|
|
7
|
-
import {
|
|
8
|
-
buildContentUrl,
|
|
9
|
-
buildStorageContentUrl,
|
|
10
|
-
getContentDeliveryDomain,
|
|
11
|
-
} from "./contentUrl.js";
|
|
12
|
-
|
|
13
|
-
describe("buildContentUrl", () => {
|
|
14
|
-
it("produces the /content/accounts/{cfg}/{acc}/messages/{msg}/parts/{part} layout the Lambda@Edge expects", () => {
|
|
15
|
-
const url = buildContentUrl({
|
|
16
|
-
domain: "https://abc123.cloudfront.net",
|
|
17
|
-
accountConfigId: "cfg-alice",
|
|
18
|
-
accountId: "acc-alice",
|
|
19
|
-
messageId: "msg-1",
|
|
20
|
-
partPath: "1.2",
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
assert.equal(
|
|
24
|
-
url,
|
|
25
|
-
"https://abc123.cloudfront.net/content/accounts/cfg-alice/acc-alice/messages/msg-1/parts/1.2",
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("prepends https:// when the domain is given as a bare hostname", () => {
|
|
30
|
-
const url = buildContentUrl({
|
|
31
|
-
domain: "abc123.cloudfront.net",
|
|
32
|
-
accountConfigId: "cfg-alice",
|
|
33
|
-
accountId: "acc-alice",
|
|
34
|
-
messageId: "msg-1",
|
|
35
|
-
partPath: "1",
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
assert.equal(url.startsWith("https://abc123.cloudfront.net/"), true);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("strips a trailing slash on the domain so the path doesn't double up", () => {
|
|
42
|
-
const url = buildContentUrl({
|
|
43
|
-
domain: "https://abc123.cloudfront.net/",
|
|
44
|
-
accountConfigId: "cfg-alice",
|
|
45
|
-
accountId: "acc-alice",
|
|
46
|
-
messageId: "msg-1",
|
|
47
|
-
partPath: "1",
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
assert.equal(
|
|
51
|
-
url,
|
|
52
|
-
"https://abc123.cloudfront.net/content/accounts/cfg-alice/acc-alice/messages/msg-1/parts/1",
|
|
53
|
-
);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it("encodes each segment of partPath defensively (forward-slash kept as separator)", () => {
|
|
57
|
-
const url = buildContentUrl({
|
|
58
|
-
domain: "https://cdn.test",
|
|
59
|
-
accountConfigId: "cfg",
|
|
60
|
-
accountId: "acc",
|
|
61
|
-
messageId: "msg",
|
|
62
|
-
partPath: "1/strange path/3",
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
assert.equal(
|
|
66
|
-
url,
|
|
67
|
-
"https://cdn.test/content/accounts/cfg/acc/messages/msg/parts/1/strange%20path/3",
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("addresses an outbox attachment by its storage key", () => {
|
|
72
|
-
const url = buildStorageContentUrl({
|
|
73
|
-
domain: "https://cdn.test",
|
|
74
|
-
storageKey: "accounts/cfg/acc/outbox/ob-1/attachments/att-1",
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
assert.equal(
|
|
78
|
-
url,
|
|
79
|
-
"https://cdn.test/content/accounts/cfg/acc/outbox/ob-1/attachments/att-1",
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("leaves the URL unsigned when no signer is provided (AWS/Lambda@Edge path)", () => {
|
|
84
|
-
const url = buildContentUrl({
|
|
85
|
-
domain: "https://abc123.cloudfront.net",
|
|
86
|
-
accountConfigId: "cfg-alice",
|
|
87
|
-
accountId: "acc-alice",
|
|
88
|
-
messageId: "msg-1",
|
|
89
|
-
partPath: "1",
|
|
90
|
-
});
|
|
91
|
-
assert.equal(url.includes("?"), false);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it("appends an exp/sig signature over the decoded storage path when a signer is provided", () => {
|
|
95
|
-
const secret = "test-master-secret-at-least-32-chars-long";
|
|
96
|
-
const url = buildContentUrl({
|
|
97
|
-
domain: "https://api.example.test",
|
|
98
|
-
accountConfigId: "cfg-alice",
|
|
99
|
-
accountId: "acc-alice",
|
|
100
|
-
messageId: "msg-1",
|
|
101
|
-
partPath: "1.2",
|
|
102
|
-
sign: createContentSigner(secret),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const parsed = new URL(url);
|
|
106
|
-
const exp = parsed.searchParams.get("exp");
|
|
107
|
-
const sig = parsed.searchParams.get("sig");
|
|
108
|
-
assert.ok(exp);
|
|
109
|
-
assert.ok(sig);
|
|
110
|
-
// The signature must verify against the same decoded storage path the
|
|
111
|
-
// `/content` route recomputes from `req.path`.
|
|
112
|
-
const relativePath = parsed.pathname.replace(/^\/content\//, "");
|
|
113
|
-
const result = verifyContentSignature(
|
|
114
|
-
relativePath,
|
|
115
|
-
exp,
|
|
116
|
-
sig,
|
|
117
|
-
secret,
|
|
118
|
-
Math.floor(Date.now() / 1000),
|
|
119
|
-
);
|
|
120
|
-
assert.deepEqual(result, { valid: true });
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("nests accountConfigId before accountId so the Lambda@Edge prefix check matches", () => {
|
|
124
|
-
const url = buildContentUrl({
|
|
125
|
-
domain: "https://cdn.test",
|
|
126
|
-
accountConfigId: "CFG",
|
|
127
|
-
accountId: "ACC",
|
|
128
|
-
messageId: "MSG",
|
|
129
|
-
partPath: "1",
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const segments = new URL(url).pathname.split("/");
|
|
133
|
-
// ["", "content", "accounts", "CFG", "ACC", "messages", "MSG", "parts", "1"]
|
|
134
|
-
assert.equal(segments[1], "content");
|
|
135
|
-
assert.equal(segments[2], "accounts");
|
|
136
|
-
assert.equal(segments[3], "CFG");
|
|
137
|
-
assert.equal(segments[4], "ACC");
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe("getContentDeliveryDomain", () => {
|
|
142
|
-
const ORIGINAL = process.env.CONTENT_DELIVERY_DOMAIN;
|
|
143
|
-
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
delete process.env.CONTENT_DELIVERY_DOMAIN;
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
afterEach(() => {
|
|
149
|
-
if (ORIGINAL === undefined) delete process.env.CONTENT_DELIVERY_DOMAIN;
|
|
150
|
-
else process.env.CONTENT_DELIVERY_DOMAIN = ORIGINAL;
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("throws when CONTENT_DELIVERY_DOMAIN is unset (fail loud over silent placeholder, #299)", () => {
|
|
154
|
-
assert.throws(
|
|
155
|
-
() => getContentDeliveryDomain(),
|
|
156
|
-
/CONTENT_DELIVERY_DOMAIN is not set/,
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("throws when CONTENT_DELIVERY_DOMAIN is empty", () => {
|
|
161
|
-
process.env.CONTENT_DELIVERY_DOMAIN = "";
|
|
162
|
-
assert.throws(
|
|
163
|
-
() => getContentDeliveryDomain(),
|
|
164
|
-
/CONTENT_DELIVERY_DOMAIN is not set/,
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("returns the env var value when set", () => {
|
|
169
|
-
process.env.CONTENT_DELIVERY_DOMAIN = "https://abc.cloudfront.net";
|
|
170
|
-
assert.equal(getContentDeliveryDomain(), "https://abc.cloudfront.net");
|
|
171
|
-
});
|
|
172
|
-
});
|
package/src/derive/contentUrl.ts
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Build the public CloudFront URL that fetches a single body part for a
|
|
3
|
-
* message. The path layout is what the Lambda@Edge JWT verifier expects
|
|
4
|
-
* (PR 1 of #224): `/content/accounts/{accountConfigId}/{accountId}/messages/
|
|
5
|
-
* {messageId}/parts/{partPath}`.
|
|
6
|
-
*
|
|
7
|
-
* The function is intentionally pure — domain comes from configuration so
|
|
8
|
-
* tests can pin the output without env vars. `partPath` is passed through
|
|
9
|
-
* `encodeURIComponent` because IMAP section paths are dot-separated digits
|
|
10
|
-
* (e.g. `1`, `1.2`, `2.1.3`) but a defensive encode keeps the builder safe
|
|
11
|
-
* if the path ever carries unexpected characters.
|
|
12
|
-
*/
|
|
13
|
-
import type { ContentSigner } from "./contentSignature.js";
|
|
14
|
-
|
|
15
|
-
export interface BuildContentUrlInput {
|
|
16
|
-
domain: string;
|
|
17
|
-
accountConfigId: string;
|
|
18
|
-
accountId: string;
|
|
19
|
-
messageId: string;
|
|
20
|
-
partPath: string;
|
|
21
|
-
/**
|
|
22
|
-
* When present (self-host stack), append an `exp`/`sig` signature scoped to
|
|
23
|
-
* this account's storage path. Absent on AWS, where Lambda@Edge guards the
|
|
24
|
-
* URL and the path stays unsigned. See `contentSignature.ts`.
|
|
25
|
-
*/
|
|
26
|
-
sign?: ContentSigner;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface BuildStorageContentUrlInput {
|
|
30
|
-
domain: string;
|
|
31
|
-
storageKey: string;
|
|
32
|
-
sign?: ContentSigner;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export const buildStorageContentUrl = (
|
|
36
|
-
input: BuildStorageContentUrlInput,
|
|
37
|
-
): string => {
|
|
38
|
-
const { domain, storageKey, sign } = input;
|
|
39
|
-
const normalizedDomain = domain.replace(/\/+$/, "");
|
|
40
|
-
const base = normalizedDomain.startsWith("http")
|
|
41
|
-
? normalizedDomain
|
|
42
|
-
: `https://${normalizedDomain}`;
|
|
43
|
-
// The signed message is the decoded, account-scoped storage path — the same
|
|
44
|
-
// value the `/content` route recomputes from `req.path`. Keep it in lockstep
|
|
45
|
-
// with the verifier's `req.path.replace(/^\/content\//, "")`.
|
|
46
|
-
const safeKey = storageKey
|
|
47
|
-
.split("/")
|
|
48
|
-
.map((segment) => encodeURIComponent(segment))
|
|
49
|
-
.join("/");
|
|
50
|
-
const url = `${base}/content/${safeKey}`;
|
|
51
|
-
if (!sign) return url;
|
|
52
|
-
const { exp, sig } = sign(storageKey);
|
|
53
|
-
return `${url}?exp=${exp}&sig=${encodeURIComponent(sig)}`;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export const buildContentUrl = (input: BuildContentUrlInput): string => {
|
|
57
|
-
const { domain, accountConfigId, accountId, messageId, partPath, sign } =
|
|
58
|
-
input;
|
|
59
|
-
return buildStorageContentUrl({
|
|
60
|
-
domain,
|
|
61
|
-
storageKey: `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${partPath}`,
|
|
62
|
-
sign,
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Read the CloudFront distribution domain from the environment. The CDK
|
|
68
|
-
* wires `CONTENT_DELIVERY_DOMAIN` onto the API Lambda from the
|
|
69
|
-
* ContentDelivery stack's published SSM parameter (`/{stage}/Remit/frontendUrl`).
|
|
70
|
-
*
|
|
71
|
-
* Throws when the env var is missing or empty. `BodyPartResponse.contentUrl`
|
|
72
|
-
* is required + URL-formatted in the API contract, so silently emitting a
|
|
73
|
-
* placeholder string would ship a contract lie to clients. Failing loud here
|
|
74
|
-
* surfaces the missing wire-up as a 500 the deploy team can act on (#299).
|
|
75
|
-
* Production synth hard-codes a syntactically-valid placeholder URL via
|
|
76
|
-
* `infra/bin/infra.ts` so this only fires when the env is actually unset.
|
|
77
|
-
*/
|
|
78
|
-
export const getContentDeliveryDomain = (): string => {
|
|
79
|
-
const value = process.env.CONTENT_DELIVERY_DOMAIN;
|
|
80
|
-
if (!value || value.length === 0) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
"CONTENT_DELIVERY_DOMAIN is not set; BodyPartResponse.contentUrl cannot be built. Wire the CloudFront distribution domain via the API Lambda env (see infra/bin/infra.ts).",
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
return value;
|
|
86
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { describe, it } from "node:test";
|
|
3
|
-
import type { AddressFlags } from "@remit/api-openapi-types";
|
|
4
|
-
import { deriveMuted } from "./deriveMuted.js";
|
|
5
|
-
|
|
6
|
-
const SET_AT = 1_700_000_000_000;
|
|
7
|
-
|
|
8
|
-
describe("deriveMuted", () => {
|
|
9
|
-
it("returns false when flags is undefined", () => {
|
|
10
|
-
assert.equal(deriveMuted(undefined), false);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("returns false for an empty flags object", () => {
|
|
14
|
-
assert.equal(deriveMuted({}), false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("returns false when muted.value is false", () => {
|
|
18
|
-
const flags: AddressFlags = { muted: { value: false, setAt: SET_AT } };
|
|
19
|
-
assert.equal(deriveMuted(flags), false);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("returns true when muted.value is true", () => {
|
|
23
|
-
const flags: AddressFlags = { muted: { value: true, setAt: SET_AT } };
|
|
24
|
-
assert.equal(deriveMuted(flags), true);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("ignores other flags (orthogonal axis)", () => {
|
|
28
|
-
const flags: AddressFlags = {
|
|
29
|
-
vip: { value: true, setAt: SET_AT },
|
|
30
|
-
wellknown: { value: true, setAt: SET_AT },
|
|
31
|
-
};
|
|
32
|
-
assert.equal(deriveMuted(flags), false);
|
|
33
|
-
});
|
|
34
|
-
});
|