@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,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map an Address row to the wire-format `VipSuggestionEntry` returned by
|
|
3
|
+
* `GET /me/vip-suggestions` (issue #234). Absent counters are normalised to
|
|
4
|
+
* zero so the UI can render `12 received · 4 sent · 2 replies` without a
|
|
5
|
+
* `?? 0` fallback at every render site.
|
|
6
|
+
*/
|
|
7
|
+
export const toVipSuggestionEntry = (item) => ({
|
|
8
|
+
addressId: item.addressId,
|
|
9
|
+
displayName: item.displayName,
|
|
10
|
+
normalizedEmail: item.normalizedEmail,
|
|
11
|
+
inboundCount: item.inboundCount ?? 0,
|
|
12
|
+
outboundCount: item.outboundCount ?? 0,
|
|
13
|
+
replyCount: item.replyCount ?? 0,
|
|
14
|
+
lastInboundAt: item.lastInboundAt,
|
|
15
|
+
lastReplyAt: item.lastReplyAt,
|
|
16
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { OpenAPISpec } from "./api.js";
|
|
2
|
+
export { configFromEnv } from "./config/from-env.js";
|
|
3
|
+
export { createApiEventHandler } from "./lambda.js";
|
|
4
|
+
export declare const handleApiEvent: import("./lambda.js").ApiEventHandler;
|
|
5
|
+
export declare const handler: (event: import("aws-lambda").APIGatewayProxyEvent, context: import("aws-lambda").Context) => Promise<import("aws-lambda").APIGatewayProxyResult>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { inspect } from "node:util";
|
|
2
|
+
import { logger } from "@remit/logger";
|
|
3
|
+
import { installNodeLogger, withTelemetry } from "@remit/logger-lambda/node";
|
|
4
|
+
import { assertLocalBypassNotInDeployedEnv, localIdentityFromEnv, } from "./auth.js";
|
|
5
|
+
import { configFromEnv } from "./config/from-env.js";
|
|
6
|
+
import { handlers, publicHandlers } from "./handlers/index.js";
|
|
7
|
+
import { createApiEventHandler } from "./lambda.js";
|
|
8
|
+
import { serveApi } from "./serve.js";
|
|
9
|
+
import { getClient } from "./service/data-client.js";
|
|
10
|
+
installNodeLogger();
|
|
11
|
+
// Boot-time refusal: the local auth-bypass overrides must never be set in a
|
|
12
|
+
// deployed environment, so a leaked env var fails the process at startup
|
|
13
|
+
// rather than silently opening authentication at runtime.
|
|
14
|
+
assertLocalBypassNotInDeployedEnv(process.env);
|
|
15
|
+
export { OpenAPISpec } from "./api.js";
|
|
16
|
+
export { configFromEnv } from "./config/from-env.js";
|
|
17
|
+
export { createApiEventHandler } from "./lambda.js";
|
|
18
|
+
export const handleApiEvent = createApiEventHandler(serveApi({ handlers, publicHandlers }), {
|
|
19
|
+
config: configFromEnv(process.env),
|
|
20
|
+
localIdentity: localIdentityFromEnv(process.env),
|
|
21
|
+
client: getClient,
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Last-resort net for a leaked background rejection.
|
|
25
|
+
*
|
|
26
|
+
* Every fire-and-forget side effect is meant to be contained at its source (see
|
|
27
|
+
* service/fire-and-forget.ts), so reaching here is a bug — an uncontained
|
|
28
|
+
* `void promise` somewhere. The API process (dev-server / warm Lambda container)
|
|
29
|
+
* shares one event loop across requests, so without this handler such a leak
|
|
30
|
+
* would surface as a spurious 500 on whatever request was in flight. We log it
|
|
31
|
+
* LOUDLY with a distinct alert so it is observable, and deliberately do NOT exit
|
|
32
|
+
* — a stray background rejection must never take the API down or fail a request.
|
|
33
|
+
* (The worker process intentionally has no such net: there each message is its
|
|
34
|
+
* own invocation and a rejection should crash it.)
|
|
35
|
+
*/
|
|
36
|
+
const unhandledRejectionRegistered = Symbol.for("remit.backend.unhandledRejectionNet");
|
|
37
|
+
const globalProcess = process;
|
|
38
|
+
if (!globalProcess[unhandledRejectionRegistered]) {
|
|
39
|
+
globalProcess[unhandledRejectionRegistered] = true;
|
|
40
|
+
process.on("unhandledRejection", (reason) => {
|
|
41
|
+
logger.error({
|
|
42
|
+
alert: "unhandled_rejection",
|
|
43
|
+
errorName: reason?.name,
|
|
44
|
+
errorCode: reason?.Code ??
|
|
45
|
+
reason?.code,
|
|
46
|
+
error: inspect(reason),
|
|
47
|
+
}, "Unhandled promise rejection leaked into the API event loop (contained, request not failed)");
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// The Lambda and Node entry point. `withTelemetry` installs the Node logger and
|
|
51
|
+
// its process metrics; a host without Node's process APIs imports
|
|
52
|
+
// `@remit/backend/api` instead and plugs in its own logger with `setLogger`.
|
|
53
|
+
export const handler = withTelemetry(handleApiEvent);
|
|
@@ -5,7 +5,4 @@
|
|
|
5
5
|
* absence (e.g. a third-party token, a dev-server response body) — never to
|
|
6
6
|
* soften an internal contract, which must stay a fatal uncaught `JSON.parse`.
|
|
7
7
|
*/
|
|
8
|
-
export const safeJsonParse
|
|
9
|
-
new Promise((resolve) => {
|
|
10
|
-
resolve(JSON.parse(raw));
|
|
11
|
-
});
|
|
8
|
+
export declare const safeJsonParse: <T>(raw: string) => Promise<T>;
|
package/dist/src/json.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse JSON in a Promise so a malformed string surfaces as a rejection the
|
|
3
|
+
* caller can `.catch()`, instead of a synchronous throw that would need a block
|
|
4
|
+
* try/catch. Use only where an unparseable value is an expected, recoverable
|
|
5
|
+
* absence (e.g. a third-party token, a dev-server response body) — never to
|
|
6
|
+
* soften an internal contract, which must stay a fatal uncaught `JSON.parse`.
|
|
7
|
+
*/
|
|
8
|
+
export const safeJsonParse = (raw) => new Promise((resolve) => {
|
|
9
|
+
resolve(JSON.parse(raw));
|
|
10
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type JwtVerifier } from "@remit/auth-service/verifier";
|
|
2
|
+
import type { APIGatewayProxyEvent } from "aws-lambda";
|
|
3
|
+
import type { HandlerIdentity } from "./types.js";
|
|
4
|
+
/** Test-only override for the memoized verifier. Pass null to reset. */
|
|
5
|
+
export declare const _setVerifierForTest: (v: JwtVerifier | null) => void;
|
|
6
|
+
/**
|
|
7
|
+
* The caller of a self-host request, from a better-auth RS256 JWT.
|
|
8
|
+
*
|
|
9
|
+
* Claims the edge tier (APISIX) already verified and injected are trusted as
|
|
10
|
+
* they stand. Otherwise a bearer token is verified here; a token that fails
|
|
11
|
+
* verification is an anonymous caller, so the operation table answers 401. With
|
|
12
|
+
* no token the local bypass, when one is configured, is the caller. Only an
|
|
13
|
+
* operation that needs a caller resolves this, so a public route never does.
|
|
14
|
+
*/
|
|
15
|
+
export declare const identityFromSelfHostRequest: (event: APIGatewayProxyEvent, localIdentity: HandlerIdentity) => Promise<HandlerIdentity>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createJwtVerifier, extractBearerToken, resolveVerifierConfig, } from "@remit/auth-service/verifier";
|
|
2
|
+
import { logger } from "@remit/logger";
|
|
3
|
+
import { getIdentityFromEvent, userFromClaims } from "./auth.js";
|
|
4
|
+
let verifier = null;
|
|
5
|
+
const getVerifier = () => {
|
|
6
|
+
if (!verifier)
|
|
7
|
+
verifier = createJwtVerifier(resolveVerifierConfig());
|
|
8
|
+
return verifier;
|
|
9
|
+
};
|
|
10
|
+
/** Test-only override for the memoized verifier. Pass null to reset. */
|
|
11
|
+
export const _setVerifierForTest = (v) => {
|
|
12
|
+
verifier = v;
|
|
13
|
+
};
|
|
14
|
+
const readHeader = (headers, name) => {
|
|
15
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
16
|
+
if (key.toLowerCase() === name && typeof value === "string")
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* The caller of a self-host request, from a better-auth RS256 JWT.
|
|
23
|
+
*
|
|
24
|
+
* Claims the edge tier (APISIX) already verified and injected are trusted as
|
|
25
|
+
* they stand. Otherwise a bearer token is verified here; a token that fails
|
|
26
|
+
* verification is an anonymous caller, so the operation table answers 401. With
|
|
27
|
+
* no token the local bypass, when one is configured, is the caller. Only an
|
|
28
|
+
* operation that needs a caller resolves this, so a public route never does.
|
|
29
|
+
*/
|
|
30
|
+
export const identityFromSelfHostRequest = async (event, localIdentity) => {
|
|
31
|
+
const existingSub = event.requestContext?.authorizer?.claims?.sub;
|
|
32
|
+
if (typeof existingSub === "string" && existingSub.length > 0) {
|
|
33
|
+
return getIdentityFromEvent(event, localIdentity);
|
|
34
|
+
}
|
|
35
|
+
const token = extractBearerToken(readHeader(event.headers, "authorization"));
|
|
36
|
+
if (!token)
|
|
37
|
+
return localIdentity;
|
|
38
|
+
const claims = await getVerifier()(token).catch((err) => {
|
|
39
|
+
logger.warn({ err: err instanceof Error ? err.message : String(err) }, "better-auth JWT verification failed");
|
|
40
|
+
return null;
|
|
41
|
+
});
|
|
42
|
+
if (!claims)
|
|
43
|
+
return { kind: "anonymous" };
|
|
44
|
+
return userFromClaims(claims.sub, claims.email);
|
|
45
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
|
|
2
|
+
import type { ApiConfig } from "./config/api-config.js";
|
|
3
|
+
import type { ServedApi } from "./serve.js";
|
|
4
|
+
import type { RemitClient } from "./service/data-client.js";
|
|
5
|
+
import type { QueueName } from "./service/queues.js";
|
|
6
|
+
import type { HandlerIdentity } from "./types.js";
|
|
7
|
+
export interface ApiEventHandlerOptions<K extends QueueName> {
|
|
8
|
+
config: ApiConfig;
|
|
9
|
+
localIdentity: HandlerIdentity;
|
|
10
|
+
client: () => Promise<RemitClient<K>>;
|
|
11
|
+
}
|
|
12
|
+
export type ApiEventHandler = (event: APIGatewayProxyEvent, context: Context) => Promise<APIGatewayProxyResult>;
|
|
13
|
+
export declare const createApiEventHandler: <K extends QueueName>(served: ServedApi<K>, { config, localIdentity, client }: ApiEventHandlerOptions<NoInfer<K>>) => ApiEventHandler;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getIdentityFromEvent } from "./auth.js";
|
|
2
|
+
import { identityFromSelfHostRequest } from "./jwt-auth.js";
|
|
3
|
+
import { normalizeRequest } from "./request.js";
|
|
4
|
+
const queryOf = (event) => {
|
|
5
|
+
if (event.multiValueQueryStringParameters) {
|
|
6
|
+
return event.multiValueQueryStringParameters;
|
|
7
|
+
}
|
|
8
|
+
return Object.fromEntries(Object.entries(event.queryStringParameters ?? {}).map(([key, value]) => [
|
|
9
|
+
key,
|
|
10
|
+
value === undefined ? undefined : [value],
|
|
11
|
+
]));
|
|
12
|
+
};
|
|
13
|
+
const headersOf = (event) => ({
|
|
14
|
+
...event.headers,
|
|
15
|
+
...Object.fromEntries(Object.entries(event.multiValueHeaders ?? {}).map(([name, values]) => [
|
|
16
|
+
name,
|
|
17
|
+
values?.join(", "),
|
|
18
|
+
])),
|
|
19
|
+
});
|
|
20
|
+
const normalizeApiGatewayEvent = (event) => normalizeRequest({
|
|
21
|
+
method: event.httpMethod,
|
|
22
|
+
path: event.path,
|
|
23
|
+
query: queryOf(event),
|
|
24
|
+
headers: headersOf(event),
|
|
25
|
+
body: event.body,
|
|
26
|
+
});
|
|
27
|
+
export const createApiEventHandler = (served, { config, localIdentity, client }) => (event, context) => served.dispatch(normalizeApiGatewayEvent(event), {
|
|
28
|
+
config,
|
|
29
|
+
client,
|
|
30
|
+
identity: async () => config.auth.mode === "BetterAuthJwt"
|
|
31
|
+
? identityFromSelfHostRequest(event, localIdentity)
|
|
32
|
+
: getIdentityFromEvent(event, localIdentity),
|
|
33
|
+
}, context.awsRequestId);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Request } from "openapi-backend";
|
|
2
|
+
export interface HostRequest {
|
|
3
|
+
method: string;
|
|
4
|
+
path: string;
|
|
5
|
+
query: Readonly<Record<string, readonly string[] | undefined>> | null;
|
|
6
|
+
headers: Readonly<Record<string, string | undefined>> | null;
|
|
7
|
+
body: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare const normalizeRequest: (request: HostRequest) => Request;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const definedHeaders = (map) => Object.fromEntries(Object.entries(map ?? {}).filter((entry) => entry[1] !== undefined));
|
|
2
|
+
const collapsedQuery = (map) => {
|
|
3
|
+
const query = {};
|
|
4
|
+
for (const [key, values] of Object.entries(map ?? {})) {
|
|
5
|
+
if (!values || values.length === 0)
|
|
6
|
+
continue;
|
|
7
|
+
query[key] = values.length === 1 ? values[0] : [...values];
|
|
8
|
+
}
|
|
9
|
+
return query;
|
|
10
|
+
};
|
|
11
|
+
export const normalizeRequest = (request) => ({
|
|
12
|
+
method: request.method,
|
|
13
|
+
path: request.path,
|
|
14
|
+
query: collapsedQuery(request.query),
|
|
15
|
+
body: request.body === "" ? null : request.body,
|
|
16
|
+
headers: definedHeaders(request.headers),
|
|
17
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { APIGatewayProxyResult } from "aws-lambda";
|
|
2
|
+
import type { Context as OpenAPIContext } from "openapi-backend";
|
|
3
|
+
import type { ApiConfig } from "./config/api-config.js";
|
|
4
|
+
declare const RAW_API_RESPONSE: unique symbol;
|
|
5
|
+
/**
|
|
6
|
+
* A response the caller reads by something other than its JSON body — an
|
|
7
|
+
* iCalendar feed, an OAuth redirect's Location, and whatever later joins them.
|
|
8
|
+
*
|
|
9
|
+
* Every other handler returns a plain object and lets this module decide the
|
|
10
|
+
* status, the content type and the serialization. That is the right default and
|
|
11
|
+
* stays the default; a handler that has to own its own media type or headers
|
|
12
|
+
* says so with `rawApiResponse` rather than by returning a shape this one has
|
|
13
|
+
* to guess at.
|
|
14
|
+
* `dispatch` still adds the envelope headers, so a raw response is not a way
|
|
15
|
+
* around them.
|
|
16
|
+
*/
|
|
17
|
+
export interface RawApiResponse {
|
|
18
|
+
statusCode: number;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
body: string;
|
|
21
|
+
}
|
|
22
|
+
type MarkedRawApiResponse = RawApiResponse & {
|
|
23
|
+
readonly [RAW_API_RESPONSE]: true;
|
|
24
|
+
};
|
|
25
|
+
export declare const rawApiResponse: (response: RawApiResponse) => MarkedRawApiResponse;
|
|
26
|
+
/**
|
|
27
|
+
* The headers every response carries whatever its body is: the CORS grant the
|
|
28
|
+
* browser needs, and the id the request's log lines are already tagged with.
|
|
29
|
+
* Returning that id is what makes a bug report's "correlation id" resolve to a
|
|
30
|
+
* server-side line; the browser can only read a non-safelisted header when it
|
|
31
|
+
* is exposed.
|
|
32
|
+
*/
|
|
33
|
+
export interface EnvelopeInput {
|
|
34
|
+
origin: string | undefined;
|
|
35
|
+
allowedOrigins: readonly string[];
|
|
36
|
+
correlationId: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const resolveAllowedOrigin: (origin: string | undefined, allowed: readonly string[]) => string;
|
|
39
|
+
export declare const envelopeHeaders: ({ origin, allowedOrigins, correlationId, }: EnvelopeInput) => Record<string, string>;
|
|
40
|
+
export declare const formatResponse: (body: Record<string, unknown>, statusCode?: number) => APIGatewayProxyResult;
|
|
41
|
+
export declare const postResponseHandler: (context: OpenAPIContext, scope: {
|
|
42
|
+
config: ApiConfig;
|
|
43
|
+
}) => APIGatewayProxyResult;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { logger } from "@remit/logger";
|
|
2
|
+
const RAW_API_RESPONSE = Symbol("remit.rawApiResponse");
|
|
3
|
+
export const rawApiResponse = (response) => Object.assign(response, { [RAW_API_RESPONSE]: true });
|
|
4
|
+
const isRawApiResponse = (body) => RAW_API_RESPONSE in body;
|
|
5
|
+
export const resolveAllowedOrigin = (origin, allowed) => {
|
|
6
|
+
if (allowed.length === 0)
|
|
7
|
+
return "*";
|
|
8
|
+
if (origin && allowed.includes(origin))
|
|
9
|
+
return origin;
|
|
10
|
+
return allowed[0];
|
|
11
|
+
};
|
|
12
|
+
export const envelopeHeaders = ({ origin, allowedOrigins, correlationId, }) => {
|
|
13
|
+
const allowOrigin = resolveAllowedOrigin(origin, allowedOrigins);
|
|
14
|
+
const headers = {
|
|
15
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
16
|
+
"Access-Control-Allow-Headers": "Authorization,Content-Type",
|
|
17
|
+
"Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
18
|
+
Vary: "Origin",
|
|
19
|
+
};
|
|
20
|
+
if (allowOrigin !== "*") {
|
|
21
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
22
|
+
}
|
|
23
|
+
headers["x-correlation-id"] = correlationId;
|
|
24
|
+
headers["Access-Control-Expose-Headers"] = "x-correlation-id";
|
|
25
|
+
return headers;
|
|
26
|
+
};
|
|
27
|
+
export const formatResponse = (body, statusCode = 200) => {
|
|
28
|
+
if (body.statusCode && typeof body.statusCode === "number") {
|
|
29
|
+
statusCode = body.statusCode;
|
|
30
|
+
}
|
|
31
|
+
if (isRawApiResponse(body)) {
|
|
32
|
+
logger.debug({ statusCode: body.statusCode }, "response");
|
|
33
|
+
return {
|
|
34
|
+
statusCode: body.statusCode,
|
|
35
|
+
headers: body.headers,
|
|
36
|
+
body: body.body,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if ("body" in body && body.body && typeof body.body === "object") {
|
|
40
|
+
body = body.body;
|
|
41
|
+
}
|
|
42
|
+
logger.debug({ statusCode }, "response");
|
|
43
|
+
return {
|
|
44
|
+
statusCode: statusCode,
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
export const postResponseHandler = (context, scope) => {
|
|
50
|
+
const { api, response, operation } = context;
|
|
51
|
+
if (response.statusCode)
|
|
52
|
+
return formatResponse(response, response.statusCode);
|
|
53
|
+
if (scope.config.responseValidation === "Skip")
|
|
54
|
+
return formatResponse(response, 200);
|
|
55
|
+
const { valid, errors } = api.validateResponse(response, operation);
|
|
56
|
+
if (valid)
|
|
57
|
+
return formatResponse(response, 200);
|
|
58
|
+
const { operationId } = operation;
|
|
59
|
+
const errorDetails = {
|
|
60
|
+
errors,
|
|
61
|
+
operationId,
|
|
62
|
+
responseKeys: Object.keys(response),
|
|
63
|
+
};
|
|
64
|
+
errors?.forEach((error) => {
|
|
65
|
+
if (error.instancePath) {
|
|
66
|
+
const pathParts = error.instancePath.split("/").filter(Boolean);
|
|
67
|
+
let value = response;
|
|
68
|
+
for (const part of pathParts) {
|
|
69
|
+
value = value?.[part];
|
|
70
|
+
}
|
|
71
|
+
errorDetails[`problematicValue_${error.instancePath}`] = {
|
|
72
|
+
path: error.instancePath,
|
|
73
|
+
value: JSON.stringify(value).substring(0, 500),
|
|
74
|
+
error: error.message,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
logger.error(errorDetails, "Response validation failed");
|
|
79
|
+
return formatResponse(response, 200);
|
|
80
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { APIGatewayProxyResult } from "aws-lambda";
|
|
2
|
+
import type { Request as OpenAPIRequest } from "openapi-backend";
|
|
3
|
+
import type { ApiConfig } from "./config/api-config.js";
|
|
4
|
+
import type { RemitClient } from "./service/data-client.js";
|
|
5
|
+
import type { QueueName } from "./service/queues.js";
|
|
6
|
+
import type { HandlerIdentity, OperationHandler, OperationIds, PublicOperationHandler, PublicOperationIds } from "./types.js";
|
|
7
|
+
export type { ApiConfig } from "./config/api-config.js";
|
|
8
|
+
export type { HandlerContext, HandlerIdentity, PublicHandlerContext, UserIdentity, } from "./types.js";
|
|
9
|
+
export interface RequestHost<K extends QueueName> {
|
|
10
|
+
config: ApiConfig;
|
|
11
|
+
client: () => Promise<RemitClient<K>>;
|
|
12
|
+
identity: () => Promise<HandlerIdentity>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* The operations a host serves, each typed by the queues it sends to. An
|
|
16
|
+
* operation the host leaves out answers 501.
|
|
17
|
+
*/
|
|
18
|
+
export interface ApiRegistry<K extends QueueName> {
|
|
19
|
+
handlers: Partial<Record<Exclude<OperationIds, PublicOperationIds>, OperationHandler<OperationIds, K>>>;
|
|
20
|
+
publicHandlers: Partial<Record<PublicOperationIds, PublicOperationHandler<K>>>;
|
|
21
|
+
}
|
|
22
|
+
export interface ServedApi<K extends QueueName> {
|
|
23
|
+
dispatch: (request: OpenAPIRequest, host: RequestHost<K>, correlationId: string) => Promise<APIGatewayProxyResult>;
|
|
24
|
+
handleFetch: (request: Request, host: RequestHost<K>, correlationId: string) => Promise<Response>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register a host's operations and return the two ways in: a normalised
|
|
28
|
+
* request, or a Fetch `Request`. Each request's host supplies the client, and
|
|
29
|
+
* the client's queues must cover every operation in the registry.
|
|
30
|
+
*/
|
|
31
|
+
export declare const serveApi: <K extends QueueName>(registry: ApiRegistry<NoInfer<K>>) => ServedApi<K>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { redactCalendarFeedPath } from "@remit/calendar-service";
|
|
2
|
+
import { logger, withLogContext } from "@remit/logger";
|
|
3
|
+
import { api } from "./api.js";
|
|
4
|
+
import { defaultErrorCode, handleError } from "./error.js";
|
|
5
|
+
import { normalizeRequest } from "./request.js";
|
|
6
|
+
import { envelopeHeaders } from "./response.js";
|
|
7
|
+
const unauthenticated = () => ({
|
|
8
|
+
statusCode: 401,
|
|
9
|
+
body: { code: defaultErrorCode(401), message: "Unauthorized" },
|
|
10
|
+
});
|
|
11
|
+
const isDefined = (value) => value !== undefined;
|
|
12
|
+
const entriesOf = (record) => Object.entries(record).flatMap(([key, value]) => isDefined(value) ? [[key, value]] : []);
|
|
13
|
+
/**
|
|
14
|
+
* Register a host's operations and return the two ways in: a normalised
|
|
15
|
+
* request, or a Fetch `Request`. Each request's host supplies the client, and
|
|
16
|
+
* the client's queues must cover every operation in the registry.
|
|
17
|
+
*/
|
|
18
|
+
export const serveApi = (registry) => {
|
|
19
|
+
api.register({
|
|
20
|
+
...Object.fromEntries(entriesOf(registry.handlers).map(([operationId, handler]) => [
|
|
21
|
+
operationId,
|
|
22
|
+
async (c, host) => {
|
|
23
|
+
const identity = await host.identity();
|
|
24
|
+
if (identity.kind === "anonymous")
|
|
25
|
+
return unauthenticated();
|
|
26
|
+
return handler(c, {
|
|
27
|
+
client: await host.client(),
|
|
28
|
+
identity,
|
|
29
|
+
config: host.config,
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
])),
|
|
33
|
+
...Object.fromEntries(entriesOf(registry.publicHandlers).map(([operationId, handler]) => [
|
|
34
|
+
operationId,
|
|
35
|
+
async (c, host) => handler(c, { client: await host.client(), config: host.config }),
|
|
36
|
+
])),
|
|
37
|
+
});
|
|
38
|
+
return { dispatch, handleFetch };
|
|
39
|
+
};
|
|
40
|
+
const readHeader = (headers, name) => {
|
|
41
|
+
for (const [key, value] of Object.entries(headers ?? {})) {
|
|
42
|
+
if (key.toLowerCase() === name && typeof value === "string")
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
const dispatch = async (request, host, correlationId) => {
|
|
48
|
+
// A calendar feed carries its credential in the path, so the raw path is a
|
|
49
|
+
// secret and cannot be a log field (issue #1065). Redacted once, here, where
|
|
50
|
+
// every line inside the request picks it up from the scope.
|
|
51
|
+
const path = redactCalendarFeedPath(request.path);
|
|
52
|
+
const { method } = request;
|
|
53
|
+
return withLogContext({ path, method }, async () => {
|
|
54
|
+
logger.debug({ method, path }, "Request received");
|
|
55
|
+
const startedAt = Date.now();
|
|
56
|
+
const answered = await api
|
|
57
|
+
.handleRequest(request, host)
|
|
58
|
+
.catch(handleError);
|
|
59
|
+
const result = {
|
|
60
|
+
...answered,
|
|
61
|
+
headers: {
|
|
62
|
+
...envelopeHeaders({
|
|
63
|
+
origin: readHeader(request.headers, "origin"),
|
|
64
|
+
allowedOrigins: host.config.corsAllowedOrigins,
|
|
65
|
+
correlationId,
|
|
66
|
+
}),
|
|
67
|
+
...answered.headers,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
// One structured access line per request: method, path, status, and
|
|
71
|
+
// duration. The correlation id is inlined as a top-level field so a bug
|
|
72
|
+
// report quoting the id can grep logs by it directly, and emitted at
|
|
73
|
+
// info so it is on by default in production (the debug "Request
|
|
74
|
+
// received" above already filters out under normal log levels).
|
|
75
|
+
// biome-ignore lint/plugin/no-logger-info: one access line per request is an audit-grade signal (issue #1054)
|
|
76
|
+
logger.info({
|
|
77
|
+
method,
|
|
78
|
+
path,
|
|
79
|
+
statusCode: result.statusCode,
|
|
80
|
+
durationMs: Date.now() - startedAt,
|
|
81
|
+
correlationId,
|
|
82
|
+
}, "Request completed");
|
|
83
|
+
return result;
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
const queryOf = (params) => {
|
|
87
|
+
const query = {};
|
|
88
|
+
for (const [key, value] of params) {
|
|
89
|
+
query[key] = [...(query[key] ?? []), value];
|
|
90
|
+
}
|
|
91
|
+
return query;
|
|
92
|
+
};
|
|
93
|
+
const NULL_BODY_STATUSES = new Set([101, 103, 204, 205, 304]);
|
|
94
|
+
const toFetchResponse = (result) => {
|
|
95
|
+
const headers = new Headers();
|
|
96
|
+
for (const [name, value] of Object.entries(result.headers ?? {})) {
|
|
97
|
+
headers.set(name, String(value));
|
|
98
|
+
}
|
|
99
|
+
for (const [name, values] of Object.entries(result.multiValueHeaders ?? {})) {
|
|
100
|
+
for (const value of values)
|
|
101
|
+
headers.append(name, String(value));
|
|
102
|
+
}
|
|
103
|
+
const body = NULL_BODY_STATUSES.has(result.statusCode) ? null : result.body;
|
|
104
|
+
return new Response(body, { status: result.statusCode, headers });
|
|
105
|
+
};
|
|
106
|
+
const handleFetch = async (request, host, correlationId) => {
|
|
107
|
+
const url = new URL(request.url);
|
|
108
|
+
const body = request.body === null ? null : await request.text();
|
|
109
|
+
const result = await dispatch(normalizeRequest({
|
|
110
|
+
method: request.method,
|
|
111
|
+
path: url.pathname,
|
|
112
|
+
query: queryOf(url.searchParams),
|
|
113
|
+
headers: Object.fromEntries(request.headers),
|
|
114
|
+
body,
|
|
115
|
+
}), host, correlationId);
|
|
116
|
+
return toFetchResponse(result);
|
|
117
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AccountDataPurgeEvent, AccountItem, IAccountRepository, IConfigImportRepository, QueueProducer } from "@remit/data-ports";
|
|
2
|
+
import type { Logger } from "@remit/logger";
|
|
3
|
+
export interface AccountDeletionDeps {
|
|
4
|
+
account: Pick<IAccountRepository, "update">;
|
|
5
|
+
queue: QueueProducer<AccountDataPurgeEvent>;
|
|
6
|
+
log: Logger;
|
|
7
|
+
}
|
|
8
|
+
export declare const deleteAccount: (deps: AccountDeletionDeps, account: Pick<AccountItem, "accountId" | "accountConfigId">, deletedAt: number) => Promise<void>;
|
|
9
|
+
export interface HalfDeletedAccountDeps extends AccountDeletionDeps {
|
|
10
|
+
account: Pick<IAccountRepository, "update" | "listAll">;
|
|
11
|
+
configImport: Pick<IConfigImportRepository, "listByAccountConfig">;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Before #1384 a delete stored `isActive=false` and dropped `deletedAt`. Of the
|
|
15
|
+
* writers in this repo only that delete and a config import leave an account
|
|
16
|
+
* inactive, and an import names each account it creates in its stored
|
|
17
|
+
* document. The public update endpoint accepts `isActive` as sent, so a caller
|
|
18
|
+
* outside the repo can also leave one inactive.
|
|
19
|
+
*/
|
|
20
|
+
export declare const finishHalfDeletedAccounts: (deps: HalfDeletedAccountDeps) => Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readConfigDocument } from "@remit/config-format";
|
|
2
|
+
export const deleteAccount = async (deps, account, deletedAt) => {
|
|
3
|
+
const { accountId, accountConfigId } = account;
|
|
4
|
+
// Purge first: an account whose request never left still reads as not deleted, so it is found and asked for again.
|
|
5
|
+
await deps.queue.send({
|
|
6
|
+
body: { type: "AccountDataPurge", accountId, accountConfigId },
|
|
7
|
+
});
|
|
8
|
+
await deps.account.update(accountId, { deletedAt, isActive: false });
|
|
9
|
+
deps.log.info({ accountId, accountConfigId }, "Account data purge initiated");
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Before #1384 a delete stored `isActive=false` and dropped `deletedAt`. Of the
|
|
13
|
+
* writers in this repo only that delete and a config import leave an account
|
|
14
|
+
* inactive, and an import names each account it creates in its stored
|
|
15
|
+
* document. The public update endpoint accepts `isActive` as sent, so a caller
|
|
16
|
+
* outside the repo can also leave one inactive.
|
|
17
|
+
*/
|
|
18
|
+
export const finishHalfDeletedAccounts = async (deps) => {
|
|
19
|
+
const imported = new Map();
|
|
20
|
+
for (const account of await deps.account.listAll()) {
|
|
21
|
+
if (account.isActive || account.deletedAt !== undefined)
|
|
22
|
+
continue;
|
|
23
|
+
let names = imported.get(account.accountConfigId);
|
|
24
|
+
if (!names) {
|
|
25
|
+
const imports = await deps.configImport.listByAccountConfig(account.accountConfigId);
|
|
26
|
+
names = new Set(imports.flatMap((row) => readConfigDocument(row.document).accounts.map((a) => a.accountId)));
|
|
27
|
+
imported.set(account.accountConfigId, names);
|
|
28
|
+
}
|
|
29
|
+
if (names.has(account.accountId))
|
|
30
|
+
continue;
|
|
31
|
+
await deleteAccount(deps, account, Date.now());
|
|
32
|
+
}
|
|
33
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createSqliteRepositories, openSqliteDatabases, sqliteDatabasePathsFromEnv, } from "@remit/drizzle-service";
|
|
2
|
+
import { createRemitClient, } from "./create-remit-client.js";
|
|
3
|
+
import { buildSharedDeps, sqsQueues, sqsQueueUrlsFromEnv, } from "./shared-deps.js";
|
|
4
|
+
// The SQLite adapter composition (RFC 036). Every repo runs on the database
|
|
5
|
+
// that holds its tables: app, tenant mail or tenant search (#1420).
|
|
6
|
+
export const composeSqliteClient = async (shared) => {
|
|
7
|
+
const databases = await openSqliteDatabases(sqliteDatabasePathsFromEnv());
|
|
8
|
+
return createRemitClient({
|
|
9
|
+
repositories: createSqliteRepositories(databases),
|
|
10
|
+
...shared,
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
export const buildSqliteClient = () => composeSqliteClient(buildSharedDeps(sqsQueues(sqsQueueUrlsFromEnv())));
|