@open-mercato/channel-imap 0.6.4
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/.turbo/turbo-build.log +2 -0
- package/AGENTS.md +56 -0
- package/build.mjs +7 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-001.spec.js +62 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-001.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-002.spec.js +19 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-002.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-003.spec.js +16 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-003.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-021.spec.js +26 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-021.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-022.spec.js +27 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-022.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-023.spec.js +15 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-023.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-024.spec.js +15 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-024.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-025.spec.js +6 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-025.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-026.spec.js +6 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-026.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-027.spec.js +6 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-027.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-028.spec.js +6 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-028.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-029.spec.js +48 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-029.spec.js.map +7 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-030.spec.js +6 -0
- package/dist/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-030.spec.js.map +7 -0
- package/dist/modules/channel_imap/acl.js +10 -0
- package/dist/modules/channel_imap/acl.js.map +7 -0
- package/dist/modules/channel_imap/di.js +23 -0
- package/dist/modules/channel_imap/di.js.map +7 -0
- package/dist/modules/channel_imap/index.js +9 -0
- package/dist/modules/channel_imap/index.js.map +7 -0
- package/dist/modules/channel_imap/integration.js +135 -0
- package/dist/modules/channel_imap/integration.js.map +7 -0
- package/dist/modules/channel_imap/lib/adapter.js +291 -0
- package/dist/modules/channel_imap/lib/adapter.js.map +7 -0
- package/dist/modules/channel_imap/lib/capabilities.js +8 -0
- package/dist/modules/channel_imap/lib/capabilities.js.map +7 -0
- package/dist/modules/channel_imap/lib/convert-outbound.js +54 -0
- package/dist/modules/channel_imap/lib/convert-outbound.js.map +7 -0
- package/dist/modules/channel_imap/lib/credentials.js +104 -0
- package/dist/modules/channel_imap/lib/credentials.js.map +7 -0
- package/dist/modules/channel_imap/lib/health.js +39 -0
- package/dist/modules/channel_imap/lib/health.js.map +7 -0
- package/dist/modules/channel_imap/lib/host-pinning.js +34 -0
- package/dist/modules/channel_imap/lib/host-pinning.js.map +7 -0
- package/dist/modules/channel_imap/lib/imap-client.js +210 -0
- package/dist/modules/channel_imap/lib/imap-client.js.map +7 -0
- package/dist/modules/channel_imap/lib/normalize-inbound.js +19 -0
- package/dist/modules/channel_imap/lib/normalize-inbound.js.map +7 -0
- package/dist/modules/channel_imap/lib/smtp-client.js +113 -0
- package/dist/modules/channel_imap/lib/smtp-client.js.map +7 -0
- package/dist/modules/channel_imap/lib/transport.js +17 -0
- package/dist/modules/channel_imap/lib/transport.js.map +7 -0
- package/dist/modules/channel_imap/lib/validate-credentials.js +69 -0
- package/dist/modules/channel_imap/lib/validate-credentials.js.map +7 -0
- package/dist/modules/channel_imap/setup.js +25 -0
- package/dist/modules/channel_imap/setup.js.map +7 -0
- package/dist/modules/channel_imap/widgets/injection/connect/widget.client.js +337 -0
- package/dist/modules/channel_imap/widgets/injection/connect/widget.client.js.map +7 -0
- package/dist/modules/channel_imap/widgets/injection/connect/widget.js +17 -0
- package/dist/modules/channel_imap/widgets/injection/connect/widget.js.map +7 -0
- package/dist/modules/channel_imap/widgets/injection-table.js +14 -0
- package/dist/modules/channel_imap/widgets/injection-table.js.map +7 -0
- package/jest.config.cjs +34 -0
- package/package.json +99 -0
- package/src/index.ts +1 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-001.spec.ts +80 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-002.spec.ts +28 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-003.spec.ts +23 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-021.spec.ts +40 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-022.spec.ts +38 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-023.spec.ts +31 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-024.spec.ts +27 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-025.spec.ts +23 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-026.spec.ts +18 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-027.spec.ts +18 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-028.spec.ts +19 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-029.spec.ts +72 -0
- package/src/modules/channel_imap/__integration__/TC-CHANNEL-EMAIL-030.spec.ts +19 -0
- package/src/modules/channel_imap/acl.ts +6 -0
- package/src/modules/channel_imap/di.ts +26 -0
- package/src/modules/channel_imap/index.ts +6 -0
- package/src/modules/channel_imap/integration.ts +131 -0
- package/src/modules/channel_imap/lib/__tests__/adapter.test.ts +499 -0
- package/src/modules/channel_imap/lib/__tests__/convert-outbound.test.ts +73 -0
- package/src/modules/channel_imap/lib/__tests__/credentials.test.ts +154 -0
- package/src/modules/channel_imap/lib/__tests__/host-pinning.test.ts +68 -0
- package/src/modules/channel_imap/lib/__tests__/imap-client.test.ts +180 -0
- package/src/modules/channel_imap/lib/__tests__/normalize-inbound.test.ts +126 -0
- package/src/modules/channel_imap/lib/__tests__/transport.test.ts +68 -0
- package/src/modules/channel_imap/lib/__tests__/validate-credentials.test.ts +156 -0
- package/src/modules/channel_imap/lib/adapter.ts +451 -0
- package/src/modules/channel_imap/lib/capabilities.ts +16 -0
- package/src/modules/channel_imap/lib/convert-outbound.ts +79 -0
- package/src/modules/channel_imap/lib/credentials.ts +172 -0
- package/src/modules/channel_imap/lib/health.ts +70 -0
- package/src/modules/channel_imap/lib/host-pinning.ts +59 -0
- package/src/modules/channel_imap/lib/imap-client.ts +382 -0
- package/src/modules/channel_imap/lib/normalize-inbound.ts +47 -0
- package/src/modules/channel_imap/lib/smtp-client.ts +214 -0
- package/src/modules/channel_imap/lib/transport.ts +37 -0
- package/src/modules/channel_imap/lib/validate-credentials.ts +98 -0
- package/src/modules/channel_imap/setup.ts +34 -0
- package/src/modules/channel_imap/widgets/injection/connect/widget.client.tsx +359 -0
- package/src/modules/channel_imap/widgets/injection/connect/widget.ts +16 -0
- package/src/modules/channel_imap/widgets/injection-table.ts +12 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +7 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/channel_imap/lib/smtp-client.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ImapCredentials } from './credentials'\nimport { resolveSafeHostAddress } from './host-pinning'\nimport { assertTransportAllowed } from './transport'\n\n/**\n * Outbound SMTP client wrapper. Same trade-offs as `imap-client.ts`: we wrap\n * `nodemailer` behind a tiny interface so tests can swap in a mock and the\n * adapter doesn't import SDK types directly.\n */\n\nexport interface SmtpConnectionOptions {\n host: string\n port: number\n user: string\n pass: string\n transport: 'tls' | 'starttls' | 'none'\n timeoutMs?: number\n}\n\nexport interface SmtpMessage {\n from: string\n to: string[]\n cc?: string[]\n bcc?: string[]\n subject?: string\n text?: string\n html?: string\n /** RFC2822 Message-ID; if omitted nodemailer generates one. */\n messageId?: string\n /** RFC2822 In-Reply-To (single value). */\n inReplyTo?: string\n /** RFC2822 References (whitespace-delimited list). */\n references?: string[]\n attachments?: Array<{\n filename: string\n content: Buffer\n contentType?: string\n cid?: string\n inline?: boolean\n }>\n headers?: Record<string, string>\n}\n\nexport interface SmtpSendResult {\n /** Effective Message-ID. */\n messageId: string\n /** Raw RFC2822 message buffer (used for Sent-folder append). */\n raw: Buffer\n /** Provider response string. */\n response?: string\n}\n\nexport interface SmtpClient {\n verify(options: SmtpConnectionOptions): Promise<void>\n send(options: SmtpConnectionOptions, message: SmtpMessage): Promise<SmtpSendResult>\n}\n\nclass NodemailerClient implements SmtpClient {\n async verify(options: SmtpConnectionOptions): Promise<void> {\n const { transporter } = await this.createTransporter(options)\n try {\n await transporter.verify()\n } finally {\n // Mirror send(): close on every path so a failed verify (wrong password,\n // unreachable host \u2014 the common case) does not leak the socket pool.\n transporter.close()\n }\n }\n\n async send(options: SmtpConnectionOptions, message: SmtpMessage): Promise<SmtpSendResult> {\n const { transporter, MailComposer } = await this.createTransporter(options)\n try {\n const mailOptions: Record<string, unknown> = {\n from: message.from,\n to: message.to,\n cc: message.cc,\n bcc: message.bcc,\n subject: message.subject,\n text: message.text,\n html: message.html,\n messageId: message.messageId,\n inReplyTo: message.inReplyTo,\n references: message.references,\n attachments: message.attachments?.map((a) => ({\n filename: a.filename,\n content: a.content,\n contentType: a.contentType,\n cid: a.cid,\n contentDisposition: a.inline ? 'inline' : 'attachment',\n })),\n headers: message.headers,\n }\n\n // Build the RFC2822 bytes ourselves via MailComposer so we can capture\n // them for the Sent-folder append (review H1, 2026-05-26).\n // nodemailer's `transporter.sendMail` info object does NOT contain `raw`\n // unless you configure a streamTransport, so naively reading\n // `info.raw` produces a 0-byte buffer and the Sent-folder append uploads\n // a corrupt message.\n let raw: Buffer = Buffer.alloc(0)\n let composedMessageId = message.messageId\n if (typeof MailComposer === 'function') {\n try {\n const composed = new MailComposer(mailOptions) as unknown as {\n compile: () => {\n build: (callback: (err: Error | null, output: Buffer) => void) => void\n messageId?: () => string | undefined\n }\n }\n const compiled = composed.compile()\n raw = await new Promise<Buffer>((resolve, reject) => {\n compiled.build((err, output) => {\n if (err) reject(err)\n else resolve(output)\n })\n })\n const messageIdFn = compiled.messageId\n if (typeof messageIdFn === 'function') {\n composedMessageId = messageIdFn.call(compiled) ?? composedMessageId\n }\n } catch (composeError) {\n // MailComposer build failed: the send below still delivers the mail, but we\n // cannot capture the RFC2822 bytes, so the caller skips the Sent-folder append.\n // Log so operators can diagnose missing Sent archival.\n raw = Buffer.alloc(0)\n console.warn(\n '[internal] channel_imap: failed to build RFC2822 bytes for Sent-folder append:',\n composeError instanceof Error ? composeError.message : composeError,\n )\n }\n }\n\n const info = (await transporter.sendMail(mailOptions)) as {\n messageId?: string\n envelope?: { messageId?: string }\n response?: string\n }\n const id = info.messageId ?? composedMessageId ?? info.envelope?.messageId\n if (!id) throw new Error('[internal] SMTP server did not return a Message-ID')\n return { messageId: id, raw, response: info.response }\n } finally {\n transporter.close()\n }\n }\n\n private async createTransporter(\n options: SmtpConnectionOptions,\n ): Promise<{\n transporter: NodemailerTransporter\n MailComposer: (new (mail: Record<string, unknown>) => unknown) | undefined\n }> {\n const mod = (await import('nodemailer')) as unknown as {\n default?: {\n createTransport: (opts: Record<string, unknown>) => NodemailerTransporter\n MailComposer?: new (mail: Record<string, unknown>) => unknown\n }\n createTransport?: (opts: Record<string, unknown>) => NodemailerTransporter\n MailComposer?: new (mail: Record<string, unknown>) => unknown\n }\n const createTransport = mod.createTransport ?? mod.default?.createTransport\n if (typeof createTransport !== 'function') {\n throw new Error('nodemailer.createTransport is unavailable')\n }\n const MailComposer = mod.MailComposer ?? mod.default?.MailComposer\n // Resolve + pin the SMTP host to a validated public IP at connect time\n // (DNS-rebinding-safe), keeping the hostname as the TLS servername for SNI +\n // certificate hostname verification.\n const pinned = await resolveSafeHostAddress(options.host)\n const transporter = createTransport({\n host: pinned.host,\n port: options.port,\n secure: options.transport === 'tls',\n requireTLS: options.transport === 'starttls',\n auth: { user: options.user, pass: options.pass },\n connectionTimeout: options.timeoutMs ?? 10_000,\n // Reject downgrade attacks: only allow cleartext when the operator\n // explicitly opts into `transport: 'none'`. Even then, refuse to skip\n // certificate verification on STARTTLS / TLS.\n tls:\n options.transport === 'none'\n ? undefined\n : { rejectUnauthorized: true, ...(pinned.servername ? { servername: pinned.servername } : {}) },\n })\n return { transporter, MailComposer }\n }\n}\n\ninterface NodemailerTransporter {\n verify(): Promise<true>\n sendMail(options: Record<string, unknown>): Promise<unknown>\n close(): void\n}\n\nlet cachedClient: SmtpClient | null = null\n\nexport function getSmtpClient(): SmtpClient {\n if (!cachedClient) cachedClient = new NodemailerClient()\n return cachedClient\n}\n\nexport function setSmtpClient(client: SmtpClient | null): void {\n cachedClient = client\n}\n\nexport function credentialsToSmtpConnection(credentials: ImapCredentials): SmtpConnectionOptions {\n assertTransportAllowed(credentials)\n return {\n host: credentials.smtpHost,\n port: Number(credentials.smtpPort),\n user: credentials.smtpUser,\n pass: credentials.smtpPassword,\n transport: credentials.smtpTls,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
2
|
+
const INSECURE_TRANSPORT_MESSAGE = "Cleartext transport (None) is not allowed. Use STARTTLS or implicit TLS. An operator must set OM_CHANNEL_IMAP_ALLOW_INSECURE_TRANSPORT=true to permit it.";
|
|
3
|
+
function isInsecureTransportAllowed() {
|
|
4
|
+
return parseBooleanWithDefault(process.env.OM_CHANNEL_IMAP_ALLOW_INSECURE_TRANSPORT, false);
|
|
5
|
+
}
|
|
6
|
+
function assertTransportAllowed(credentials) {
|
|
7
|
+
if (isInsecureTransportAllowed()) return;
|
|
8
|
+
if (credentials.imapTls === "none" || credentials.smtpTls === "none") {
|
|
9
|
+
throw new Error(`[internal] ${INSECURE_TRANSPORT_MESSAGE}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export {
|
|
13
|
+
INSECURE_TRANSPORT_MESSAGE,
|
|
14
|
+
assertTransportAllowed,
|
|
15
|
+
isInsecureTransportAllowed
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/channel_imap/lib/transport.ts"],
|
|
4
|
+
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport type { ImapCredentials } from './credentials'\n\n/**\n * Single source of truth for the cleartext-transport policy.\n *\n * `imapTls`/`smtpTls === 'none'` disables TLS entirely and sends the password in\n * the clear over an attacker-controlled host string. The credential schema still\n * *permits* `'none'`, so this guard \u2014 not the schema \u2014 is what actually rejects\n * it. Centralizing it here lets every code path (validate, health, send, poll,\n * import) enforce one rule: a stored blob with `'none'` is refused on every\n * connection build unless an operator opts in via\n * `OM_CHANNEL_IMAP_ALLOW_INSECURE_TRANSPORT=true`. `'starttls'`/`'tls'` are\n * always allowed.\n */\n\nexport const INSECURE_TRANSPORT_MESSAGE =\n 'Cleartext transport (None) is not allowed. Use STARTTLS or implicit TLS. ' +\n 'An operator must set OM_CHANNEL_IMAP_ALLOW_INSECURE_TRANSPORT=true to permit it.'\n\nexport function isInsecureTransportAllowed(): boolean {\n return parseBooleanWithDefault(process.env.OM_CHANNEL_IMAP_ALLOW_INSECURE_TRANSPORT, false)\n}\n\n/**\n * Throws when either transport is cleartext (`'none'`) and the operator opt-in\n * flag is unset. Called inside the credentials \u2192 connection translators so it\n * runs on every IMAP/SMTP connection build \u2014 including reads of credential blobs\n * persisted while the flag was set, or written via a path that bypassed\n * `validateImapCredentials`.\n */\nexport function assertTransportAllowed(credentials: Pick<ImapCredentials, 'imapTls' | 'smtpTls'>): void {\n if (isInsecureTransportAllowed()) return\n if (credentials.imapTls === 'none' || credentials.smtpTls === 'none') {\n throw new Error(`[internal] ${INSECURE_TRANSPORT_MESSAGE}`)\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,+BAA+B;AAgBjC,MAAM,6BACX;AAGK,SAAS,6BAAsC;AACpD,SAAO,wBAAwB,QAAQ,IAAI,0CAA0C,KAAK;AAC5F;AASO,SAAS,uBAAuB,aAAiE;AACtG,MAAI,2BAA2B,EAAG;AAClC,MAAI,YAAY,YAAY,UAAU,YAAY,YAAY,QAAQ;AACpE,UAAM,IAAI,MAAM,cAAc,0BAA0B,EAAE;AAAA,EAC5D;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { imapCredentialsSchema } from "./credentials.js";
|
|
2
|
+
import {
|
|
3
|
+
credentialsToConnection,
|
|
4
|
+
getImapClient
|
|
5
|
+
} from "./imap-client.js";
|
|
6
|
+
import {
|
|
7
|
+
credentialsToSmtpConnection,
|
|
8
|
+
getSmtpClient
|
|
9
|
+
} from "./smtp-client.js";
|
|
10
|
+
import { INSECURE_TRANSPORT_MESSAGE, isInsecureTransportAllowed } from "./transport.js";
|
|
11
|
+
async function validateImapCredentials(rawCredentials) {
|
|
12
|
+
const parsed = imapCredentialsSchema.safeParse(rawCredentials);
|
|
13
|
+
if (!parsed.success) {
|
|
14
|
+
const errors = {};
|
|
15
|
+
for (const issue of parsed.error.issues) {
|
|
16
|
+
const path = issue.path[0];
|
|
17
|
+
if (typeof path !== "string") continue;
|
|
18
|
+
if (!errors[path]) errors[path] = issue.message;
|
|
19
|
+
}
|
|
20
|
+
return { ok: false, errors };
|
|
21
|
+
}
|
|
22
|
+
const credentials = parsed.data;
|
|
23
|
+
if (!isInsecureTransportAllowed()) {
|
|
24
|
+
const insecureTransportErrors = {};
|
|
25
|
+
if (credentials.imapTls === "none") insecureTransportErrors.imapTls = INSECURE_TRANSPORT_MESSAGE;
|
|
26
|
+
if (credentials.smtpTls === "none") insecureTransportErrors.smtpTls = INSECURE_TRANSPORT_MESSAGE;
|
|
27
|
+
if (Object.keys(insecureTransportErrors).length > 0) {
|
|
28
|
+
return { ok: false, errors: insecureTransportErrors };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const imap = getImapClient();
|
|
32
|
+
const smtp = getSmtpClient();
|
|
33
|
+
try {
|
|
34
|
+
await imap.connectAndValidate(credentialsToConnection(credentials));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
ok: false,
|
|
38
|
+
errors: {
|
|
39
|
+
imapPassword: classifyAuthError(error, "IMAP login failed.")
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await smtp.verify(credentialsToSmtpConnection(credentials));
|
|
45
|
+
} catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
errors: {
|
|
49
|
+
smtpPassword: classifyAuthError(error, "SMTP login failed.")
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { ok: true };
|
|
54
|
+
}
|
|
55
|
+
function classifyAuthError(error, fallback) {
|
|
56
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
57
|
+
console.warn("[internal] channel_imap credential validation failed:", message);
|
|
58
|
+
if (/auth|login|credentials|535|454|530/i.test(message)) {
|
|
59
|
+
return "Authentication rejected by the server. Check the username and password.";
|
|
60
|
+
}
|
|
61
|
+
if (/timeout|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN/i.test(message)) {
|
|
62
|
+
return "Could not reach the server. Check the host and port.";
|
|
63
|
+
}
|
|
64
|
+
return fallback;
|
|
65
|
+
}
|
|
66
|
+
export {
|
|
67
|
+
validateImapCredentials
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=validate-credentials.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/channel_imap/lib/validate-credentials.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ValidateCredentialsResult } from '@open-mercato/core/modules/communication_channels/lib/adapter'\nimport { imapCredentialsSchema } from './credentials'\nimport {\n credentialsToConnection,\n getImapClient,\n} from './imap-client'\nimport {\n credentialsToSmtpConnection,\n getSmtpClient,\n} from './smtp-client'\nimport { INSECURE_TRANSPORT_MESSAGE, isInsecureTransportAllowed } from './transport'\n\n/**\n * Validate IMAP+SMTP credentials by attempting a live LOGIN on both servers.\n *\n * Strategy:\n * 1. Zod-parse the credential payload \u2014 returns shape errors first.\n * 2. Open IMAP, capture capabilities, log out.\n * 3. Run SMTP `verify` (extends EHLO, optional STARTTLS, AUTH LOGIN ping).\n *\n * Returns `{ ok: false, errors }` with field-level messages so the hub can pass\n * them straight to `createCrudFormError` and the CrudForm inline-highlights the\n * offending input. Returns `{ ok: true }` only when both servers accept the login.\n */\n\nexport async function validateImapCredentials(\n rawCredentials: unknown,\n): Promise<ValidateCredentialsResult> {\n const parsed = imapCredentialsSchema.safeParse(rawCredentials)\n if (!parsed.success) {\n const errors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const path = issue.path[0]\n if (typeof path !== 'string') continue\n // First error wins per field \u2014 CrudForm only renders one per field anyway.\n if (!errors[path]) errors[path] = issue.message\n }\n return { ok: false, errors }\n }\n\n const credentials = parsed.data\n\n // Reject cleartext transport by default. The shared `transport` helper is the\n // single source of truth for the policy (and enforces it again at connection\n // build time for every op); here we surface it as field-level errors so the\n // connect form can inline-highlight the offending TLS selector without\n // touching the network.\n if (!isInsecureTransportAllowed()) {\n const insecureTransportErrors: Record<string, string> = {}\n if (credentials.imapTls === 'none') insecureTransportErrors.imapTls = INSECURE_TRANSPORT_MESSAGE\n if (credentials.smtpTls === 'none') insecureTransportErrors.smtpTls = INSECURE_TRANSPORT_MESSAGE\n if (Object.keys(insecureTransportErrors).length > 0) {\n return { ok: false, errors: insecureTransportErrors }\n }\n }\n\n const imap = getImapClient()\n const smtp = getSmtpClient()\n\n try {\n await imap.connectAndValidate(credentialsToConnection(credentials))\n } catch (error) {\n return {\n ok: false,\n errors: {\n imapPassword: classifyAuthError(error, 'IMAP login failed.'),\n },\n }\n }\n\n try {\n await smtp.verify(credentialsToSmtpConnection(credentials))\n } catch (error) {\n return {\n ok: false,\n errors: {\n smtpPassword: classifyAuthError(error, 'SMTP login failed.'),\n },\n }\n }\n\n return { ok: true }\n}\n\nfunction classifyAuthError(error: unknown, fallback: string): string {\n // Keep the coarse classification but never echo raw upstream server text\n // (banners, internal hostnames) back to the client. Log the full original\n // message server-side for diagnostics instead.\n const message = error instanceof Error ? error.message : String(error ?? '')\n console.warn('[internal] channel_imap credential validation failed:', message)\n if (/auth|login|credentials|535|454|530/i.test(message)) {\n return 'Authentication rejected by the server. Check the username and password.'\n }\n if (/timeout|ETIMEDOUT|ECONNREFUSED|ENOTFOUND|EAI_AGAIN/i.test(message)) {\n return 'Could not reach the server. Check the host and port.'\n }\n return fallback\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AACtC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B,kCAAkC;AAevE,eAAsB,wBACpB,gBACoC;AACpC,QAAM,SAAS,sBAAsB,UAAU,cAAc;AAC7D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAiC,CAAC;AACxC,eAAW,SAAS,OAAO,MAAM,QAAQ;AACvC,YAAM,OAAO,MAAM,KAAK,CAAC;AACzB,UAAI,OAAO,SAAS,SAAU;AAE9B,UAAI,CAAC,OAAO,IAAI,EAAG,QAAO,IAAI,IAAI,MAAM;AAAA,IAC1C;AACA,WAAO,EAAE,IAAI,OAAO,OAAO;AAAA,EAC7B;AAEA,QAAM,cAAc,OAAO;AAO3B,MAAI,CAAC,2BAA2B,GAAG;AACjC,UAAM,0BAAkD,CAAC;AACzD,QAAI,YAAY,YAAY,OAAQ,yBAAwB,UAAU;AACtE,QAAI,YAAY,YAAY,OAAQ,yBAAwB,UAAU;AACtE,QAAI,OAAO,KAAK,uBAAuB,EAAE,SAAS,GAAG;AACnD,aAAO,EAAE,IAAI,OAAO,QAAQ,wBAAwB;AAAA,IACtD;AAAA,EACF;AAEA,QAAM,OAAO,cAAc;AAC3B,QAAM,OAAO,cAAc;AAE3B,MAAI;AACF,UAAM,KAAK,mBAAmB,wBAAwB,WAAW,CAAC;AAAA,EACpE,SAAS,OAAO;AACd,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,QACN,cAAc,kBAAkB,OAAO,oBAAoB;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,KAAK,OAAO,4BAA4B,WAAW,CAAC;AAAA,EAC5D,SAAS,OAAO;AACd,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,QAAQ;AAAA,QACN,cAAc,kBAAkB,OAAO,oBAAoB;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,KAAK;AACpB;AAEA,SAAS,kBAAkB,OAAgB,UAA0B;AAInE,QAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,EAAE;AAC3E,UAAQ,KAAK,yDAAyD,OAAO;AAC7E,MAAI,sCAAsC,KAAK,OAAO,GAAG;AACvD,WAAO;AAAA,EACT;AACA,MAAI,sDAAsD,KAAK,OAAO,GAAG;AACvE,WAAO;AAAA,EACT;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hasChannelAdapter,
|
|
3
|
+
registerChannelAdapter
|
|
4
|
+
} from "@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton";
|
|
5
|
+
import { getImapChannelAdapter } from "./lib/adapter.js";
|
|
6
|
+
function ensureImapAdapterRegistered() {
|
|
7
|
+
if (hasChannelAdapter("imap")) return;
|
|
8
|
+
registerChannelAdapter(getImapChannelAdapter());
|
|
9
|
+
}
|
|
10
|
+
ensureImapAdapterRegistered();
|
|
11
|
+
const setup = {
|
|
12
|
+
defaultRoleFeatures: {
|
|
13
|
+
superadmin: ["channel_imap.view", "channel_imap.configure"],
|
|
14
|
+
admin: ["channel_imap.view", "channel_imap.configure"]
|
|
15
|
+
},
|
|
16
|
+
async onTenantCreated() {
|
|
17
|
+
ensureImapAdapterRegistered();
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var setup_default = setup;
|
|
21
|
+
export {
|
|
22
|
+
setup_default as default,
|
|
23
|
+
setup
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/modules/channel_imap/setup.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport {\n hasChannelAdapter,\n registerChannelAdapter,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'\nimport { getImapChannelAdapter } from './lib/adapter'\n\n/**\n * The IMAP provider registers its `ChannelAdapter` exactly once per process at\n * import time. We guard with `hasChannelAdapter` so dev-mode HMR or repeated\n * imports during tests don't throw the registry's \"duplicate providerKey\" error.\n *\n * No per-tenant onTenantCreated work is needed: IMAP credentials are connected\n * by individual users via the `/backend/profile/communication-channels` page,\n * not via tenant-bootstrap env presets.\n */\nfunction ensureImapAdapterRegistered(): void {\n if (hasChannelAdapter('imap')) return\n registerChannelAdapter(getImapChannelAdapter())\n}\n\nensureImapAdapterRegistered()\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n superadmin: ['channel_imap.view', 'channel_imap.configure'],\n admin: ['channel_imap.view', 'channel_imap.configure'],\n },\n async onTenantCreated() {\n ensureImapAdapterRegistered()\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;AAWtC,SAAS,8BAAoC;AAC3C,MAAI,kBAAkB,MAAM,EAAG;AAC/B,yBAAuB,sBAAsB,CAAC;AAChD;AAEA,4BAA4B;AAErB,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA,IACnB,YAAY,CAAC,qBAAqB,wBAAwB;AAAA,IAC1D,OAAO,CAAC,qBAAqB,wBAAwB;AAAA,EACvD;AAAA,EACA,MAAM,kBAAkB;AACtB,gCAA4B;AAAA,EAC9B;AACF;AAEA,IAAO,gBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
6
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
7
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
|
+
import {
|
|
10
|
+
Dialog,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogFooter,
|
|
13
|
+
DialogHeader,
|
|
14
|
+
DialogTitle
|
|
15
|
+
} from "@open-mercato/ui/primitives/dialog";
|
|
16
|
+
import { Input } from "@open-mercato/ui/primitives/input";
|
|
17
|
+
import { Label } from "@open-mercato/ui/primitives/label";
|
|
18
|
+
import { PasswordInput } from "@open-mercato/ui/primitives/password-input";
|
|
19
|
+
import {
|
|
20
|
+
Select,
|
|
21
|
+
SelectContent,
|
|
22
|
+
SelectItem,
|
|
23
|
+
SelectTrigger,
|
|
24
|
+
SelectValue
|
|
25
|
+
} from "@open-mercato/ui/primitives/select";
|
|
26
|
+
const INITIAL_FORM = {
|
|
27
|
+
displayName: "",
|
|
28
|
+
fromAddress: "",
|
|
29
|
+
imapHost: "",
|
|
30
|
+
imapPort: "993",
|
|
31
|
+
imapTls: "tls",
|
|
32
|
+
imapUser: "",
|
|
33
|
+
imapPassword: "",
|
|
34
|
+
smtpHost: "",
|
|
35
|
+
smtpPort: "465",
|
|
36
|
+
smtpTls: "tls",
|
|
37
|
+
smtpUser: "",
|
|
38
|
+
smtpPassword: ""
|
|
39
|
+
};
|
|
40
|
+
function ConnectImapWidget({
|
|
41
|
+
context
|
|
42
|
+
}) {
|
|
43
|
+
const t = useT();
|
|
44
|
+
const widgetContext = context;
|
|
45
|
+
const [open, setOpen] = React.useState(false);
|
|
46
|
+
const [pending, setPending] = React.useState(false);
|
|
47
|
+
const [form, setForm] = React.useState(INITIAL_FORM);
|
|
48
|
+
const [fieldErrors, setFieldErrors] = React.useState({});
|
|
49
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
50
|
+
contextId: "channel-imap-connect",
|
|
51
|
+
blockedMessage: t("communication_channels.profile.connect.blocked", "Connection blocked by validation")
|
|
52
|
+
});
|
|
53
|
+
const mutationContext = React.useMemo(
|
|
54
|
+
() => ({ providerKey: "imap", retryLastMutation }),
|
|
55
|
+
[retryLastMutation]
|
|
56
|
+
);
|
|
57
|
+
const update = React.useCallback(
|
|
58
|
+
(key, value) => {
|
|
59
|
+
setForm((current) => ({ ...current, [key]: value }));
|
|
60
|
+
setFieldErrors((current) => {
|
|
61
|
+
if (!current[key]) return current;
|
|
62
|
+
const next = { ...current };
|
|
63
|
+
delete next[key];
|
|
64
|
+
return next;
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[]
|
|
68
|
+
);
|
|
69
|
+
const submit = React.useCallback(async () => {
|
|
70
|
+
if (pending) return;
|
|
71
|
+
setPending(true);
|
|
72
|
+
setFieldErrors({});
|
|
73
|
+
try {
|
|
74
|
+
const response = await runMutation({
|
|
75
|
+
context: mutationContext,
|
|
76
|
+
mutationPayload: { providerKey: "imap", displayName: form.displayName },
|
|
77
|
+
operation: () => apiCall("/api/communication_channels/channels/connect/credentials", {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "content-type": "application/json" },
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
providerKey: "imap",
|
|
82
|
+
displayName: form.displayName.trim() || form.fromAddress.trim(),
|
|
83
|
+
pollIntervalSeconds: 300,
|
|
84
|
+
credentials: {
|
|
85
|
+
imapHost: form.imapHost.trim(),
|
|
86
|
+
imapPort: Number(form.imapPort),
|
|
87
|
+
imapTls: form.imapTls,
|
|
88
|
+
imapUser: form.imapUser.trim(),
|
|
89
|
+
imapPassword: form.imapPassword,
|
|
90
|
+
smtpHost: form.smtpHost.trim(),
|
|
91
|
+
smtpPort: Number(form.smtpPort),
|
|
92
|
+
smtpTls: form.smtpTls,
|
|
93
|
+
smtpUser: form.smtpUser.trim() || form.imapUser.trim(),
|
|
94
|
+
smtpPassword: form.smtpPassword || form.imapPassword,
|
|
95
|
+
fromAddress: form.fromAddress.trim()
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
});
|
|
100
|
+
const body = response.result;
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
setFieldErrors(body?.fieldErrors ?? {});
|
|
103
|
+
flash(
|
|
104
|
+
body?.error ?? t("communication_channels.profile.connect.credentialsFailed", "Could not connect mailbox."),
|
|
105
|
+
"error"
|
|
106
|
+
);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
flash(t("communication_channels.profile.connect.connected", "Channel connected."), "success");
|
|
110
|
+
setOpen(false);
|
|
111
|
+
setForm(INITIAL_FORM);
|
|
112
|
+
widgetContext?.reload?.();
|
|
113
|
+
} finally {
|
|
114
|
+
setPending(false);
|
|
115
|
+
}
|
|
116
|
+
}, [form, mutationContext, pending, runMutation, t, widgetContext]);
|
|
117
|
+
const onDialogKeyDown = React.useCallback(
|
|
118
|
+
(event) => {
|
|
119
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
void submit();
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[submit]
|
|
125
|
+
);
|
|
126
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
127
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setOpen(true), children: t("communication_channels.profile.connect.imap", "Connect IMAP") }),
|
|
128
|
+
/* @__PURE__ */ jsx(Dialog, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(DialogContent, { onKeyDown: onDialogKeyDown, children: [
|
|
129
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("communication_channels.profile.connect.imapTitle", "Connect IMAP mailbox") }) }),
|
|
130
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 py-2", children: [
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
Field,
|
|
133
|
+
{
|
|
134
|
+
label: t("communication_channels.profile.connect.fields.displayName", "Display name"),
|
|
135
|
+
error: fieldErrors.displayName,
|
|
136
|
+
children: /* @__PURE__ */ jsx(
|
|
137
|
+
Input,
|
|
138
|
+
{
|
|
139
|
+
value: form.displayName,
|
|
140
|
+
onChange: (event) => update("displayName", event.target.value),
|
|
141
|
+
"aria-invalid": Boolean(fieldErrors.displayName)
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
Field,
|
|
148
|
+
{
|
|
149
|
+
label: t("communication_channels.profile.connect.fields.fromAddress", "From address"),
|
|
150
|
+
error: fieldErrors.fromAddress,
|
|
151
|
+
children: /* @__PURE__ */ jsx(
|
|
152
|
+
Input,
|
|
153
|
+
{
|
|
154
|
+
type: "email",
|
|
155
|
+
value: form.fromAddress,
|
|
156
|
+
onChange: (event) => update("fromAddress", event.target.value),
|
|
157
|
+
"aria-invalid": Boolean(fieldErrors.fromAddress)
|
|
158
|
+
}
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
163
|
+
/* @__PURE__ */ jsx(
|
|
164
|
+
Field,
|
|
165
|
+
{
|
|
166
|
+
label: t("communication_channels.profile.connect.fields.imapHost", "IMAP host"),
|
|
167
|
+
error: fieldErrors.imapHost,
|
|
168
|
+
children: /* @__PURE__ */ jsx(
|
|
169
|
+
Input,
|
|
170
|
+
{
|
|
171
|
+
value: form.imapHost,
|
|
172
|
+
onChange: (event) => update("imapHost", event.target.value),
|
|
173
|
+
"aria-invalid": Boolean(fieldErrors.imapHost)
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsx(
|
|
179
|
+
Field,
|
|
180
|
+
{
|
|
181
|
+
label: t("communication_channels.profile.connect.fields.imapPort", "IMAP port"),
|
|
182
|
+
error: fieldErrors.imapPort,
|
|
183
|
+
children: /* @__PURE__ */ jsx(
|
|
184
|
+
Input,
|
|
185
|
+
{
|
|
186
|
+
inputMode: "numeric",
|
|
187
|
+
value: form.imapPort,
|
|
188
|
+
onChange: (event) => update("imapPort", event.target.value),
|
|
189
|
+
"aria-invalid": Boolean(fieldErrors.imapPort)
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
196
|
+
/* @__PURE__ */ jsx(
|
|
197
|
+
Field,
|
|
198
|
+
{
|
|
199
|
+
label: t("communication_channels.profile.connect.fields.imapTls", "IMAP security"),
|
|
200
|
+
error: fieldErrors.imapTls,
|
|
201
|
+
children: /* @__PURE__ */ jsx(TlsSelect, { value: form.imapTls, onChange: (value) => update("imapTls", value) })
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
/* @__PURE__ */ jsx(
|
|
205
|
+
Field,
|
|
206
|
+
{
|
|
207
|
+
label: t("communication_channels.profile.connect.fields.imapUser", "IMAP username"),
|
|
208
|
+
error: fieldErrors.imapUser,
|
|
209
|
+
children: /* @__PURE__ */ jsx(
|
|
210
|
+
Input,
|
|
211
|
+
{
|
|
212
|
+
value: form.imapUser,
|
|
213
|
+
onChange: (event) => update("imapUser", event.target.value),
|
|
214
|
+
"aria-invalid": Boolean(fieldErrors.imapUser)
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
] }),
|
|
220
|
+
/* @__PURE__ */ jsx(
|
|
221
|
+
Field,
|
|
222
|
+
{
|
|
223
|
+
label: t("communication_channels.profile.connect.fields.imapPassword", "IMAP password"),
|
|
224
|
+
error: fieldErrors.imapPassword,
|
|
225
|
+
children: /* @__PURE__ */ jsx(
|
|
226
|
+
PasswordInput,
|
|
227
|
+
{
|
|
228
|
+
value: form.imapPassword,
|
|
229
|
+
onChange: (event) => update("imapPassword", event.target.value),
|
|
230
|
+
"aria-invalid": Boolean(fieldErrors.imapPassword)
|
|
231
|
+
}
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
236
|
+
/* @__PURE__ */ jsx(
|
|
237
|
+
Field,
|
|
238
|
+
{
|
|
239
|
+
label: t("communication_channels.profile.connect.fields.smtpHost", "SMTP host"),
|
|
240
|
+
error: fieldErrors.smtpHost,
|
|
241
|
+
children: /* @__PURE__ */ jsx(
|
|
242
|
+
Input,
|
|
243
|
+
{
|
|
244
|
+
value: form.smtpHost,
|
|
245
|
+
onChange: (event) => update("smtpHost", event.target.value),
|
|
246
|
+
"aria-invalid": Boolean(fieldErrors.smtpHost)
|
|
247
|
+
}
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
),
|
|
251
|
+
/* @__PURE__ */ jsx(
|
|
252
|
+
Field,
|
|
253
|
+
{
|
|
254
|
+
label: t("communication_channels.profile.connect.fields.smtpPort", "SMTP port"),
|
|
255
|
+
error: fieldErrors.smtpPort,
|
|
256
|
+
children: /* @__PURE__ */ jsx(
|
|
257
|
+
Input,
|
|
258
|
+
{
|
|
259
|
+
inputMode: "numeric",
|
|
260
|
+
value: form.smtpPort,
|
|
261
|
+
onChange: (event) => update("smtpPort", event.target.value),
|
|
262
|
+
"aria-invalid": Boolean(fieldErrors.smtpPort)
|
|
263
|
+
}
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
] }),
|
|
268
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 md:grid-cols-2", children: [
|
|
269
|
+
/* @__PURE__ */ jsx(
|
|
270
|
+
Field,
|
|
271
|
+
{
|
|
272
|
+
label: t("communication_channels.profile.connect.fields.smtpTls", "SMTP security"),
|
|
273
|
+
error: fieldErrors.smtpTls,
|
|
274
|
+
children: /* @__PURE__ */ jsx(TlsSelect, { value: form.smtpTls, onChange: (value) => update("smtpTls", value) })
|
|
275
|
+
}
|
|
276
|
+
),
|
|
277
|
+
/* @__PURE__ */ jsx(
|
|
278
|
+
Field,
|
|
279
|
+
{
|
|
280
|
+
label: t("communication_channels.profile.connect.fields.smtpUser", "SMTP username"),
|
|
281
|
+
error: fieldErrors.smtpUser,
|
|
282
|
+
children: /* @__PURE__ */ jsx(
|
|
283
|
+
Input,
|
|
284
|
+
{
|
|
285
|
+
value: form.smtpUser,
|
|
286
|
+
onChange: (event) => update("smtpUser", event.target.value),
|
|
287
|
+
"aria-invalid": Boolean(fieldErrors.smtpUser)
|
|
288
|
+
}
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
)
|
|
292
|
+
] }),
|
|
293
|
+
/* @__PURE__ */ jsx(
|
|
294
|
+
Field,
|
|
295
|
+
{
|
|
296
|
+
label: t("communication_channels.profile.connect.fields.smtpPassword", "SMTP password"),
|
|
297
|
+
error: fieldErrors.smtpPassword,
|
|
298
|
+
children: /* @__PURE__ */ jsx(
|
|
299
|
+
PasswordInput,
|
|
300
|
+
{
|
|
301
|
+
value: form.smtpPassword,
|
|
302
|
+
onChange: (event) => update("smtpPassword", event.target.value),
|
|
303
|
+
"aria-invalid": Boolean(fieldErrors.smtpPassword)
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
] }),
|
|
309
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
310
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setOpen(false), disabled: pending, children: t("communication_channels.profile.connect.cancel", "Cancel") }),
|
|
311
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: () => void submit(), disabled: pending, children: pending ? t("communication_channels.profile.connect.connecting", "Connecting...") : t("communication_channels.profile.connect.save", "Connect") })
|
|
312
|
+
] })
|
|
313
|
+
] }) })
|
|
314
|
+
] });
|
|
315
|
+
}
|
|
316
|
+
function Field(props) {
|
|
317
|
+
return /* @__PURE__ */ jsxs("label", { className: "grid gap-1.5", children: [
|
|
318
|
+
/* @__PURE__ */ jsx(Label, { asChild: true, children: /* @__PURE__ */ jsx("span", { children: props.label }) }),
|
|
319
|
+
props.children,
|
|
320
|
+
props.error ? /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: props.error }) : null
|
|
321
|
+
] });
|
|
322
|
+
}
|
|
323
|
+
function TlsSelect(props) {
|
|
324
|
+
const t = useT();
|
|
325
|
+
return /* @__PURE__ */ jsxs(Select, { value: props.value, onValueChange: (value) => props.onChange(value), children: [
|
|
326
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
327
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
328
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "tls", children: t("communication_channels.profile.connect.tls.tls", "TLS") }),
|
|
329
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "starttls", children: t("communication_channels.profile.connect.tls.starttls", "STARTTLS") }),
|
|
330
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "none", children: t("communication_channels.profile.connect.tls.none", "None") })
|
|
331
|
+
] })
|
|
332
|
+
] });
|
|
333
|
+
}
|
|
334
|
+
export {
|
|
335
|
+
ConnectImapWidget as default
|
|
336
|
+
};
|
|
337
|
+
//# sourceMappingURL=widget.client.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/channel_imap/widgets/injection/connect/widget.client.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\n\ntype WidgetContext = Record<string, unknown> & {\n reload?: () => void\n}\n\ntype ConnectResponse = {\n channelId?: string\n error?: string\n fieldErrors?: Record<string, string>\n}\n\ntype TlsMode = 'tls' | 'starttls' | 'none'\n\ntype FormState = {\n displayName: string\n fromAddress: string\n imapHost: string\n imapPort: string\n imapTls: TlsMode\n imapUser: string\n imapPassword: string\n smtpHost: string\n smtpPort: string\n smtpTls: TlsMode\n smtpUser: string\n smtpPassword: string\n}\n\nconst INITIAL_FORM: FormState = {\n displayName: '',\n fromAddress: '',\n imapHost: '',\n imapPort: '993',\n imapTls: 'tls',\n imapUser: '',\n imapPassword: '',\n smtpHost: '',\n smtpPort: '465',\n smtpTls: 'tls',\n smtpUser: '',\n smtpPassword: '',\n}\n\nexport default function ConnectImapWidget({\n context,\n}: InjectionWidgetComponentProps<Record<string, unknown>, Record<string, unknown>>) {\n const t = useT()\n const widgetContext = context as WidgetContext | undefined\n const [open, setOpen] = React.useState(false)\n const [pending, setPending] = React.useState(false)\n const [form, setForm] = React.useState<FormState>(INITIAL_FORM)\n const [fieldErrors, setFieldErrors] = React.useState<Record<string, string>>({})\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: 'channel-imap-connect',\n blockedMessage: t('communication_channels.profile.connect.blocked', 'Connection blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({ providerKey: 'imap', retryLastMutation }),\n [retryLastMutation],\n )\n\n const update = React.useCallback(\n <K extends keyof FormState>(key: K, value: FormState[K]) => {\n setForm((current) => ({ ...current, [key]: value }))\n setFieldErrors((current) => {\n if (!current[key]) return current\n const next = { ...current }\n delete next[key]\n return next\n })\n },\n [],\n )\n\n const submit = React.useCallback(async () => {\n if (pending) return\n setPending(true)\n setFieldErrors({})\n try {\n const response = await runMutation({\n context: mutationContext,\n mutationPayload: { providerKey: 'imap', displayName: form.displayName },\n operation: () =>\n apiCall<ConnectResponse>('/api/communication_channels/channels/connect/credentials', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n providerKey: 'imap',\n displayName: form.displayName.trim() || form.fromAddress.trim(),\n pollIntervalSeconds: 300,\n credentials: {\n imapHost: form.imapHost.trim(),\n imapPort: Number(form.imapPort),\n imapTls: form.imapTls,\n imapUser: form.imapUser.trim(),\n imapPassword: form.imapPassword,\n smtpHost: form.smtpHost.trim(),\n smtpPort: Number(form.smtpPort),\n smtpTls: form.smtpTls,\n smtpUser: (form.smtpUser.trim() || form.imapUser.trim()),\n smtpPassword: form.smtpPassword || form.imapPassword,\n fromAddress: form.fromAddress.trim(),\n },\n }),\n }),\n })\n const body = response.result as ConnectResponse | undefined\n if (!response.ok) {\n setFieldErrors(body?.fieldErrors ?? {})\n flash(\n body?.error ??\n t('communication_channels.profile.connect.credentialsFailed', 'Could not connect mailbox.'),\n 'error',\n )\n return\n }\n flash(t('communication_channels.profile.connect.connected', 'Channel connected.'), 'success')\n setOpen(false)\n setForm(INITIAL_FORM)\n widgetContext?.reload?.()\n } finally {\n setPending(false)\n }\n }, [form, mutationContext, pending, runMutation, t, widgetContext])\n\n const onDialogKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submit()\n }\n },\n [submit],\n )\n\n return (\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => setOpen(true)}>\n {t('communication_channels.profile.connect.imap', 'Connect IMAP')}\n </Button>\n <Dialog open={open} onOpenChange={setOpen}>\n <DialogContent onKeyDown={onDialogKeyDown}>\n <DialogHeader>\n <DialogTitle>\n {t('communication_channels.profile.connect.imapTitle', 'Connect IMAP mailbox')}\n </DialogTitle>\n </DialogHeader>\n\n <div className=\"grid gap-4 py-2\">\n <Field\n label={t('communication_channels.profile.connect.fields.displayName', 'Display name')}\n error={fieldErrors.displayName}\n >\n <Input\n value={form.displayName}\n onChange={(event) => update('displayName', event.target.value)}\n aria-invalid={Boolean(fieldErrors.displayName)}\n />\n </Field>\n <Field\n label={t('communication_channels.profile.connect.fields.fromAddress', 'From address')}\n error={fieldErrors.fromAddress}\n >\n <Input\n type=\"email\"\n value={form.fromAddress}\n onChange={(event) => update('fromAddress', event.target.value)}\n aria-invalid={Boolean(fieldErrors.fromAddress)}\n />\n </Field>\n\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field\n label={t('communication_channels.profile.connect.fields.imapHost', 'IMAP host')}\n error={fieldErrors.imapHost}\n >\n <Input\n value={form.imapHost}\n onChange={(event) => update('imapHost', event.target.value)}\n aria-invalid={Boolean(fieldErrors.imapHost)}\n />\n </Field>\n <Field\n label={t('communication_channels.profile.connect.fields.imapPort', 'IMAP port')}\n error={fieldErrors.imapPort}\n >\n <Input\n inputMode=\"numeric\"\n value={form.imapPort}\n onChange={(event) => update('imapPort', event.target.value)}\n aria-invalid={Boolean(fieldErrors.imapPort)}\n />\n </Field>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field\n label={t('communication_channels.profile.connect.fields.imapTls', 'IMAP security')}\n error={fieldErrors.imapTls}\n >\n <TlsSelect value={form.imapTls} onChange={(value) => update('imapTls', value)} />\n </Field>\n <Field\n label={t('communication_channels.profile.connect.fields.imapUser', 'IMAP username')}\n error={fieldErrors.imapUser}\n >\n <Input\n value={form.imapUser}\n onChange={(event) => update('imapUser', event.target.value)}\n aria-invalid={Boolean(fieldErrors.imapUser)}\n />\n </Field>\n </div>\n\n <Field\n label={t('communication_channels.profile.connect.fields.imapPassword', 'IMAP password')}\n error={fieldErrors.imapPassword}\n >\n <PasswordInput\n value={form.imapPassword}\n onChange={(event) => update('imapPassword', event.target.value)}\n aria-invalid={Boolean(fieldErrors.imapPassword)}\n />\n </Field>\n\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field\n label={t('communication_channels.profile.connect.fields.smtpHost', 'SMTP host')}\n error={fieldErrors.smtpHost}\n >\n <Input\n value={form.smtpHost}\n onChange={(event) => update('smtpHost', event.target.value)}\n aria-invalid={Boolean(fieldErrors.smtpHost)}\n />\n </Field>\n <Field\n label={t('communication_channels.profile.connect.fields.smtpPort', 'SMTP port')}\n error={fieldErrors.smtpPort}\n >\n <Input\n inputMode=\"numeric\"\n value={form.smtpPort}\n onChange={(event) => update('smtpPort', event.target.value)}\n aria-invalid={Boolean(fieldErrors.smtpPort)}\n />\n </Field>\n </div>\n\n <div className=\"grid gap-3 md:grid-cols-2\">\n <Field\n label={t('communication_channels.profile.connect.fields.smtpTls', 'SMTP security')}\n error={fieldErrors.smtpTls}\n >\n <TlsSelect value={form.smtpTls} onChange={(value) => update('smtpTls', value)} />\n </Field>\n <Field\n label={t('communication_channels.profile.connect.fields.smtpUser', 'SMTP username')}\n error={fieldErrors.smtpUser}\n >\n <Input\n value={form.smtpUser}\n onChange={(event) => update('smtpUser', event.target.value)}\n aria-invalid={Boolean(fieldErrors.smtpUser)}\n />\n </Field>\n </div>\n\n <Field\n label={t('communication_channels.profile.connect.fields.smtpPassword', 'SMTP password')}\n error={fieldErrors.smtpPassword}\n >\n <PasswordInput\n value={form.smtpPassword}\n onChange={(event) => update('smtpPassword', event.target.value)}\n aria-invalid={Boolean(fieldErrors.smtpPassword)}\n />\n </Field>\n </div>\n\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setOpen(false)} disabled={pending}>\n {t('communication_channels.profile.connect.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={() => void submit()} disabled={pending}>\n {pending\n ? t('communication_channels.profile.connect.connecting', 'Connecting...')\n : t('communication_channels.profile.connect.save', 'Connect')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </>\n )\n}\n\nfunction Field(props: {\n label: string\n error?: string\n children: React.ReactNode\n}) {\n return (\n <label className=\"grid gap-1.5\">\n <Label asChild>\n <span>{props.label}</span>\n </Label>\n {props.children}\n {props.error ? <span className=\"text-xs text-destructive\">{props.error}</span> : null}\n </label>\n )\n}\n\nfunction TlsSelect(props: { value: TlsMode; onChange: (value: TlsMode) => void }) {\n const t = useT()\n return (\n <Select value={props.value} onValueChange={(value) => props.onChange(value as TlsMode)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"tls\">\n {t('communication_channels.profile.connect.tls.tls', 'TLS')}\n </SelectItem>\n <SelectItem value=\"starttls\">\n {t('communication_channels.profile.connect.tls.starttls', 'STARTTLS')}\n </SelectItem>\n <SelectItem value=\"none\">\n {t('communication_channels.profile.connect.tls.none', 'None')}\n </SelectItem>\n </SelectContent>\n </Select>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ConnectImapWidget from "./widget.client.js";
|
|
2
|
+
const widget = {
|
|
3
|
+
metadata: {
|
|
4
|
+
id: "channel_imap.injection.connect",
|
|
5
|
+
title: "Connect IMAP",
|
|
6
|
+
description: "Connects a per-user IMAP and SMTP mailbox.",
|
|
7
|
+
features: ["communication_channels.connect_user_channel"],
|
|
8
|
+
priority: 100,
|
|
9
|
+
enabled: true
|
|
10
|
+
},
|
|
11
|
+
Widget: ConnectImapWidget
|
|
12
|
+
};
|
|
13
|
+
var widget_default = widget;
|
|
14
|
+
export {
|
|
15
|
+
widget_default as default
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=widget.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/channel_imap/widgets/injection/connect/widget.ts"],
|
|
4
|
+
"sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport ConnectImapWidget from './widget.client'\n\nconst widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {\n metadata: {\n id: 'channel_imap.injection.connect',\n title: 'Connect IMAP',\n description: 'Connects a per-user IMAP and SMTP mailbox.',\n features: ['communication_channels.connect_user_channel'],\n priority: 100,\n enabled: true,\n },\n Widget: ConnectImapWidget,\n}\n\nexport default widget\n"],
|
|
5
|
+
"mappings": "AACA,OAAO,uBAAuB;AAE9B,MAAM,SAAkF;AAAA,EACtF,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,6CAA6C;AAAA,IACxD,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,iBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const injectionTable = {
|
|
2
|
+
"profile:communication-channels:connect": [
|
|
3
|
+
{
|
|
4
|
+
widgetId: "channel_imap.injection.connect",
|
|
5
|
+
priority: 100
|
|
6
|
+
}
|
|
7
|
+
]
|
|
8
|
+
};
|
|
9
|
+
var injection_table_default = injectionTable;
|
|
10
|
+
export {
|
|
11
|
+
injection_table_default as default,
|
|
12
|
+
injectionTable
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=injection-table.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/channel_imap/widgets/injection-table.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nexport const injectionTable: ModuleInjectionTable = {\n 'profile:communication-channels:connect': [\n {\n widgetId: 'channel_imap.injection.connect',\n priority: 100,\n },\n ],\n}\n\nexport default injectionTable\n"],
|
|
5
|
+
"mappings": "AAEO,MAAM,iBAAuC;AAAA,EAClD,0CAA0C;AAAA,IACxC;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|