@cosmicdrift/kumiko-bundled-features 0.392.0 → 0.392.2

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.
@@ -41,7 +41,7 @@ function ViewAllLink() {
41
41
  const { Link } = usePrimitives();
42
42
  const nav = useNav();
43
43
  const target = { screenId: MY_CAPS_SCREEN_ID };
44
- return (_jsx(Link, { href: nav.hrefFor(target), onPress: () => nav.navigate(target), variant: "muted", testId: "cap-cards-view-all", children: t("cap-overview.cards.viewAll") }));
44
+ return (_jsx(Link, { href: nav.hrefFor(target), onPress: () => nav.navigate(target), testId: "cap-cards-view-all", children: t("cap-overview.cards.viewAll") }));
45
45
  }
46
46
  function CapUsageSummaryRow({ cap }) {
47
47
  const t = useTranslation();
@@ -11,7 +11,7 @@ import type { OutboundMailMessage, OutboundMailSent } from "./outbound-mail.ts";
11
11
  * secrets — tenant secret reads (IMAP password, OAuth refresh token)
12
12
  * auditUserId — audit identity for secret reads. Handler path: unset,
13
13
  * requireSecretsContext falls back to ctx.user.id; worker/provider
14
- * path: system identity set by the resolver.
14
+ * path: the supervisor sets the system identity.
15
15
  * metricsFor — metrics handle of a feature by name, same behavior as
16
16
  * HandlerContext.metricsFor: unregistered metrics are a silent
17
17
  * no-op instead of a throw.
@@ -44,7 +44,8 @@
44
44
  // racing a new holder.
45
45
  import { configuredPiiSubjectKms, decryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
46
46
  import { fetchOne, insertOne, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/db";
47
- import { systemScopedRunner } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
47
+ import { SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
48
+ import { systemScopedRunner, tenantScopedSecrets, } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
48
49
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
49
50
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
50
51
  import { InboundMailAccountStatuses, InboundMailFoundationHandlers } from "./constants.js";
@@ -80,13 +81,21 @@ export function createInboundMailSupervisor(deps) {
80
81
  // configured TTL — real deployments (default 90s) never hit it.
81
82
  const renewIntervalMs = Math.max(50, Math.floor((leaseTtlSeconds * 1000) / 3));
82
83
  const log = deps.log ?? (() => { });
84
+ // The supervisor runs outside any request: secret reads need an audit
85
+ // identity, and under enforced row-level security they need the account's
86
+ // tenant scope, or the credential row is invisible and reads as missing.
87
+ const providerCtx = {
88
+ ...deps.providerCtx,
89
+ auditUserId: deps.providerCtx.auditUserId ?? SYSTEM_USER_ID,
90
+ ...(deps.providerCtx.secrets && { secrets: tenantScopedSecrets(deps.providerCtx.secrets) }),
91
+ };
83
92
  const systemDb = systemScopedRunner(deps.db, "mail sync supervisor polls accounts and cursors of all tenants outside a request");
84
93
  const oauthTokens = deps.oauthTokens ??
85
- (deps.providerCtx.secrets
94
+ (providerCtx.secrets
86
95
  ? createOAuthAccessTokenManager({
87
96
  db: systemDb,
88
- secrets: deps.providerCtx.secrets,
89
- providerCtx: deps.providerCtx,
97
+ secrets: providerCtx.secrets,
98
+ providerCtx,
90
99
  })
91
100
  : undefined);
92
101
  // Undefined for non-OAuth accounts. A refresh failure propagates into the
@@ -294,7 +303,7 @@ export function createInboundMailSupervisor(deps) {
294
303
  // exhausted.
295
304
  for (;;) {
296
305
  const accessToken = await resolveAccessToken(account, plugin);
297
- const result = await plugin.fetch(deps.providerCtx, account, cursor, {
306
+ const result = await plugin.fetch(providerCtx, account, cursor, {
298
307
  backfillWindowDays,
299
308
  maxMessages: budget,
300
309
  filterUnknownMessageIds: unknownMessageIdsFilterFor(account),
@@ -317,7 +326,7 @@ export function createInboundMailSupervisor(deps) {
317
326
  for (const account of accounts) {
318
327
  let plugin;
319
328
  try {
320
- plugin = resolveInboundProviderForAccount(deps.providerCtx, account);
329
+ plugin = resolveInboundProviderForAccount(providerCtx, account);
321
330
  }
322
331
  catch (err) {
323
332
  log(`inbound-mail: ${describeError(err)}`);
@@ -461,7 +470,7 @@ export function createInboundMailSupervisor(deps) {
461
470
  state.starting = true;
462
471
  try {
463
472
  const accessToken = await resolveAccessToken(account, plugin);
464
- const stop = await plugin.watch(deps.providerCtx, account, {
473
+ const stop = await plugin.watch(providerCtx, account, {
465
474
  onMessages: async (msgs) => {
466
475
  try {
467
476
  await ingestBatch(account, msgs, "watch");
@@ -1,5 +1,18 @@
1
1
  import * as z from "zod";
2
+ import { type ImapCredentialDocument } from "./credential-document.ts";
3
+ declare const connectMailboxSchema: z.ZodObject<{
4
+ host: z.ZodString;
5
+ port: z.ZodDefault<z.ZodNumber>;
6
+ user: z.ZodString;
7
+ displayName: z.ZodString;
8
+ address: z.ZodEmail;
9
+ password: z.ZodString;
10
+ smtpHost: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
11
+ smtpPort: z.ZodNumber;
12
+ }, z.core.$strip>;
13
+ type ConnectMailboxPayload = z.infer<typeof connectMailboxSchema>;
2
14
  export declare function deriveSmtpHost(imapHost: string): string;
15
+ export declare function credentialDocumentOf(payload: ConnectMailboxPayload): ImapCredentialDocument;
3
16
  export declare const connectMailboxHandler: import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"connect-mailbox", z.ZodObject<{
4
17
  host: z.ZodString;
5
18
  port: z.ZodDefault<z.ZodNumber>;
@@ -8,6 +21,8 @@ export declare const connectMailboxHandler: import("@cosmicdrift/kumiko-types/de
8
21
  address: z.ZodEmail;
9
22
  password: z.ZodString;
10
23
  smtpHost: z.ZodOptional<z.ZodNullable<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>>;
24
+ smtpPort: z.ZodNumber;
11
25
  }, z.core.$strip>, {
12
26
  accountId: string;
13
27
  }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, null>;
28
+ export {};
@@ -8,12 +8,12 @@ import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
9
9
  import * as z from "zod";
10
10
  import { IMAP_CONNECT_ERRORS, IMAP_PROVIDER_KEY, SMTP_CONNECT_ERRORS } from "./constants.js";
11
- import { imapCredentialDocumentSchema, SMTP_IMPLICIT_TLS_PORT, smtpHostSchema, } from "./credential-document.js";
11
+ import { imapCredentialDocumentSchema, smtpHostSchema, } from "./credential-document.js";
12
12
  import { connectOnce, IMAP_HOST_UNREACHABLE_MESSAGE } from "./imap-client.js";
13
13
  import { SmtpFailureError, verifySmtpLogin } from "./smtp-client.js";
14
- // No `secure` and no SMTP port input: the password never crosses the wire in clear text, so a
15
- // payload's secure:false or smtpPort is stripped like any unknown key and both logins always use
16
- // implicit TLS (SMTP on port 465).
14
+ // No `secure`: the password never crosses the wire in clear text, so a payload's secure:false is
15
+ // stripped like any unknown key. IMAP uses implicit TLS; SMTP uses implicit TLS on 465 and
16
+ // enforced STARTTLS on every other port.
17
17
  const connectMailboxSchema = imapCredentialDocumentSchema
18
18
  .pick({ host: true, port: true, user: true })
19
19
  .extend({
@@ -21,6 +21,7 @@ const connectMailboxSchema = imapCredentialDocumentSchema
21
21
  address: z.email().max(320),
22
22
  password: z.string().min(1).max(1000),
23
23
  smtpHost: z.union([z.literal(""), smtpHostSchema]).nullish(),
24
+ smtpPort: z.number().int().min(1).max(65535),
24
25
  });
25
26
  const connectedAccountSchema = z.object({ accountId: z.string() });
26
27
  // Matched on the mapped error text, which also carries the host, so only markers no host name
@@ -34,10 +35,10 @@ const SECRET_TRANSACTION_REASON = "the credential secret is written on the reque
34
35
  export function deriveSmtpHost(imapHost) {
35
36
  return /^imap\./i.test(imapHost) ? `smtp.${imapHost.slice("imap.".length)}` : imapHost;
36
37
  }
37
- function credentialDocumentOf(payload) {
38
- const { host, port, user, password } = payload;
38
+ export function credentialDocumentOf(payload) {
39
+ const { host, port, user, password, smtpPort } = payload;
39
40
  const smtpHost = payload.smtpHost || deriveSmtpHost(host);
40
- return { host, port, secure: true, user, password, smtpHost, smtpPort: SMTP_IMPLICIT_TLS_PORT };
41
+ return { host, port, secure: true, user, password, smtpHost, smtpPort };
41
42
  }
42
43
  // The raw error carries the host and the server response; the tenant only gets the category.
43
44
  // A blocked host and an unresolvable one share one text on purpose (see createImapClient).
@@ -97,7 +98,7 @@ export const connectMailboxHandler = defineWriteHandler({
97
98
  if (smtpFailure) {
98
99
  const { reason, i18nKey } = SMTP_CONNECT_ERRORS[smtpFailure];
99
100
  ctx.obs.log.warn("smtp connect check failed", { reason });
100
- return writeFailure(new UnprocessableError(reason, { i18nKey }));
101
+ return writeFailure(new UnprocessableError(reason, { i18nKey, i18nParams: { port: doc.smtpPort } }));
101
102
  }
102
103
  return { isSuccess: true, data: null };
103
104
  },
@@ -39,3 +39,4 @@ export declare const SMTP_CONNECT_ERRORS: {
39
39
  readonly i18nKey: "inboundMail.imap.connect.errors.smtpConnectFailed";
40
40
  };
41
41
  };
42
+ export declare const SMTP_SUBMISSION_PORT = 587;
@@ -37,3 +37,4 @@ export const SMTP_CONNECT_ERRORS = {
37
37
  i18nKey: "inboundMail.imap.connect.errors.smtpConnectFailed",
38
38
  },
39
39
  };
40
+ export const SMTP_SUBMISSION_PORT = 587;
@@ -1,6 +1,5 @@
1
1
  import { type InboundMailContext, type MailAccountRecord } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
2
2
  import * as z from "zod";
3
- export declare const SMTP_IMPLICIT_TLS_PORT = 465;
4
3
  export declare const smtpHostSchema: z.ZodString;
5
4
  export declare const imapCredentialDocumentSchema: z.ZodObject<{
6
5
  host: z.ZodString;
@@ -21,8 +21,7 @@
21
21
  import { InboundAuthError, inboundCredentialSecretKey, } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
22
22
  import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
23
23
  import * as z from "zod";
24
- import { FEATURE_NAME } from "./constants.js";
25
- export const SMTP_IMPLICIT_TLS_PORT = 465;
24
+ import { FEATURE_NAME, SMTP_SUBMISSION_PORT } from "./constants.js";
26
25
  // A bare host name: a port, path or whitespace would point the connect somewhere the guard did
27
26
  // not check.
28
27
  export const smtpHostSchema = z
@@ -38,7 +37,7 @@ export const imapCredentialDocumentSchema = z.object({
38
37
  password: z.string().min(1).optional(),
39
38
  accessToken: z.string().min(1).optional(),
40
39
  smtpHost: smtpHostSchema.optional(),
41
- smtpPort: z.number().int().min(1).max(65535).default(SMTP_IMPLICIT_TLS_PORT),
40
+ smtpPort: z.number().int().min(1).max(65535).default(SMTP_SUBMISSION_PORT),
42
41
  });
43
42
  export function parseImapCredentialDocument(raw) {
44
43
  let json;
@@ -12,10 +12,11 @@ export const INBOUND_PROVIDER_IMAP_I18N = {
12
12
  [formFieldKey("user")]: { en: "User name" },
13
13
  [formFieldKey("password")]: { en: "Password" },
14
14
  [formFieldKey("smtpHost")]: { en: "Outgoing server (SMTP)" },
15
+ [formFieldKey("smtpPort")]: { en: "SMTP port" },
15
16
  "inboundMail.imap.connect.server": { en: "Server sign-in" },
16
17
  "inboundMail.imap.connect.outgoing": { en: "Sending" },
17
18
  "inboundMail.imap.connect.outgoingHint": {
18
- en: "Replies go out from this address over your own mail server, with the same user name and password. Leave the field empty if the server is called like the IMAP server, with smtp. instead of imap.",
19
+ en: "Replies go out from this address over your own mail server, with the same user name and password. Leave the field empty if the server is called like the IMAP server, with smtp. instead of imap. Usually 587, even when your provider lists 465/SSL.",
19
20
  },
20
21
  "inboundMail.imap.connect.submit": { en: "Check and connect" },
21
22
  [IMAP_CONNECT_ERRORS.hostUnreachable.i18nKey]: {
@@ -37,9 +38,9 @@ export const INBOUND_PROVIDER_IMAP_I18N = {
37
38
  en: "Receiving works, but sending does not: the outgoing server rejected the sign-in.",
38
39
  },
39
40
  [SMTP_CONNECT_ERRORS.tlsFailed.i18nKey]: {
40
- en: "Receiving works, but sending does not: the encrypted connection to the outgoing server on port 465 failed.",
41
+ en: "Receiving works, but sending does not: the encrypted connection to the outgoing server on port {port} failed.",
41
42
  },
42
43
  [SMTP_CONNECT_ERRORS.connectFailed.i18nKey]: {
43
- en: "Receiving works, but sending does not: no connection to the outgoing server on port 465.",
44
+ en: "Receiving works, but sending does not: no connection to the outgoing server on port {port}.",
44
45
  },
45
46
  };
@@ -1,6 +1,6 @@
1
1
  import { IMAP_CONNECT_SCREEN_ID, MAIL_ACCOUNT_LIST_SCREEN_QN, } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
2
2
  import { i18nKey } from "@cosmicdrift/kumiko-framework/engine";
3
- import { ImapHandlers } from "./constants.js";
3
+ import { ImapHandlers, SMTP_SUBMISSION_PORT } from "./constants.js";
4
4
  // Reached from the toolbar of the foundation's mail account list, never from the nav.
5
5
  export const imapConnectScreen = {
6
6
  id: IMAP_CONNECT_SCREEN_ID,
@@ -15,6 +15,14 @@ export const imapConnectScreen = {
15
15
  user: { type: "text", required: true, maxLength: 320 },
16
16
  password: { type: "text", required: true, format: "password", sensitive: true },
17
17
  smtpHost: { type: "text", maxLength: 255 },
18
+ smtpPort: {
19
+ type: "number",
20
+ integer: true,
21
+ required: true,
22
+ min: 1,
23
+ max: 65535,
24
+ default: SMTP_SUBMISSION_PORT,
25
+ },
18
26
  },
19
27
  layout: {
20
28
  sections: [
@@ -27,7 +35,7 @@ export const imapConnectScreen = {
27
35
  {
28
36
  title: i18nKey("inboundMail.imap.connect.outgoing"),
29
37
  description: i18nKey("inboundMail.imap.connect.outgoingHint"),
30
- fields: ["smtpHost"],
38
+ fields: ["smtpHost", "smtpPort"],
31
39
  },
32
40
  ],
33
41
  },
@@ -1,4 +1,5 @@
1
1
  import { type MailConnectTarget } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
2
+ export declare const SMTP_IMPLICIT_TLS_PORT = 465;
2
3
  export type SmtpLogin = {
3
4
  readonly host: string;
4
5
  readonly port: number;
@@ -14,7 +15,8 @@ export declare function classifySmtpError(err: unknown): SmtpFailure;
14
15
  export declare function smtpTransportOptions(login: SmtpLogin, target: MailConnectTarget): {
15
16
  readonly host: string;
16
17
  readonly port: number;
17
- readonly secure: true;
18
+ readonly secure: boolean;
19
+ readonly requireTLS?: boolean | undefined;
18
20
  readonly servername?: string | undefined;
19
21
  readonly auth: {
20
22
  readonly user: string;
@@ -1,11 +1,12 @@
1
1
  // nodemailer wiring for sending from a connected mailbox: one short-lived, non-pooled connection
2
- // per call, always implicit TLS, pinned to the address the mail host guard approved.
2
+ // per call, TLS enforced (implicit on 465, STARTTLS otherwise), pinned to the address the mail host guard approved.
3
3
  import { BlockedHostError, resolveMailConnectTarget, } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
4
4
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
5
5
  import { errorCodeOf, propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
6
6
  import { createTransport } from "nodemailer";
7
7
  import { imapMailHostGuard } from "./imap-client.js";
8
8
  const log = createFallbackLogger("inbound-provider-imap");
9
+ export const SMTP_IMPLICIT_TLS_PORT = 465;
9
10
  // nodemailer's defaults wait minutes; the connect form holds a request open meanwhile.
10
11
  const CONNECTION_TIMEOUT_MS = 10_000;
11
12
  const GREETING_TIMEOUT_MS = 10_000;
@@ -43,7 +44,9 @@ export function smtpTransportOptions(login, target) {
43
44
  return {
44
45
  host: target.host,
45
46
  port: login.port,
46
- secure: true,
47
+ secure: login.port === SMTP_IMPLICIT_TLS_PORT,
48
+ // Without it nodemailer sends AUTH in clear text when the server offers no STARTTLS.
49
+ ...(login.port !== SMTP_IMPLICIT_TLS_PORT && { requireTLS: true }),
47
50
  ...(target.servername && { servername: target.servername }),
48
51
  auth: { user: login.user, pass: login.password },
49
52
  connectionTimeout: CONNECTION_TIMEOUT_MS,
@@ -1,4 +1,4 @@
1
- import type { InboundMailProviderPlugin, RawInboundMessage } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
1
+ import { type InboundMailProviderPlugin, type RawInboundMessage } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
2
2
  export declare const INMEMORY_PROVIDER_KEY = "inmemory";
3
3
  /** Test helper: adds a message to the mailbox. If a watch is active, it is
4
4
  * pushed immediately (IDLE simulation); the next fetch also returns it by
@@ -9,6 +9,9 @@ export declare function seedInboundMessage(accountId: string, msg: RawInboundMes
9
9
  export declare function failNextFetchWith(accountId: string, err: unknown): void;
10
10
  /** Test helper: injects a one-shot error into the next verify. */
11
11
  export declare function failNextVerifyWith(accountId: string, err: unknown): void;
12
+ /** Test helper: every fetch reads this tenant secret first, like a real
13
+ * provider reads its stored credentials; a missing secret is an auth error. */
14
+ export declare function requireCredentialSecretOnFetch(accountId: string, key: string): void;
12
15
  /** Test helper: simulates the live connection terminating (the supervisor
13
16
  * must restart with backoff). */
14
17
  export declare function emitWatchError(accountId: string, err: unknown): void;
@@ -10,6 +10,8 @@
10
10
  //
11
11
  // **Pattern basis:** mail-transport-inmemory (module-level state +
12
12
  // test-helper exports). Not for production.
13
+ import { InboundAuthError, } from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
14
+ import { requireSecretsContext } from "@cosmicdrift/kumiko-bundled-features/secrets";
13
15
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
14
16
  const FEATURE_NAME = "inbound-provider-inmemory";
15
17
  export const INMEMORY_PROVIDER_KEY = "inmemory";
@@ -17,7 +19,13 @@ const accounts = new Map();
17
19
  function stateFor(accountId) {
18
20
  let state = accounts.get(accountId);
19
21
  if (!state) {
20
- state = { log: [], watch: null, nextFetchError: null, nextVerifyError: null };
22
+ state = {
23
+ log: [],
24
+ watch: null,
25
+ nextFetchError: null,
26
+ nextVerifyError: null,
27
+ credentialSecretKey: null,
28
+ };
21
29
  accounts.set(accountId, state);
22
30
  }
23
31
  return state;
@@ -40,6 +48,17 @@ export function failNextFetchWith(accountId, err) {
40
48
  export function failNextVerifyWith(accountId, err) {
41
49
  stateFor(accountId).nextVerifyError = err;
42
50
  }
51
+ /** Test helper: every fetch reads this tenant secret first, like a real
52
+ * provider reads its stored credentials; a missing secret is an auth error. */
53
+ export function requireCredentialSecretOnFetch(accountId, key) {
54
+ stateFor(accountId).credentialSecretKey = key;
55
+ }
56
+ async function readCredentialSecret(ctx, account, key) {
57
+ const secrets = requireSecretsContext({ secrets: ctx.secrets, auditUserId: ctx.auditUserId }, FEATURE_NAME);
58
+ const secret = await secrets.get(account.tenantId, key);
59
+ if (!secret)
60
+ throw new InboundAuthError(`${FEATURE_NAME}: no credential secret ${key}`);
61
+ }
43
62
  /** Test helper: simulates the live connection terminating (the supervisor
44
63
  * must restart with backoff). */
45
64
  export function emitWatchError(accountId, err) {
@@ -75,13 +94,16 @@ export const inMemoryInboundMailPlugin = {
75
94
  throw err;
76
95
  }
77
96
  },
78
- fetch: async (_ctx, account, cursor, opts) => {
97
+ fetch: async (ctx, account, cursor, opts) => {
79
98
  const state = stateFor(account.id);
80
99
  if (state.nextFetchError) {
81
100
  const err = state.nextFetchError;
82
101
  state.nextFetchError = null;
83
102
  throw err;
84
103
  }
104
+ if (state.credentialSecretKey) {
105
+ await readCredentialSecret(ctx, account, state.credentialSecretKey);
106
+ }
85
107
  const offset = offsetOf(cursor);
86
108
  const slice = state.log.slice(offset, offset + opts.maxMessages);
87
109
  const nextOffset = offset + slice.length;
@@ -1 +1 @@
1
- export { createInboundProviderInMemoryFeature, emitWatchError, failNextFetchWith, failNextVerifyWith, INMEMORY_PROVIDER_KEY, isWatching, resetInboundInMemory, seedInboundMessage, } from "./feature.ts";
1
+ export { createInboundProviderInMemoryFeature, emitWatchError, failNextFetchWith, failNextVerifyWith, INMEMORY_PROVIDER_KEY, isWatching, requireCredentialSecretOnFetch, resetInboundInMemory, seedInboundMessage, } from "./feature.ts";
@@ -1,2 +1,2 @@
1
1
  // Public API of the inbound-provider-inmemory bundled-feature.
2
- export { createInboundProviderInMemoryFeature, emitWatchError, failNextFetchWith, failNextVerifyWith, INMEMORY_PROVIDER_KEY, isWatching, resetInboundInMemory, seedInboundMessage, } from "./feature.js";
2
+ export { createInboundProviderInMemoryFeature, emitWatchError, failNextFetchWith, failNextVerifyWith, INMEMORY_PROVIDER_KEY, isWatching, requireCredentialSecretOnFetch, resetInboundInMemory, seedInboundMessage, } from "./feature.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.392.0",
3
+ "version": "0.392.2",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -558,9 +558,9 @@
558
558
  }
559
559
  },
560
560
  "dependencies": {
561
- "@cosmicdrift/kumiko-framework": "0.392.0",
562
- "@cosmicdrift/kumiko-renderer": "0.392.0",
563
- "@cosmicdrift/kumiko-types": "0.392.0",
561
+ "@cosmicdrift/kumiko-framework": "0.392.2",
562
+ "@cosmicdrift/kumiko-renderer": "0.392.2",
563
+ "@cosmicdrift/kumiko-types": "0.392.2",
564
564
  "@mollie/api-client": "^4.5.0",
565
565
  "@node-rs/argon2": "^2.0.2",
566
566
  "@tiptap/core": "^3.31.3",
@@ -1142,17 +1142,17 @@
1142
1142
  ],
1143
1143
  "devDependencies": {
1144
1144
  "@testing-library/user-event": "^14.6.1",
1145
- "@cosmicdrift/kumiko-locale-de": "0.392.0",
1146
- "@cosmicdrift/kumiko-locale-es": "0.392.0",
1147
- "@cosmicdrift/kumiko-locale-fr": "0.392.0",
1148
- "@cosmicdrift/kumiko-locale-it": "0.392.0",
1149
- "@cosmicdrift/kumiko-renderer-mail-html": "0.392.0",
1145
+ "@cosmicdrift/kumiko-locale-de": "0.392.2",
1146
+ "@cosmicdrift/kumiko-locale-es": "0.392.2",
1147
+ "@cosmicdrift/kumiko-locale-fr": "0.392.2",
1148
+ "@cosmicdrift/kumiko-locale-it": "0.392.2",
1149
+ "@cosmicdrift/kumiko-renderer-mail-html": "0.392.2",
1150
1150
  "jsqr": "^1.4.0",
1151
1151
  "unpdf": "^1.8.1",
1152
- "@cosmicdrift/kumiko-testing": "0.392.0"
1152
+ "@cosmicdrift/kumiko-testing": "0.392.2"
1153
1153
  },
1154
1154
  "peerDependencies": {
1155
- "@cosmicdrift/kumiko-testing": "0.392.0"
1155
+ "@cosmicdrift/kumiko-testing": "0.392.2"
1156
1156
  },
1157
1157
  "peerDependenciesMeta": {
1158
1158
  "@cosmicdrift/kumiko-testing": {
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.392.1",
4
+ "type": "fix",
5
+ "title": "Cap cards \"view all\" link uses the primary link style",
6
+ "detail": "The link under the cap cards no longer renders in muted grey; it uses the default link variant like other text links."
7
+ },
2
8
  {
3
9
  "version": "0.392.0",
4
10
  "type": "improvement",
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.392.2",
4
+ "type": "fix",
5
+ "title": "Mail sync no longer fails when reading IMAP credentials",
6
+ "detail": "createInboundMailSupervisor builds its provider context with auditUserId defaulting to the system user. Apps that did not set auditUserId in providerCtx got an internal error on every fetch and watch because the secrets context needs an audit identity. An explicitly set auditUserId is kept."
7
+ },
8
+ {
9
+ "version": "0.392.2",
10
+ "type": "fix",
11
+ "title": "Mail sync finds the stored IMAP credentials under row-level security",
12
+ "detail": "createInboundMailSupervisor runs outside any request, so secret reads ran without a tenant scope and the application role saw no row in read_tenant_secrets. The supervisor now wraps providerCtx.secrets with tenantScopedSecrets, which the framework exports from internal/row-level-security. inbound-provider-inmemory gains requireCredentialSecretOnFetch so tests can exercise credential reads; a new integration test runs the secrets against an application-role database."
13
+ },
2
14
  {
3
15
  "version": "0.392.0",
4
16
  "type": "fix",
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.392.1",
4
+ "type": "fix",
5
+ "title": "Mailboxes connect and send over SMTP port 587 with STARTTLS",
6
+ "detail": "The connect form gets a required field \"SMTP port\", preset to 587, and stores it as smtpPort in the credential document (the schema still accepts any stored port, default now 587). Port 465 uses implicit TLS; every other port uses STARTTLS with requireTLS, so the password is never sent in clear text and the connection fails when the server offers no STARTTLS. The certificate is checked against the host name on the STARTTLS upgrade as well. The SMTP failure texts name the entered port. New key for the field label in all locale packages."
7
+ },
2
8
  {
3
9
  "version": "0.392.0",
4
10
  "type": "improvement",