@remit/backend 0.0.147 → 0.0.148

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/error.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isPublicApiError } from "@remit/data-ports/errors";
1
+ import { HostRefusedError, isPublicApiError } from "@remit/data-ports/errors";
2
2
  import { logger } from "@remit/logger";
3
3
  import { formatResponse } from "./response.js";
4
4
  /**
@@ -35,6 +35,15 @@ export const defaultErrorCode = (statusCode) => {
35
35
  return CODE_BY_STATUS[statusCode] ?? "request_refused";
36
36
  };
37
37
  export const handleError = async (error) => {
38
+ if (error instanceof HostRefusedError) {
39
+ logger.error({
40
+ error: error.message,
41
+ errorKind: "quota",
42
+ statusCode: error.statusCode,
43
+ stack: error.stack,
44
+ }, "Host refused the request");
45
+ return formatResponse({ code: "request_refused", message: error.message }, error.statusCode);
46
+ }
38
47
  if (error instanceof Error) {
39
48
  if ("statusCode" in error) {
40
49
  logger.error({
@@ -1,4 +1,5 @@
1
1
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { ensureAccountConfig } from "@remit/data-ports/account-config";
2
3
  import { AccountAuthType, AccountService, ConnectionState, } from "@remit/domain-enums";
3
4
  import { logger } from "@remit/logger";
4
5
  import { createMailOAuthService, microsoftProviderConfig, microsoftServicesGranted, orderServices, RefreshTokenError, } from "@remit/mail-oauth-service";
@@ -8,7 +9,6 @@ import { safeJsonParse } from "../json.js";
8
9
  import { rawApiResponse } from "../response.js";
9
10
  import { triggerAccountSyncSafe } from "./account.js";
10
11
  import { findActiveDuplicateMailbox } from "./account-guards.js";
11
- import { ensureAccountConfig } from "./ensure-account-config.js";
12
12
  const OUTLOOK_IMAP_HOST = "outlook.office365.com";
13
13
  const ACCOUNT_SERVICES = Object.values(AccountService);
14
14
  const isServiceList = (value) => Array.isArray(value) &&
@@ -1,3 +1,4 @@
1
+ import { ensureAccountConfig } from "@remit/data-ports/account-config";
1
2
  import { AccountAuthType, AccountService, ConnectionState, } from "@remit/domain-enums";
2
3
  import { logger } from "@remit/logger";
3
4
  import { createMailOAuthService, microsoftProviderConfig, RefreshTokenError, } from "@remit/mail-oauth-service";
@@ -11,7 +12,6 @@ import { triggerAccountSync } from "../service/trigger-sync.js";
11
12
  import { assertNoDuplicateMailbox, assertNotOAuthCreate, assertPasswordProvided, resolveSyncedServices, toAccountResponse, } from "./account-guards.js";
12
13
  import { loadAccountOverrides, upsertAccountDisplayName, writeAccountComposeLanguages, writeAccountMuted, } from "./account-overrides.js";
13
14
  import { loadSignatureForAccount, upsertAccountSignature, } from "./account-signature.js";
14
- import { ensureAccountConfig } from "./ensure-account-config.js";
15
15
  import { resolveAccountFolderAppointments } from "./folder-role-appointments.js";
16
16
  export { assertNotOAuthCreate, assertPasswordProvided, toAccountResponse };
17
17
  export const triggerAccountSyncSafe = async (client, accountId) => {
@@ -65,12 +65,12 @@ const toAccountConfigResponse = (config) => ({
65
65
  * The POST account-creation flow will materialize the real row when the user
66
66
  * adds their first account.
67
67
  */
68
- const emptyConfigResponse = (accountConfigId, userId, commit) => {
68
+ const emptyConfigResponse = (accountConfigId, commit) => {
69
69
  const now = Date.now();
70
70
  return {
71
71
  accountConfig: {
72
72
  accountConfigId,
73
- userId,
73
+ userId: accountConfigId,
74
74
  state: "active",
75
75
  createdAt: now,
76
76
  updatedAt: now,
@@ -91,11 +91,11 @@ export const ConfigOperations = {
91
91
  throw err;
92
92
  });
93
93
  if (!description || description.accountConfig.length === 0) {
94
- return emptyConfigResponse(accountConfigId, ctx.identity.sub, ctx.config.commit);
94
+ return emptyConfigResponse(accountConfigId, ctx.config.commit);
95
95
  }
96
96
  const accountConfig = description.accountConfig[0];
97
97
  if (!accountConfig) {
98
- return emptyConfigResponse(accountConfigId, ctx.identity.sub, ctx.config.commit);
98
+ return emptyConfigResponse(accountConfigId, ctx.config.commit);
99
99
  }
100
100
  // Filter out deleted accounts (tombstone pattern)
101
101
  const activeAccounts = description.account.filter((acc) => acc.deletedAt === undefined);
@@ -151,7 +151,6 @@ export const ConfigOperations = {
151
151
  appointFolderRole: (configId, accountId, role, mailboxId, lastKnownPath) => writeFolderRoleAppointment(client.accountSetting, configId, accountId, role, mailboxId, lastKnownPath),
152
152
  }, {
153
153
  accountConfigId,
154
- userId: ctx.identity.sub,
155
154
  document: body.document,
156
155
  mode: body.mode ?? "validate",
157
156
  onExisting: body.onExisting ?? "abort",
@@ -1,12 +1,10 @@
1
1
  import type { IAccountConfigRepository, IAccountExportRequestRepository, IAccountRepository, IAccountSettingRepository, IAddressRepository, ICalendarCollectionRepository, ICalendarEventIndexRepository, ICalendarFeedTokenRepository, ICalendarObjectRepository, ICalendarStore, ICalendarSuggestionRepository, IConfigImportRepository, IEnvelopeRepository, IFilterRepository, ILabelRepository, IMailboxLockRepository, IMailboxRepository, IMailboxSpecialUseRepository, IMessageFlagPushRepository, IMessageFlagRepository, IMessageLabelRepository, IMessagePlacementMoveRepository, IMessageRepository, IOrganizeJobRequestRepository, IOutboxAttachmentRepository, IOutboxMessageRepository, IQuarantineRepository, ISenderSignerStandingRepository, ISyncWarningRepository, IThreadMessageRepository } from "@remit/data-ports";
2
- import { BodySyncQueueService, BodySyncService, type FilterConfig, FlagQueueService, type IImapConnection, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, SpamReportService } from "@remit/mailbox-service";
2
+ import { BodySyncQueueService, BodySyncService, type ConnectionScope, type FilterConfig, FlagQueueService, type ImapTransport, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, SpamReportService } from "@remit/mailbox-service";
3
3
  import { type SecretsService } from "@remit/secrets-service";
4
4
  import type { StorageService } from "@remit/storage-service";
5
5
  import type { QueueName, RemitClientQueues } from "./queues.js";
6
- export interface ConnectionScope {
7
- getConnection: () => Promise<IImapConnection>;
8
- disconnect: () => Promise<void>;
9
- }
6
+ export type { ConnectionScope };
7
+ export type ConnectionScopeFactory = (accountService: IAccountRepository, secretsService: SecretsService) => (accountId: string) => Promise<ConnectionScope>;
10
8
  export interface RemitClientCore {
11
9
  accountConfig: IAccountConfigRepository;
12
10
  account: IAccountRepository;
@@ -95,11 +93,13 @@ export interface RemitClientRepositories {
95
93
  export interface RemitClientSharedDeps {
96
94
  storage: StorageService;
97
95
  secrets: SecretsService;
96
+ connectionScope: ConnectionScopeFactory;
98
97
  queues: RemitClientQueues;
99
98
  }
100
99
  export interface RemitClientDeps extends RemitClientSharedDeps {
101
100
  repositories: RemitClientRepositories;
102
101
  }
102
+ export declare const passwordConnectionScope: (transport: ImapTransport) => ConnectionScopeFactory;
103
103
  export declare const buildReadPathFilterConfig: (repositories: Pick<RemitClientRepositories, "filter" | "messageLabel" | "mailbox" | "message" | "threadMessage" | "placementMove" | "address" | "mailboxSpecialUse">, queues: Pick<RemitClientQueues, "messageManagement">) => FilterConfig;
104
104
  export declare const createRemitCore: (deps: Omit<RemitClientDeps, "queues">) => RemitClientCore;
105
105
  export declare const composeSyncServices: (core: RemitClientCore, queues: Pick<RemitClientQueues, "sync">) => SyncQueueServices;
@@ -1,38 +1,21 @@
1
1
  import { logger } from "@remit/logger";
2
- import { BodySyncQueueService, BodySyncService, createConnection, FlagPushService, FlagQueueService, fifoMailboxManagement, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, PlacementMoveService, SpamReportService, SyncWarningService, } from "@remit/mailbox-service";
2
+ import { BodySyncQueueService, BodySyncService, connectionScope, FlagPushService, FlagQueueService, fifoMailboxManagement, MailboxQueueService, MessageMoveService, OutboxAttachmentService, OutboxQueueService, PlacementMoveService, SpamReportService, SyncWarningService, } from "@remit/mailbox-service";
3
3
  import { buildFilterConfig } from "@remit/mailbox-service/filter-config";
4
4
  import { deserializeEncryptedPayload, } from "@remit/secrets-service";
5
- const buildConnectionScope = (accountService, secretsService) => async (accountId) => {
5
+ export const passwordConnectionScope = (transport) => (accountService, secretsService) => async (accountId) => {
6
6
  const account = await accountService.get(accountId);
7
7
  if (!account.passwordHash) {
8
8
  throw new Error(`Account ${accountId} has no passwordHash — OAuth accounts cannot use this connection path`);
9
9
  }
10
10
  const password = await secretsService.decrypt(deserializeEncryptedPayload(JSON.parse(account.passwordHash)));
11
- let connection = null;
12
- let connectPromise = null;
13
- const getConnection = async () => {
14
- if (connectPromise) {
15
- return connectPromise;
16
- }
17
- const conn = createConnection({
18
- user: account.username,
19
- credentials: { kind: "password", password },
20
- host: account.imapHost,
21
- port: account.imapPort,
22
- tls: account.imapTls,
23
- });
24
- connection = conn;
25
- connectPromise = conn.connect().then(() => conn);
26
- return connectPromise;
27
- };
28
- const disconnect = async () => {
29
- if (connection) {
30
- await connection.disconnect();
31
- connection = null;
32
- connectPromise = null;
33
- }
34
- };
35
- return { getConnection, disconnect };
11
+ return connectionScope({
12
+ user: account.username,
13
+ credentials: { kind: "password", password },
14
+ host: account.imapHost,
15
+ port: account.imapPort,
16
+ tls: account.imapTls,
17
+ transport,
18
+ });
36
19
  };
37
20
  // The read-path body backfill (describeMessage / getRawMessage →
38
21
  // fetchAndGetBody) materializes a body that was never sync-synced, and must run
@@ -62,7 +45,7 @@ export const buildReadPathFilterConfig = (repositories, queues) => {
62
45
  });
63
46
  };
64
47
  export const createRemitCore = (deps) => {
65
- const { repositories, storage, secrets } = deps;
48
+ const { repositories, storage, secrets, connectionScope } = deps;
66
49
  return {
67
50
  accountConfig: repositories.accountConfig,
68
51
  account: repositories.account,
@@ -103,7 +86,7 @@ export const createRemitCore = (deps) => {
103
86
  outboxAttachmentService: repositories.outboxAttachment,
104
87
  storage,
105
88
  }),
106
- createConnectionScope: buildConnectionScope(repositories.account, secrets),
89
+ createConnectionScope: connectionScope(repositories.account, secrets),
107
90
  };
108
91
  };
109
92
  export const composeSyncServices = (core, queues) => {
@@ -1,4 +1,4 @@
1
- import type { RemitClientSharedDeps } from "./create-remit-client.js";
1
+ import { type RemitClientSharedDeps } from "./create-remit-client.js";
2
2
  import type { QueueName, RemitClientQueues } from "./queues.js";
3
3
  export type SqsQueueUrls = Record<QueueName, string>;
4
4
  export declare const sqsQueueUrlsFromEnv: () => SqsQueueUrls;
@@ -1,8 +1,10 @@
1
+ import { nodeTransport } from "@remit/mailbox-service/node-transport";
1
2
  import { createCachedDataKeyProvider, createSecretsService, } from "@remit/secrets-service";
2
3
  import { createKmsDataKeyProvider, FAKE_KMS_KEY_ID, } from "@remit/secrets-service/kms";
3
4
  import { createFifoQueueProducer, createStandardQueueProducer, } from "@remit/sqs-client/producer";
4
5
  import { createStorageService } from "@remit/storage-service/s3";
5
6
  import { env } from "expect-env";
7
+ import { passwordConnectionScope, } from "./create-remit-client.js";
6
8
  export const sqsQueueUrlsFromEnv = () => ({
7
9
  sync: env.SQS_QUEUE_URL,
8
10
  accountFanout: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
@@ -22,5 +24,6 @@ export const sqsQueues = (urls) => ({
22
24
  export const buildSharedDeps = (queues) => ({
23
25
  storage: createStorageService(),
24
26
  secrets: createSecretsService(createCachedDataKeyProvider(createKmsDataKeyProvider(process.env.KMS_KEY_ID ?? FAKE_KMS_KEY_ID))),
27
+ connectionScope: passwordConnectionScope(nodeTransport()),
25
28
  queues,
26
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.147",
3
+ "version": "0.0.148",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -1,9 +0,0 @@
1
- import type { IAccountConfigRepository } from "@remit/data-ports";
2
- /**
3
- * Ensure an AccountConfig row exists for the given id. A first-time caller
4
- * coming from Cognito will not yet have one; subsequent calls are idempotent.
5
- *
6
- * We attempt a read first to avoid relying on a race on `create()` — the
7
- * deterministic id (derived from Cognito `sub`) makes both paths safe.
8
- */
9
- export declare const ensureAccountConfig: (accountConfig: IAccountConfigRepository, accountConfigId: string) => Promise<void>;
@@ -1,27 +0,0 @@
1
- import { ConflictError, NotFoundError } from "@remit/data-ports/errors";
2
- /**
3
- * Ensure an AccountConfig row exists for the given id. A first-time caller
4
- * coming from Cognito will not yet have one; subsequent calls are idempotent.
5
- *
6
- * We attempt a read first to avoid relying on a race on `create()` — the
7
- * deterministic id (derived from Cognito `sub`) makes both paths safe.
8
- */
9
- export const ensureAccountConfig = async (accountConfig, accountConfigId) => {
10
- const existing = await accountConfig.get(accountConfigId).catch((err) => {
11
- if (err instanceof NotFoundError)
12
- return undefined;
13
- throw err;
14
- });
15
- if (existing)
16
- return;
17
- await accountConfig
18
- .create({
19
- accountConfigId,
20
- userId: accountConfigId,
21
- })
22
- .catch((err) => {
23
- if (err instanceof ConflictError)
24
- return;
25
- throw err;
26
- });
27
- };