@remit/backend 0.0.144 → 0.0.146
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/src/api.d.ts +0 -2
- package/dist/src/api.js +9 -80
- package/dist/src/auth.d.ts +13 -19
- package/dist/src/auth.js +51 -43
- 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 +18 -29
- package/dist/src/config/msoauth.js +4 -28
- package/dist/src/derive/contentSignature.d.ts +3 -4
- package/dist/src/derive/contentSignature.js +4 -13
- package/dist/src/derive/contentUrl.d.ts +0 -13
- package/dist/src/derive/contentUrl.js +0 -19
- package/dist/src/handlers/account-oauth.d.ts +9 -3
- package/dist/src/handlers/account-oauth.js +197 -250
- package/dist/src/handlers/account.d.ts +13 -4
- package/dist/src/handlers/account.js +26 -39
- package/dist/src/handlers/address.d.ts +11 -3
- package/dist/src/handlers/address.js +11 -19
- package/dist/src/handlers/calendar-event.js +18 -26
- package/dist/src/handlers/calendar-feed.d.ts +2 -2
- package/dist/src/handlers/calendar-feed.js +24 -32
- package/dist/src/handlers/calendar-suggestion.js +18 -26
- package/dist/src/handlers/calendar.d.ts +1 -1
- package/dist/src/handlers/calendar.js +15 -22
- package/dist/src/handlers/config.d.ts +16 -6
- package/dist/src/handlers/config.js +23 -35
- package/dist/src/handlers/filter.d.ts +16 -3
- package/dist/src/handlers/filter.js +17 -32
- package/dist/src/handlers/folder-role.js +3 -6
- package/dist/src/handlers/index.d.ts +187 -2
- package/dist/src/handlers/index.js +6 -4
- package/dist/src/handlers/label.js +15 -22
- package/dist/src/handlers/mailbox.d.ts +20 -4
- package/dist/src/handlers/mailbox.js +18 -26
- package/dist/src/handlers/me.d.ts +10 -2
- package/dist/src/handlers/me.js +26 -47
- package/dist/src/handlers/message.d.ts +78 -6
- package/dist/src/handlers/message.js +34 -46
- package/dist/src/handlers/organize.d.ts +10 -4
- package/dist/src/handlers/organize.js +11 -20
- package/dist/src/handlers/outbox-attachment.d.ts +6 -5
- package/dist/src/handlers/outbox-attachment.js +11 -15
- package/dist/src/handlers/outbox.d.ts +25 -4
- package/dist/src/handlers/outbox.js +27 -35
- package/dist/src/handlers/sync.d.ts +19 -6
- package/dist/src/handlers/sync.js +13 -22
- package/dist/src/handlers/system-update.js +14 -27
- package/dist/src/handlers/thread.js +9 -14
- package/dist/src/handlers/unified-threads.js +3 -6
- package/dist/src/index.d.ts +4 -1
- package/dist/src/index.js +18 -2
- package/dist/src/jwt-auth.d.ts +9 -12
- package/dist/src/jwt-auth.js +19 -71
- package/dist/src/lambda.d.ts +13 -0
- package/dist/src/lambda.js +33 -0
- package/dist/src/request.d.ts +8 -2
- package/dist/src/request.js +16 -6
- package/dist/src/response.d.ts +20 -3
- package/dist/src/response.js +15 -22
- package/dist/src/serve.d.ts +31 -0
- package/dist/src/serve.js +117 -0
- package/dist/src/service/account-deletion.d.ts +2 -4
- package/dist/src/service/account-deletion.js +3 -9
- package/dist/src/service/compose-sqlite.d.ts +2 -1
- package/dist/src/service/compose-sqlite.js +5 -3
- package/dist/src/service/create-remit-client.d.ts +25 -11
- package/dist/src/service/create-remit-client.js +97 -116
- package/dist/src/service/data-client.js +3 -5
- package/dist/src/service/export-identity.d.ts +2 -1
- package/dist/src/service/export-identity.js +4 -6
- package/dist/src/service/fire-and-forget.js +1 -2
- package/dist/src/service/organize.d.ts +7 -10
- package/dist/src/service/organize.js +10 -27
- package/dist/src/service/queues.d.ts +12 -0
- package/dist/src/service/queues.js +1 -0
- package/dist/src/service/shared-deps.d.ts +6 -0
- package/dist/src/service/shared-deps.js +26 -0
- package/dist/src/service/trigger-sync.d.ts +11 -5
- package/dist/src/service/trigger-sync.js +10 -18
- package/dist/src/types.d.ts +24 -1
- package/package.json +24 -8
- package/dist/src/data-backend.d.ts +0 -12
- package/dist/src/data-backend.js +0 -12
- package/dist/src/request-context.d.ts +0 -9
- package/dist/src/request-context.js +0 -22
- package/dist/src/service/sqs.d.ts +0 -1
- package/dist/src/service/sqs.js +0 -3
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { BadRequestError, NotFoundError } from "@remit/data-ports/errors";
|
|
2
2
|
import { logger } from "@remit/logger";
|
|
3
3
|
import { ownedAccount, queueOrganizeJob, } from "@remit/mailbox-service";
|
|
4
|
-
import { env } from "expect-env";
|
|
5
|
-
import { getAccountConfigIdFromEvent, getSubFromEvent } from "../auth.js";
|
|
6
|
-
import { getClient } from "../service/data-client.js";
|
|
7
4
|
import { buildOrganizeMatchDeps, matchOrganize, ORGANIZE_MATCH_LIMIT, organizePredicateRejection, } from "../service/organize.js";
|
|
8
5
|
import { unwrap } from "../service/refusal.js";
|
|
9
6
|
/**
|
|
@@ -55,20 +52,16 @@ const requestedJob = async (client, owner, input) => {
|
|
|
55
52
|
return { ...owner, predicate };
|
|
56
53
|
};
|
|
57
54
|
export const OrganizeOperations = {
|
|
58
|
-
OrganizeOperations_createOrganizeJob: async (context,
|
|
59
|
-
const
|
|
60
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
55
|
+
OrganizeOperations_createOrganizeJob: async (context, ctx) => {
|
|
56
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
61
57
|
const { accountId } = context.request.params;
|
|
62
58
|
const input = context.request.requestBody;
|
|
63
|
-
const userId =
|
|
64
|
-
|
|
65
|
-
throw new Error("Missing Cognito `sub`: cannot attribute an organize job to a user");
|
|
66
|
-
}
|
|
67
|
-
const client = await getClient();
|
|
59
|
+
const userId = ctx.identity.sub;
|
|
60
|
+
const client = ctx.client;
|
|
68
61
|
await assertAccount(client, accountId, accountConfigId, "act");
|
|
69
62
|
const job = await queueOrganizeJob({
|
|
70
63
|
organizeJobRequest: client.organizeJobRequest,
|
|
71
|
-
|
|
64
|
+
queue: client.queues.accountFanout,
|
|
72
65
|
logger,
|
|
73
66
|
}, await requestedJob(client, { accountConfigId, userId }, input));
|
|
74
67
|
return {
|
|
@@ -77,12 +70,11 @@ export const OrganizeOperations = {
|
|
|
77
70
|
state: job.state,
|
|
78
71
|
};
|
|
79
72
|
},
|
|
80
|
-
OrganizeOperations_previewOrganize: async (context,
|
|
81
|
-
const
|
|
82
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
73
|
+
OrganizeOperations_previewOrganize: async (context, ctx) => {
|
|
74
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
83
75
|
const { accountId } = context.request.params;
|
|
84
76
|
const input = context.request.requestBody;
|
|
85
|
-
const client =
|
|
77
|
+
const client = ctx.client;
|
|
86
78
|
await assertAccount(client, accountId, accountConfigId, "read");
|
|
87
79
|
if (input.filterId) {
|
|
88
80
|
throw new BadRequestError("Preview takes a rule, not a saved filter. Run the filter to apply it.");
|
|
@@ -97,11 +89,10 @@ export const OrganizeOperations = {
|
|
|
97
89
|
},
|
|
98
90
|
};
|
|
99
91
|
export const OrganizeJobDetailOperations = {
|
|
100
|
-
OrganizeJobDetailOperations_getOrganizeJob: async (context,
|
|
101
|
-
const
|
|
102
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
92
|
+
OrganizeJobDetailOperations_getOrganizeJob: async (context, ctx) => {
|
|
93
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
103
94
|
const { accountId, organizeJobId } = context.request.params;
|
|
104
|
-
const client =
|
|
95
|
+
const client = ctx.client;
|
|
105
96
|
await assertAccount(client, accountId, accountConfigId, "read");
|
|
106
97
|
const job = await client.organizeJobRequest.get(organizeJobId);
|
|
107
98
|
if (job.accountConfigId !== accountConfigId) {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { MintOutboxAttachmentResponse, OutboxAttachmentRejection, OutboxAttachmentResponse } from "@remit/api-openapi-types";
|
|
2
2
|
import type { OutboxAttachmentItem } from "@remit/data-ports";
|
|
3
3
|
import type { Context } from "openapi-backend";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import type { ApiConfig } from "../config/api-config.js";
|
|
5
|
+
import type { HandlerContext } from "../types.js";
|
|
6
|
+
export declare const toOutboxAttachmentResponse: (config: ApiConfig) => (item: OutboxAttachmentItem) => OutboxAttachmentResponse;
|
|
7
|
+
export interface RejectionResponse {
|
|
6
8
|
statusCode: number;
|
|
7
9
|
body: OutboxAttachmentRejection;
|
|
8
10
|
}
|
|
@@ -12,12 +14,11 @@ interface RejectionResponse {
|
|
|
12
14
|
* lets the upload live on its own fleet and keeps this operation under any
|
|
13
15
|
* payload ceiling the REST tier has.
|
|
14
16
|
*/
|
|
15
|
-
export declare const mintOutboxAttachment: (context: Context,
|
|
17
|
+
export declare const mintOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<MintOutboxAttachmentResponse | RejectionResponse>;
|
|
16
18
|
/**
|
|
17
19
|
* Confirm the upload landed. The size in the response is read back from
|
|
18
20
|
* storage — on a deployment where the browser PUT straight to block storage
|
|
19
21
|
* this call is the first the API hears of the bytes, and the client's word for
|
|
20
22
|
* how many there are is not evidence.
|
|
21
23
|
*/
|
|
22
|
-
export declare const completeOutboxAttachment: (context: Context,
|
|
23
|
-
export {};
|
|
24
|
+
export declare const completeOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<OutboxAttachmentResponse | RejectionResponse>;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { OutboxAttachmentRejectionReason } from "@remit/domain-enums";
|
|
2
2
|
import { outboxAttachmentContentId } from "@remit/mailbox-service/outbox-attachment-content";
|
|
3
|
-
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
4
3
|
import { getContentSigner } from "../derive/contentSignature.js";
|
|
5
|
-
import { buildStorageContentUrl
|
|
6
|
-
import { getClient } from "../service/data-client.js";
|
|
4
|
+
import { buildStorageContentUrl } from "../derive/contentUrl.js";
|
|
7
5
|
// Every error body the API emits carries a `code` (issue #371). A refusal here
|
|
8
6
|
// is one kind, so the code is one value and `reason` says which refusal it was.
|
|
9
7
|
const ATTACHMENT_REJECTED_CODE = "attachment_rejected";
|
|
@@ -12,7 +10,7 @@ const TOO_LARGE = new Set([
|
|
|
12
10
|
OutboxAttachmentRejectionReason.MessageTooLarge,
|
|
13
11
|
OutboxAttachmentRejectionReason.TooManyAttachments,
|
|
14
12
|
]);
|
|
15
|
-
export const toOutboxAttachmentResponse = (item) => ({
|
|
13
|
+
export const toOutboxAttachmentResponse = (config) => (item) => ({
|
|
16
14
|
outboxAttachmentId: item.outboxAttachmentId,
|
|
17
15
|
outboxMessageId: item.outboxMessageId,
|
|
18
16
|
filename: item.filename,
|
|
@@ -21,9 +19,9 @@ export const toOutboxAttachmentResponse = (item) => ({
|
|
|
21
19
|
state: item.state,
|
|
22
20
|
contentId: outboxAttachmentContentId(item.outboxAttachmentId),
|
|
23
21
|
contentUrl: buildStorageContentUrl({
|
|
24
|
-
domain:
|
|
22
|
+
domain: config.contentDeliveryDomain,
|
|
25
23
|
storageKey: item.storageKey,
|
|
26
|
-
sign: getContentSigner(),
|
|
24
|
+
sign: getContentSigner(config),
|
|
27
25
|
}),
|
|
28
26
|
});
|
|
29
27
|
const refuse = (detail) => ({
|
|
@@ -42,12 +40,11 @@ const refuse = (detail) => ({
|
|
|
42
40
|
* lets the upload live on its own fleet and keeps this operation under any
|
|
43
41
|
* payload ceiling the REST tier has.
|
|
44
42
|
*/
|
|
45
|
-
export const mintOutboxAttachment = async (context,
|
|
46
|
-
const
|
|
47
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
43
|
+
export const mintOutboxAttachment = async (context, ctx) => {
|
|
44
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
48
45
|
const { outboxMessageId } = context.request.params;
|
|
49
46
|
const input = context.request.requestBody;
|
|
50
|
-
const client =
|
|
47
|
+
const client = ctx.client;
|
|
51
48
|
const result = await client.outboxAttachment.mint({
|
|
52
49
|
accountConfigId,
|
|
53
50
|
outboxMessageId,
|
|
@@ -65,11 +62,10 @@ export const mintOutboxAttachment = async (context, ...args) => {
|
|
|
65
62
|
* this call is the first the API hears of the bytes, and the client's word for
|
|
66
63
|
* how many there are is not evidence.
|
|
67
64
|
*/
|
|
68
|
-
export const completeOutboxAttachment = async (context,
|
|
69
|
-
const
|
|
70
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
65
|
+
export const completeOutboxAttachment = async (context, ctx) => {
|
|
66
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
71
67
|
const { outboxMessageId, outboxAttachmentId } = context.request.params;
|
|
72
|
-
const client =
|
|
68
|
+
const client = ctx.client;
|
|
73
69
|
const result = await client.outboxAttachment.complete({
|
|
74
70
|
accountConfigId,
|
|
75
71
|
outboxMessageId,
|
|
@@ -77,5 +73,5 @@ export const completeOutboxAttachment = async (context, ...args) => {
|
|
|
77
73
|
});
|
|
78
74
|
if (result.outcome === "Rejected")
|
|
79
75
|
return refuse(result.rejection);
|
|
80
|
-
return toOutboxAttachmentResponse(result.attachment);
|
|
76
|
+
return toOutboxAttachmentResponse(ctx.config)(result.attachment);
|
|
81
77
|
};
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export declare const
|
|
1
|
+
import type { OutboxMessageResponse } from "@remit/api-openapi-types";
|
|
2
|
+
import type { Context } from "openapi-backend";
|
|
3
|
+
import type { HandlerContext } from "../types.js";
|
|
4
|
+
export declare const OutboxOperations: {
|
|
5
|
+
OutboxOperations_createOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
|
|
6
|
+
OutboxOperations_listOutboxMessages: (context: Context, ctx: HandlerContext) => Promise<{
|
|
7
|
+
items: never[];
|
|
8
|
+
continuationToken: null;
|
|
9
|
+
} | {
|
|
10
|
+
items: import("@remit/api-openapi-types").Components.Schemas.OutboxMessageResponse[];
|
|
11
|
+
continuationToken: string | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
export declare const OutboxDetailOperations: {
|
|
15
|
+
OutboxDetailOperations_getOutboxMessage: (context: Context, ctx: HandlerContext) => Promise<OutboxMessageResponse>;
|
|
16
|
+
OutboxDetailOperations_updateOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
|
|
17
|
+
OutboxDetailOperations_deleteOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<{
|
|
18
|
+
statusCode: number;
|
|
19
|
+
}>;
|
|
20
|
+
OutboxDetailOperations_sendOutboxMessage: (context: Context, ctx: HandlerContext<"smtp">) => Promise<OutboxMessageResponse>;
|
|
21
|
+
OutboxDetailOperations_mintOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<import("@remit/api-openapi-types").MintOutboxAttachmentResponse | import("./outbox-attachment.js").RejectionResponse>;
|
|
22
|
+
};
|
|
23
|
+
export declare const OutboxAttachmentOperations: {
|
|
24
|
+
OutboxAttachmentOperations_completeOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<import("@remit/api-openapi-types").OutboxAttachmentResponse | import("./outbox-attachment.js").RejectionResponse>;
|
|
25
|
+
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ForbiddenError } from "@remit/data-ports/errors";
|
|
2
|
-
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
3
|
-
import { getClient } from "../service/data-client.js";
|
|
4
2
|
import { completeOutboxAttachment, mintOutboxAttachment, toOutboxAttachmentResponse, } from "./outbox-attachment.js";
|
|
5
|
-
const toOutboxMessageResponse = (item, attachments = []) => ({
|
|
6
|
-
attachments: attachments.map(toOutboxAttachmentResponse),
|
|
3
|
+
const toOutboxMessageResponse = (config, item, attachments = []) => ({
|
|
4
|
+
attachments: attachments.map(toOutboxAttachmentResponse(config)),
|
|
7
5
|
outboxMessageId: item.outboxMessageId,
|
|
8
6
|
accountId: item.accountId,
|
|
9
7
|
fromAddress: item.fromAddress,
|
|
@@ -23,11 +21,10 @@ const toOutboxMessageResponse = (item, attachments = []) => ({
|
|
|
23
21
|
updatedAt: item.updatedAt,
|
|
24
22
|
});
|
|
25
23
|
export const OutboxOperations = {
|
|
26
|
-
OutboxOperations_createOutboxMessage: async (
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const client = await getClient();
|
|
24
|
+
OutboxOperations_createOutboxMessage: async (context, ctx) => {
|
|
25
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
26
|
+
const input = context.request.requestBody;
|
|
27
|
+
const client = ctx.client;
|
|
31
28
|
const account = await client.account.get(input.accountId);
|
|
32
29
|
if (account.accountConfigId !== accountConfigId) {
|
|
33
30
|
throw new ForbiddenError(`Account ${input.accountId} not in account config`);
|
|
@@ -47,7 +44,7 @@ export const OutboxOperations = {
|
|
|
47
44
|
inReplyTo: input.inReplyTo,
|
|
48
45
|
references: input.references,
|
|
49
46
|
});
|
|
50
|
-
return toOutboxMessageResponse(outbox);
|
|
47
|
+
return toOutboxMessageResponse(ctx.config, outbox);
|
|
51
48
|
}
|
|
52
49
|
const outbox = await client.outboxQueue.createDraft({
|
|
53
50
|
accountId: input.accountId,
|
|
@@ -62,41 +59,38 @@ export const OutboxOperations = {
|
|
|
62
59
|
inReplyTo: input.inReplyTo,
|
|
63
60
|
references: input.references,
|
|
64
61
|
});
|
|
65
|
-
return toOutboxMessageResponse(outbox);
|
|
62
|
+
return toOutboxMessageResponse(ctx.config, outbox);
|
|
66
63
|
},
|
|
67
|
-
OutboxOperations_listOutboxMessages: async (context,
|
|
68
|
-
const
|
|
69
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
64
|
+
OutboxOperations_listOutboxMessages: async (context, ctx) => {
|
|
65
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
70
66
|
const { continuationToken } = context.request.query;
|
|
71
|
-
const client =
|
|
67
|
+
const client = ctx.client;
|
|
72
68
|
const accounts = await client.account.listAllByAccountConfig(accountConfigId);
|
|
73
69
|
if (accounts.length === 0) {
|
|
74
70
|
return { items: [], continuationToken: null };
|
|
75
71
|
}
|
|
76
72
|
const result = await client.outboxMessage.listByAccounts(accounts.map((account) => account.accountId), { continuationToken });
|
|
77
73
|
return {
|
|
78
|
-
items: result.items.map((item) => toOutboxMessageResponse(item)),
|
|
74
|
+
items: result.items.map((item) => toOutboxMessageResponse(ctx.config, item)),
|
|
79
75
|
continuationToken: result.continuationToken,
|
|
80
76
|
};
|
|
81
77
|
},
|
|
82
78
|
};
|
|
83
79
|
export const OutboxDetailOperations = {
|
|
84
|
-
OutboxDetailOperations_getOutboxMessage: async (context,
|
|
85
|
-
const
|
|
86
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
80
|
+
OutboxDetailOperations_getOutboxMessage: async (context, ctx) => {
|
|
81
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
87
82
|
const { outboxMessageId } = context.request.params;
|
|
88
|
-
const client =
|
|
83
|
+
const client = ctx.client;
|
|
89
84
|
// Default mode "read": the scoped get refuses a foreign message with
|
|
90
85
|
// NotFound (404, no existence leak) — no separate check needed.
|
|
91
86
|
const outbox = await client.outboxMessage.get(accountConfigId, outboxMessageId);
|
|
92
|
-
return toOutboxMessageResponse(outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
|
|
87
|
+
return toOutboxMessageResponse(ctx.config, outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
|
|
93
88
|
},
|
|
94
|
-
OutboxDetailOperations_updateOutboxMessage: async (context,
|
|
95
|
-
const
|
|
96
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
89
|
+
OutboxDetailOperations_updateOutboxMessage: async (context, ctx) => {
|
|
90
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
97
91
|
const { outboxMessageId } = context.request.params;
|
|
98
92
|
const input = context.request.requestBody;
|
|
99
|
-
const client =
|
|
93
|
+
const client = ctx.client;
|
|
100
94
|
// updateDraft's own scoped get uses mode "act": a foreign message is
|
|
101
95
|
// denied with Forbidden (403), not feigned as NotFound.
|
|
102
96
|
const updated = await client.outboxQueue.updateDraft(accountConfigId, outboxMessageId, {
|
|
@@ -115,27 +109,25 @@ export const OutboxDetailOperations = {
|
|
|
115
109
|
if (input.attachmentIds !== undefined) {
|
|
116
110
|
await client.outboxAttachment.retainOnly(accountConfigId, updated.accountId, outboxMessageId, input.attachmentIds);
|
|
117
111
|
}
|
|
118
|
-
return toOutboxMessageResponse(updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
|
|
112
|
+
return toOutboxMessageResponse(ctx.config, updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
|
|
119
113
|
},
|
|
120
|
-
OutboxDetailOperations_deleteOutboxMessage: async (context,
|
|
121
|
-
const
|
|
122
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
114
|
+
OutboxDetailOperations_deleteOutboxMessage: async (context, ctx) => {
|
|
115
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
123
116
|
const { outboxMessageId } = context.request.params;
|
|
124
|
-
const client =
|
|
117
|
+
const client = ctx.client;
|
|
125
118
|
// deleteDraft's own scoped get uses mode "act": a foreign message is
|
|
126
119
|
// denied with Forbidden (403), not feigned as NotFound.
|
|
127
120
|
await client.outboxQueue.deleteDraft(accountConfigId, outboxMessageId);
|
|
128
121
|
return { statusCode: 204 };
|
|
129
122
|
},
|
|
130
|
-
OutboxDetailOperations_sendOutboxMessage: async (context,
|
|
131
|
-
const
|
|
132
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
123
|
+
OutboxDetailOperations_sendOutboxMessage: async (context, ctx) => {
|
|
124
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
133
125
|
const { outboxMessageId } = context.request.params;
|
|
134
|
-
const client =
|
|
126
|
+
const client = ctx.client;
|
|
135
127
|
// send's own scoped get uses mode "act": a foreign message is denied
|
|
136
128
|
// with Forbidden (403), not feigned as NotFound.
|
|
137
129
|
const sent = await client.outboxQueue.send(accountConfigId, outboxMessageId);
|
|
138
|
-
return toOutboxMessageResponse(sent);
|
|
130
|
+
return toOutboxMessageResponse(ctx.config, sent);
|
|
139
131
|
},
|
|
140
132
|
OutboxDetailOperations_mintOutboxAttachment: mintOutboxAttachment,
|
|
141
133
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { FifoQueueProducer } from "@remit/data-ports";
|
|
2
2
|
import { type FireAndForgetLogger } from "../service/fire-and-forget.js";
|
|
3
|
-
import
|
|
3
|
+
import { type SyncMailboxesEvent } from "../service/trigger-sync.js";
|
|
4
|
+
import type { HandlerContext } from "../types.js";
|
|
4
5
|
interface SyncTriggerDeps {
|
|
5
|
-
|
|
6
|
-
queueUrl: string;
|
|
6
|
+
queue: FifoQueueProducer<SyncMailboxesEvent>;
|
|
7
7
|
logger: FireAndForgetLogger & {
|
|
8
8
|
info: (fields: Record<string, unknown>, message: string) => void;
|
|
9
9
|
};
|
|
@@ -23,6 +23,19 @@ interface SyncTriggerDeps {
|
|
|
23
23
|
* loudly with the alertable structured fields, and resolves to void. A genuine
|
|
24
24
|
* account-lookup / ownership failure still propagates and 500s as normal.
|
|
25
25
|
*/
|
|
26
|
-
export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps
|
|
27
|
-
export declare const SyncOperations:
|
|
26
|
+
export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps: SyncTriggerDeps) => Promise<void>;
|
|
27
|
+
export declare const SyncOperations: {
|
|
28
|
+
SyncOperations_triggerSync: (context: import("openapi-backend").Context<any, any, any, any, any, import("openapi-backend").Document>, ctx: HandlerContext<"sync">) => Promise<{
|
|
29
|
+
triggered: boolean;
|
|
30
|
+
message: string;
|
|
31
|
+
}>;
|
|
32
|
+
SyncOperations_getSyncStatus: (context: import("openapi-backend").Context<any, any, any, any, any, import("openapi-backend").Document>, ctx: HandlerContext) => Promise<{
|
|
33
|
+
accountId: string;
|
|
34
|
+
syncPhase: "error" | "idle" | "discovering_mailboxes" | "syncing_inbox" | "syncing_others" | "complete" | undefined;
|
|
35
|
+
mailboxCountTotal: number | undefined;
|
|
36
|
+
mailboxCountSynced: number | undefined;
|
|
37
|
+
lastSyncAt: number | undefined;
|
|
38
|
+
mailboxes: import("./sync-progress.js").MailboxSyncProgress[];
|
|
39
|
+
}>;
|
|
40
|
+
};
|
|
28
41
|
export {};
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
import { logger } from "@remit/logger";
|
|
2
2
|
import { ownedAccount } from "@remit/mailbox-service";
|
|
3
|
-
import { env } from "expect-env";
|
|
4
|
-
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
5
|
-
import { getClient } from "../service/data-client.js";
|
|
6
3
|
import { fireAndForget, } from "../service/fire-and-forget.js";
|
|
7
4
|
import { unwrap } from "../service/refusal.js";
|
|
8
|
-
import {
|
|
9
|
-
import { triggerAccountSync } from "../service/trigger-sync.js";
|
|
5
|
+
import { triggerAccountSync, } from "../service/trigger-sync.js";
|
|
10
6
|
import { toMailboxSyncProgress } from "./sync-progress.js";
|
|
11
|
-
const defaultSyncTriggerDeps = () => ({
|
|
12
|
-
sqsClient,
|
|
13
|
-
queueUrl: env.SQS_QUEUE_URL,
|
|
14
|
-
logger,
|
|
15
|
-
});
|
|
16
7
|
/**
|
|
17
8
|
* Fire-and-forget enqueue for the POST /sync kick.
|
|
18
9
|
*
|
|
@@ -28,11 +19,10 @@ const defaultSyncTriggerDeps = () => ({
|
|
|
28
19
|
* loudly with the alertable structured fields, and resolves to void. A genuine
|
|
29
20
|
* account-lookup / ownership failure still propagates and 500s as normal.
|
|
30
21
|
*/
|
|
31
|
-
export const triggerSyncSafe = async (accountId, accountConfigId, deps
|
|
22
|
+
export const triggerSyncSafe = async (accountId, accountConfigId, deps) => {
|
|
32
23
|
await fireAndForget(async () => {
|
|
33
24
|
const { eventId } = await triggerAccountSync({
|
|
34
|
-
|
|
35
|
-
queueUrl: deps.queueUrl,
|
|
25
|
+
queue: deps.queue,
|
|
36
26
|
accountId,
|
|
37
27
|
// POST /sync asks for a sync of this account by name — the refresh
|
|
38
28
|
// control, pull-to-refresh, and the client's automatic poll all land
|
|
@@ -49,23 +39,24 @@ export const triggerSyncSafe = async (accountId, accountConfigId, deps = default
|
|
|
49
39
|
});
|
|
50
40
|
};
|
|
51
41
|
export const SyncOperations = {
|
|
52
|
-
SyncOperations_triggerSync: async (context,
|
|
53
|
-
const
|
|
54
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
42
|
+
SyncOperations_triggerSync: async (context, ctx) => {
|
|
43
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
55
44
|
const { accountId } = context.request.params;
|
|
56
|
-
const account = await
|
|
45
|
+
const account = await ctx.client.account.get(accountId);
|
|
57
46
|
unwrap(ownedAccount(account, accountConfigId, "act"));
|
|
58
|
-
void triggerSyncSafe(account.accountId, accountConfigId
|
|
47
|
+
void triggerSyncSafe(account.accountId, accountConfigId, {
|
|
48
|
+
queue: ctx.client.queues.sync,
|
|
49
|
+
logger,
|
|
50
|
+
});
|
|
59
51
|
return {
|
|
60
52
|
triggered: true,
|
|
61
53
|
message: `Sync triggered for account ${accountId}`,
|
|
62
54
|
};
|
|
63
55
|
},
|
|
64
|
-
SyncOperations_getSyncStatus: async (context,
|
|
65
|
-
const
|
|
66
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
56
|
+
SyncOperations_getSyncStatus: async (context, ctx) => {
|
|
57
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
67
58
|
const { accountId } = context.request.params;
|
|
68
|
-
const client =
|
|
59
|
+
const client = ctx.client;
|
|
69
60
|
const account = await client.account.get(accountId);
|
|
70
61
|
unwrap(ownedAccount(account, accountConfigId, "read"));
|
|
71
62
|
// Collect all mailboxes (read-only — does not write or contend with sync lock)
|
|
@@ -2,15 +2,11 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { ConflictError, HTTPError } from "@remit/data-ports/errors";
|
|
5
|
-
import { getSubFromEvent } from "../auth.js";
|
|
6
5
|
import { defaultErrorCode } from "../error.js";
|
|
7
6
|
class TargetVersionMismatchError extends HTTPError {
|
|
8
7
|
name = "TargetVersionMismatchError";
|
|
9
8
|
statusCode = 422;
|
|
10
9
|
}
|
|
11
|
-
const DEFAULT_CONTROL_DIR = "/data/control";
|
|
12
|
-
const controlDir = () => process.env.REMIT_UPDATE_CONTROL_DIR ?? DEFAULT_CONTROL_DIR;
|
|
13
|
-
const manifestUrl = () => process.env.REMIT_UPDATE_MANIFEST_URL;
|
|
14
10
|
/**
|
|
15
11
|
* The running version is a fact the updater owns: it reads the tag from `.env`
|
|
16
12
|
* and writes it into `state.json`, which is the only honest source across an
|
|
@@ -33,24 +29,23 @@ const refuse = (statusCode, message) => ({
|
|
|
33
29
|
body: { code: defaultErrorCode(statusCode), message },
|
|
34
30
|
});
|
|
35
31
|
const notFound = () => refuse(404, "Not found");
|
|
36
|
-
const unauthorized = () => refuse(401, "Unauthorized");
|
|
37
32
|
/**
|
|
38
33
|
* The self-update seam is off unless a manifest URL is configured (RFC 037 D8),
|
|
39
34
|
* which the hosted deployment leaves unset. Both endpoints answer 404 in that
|
|
40
35
|
* case. Where it is on, any authenticated caller may use it — the account list
|
|
41
36
|
* is the trust boundary of a self-hosted instance.
|
|
42
37
|
*/
|
|
43
|
-
const guardManifestConfigured = () =>
|
|
38
|
+
const guardManifestConfigured = (config) => config.selfUpdate === "Enabled" ? null : notFound();
|
|
44
39
|
/**
|
|
45
40
|
* Read `state.json` off the control volume. A missing file is an instance that
|
|
46
41
|
* has never checked or updated (RFC 037 D8), not an error, so it reads as
|
|
47
42
|
* `null`; the caller renders the disabled/empty resource. Any other read or
|
|
48
43
|
* parse failure propagates.
|
|
49
44
|
*/
|
|
50
|
-
const readState = () => {
|
|
45
|
+
const readState = (controlDir) => {
|
|
51
46
|
let raw;
|
|
52
47
|
try {
|
|
53
|
-
raw = readFileSync(join(controlDir
|
|
48
|
+
raw = readFileSync(join(controlDir, "state.json"), "utf8");
|
|
54
49
|
}
|
|
55
50
|
catch (error) {
|
|
56
51
|
if (error.code === "ENOENT")
|
|
@@ -78,8 +73,7 @@ const checkedVersion = (state) => state?.check.status === "ok" ? state.check.lat
|
|
|
78
73
|
* so the backend never writes one. The temp file sits in the same directory as
|
|
79
74
|
* the target for the rename to be atomic on one filesystem.
|
|
80
75
|
*/
|
|
81
|
-
const writeRequest = (request) => {
|
|
82
|
-
const dir = controlDir();
|
|
76
|
+
const writeRequest = (dir, request) => {
|
|
83
77
|
const tmp = join(dir, `.request.json.${request.runId}.tmp`);
|
|
84
78
|
writeFileSync(tmp, JSON.stringify(request), { mode: 0o644 });
|
|
85
79
|
renameSync(tmp, join(dir, "request.json"));
|
|
@@ -91,8 +85,7 @@ const writeRequest = (request) => {
|
|
|
91
85
|
* request.json it is written atomically and carries no authority: its presence
|
|
92
86
|
* is the whole message, so it is empty.
|
|
93
87
|
*/
|
|
94
|
-
const writeCheckRequest = () => {
|
|
95
|
-
const dir = controlDir();
|
|
88
|
+
const writeCheckRequest = (dir) => {
|
|
96
89
|
const tmp = join(dir, `.check-request.json.tmp`);
|
|
97
90
|
writeFileSync(tmp, JSON.stringify({}), { mode: 0o644 });
|
|
98
91
|
renameSync(tmp, join(dir, "check-request.json"));
|
|
@@ -123,13 +116,10 @@ const requestedResource = (state, runId, targetVersion, requestedAt) => {
|
|
|
123
116
|
};
|
|
124
117
|
};
|
|
125
118
|
export const SystemOperations = {
|
|
126
|
-
SystemOperations_getSystemUpdate: async (context,
|
|
127
|
-
const offSurface = guardManifestConfigured();
|
|
119
|
+
SystemOperations_getSystemUpdate: async (context, ctx) => {
|
|
120
|
+
const offSurface = guardManifestConfigured(ctx.config);
|
|
128
121
|
if (offSurface)
|
|
129
122
|
return offSurface;
|
|
130
|
-
const event = args[0];
|
|
131
|
-
if (!getSubFromEvent(event))
|
|
132
|
-
return unauthorized();
|
|
133
123
|
// A refresh asks for a fresh answer, and only the updater can fetch one, so
|
|
134
124
|
// the request goes on the control seam for its watch loop to pick up (#599).
|
|
135
125
|
// The answer is the stored state as it stands, `lastCheckedAt` included: the
|
|
@@ -137,19 +127,16 @@ export const SystemOperations = {
|
|
|
137
127
|
// response never has to claim a verdict the updater has not reached.
|
|
138
128
|
const { refresh } = context.request.query;
|
|
139
129
|
if (refresh === true)
|
|
140
|
-
writeCheckRequest();
|
|
141
|
-
return readState() ?? emptyResource();
|
|
130
|
+
writeCheckRequest(ctx.config.updateControlDir);
|
|
131
|
+
return readState(ctx.config.updateControlDir) ?? emptyResource();
|
|
142
132
|
},
|
|
143
|
-
SystemOperations_applySystemUpdate: async (context,
|
|
144
|
-
const offSurface = guardManifestConfigured();
|
|
133
|
+
SystemOperations_applySystemUpdate: async (context, ctx) => {
|
|
134
|
+
const offSurface = guardManifestConfigured(ctx.config);
|
|
145
135
|
if (offSurface)
|
|
146
136
|
return offSurface;
|
|
147
|
-
const
|
|
148
|
-
const sub = getSubFromEvent(event);
|
|
149
|
-
if (!sub)
|
|
150
|
-
return unauthorized();
|
|
137
|
+
const sub = ctx.identity.sub;
|
|
151
138
|
const { targetVersion } = context.request.requestBody;
|
|
152
|
-
const state = readState();
|
|
139
|
+
const state = readState(ctx.config.updateControlDir);
|
|
153
140
|
if (isRunInFlight(state?.run)) {
|
|
154
141
|
throw new ConflictError("An update is already in progress.");
|
|
155
142
|
}
|
|
@@ -158,7 +145,7 @@ export const SystemOperations = {
|
|
|
158
145
|
}
|
|
159
146
|
const runId = randomUUID();
|
|
160
147
|
const requestedAt = new Date().toISOString();
|
|
161
|
-
writeRequest({
|
|
148
|
+
writeRequest(ctx.config.updateControlDir, {
|
|
162
149
|
runId,
|
|
163
150
|
targetVersion,
|
|
164
151
|
requestedAt,
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { NotFoundError } from "@remit/data-ports/errors";
|
|
2
2
|
import { isValidMessageId } from "@remit/data-ports/id";
|
|
3
|
-
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
4
3
|
import { enrichThreadRows, } from "../derive/enrichThreadRows.js";
|
|
5
4
|
import { filterByOffRowCriteria, hasOffRowCriteria, } from "../derive/filterThreadCriteria.js";
|
|
6
|
-
import { getClient } from "../service/data-client.js";
|
|
7
5
|
const DEFAULT_THREADS_PAGE_SIZE = 50;
|
|
8
6
|
// Project to fields used by ListThreadsResponse — keep in sync with handler mapping.
|
|
9
7
|
// Includes ThreadMessage table key fields (accountConfigId, threadMessageId) and
|
|
@@ -183,12 +181,11 @@ export const dedupeThreadMessages = (rows) => {
|
|
|
183
181
|
});
|
|
184
182
|
};
|
|
185
183
|
export const ThreadOperations = {
|
|
186
|
-
ThreadOperations_listThreads: async (context,
|
|
187
|
-
const
|
|
188
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
184
|
+
ThreadOperations_listThreads: async (context, ctx) => {
|
|
185
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
189
186
|
const { mailboxId } = context.request.params;
|
|
190
187
|
const { continuationToken, order } = context.request.query;
|
|
191
|
-
const client =
|
|
188
|
+
const client = ctx.client;
|
|
192
189
|
const result = await client.threadMessage.listByMailbox(accountConfigId, mailboxId, buildListThreadsOptions({ continuationToken, order }));
|
|
193
190
|
const items = await enrichThreadRows(result.items, client, accountConfigId);
|
|
194
191
|
return {
|
|
@@ -196,12 +193,11 @@ export const ThreadOperations = {
|
|
|
196
193
|
continuationToken: result.continuationToken,
|
|
197
194
|
};
|
|
198
195
|
},
|
|
199
|
-
ThreadOperations_searchThreads: async (context,
|
|
200
|
-
const
|
|
201
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
196
|
+
ThreadOperations_searchThreads: async (context, ctx) => {
|
|
197
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
202
198
|
const { mailboxId } = context.request.params;
|
|
203
199
|
const raw = context.request.query;
|
|
204
|
-
return executeThreadSearch(
|
|
200
|
+
return executeThreadSearch(ctx.client, accountConfigId, mailboxId, {
|
|
205
201
|
...raw,
|
|
206
202
|
senderTrust: toArray(raw.senderTrust),
|
|
207
203
|
category: toArray(raw.category),
|
|
@@ -209,12 +205,11 @@ export const ThreadOperations = {
|
|
|
209
205
|
},
|
|
210
206
|
};
|
|
211
207
|
export const ThreadDetailOperations = {
|
|
212
|
-
ThreadDetailOperations_listThreadMessages: async (context,
|
|
213
|
-
const
|
|
214
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
208
|
+
ThreadDetailOperations_listThreadMessages: async (context, ctx) => {
|
|
209
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
215
210
|
const { threadId } = context.request.params;
|
|
216
211
|
const { order } = context.request.query;
|
|
217
|
-
const client =
|
|
212
|
+
const client = ctx.client;
|
|
218
213
|
const result = await client.threadMessage.listByThread(threadId, accountConfigId, buildListThreadMessagesOptions({ order }));
|
|
219
214
|
// Defense-in-depth: the query is already scoped to accountConfigId; assert
|
|
220
215
|
// no foreign row slips into the response. `read`-class → 404, no existence
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { isVirtualCopyMailbox } from "@remit/data-ports/virtual-copy";
|
|
2
2
|
import { MailboxSpecialUse } from "@remit/domain-enums";
|
|
3
3
|
import pMap from "p-map";
|
|
4
|
-
import { getAccountConfigIdFromEvent } from "../auth.js";
|
|
5
4
|
import { enrichThreadRows, } from "../derive/enrichThreadRows.js";
|
|
6
|
-
import { getClient } from "../service/data-client.js";
|
|
7
5
|
import { groupAccountOverrides, groupMailboxOverrides, } from "./account-overrides.js";
|
|
8
6
|
const DEFAULT_UNIFIED_THREADS_PAGE_SIZE = 50;
|
|
9
7
|
const MAILBOX_LIST_CONCURRENCY = 5;
|
|
@@ -374,9 +372,8 @@ const toBoolean = (value) => {
|
|
|
374
372
|
return undefined;
|
|
375
373
|
};
|
|
376
374
|
export const UnifiedThreadOperations = {
|
|
377
|
-
UnifiedThreadOperations_listAllThreads: async (context,
|
|
378
|
-
const
|
|
379
|
-
const accountConfigId = getAccountConfigIdFromEvent(event);
|
|
375
|
+
UnifiedThreadOperations_listAllThreads: async (context, ctx) => {
|
|
376
|
+
const accountConfigId = ctx.identity.accountConfigId;
|
|
380
377
|
const { continuationToken, order, limit, starred, query, from, subject, accountId, mailboxId, category, unread, attachments, muted, count, results, } = context.request.query;
|
|
381
378
|
// Whitespace-only text is not a search: it would widen the scope to every
|
|
382
379
|
// folder while matching nothing in particular. The two field parameters
|
|
@@ -384,7 +381,7 @@ export const UnifiedThreadOperations = {
|
|
|
384
381
|
const searchText = query?.trim();
|
|
385
382
|
const fromText = from?.trim();
|
|
386
383
|
const subjectText = subject?.trim();
|
|
387
|
-
return executeUnifiedThreadListing(
|
|
384
|
+
return executeUnifiedThreadListing(ctx.client, accountConfigId, {
|
|
388
385
|
continuationToken,
|
|
389
386
|
order,
|
|
390
387
|
limit,
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
export {
|
|
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;
|
|
2
5
|
export declare const handler: (event: import("aws-lambda").APIGatewayProxyEvent, context: import("aws-lambda").Context) => Promise<import("aws-lambda").APIGatewayProxyResult>;
|