fluxmail 0.0.1 → 0.2.0
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/README.md +37 -10
- package/dist/accounts/googleAuth.d.ts +31 -0
- package/dist/accounts/googleAuth.d.ts.map +1 -0
- package/dist/accounts/googleAuth.js +147 -0
- package/dist/accounts/googleAuth.js.map +1 -0
- package/dist/accounts/registry.d.ts +37 -0
- package/dist/accounts/registry.d.ts.map +1 -0
- package/dist/accounts/registry.js +278 -0
- package/dist/accounts/registry.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +629 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +40 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +216 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +15 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +15 -0
- package/dist/context.js.map +1 -0
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +118 -0
- package/dist/http/app.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/licensing/activation.d.ts +5 -0
- package/dist/licensing/activation.d.ts.map +1 -0
- package/dist/licensing/activation.js +11 -0
- package/dist/licensing/activation.js.map +1 -0
- package/dist/licensing/client.d.ts +52 -0
- package/dist/licensing/client.d.ts.map +1 -0
- package/dist/licensing/client.js +75 -0
- package/dist/licensing/client.js.map +1 -0
- package/dist/licensing/entitlements.d.ts +55 -0
- package/dist/licensing/entitlements.d.ts.map +1 -0
- package/dist/licensing/entitlements.js +116 -0
- package/dist/licensing/entitlements.js.map +1 -0
- package/dist/licensing/lease.d.ts +47 -0
- package/dist/licensing/lease.d.ts.map +1 -0
- package/dist/licensing/lease.js +103 -0
- package/dist/licensing/lease.js.map +1 -0
- package/dist/licensing/refresher.d.ts +46 -0
- package/dist/licensing/refresher.d.ts.map +1 -0
- package/dist/licensing/refresher.js +150 -0
- package/dist/licensing/refresher.js.map +1 -0
- package/dist/mcp/buildServer.d.ts +28 -0
- package/dist/mcp/buildServer.d.ts.map +1 -0
- package/dist/mcp/buildServer.js +414 -0
- package/dist/mcp/buildServer.js.map +1 -0
- package/dist/scheduler/sendScheduler.d.ts +38 -0
- package/dist/scheduler/sendScheduler.d.ts.map +1 -0
- package/dist/scheduler/sendScheduler.js +128 -0
- package/dist/scheduler/sendScheduler.js.map +1 -0
- package/dist/service/emailService.d.ts +139 -0
- package/dist/service/emailService.d.ts.map +1 -0
- package/dist/service/emailService.js +380 -0
- package/dist/service/emailService.js.map +1 -0
- package/dist/storage/apiKeys.d.ts +27 -0
- package/dist/storage/apiKeys.d.ts.map +1 -0
- package/dist/storage/apiKeys.js +49 -0
- package/dist/storage/apiKeys.js.map +1 -0
- package/dist/storage/crypto.d.ts +4 -0
- package/dist/storage/crypto.d.ts.map +1 -0
- package/dist/storage/crypto.js +21 -0
- package/dist/storage/crypto.js.map +1 -0
- package/dist/storage/db.d.ts +1041 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +211 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/imapState.d.ts +15 -0
- package/dist/storage/imapState.d.ts.map +1 -0
- package/dist/storage/imapState.js +89 -0
- package/dist/storage/imapState.js.map +1 -0
- package/dist/storage/members.d.ts +29 -0
- package/dist/storage/members.d.ts.map +1 -0
- package/dist/storage/members.js +67 -0
- package/dist/storage/members.js.map +1 -0
- package/dist/storage/scheduledSends.d.ts +60 -0
- package/dist/storage/scheduledSends.d.ts.map +1 -0
- package/dist/storage/scheduledSends.js +169 -0
- package/dist/storage/scheduledSends.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/package.json +49 -15
- package/bin/fluxmail.js +0 -4
- package/src/cli.js +0 -36
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { computeReplyRecipients, EmailError, formatAddressList, forwardSubject, isEmailError, } from '@fluxmail/core';
|
|
2
|
+
import { assertWithinQuota, checkLicenseState } from '../licensing/entitlements.js';
|
|
3
|
+
import { listMembers } from '../storage/members.js';
|
|
4
|
+
import { cancelScheduledSend, countPending, createScheduledSend, findPendingByDraft, getScheduledSend, listScheduledSends, markSent, } from '../storage/scheduledSends.js';
|
|
5
|
+
const ADMIN_SCOPE = { memberId: null };
|
|
6
|
+
const SCHEDULE_GRACE_MS = 60_000;
|
|
7
|
+
const SCHEDULE_MAX_HORIZON_MS = 365 * 24 * 3_600_000;
|
|
8
|
+
/** Parse and validate a sendAt timestamp; returns epoch ms. */
|
|
9
|
+
export function resolveSendAt(sendAtIso, now = Date.now()) {
|
|
10
|
+
// Require an ISO 8601 shape: Date.parse alone is lenient enough to accept junk.
|
|
11
|
+
const sendAt = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(sendAtIso) ? Date.parse(sendAtIso) : NaN;
|
|
12
|
+
if (Number.isNaN(sendAt)) {
|
|
13
|
+
throw new EmailError('invalid_request', `Could not parse sendAt: "${sendAtIso}" (expected ISO 8601)`);
|
|
14
|
+
}
|
|
15
|
+
if (sendAt < now - SCHEDULE_GRACE_MS) {
|
|
16
|
+
throw new EmailError('invalid_request', 'sendAt is in the past; leave it out to send now');
|
|
17
|
+
}
|
|
18
|
+
if (sendAt > now + SCHEDULE_MAX_HORIZON_MS) {
|
|
19
|
+
throw new EmailError('invalid_request', 'sendAt is more than a year away');
|
|
20
|
+
}
|
|
21
|
+
return sendAt;
|
|
22
|
+
}
|
|
23
|
+
function toScheduledInfo(row) {
|
|
24
|
+
return {
|
|
25
|
+
scheduleId: row.id,
|
|
26
|
+
accountId: row.accountId,
|
|
27
|
+
draftId: row.draftId,
|
|
28
|
+
sendAt: new Date(row.sendAt).toISOString(),
|
|
29
|
+
status: row.status,
|
|
30
|
+
attempts: row.attempts,
|
|
31
|
+
...(row.subject !== null ? { subject: row.subject } : {}),
|
|
32
|
+
...(row.toRecipients !== null ? { to: row.toRecipients } : {}),
|
|
33
|
+
...(row.lastError !== null ? { lastError: row.lastError } : {}),
|
|
34
|
+
...(row.sentMessageId !== null ? { sentMessageId: row.sentMessageId } : {}),
|
|
35
|
+
...(row.sentThreadId !== null ? { sentThreadId: row.sentThreadId } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* All business logic lives here: account routing, reply/forward computation,
|
|
40
|
+
* entitlement enforcement, auth-failure bookkeeping. MCP tools (and the future
|
|
41
|
+
* REST API / CLI) are thin wrappers over this service.
|
|
42
|
+
*/
|
|
43
|
+
export class EmailService {
|
|
44
|
+
registry;
|
|
45
|
+
db;
|
|
46
|
+
scope;
|
|
47
|
+
/** Wired to SendScheduler.wake() in long-lived processes; a no-op for one-shot CLI commands. */
|
|
48
|
+
onScheduleChanged = () => { };
|
|
49
|
+
constructor(registry, db, scope = ADMIN_SCOPE) {
|
|
50
|
+
this.registry = registry;
|
|
51
|
+
this.db = db;
|
|
52
|
+
this.scope = scope;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A view of this service restricted to one member's mailboxes (shared + owned).
|
|
56
|
+
* The HTTP layer scopes each request to the authenticated API key's member;
|
|
57
|
+
* unscoped admin keys keep full access.
|
|
58
|
+
*/
|
|
59
|
+
withScope(scope) {
|
|
60
|
+
const scoped = new EmailService(this.registry, this.db, scope);
|
|
61
|
+
scoped.onScheduleChanged = () => this.onScheduleChanged();
|
|
62
|
+
return scoped;
|
|
63
|
+
}
|
|
64
|
+
/** Shared mailboxes and, for member keys, the member's own mailboxes. */
|
|
65
|
+
canAccess(account) {
|
|
66
|
+
return this.scope.memberId === null || account.memberId == null || account.memberId === this.scope.memberId;
|
|
67
|
+
}
|
|
68
|
+
accessibleAccounts() {
|
|
69
|
+
const all = this.registry.listAccounts();
|
|
70
|
+
return this.scope.memberId === null ? all : all.filter((a) => this.canAccess(a));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve an account id within the current scope. A member-scoped key defaults
|
|
74
|
+
* to its sole accessible mailbox and can never reach another member's mailbox;
|
|
75
|
+
* inaccessible ids surface as not_found so a key cannot probe for their existence.
|
|
76
|
+
*/
|
|
77
|
+
resolveScopedAccountId(accountId) {
|
|
78
|
+
if (accountId !== undefined) {
|
|
79
|
+
const account = this.registry.getAccount(accountId);
|
|
80
|
+
if (!this.canAccess(account)) {
|
|
81
|
+
throw new EmailError('not_found', `No account with id "${accountId}"`);
|
|
82
|
+
}
|
|
83
|
+
return account.id;
|
|
84
|
+
}
|
|
85
|
+
if (this.scope.memberId === null)
|
|
86
|
+
return this.registry.resolveAccountId();
|
|
87
|
+
const accessible = this.accessibleAccounts();
|
|
88
|
+
if (accessible.length === 0) {
|
|
89
|
+
throw new EmailError('invalid_request', 'No email accounts are available for this member.');
|
|
90
|
+
}
|
|
91
|
+
if (accessible.length > 1) {
|
|
92
|
+
throw new EmailError('invalid_request', `Multiple accounts are available; specify accountId. Available: ${accessible
|
|
93
|
+
.map((a) => `${a.id} (${a.email})`)
|
|
94
|
+
.join(', ')}`);
|
|
95
|
+
}
|
|
96
|
+
return accessible[0].id;
|
|
97
|
+
}
|
|
98
|
+
/** Route a call to the right provider, recording auth failures on the account. */
|
|
99
|
+
async withProvider(accountId, fn) {
|
|
100
|
+
const resolvedId = this.resolveScopedAccountId(accountId);
|
|
101
|
+
const account = this.registry.getAccount(resolvedId);
|
|
102
|
+
if (account.status === 'disabled') {
|
|
103
|
+
throw new EmailError('invalid_request', `Account ${resolvedId} is disabled`);
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
const result = await fn(this.registry.getProvider(resolvedId), resolvedId, account);
|
|
107
|
+
if (account.status === 'auth_error')
|
|
108
|
+
this.registry.markStatus(resolvedId, 'active');
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (isEmailError(err) && err.code === 'auth_expired') {
|
|
113
|
+
this.registry.markStatus(resolvedId, 'auth_error');
|
|
114
|
+
}
|
|
115
|
+
throw err;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
listAccounts() {
|
|
119
|
+
return this.accessibleAccounts();
|
|
120
|
+
}
|
|
121
|
+
async status() {
|
|
122
|
+
const accounts = this.accessibleAccounts();
|
|
123
|
+
const errors = new Map();
|
|
124
|
+
const warnings = new Map();
|
|
125
|
+
await Promise.all(accounts
|
|
126
|
+
.filter((account) => account.status !== 'disabled')
|
|
127
|
+
.map(async (account) => {
|
|
128
|
+
try {
|
|
129
|
+
await this.registry.getProvider(account.id).testConnection();
|
|
130
|
+
const provider = this.registry.getProvider(account.id);
|
|
131
|
+
if (provider.getFolderWarnings) {
|
|
132
|
+
const folderWarnings = await provider.getFolderWarnings();
|
|
133
|
+
if (folderWarnings.length)
|
|
134
|
+
warnings.set(account.id, folderWarnings.map((warning) => warning.message));
|
|
135
|
+
}
|
|
136
|
+
if (account.status === 'auth_error')
|
|
137
|
+
this.registry.markStatus(account.id, 'active');
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
if (isEmailError(err) && err.code === 'auth_expired') {
|
|
141
|
+
this.registry.markStatus(account.id, 'auth_error');
|
|
142
|
+
}
|
|
143
|
+
errors.set(account.id, isEmailError(err)
|
|
144
|
+
? { code: err.code, message: err.message }
|
|
145
|
+
: { code: 'internal', message: err instanceof Error ? err.message : String(err) });
|
|
146
|
+
}
|
|
147
|
+
}));
|
|
148
|
+
const admin = this.scope.memberId === null;
|
|
149
|
+
const license = admin ? checkLicenseState(this.db) : undefined;
|
|
150
|
+
return {
|
|
151
|
+
// Re-read so statuses reflect any markStatus writes from the live checks above.
|
|
152
|
+
accounts: this.accessibleAccounts().map(({ id, provider, email, status, memberId }) => ({
|
|
153
|
+
id,
|
|
154
|
+
provider,
|
|
155
|
+
email,
|
|
156
|
+
status,
|
|
157
|
+
...(memberId ? { memberId } : {}),
|
|
158
|
+
...(errors.has(id) ? { error: errors.get(id) } : {}),
|
|
159
|
+
...(warnings.has(id) ? { warnings: warnings.get(id) } : {}),
|
|
160
|
+
})),
|
|
161
|
+
...(admin
|
|
162
|
+
? {
|
|
163
|
+
members: { count: listMembers(this.db).length },
|
|
164
|
+
entitlements: license.entitlements,
|
|
165
|
+
...(license.warning ? { licenseWarning: license.warning } : {}),
|
|
166
|
+
}
|
|
167
|
+
: {}),
|
|
168
|
+
providersAvailable: ['gmail', 'imap'],
|
|
169
|
+
scheduled: this.scheduledStatus(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Gate for MCP tool calls: throws once a lapsed license leaves the instance
|
|
174
|
+
* over the entitled caps; returns a renewal warning to attach to results
|
|
175
|
+
* while the license is in its grace period or has lapsed.
|
|
176
|
+
*/
|
|
177
|
+
enforceQuota() {
|
|
178
|
+
if (this.scope.memberId !== null) {
|
|
179
|
+
const state = checkLicenseState(this.db);
|
|
180
|
+
if (state.overQuota) {
|
|
181
|
+
throw new EmailError('entitlement_exceeded', 'This Fluxmail instance is over its plan limits. Ask an administrator to renew the license or reduce usage.');
|
|
182
|
+
}
|
|
183
|
+
// License dates and renewal state are instance administration details.
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return assertWithinQuota(this.db).warning;
|
|
187
|
+
}
|
|
188
|
+
scheduledStatus() {
|
|
189
|
+
if (this.scope.memberId === null) {
|
|
190
|
+
const { pending, nextSendAt } = countPending(this.db);
|
|
191
|
+
return {
|
|
192
|
+
pending,
|
|
193
|
+
...(nextSendAt !== undefined ? { nextSendAt: new Date(nextSendAt).toISOString() } : {}),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const accessible = new Set(this.accessibleAccounts().map((account) => account.id));
|
|
197
|
+
const active = listScheduledSends(this.db).filter((row) => accessible.has(row.accountId) && (row.status === 'pending' || row.status === 'sending'));
|
|
198
|
+
const nextSendAt = active.length ? Math.min(...active.map((row) => row.sendAt)) : undefined;
|
|
199
|
+
return {
|
|
200
|
+
pending: active.length,
|
|
201
|
+
...(nextSendAt !== undefined ? { nextSendAt: new Date(nextSendAt).toISOString() } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
listFolders(accountId) {
|
|
205
|
+
return this.withProvider(accountId, (p) => p.listFolders());
|
|
206
|
+
}
|
|
207
|
+
listMessages(accountId, q, page) {
|
|
208
|
+
return this.withProvider(accountId, (p) => p.listMessages(q, page));
|
|
209
|
+
}
|
|
210
|
+
getMessage(accountId, id) {
|
|
211
|
+
return this.withProvider(accountId, (p) => p.getMessage(id));
|
|
212
|
+
}
|
|
213
|
+
getThread(accountId, threadId) {
|
|
214
|
+
return this.withProvider(accountId, (p) => p.getThread(threadId));
|
|
215
|
+
}
|
|
216
|
+
createDraft(accountId, d) {
|
|
217
|
+
return this.withProvider(accountId, async (p, _id, account) => {
|
|
218
|
+
return p.createDraft(await this.resolveRecipients(p, account, d));
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
updateDraft(accountId, draftId, d) {
|
|
222
|
+
return this.withProvider(accountId, async (p, _id, account) => {
|
|
223
|
+
return p.updateDraft(draftId, await this.resolveRecipients(p, account, d));
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
deleteDraft(accountId, draftId) {
|
|
227
|
+
return this.withProvider(accountId, (p) => p.deleteDraft(draftId));
|
|
228
|
+
}
|
|
229
|
+
send(accountId, input) {
|
|
230
|
+
return this.withProvider(accountId, async (p, resolvedId, account) => {
|
|
231
|
+
if ('draftId' in input) {
|
|
232
|
+
const result = await p.send({ draftId: input.draftId });
|
|
233
|
+
// Sending a scheduled draft now supersedes its schedule.
|
|
234
|
+
const pending = findPendingByDraft(this.db, resolvedId, input.draftId);
|
|
235
|
+
if (pending) {
|
|
236
|
+
markSent(this.db, pending.id, result);
|
|
237
|
+
this.onScheduleChanged();
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
return p.send(await this.resolveRecipients(p, account, input));
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Draft-backed scheduled send: the content becomes a real provider draft
|
|
246
|
+
* immediately; only the schedule (draft id + fire time) is stored locally.
|
|
247
|
+
*/
|
|
248
|
+
async scheduleSend(accountId, input, sendAtIso) {
|
|
249
|
+
const sendAt = resolveSendAt(sendAtIso);
|
|
250
|
+
const { draft, resolvedId } = await this.withProvider(accountId, async (p, id, account) => {
|
|
251
|
+
let message;
|
|
252
|
+
if ('draftId' in input) {
|
|
253
|
+
message = await p.getDraft(input.draftId);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const resolved = await this.resolveRecipients(p, account, input);
|
|
257
|
+
if (!resolved.to?.length && !resolved.cc?.length && !resolved.bcc?.length) {
|
|
258
|
+
throw new EmailError('invalid_request', 'Cannot schedule a message with no recipients');
|
|
259
|
+
}
|
|
260
|
+
message = await p.createDraft(resolved);
|
|
261
|
+
}
|
|
262
|
+
return { draft: message, resolvedId: id };
|
|
263
|
+
});
|
|
264
|
+
if (!draft.draftId) {
|
|
265
|
+
throw new EmailError('provider_unavailable', 'Provider did not return a draft id');
|
|
266
|
+
}
|
|
267
|
+
const row = createScheduledSend(this.db, {
|
|
268
|
+
accountId: resolvedId,
|
|
269
|
+
draftId: draft.draftId,
|
|
270
|
+
sendAt,
|
|
271
|
+
...(draft.subject !== undefined ? { subject: draft.subject } : {}),
|
|
272
|
+
...(draft.to?.length ? { toRecipients: formatAddressList(draft.to) } : {}),
|
|
273
|
+
});
|
|
274
|
+
this.onScheduleChanged();
|
|
275
|
+
return toScheduledInfo(row);
|
|
276
|
+
}
|
|
277
|
+
listScheduled(accountId) {
|
|
278
|
+
if (accountId !== undefined) {
|
|
279
|
+
return listScheduledSends(this.db, this.resolveScopedAccountId(accountId)).map(toScheduledInfo);
|
|
280
|
+
}
|
|
281
|
+
// Listing must work across accounts, but a member key only sees its own mailboxes'.
|
|
282
|
+
const rows = listScheduledSends(this.db);
|
|
283
|
+
if (this.scope.memberId === null)
|
|
284
|
+
return rows.map(toScheduledInfo);
|
|
285
|
+
const accessible = new Set(this.accessibleAccounts().map((a) => a.id));
|
|
286
|
+
return rows.filter((r) => accessible.has(r.accountId)).map(toScheduledInfo);
|
|
287
|
+
}
|
|
288
|
+
/** Cancels a pending schedule; the provider draft is kept. */
|
|
289
|
+
cancelScheduled(scheduleId) {
|
|
290
|
+
const row = getScheduledSend(this.db, scheduleId);
|
|
291
|
+
// A member key cannot see (or cancel) schedules on mailboxes it cannot reach.
|
|
292
|
+
if (!row || !this.canAccess(this.registry.getAccount(row.accountId))) {
|
|
293
|
+
throw new EmailError('not_found', `No scheduled send with id ${scheduleId}`);
|
|
294
|
+
}
|
|
295
|
+
if (row.status !== 'pending') {
|
|
296
|
+
throw new EmailError('invalid_request', `Scheduled send ${scheduleId} is already ${row.status}`);
|
|
297
|
+
}
|
|
298
|
+
if (!cancelScheduledSend(this.db, scheduleId)) {
|
|
299
|
+
throw new EmailError('invalid_request', `Scheduled send ${scheduleId} has already started sending`);
|
|
300
|
+
}
|
|
301
|
+
this.onScheduleChanged();
|
|
302
|
+
return { scheduleId, draftId: row.draftId, draftKept: true };
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Reply recipient computation (provider-agnostic): when replying without explicit
|
|
306
|
+
* recipients, derive them from the original message: Reply-To/From for a plain
|
|
307
|
+
* reply; plus original To/Cc (minus our own address) for reply-all.
|
|
308
|
+
*/
|
|
309
|
+
async resolveRecipients(p, account, input) {
|
|
310
|
+
const { replyAll, ...draft } = input;
|
|
311
|
+
if (!draft.replyToMessageId || draft.to?.length)
|
|
312
|
+
return draft;
|
|
313
|
+
const original = await p.getMessage(draft.replyToMessageId);
|
|
314
|
+
const recipients = computeReplyRecipients(original, account.email, replyAll ?? false);
|
|
315
|
+
draft.to = recipients.to;
|
|
316
|
+
if (recipients.cc.length && !draft.cc?.length)
|
|
317
|
+
draft.cc = recipients.cc;
|
|
318
|
+
return draft;
|
|
319
|
+
}
|
|
320
|
+
forward(accountId, input) {
|
|
321
|
+
return this.withProvider(accountId, async (p) => {
|
|
322
|
+
const original = await p.getMessage(input.messageId);
|
|
323
|
+
const includeAttachments = input.includeAttachments ?? true;
|
|
324
|
+
const attachments = [];
|
|
325
|
+
if (includeAttachments) {
|
|
326
|
+
for (const meta of original.attachments ?? []) {
|
|
327
|
+
const { content } = await p.getAttachment(original.id, meta.id);
|
|
328
|
+
attachments.push({
|
|
329
|
+
filename: meta.filename,
|
|
330
|
+
mimeType: meta.mimeType,
|
|
331
|
+
content: content.toString('base64'),
|
|
332
|
+
...(meta.contentId ? { contentId: meta.contentId } : {}),
|
|
333
|
+
...(meta.disposition ? { disposition: meta.disposition } : {}),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const draft = {
|
|
338
|
+
to: input.to,
|
|
339
|
+
...(input.cc?.length ? { cc: input.cc } : {}),
|
|
340
|
+
subject: forwardSubject(original.subject),
|
|
341
|
+
body: buildForwardBody(original, input.comment),
|
|
342
|
+
...(attachments.length ? { attachments } : {}),
|
|
343
|
+
};
|
|
344
|
+
return p.send(draft);
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
modify(accountId, ids, action) {
|
|
348
|
+
return this.withProvider(accountId, (p) => p.modify(ids, action));
|
|
349
|
+
}
|
|
350
|
+
getAttachment(accountId, messageId, attachmentId) {
|
|
351
|
+
return this.withProvider(accountId, (p) => p.getAttachment(messageId, attachmentId));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
export function buildForwardBody(original, comment) {
|
|
355
|
+
const headerLines = [
|
|
356
|
+
'---------- Forwarded message ----------',
|
|
357
|
+
`From: ${original.from ? formatAddressList([original.from]) : '(unknown)'}`,
|
|
358
|
+
`Date: ${original.date}`,
|
|
359
|
+
`Subject: ${original.subject}`,
|
|
360
|
+
`To: ${formatAddressList(original.to)}`,
|
|
361
|
+
...(original.cc?.length ? [`Cc: ${formatAddressList(original.cc)}`] : []),
|
|
362
|
+
].join('\n');
|
|
363
|
+
const body = {};
|
|
364
|
+
const originalText = original.body?.text;
|
|
365
|
+
const originalHtml = original.body?.html;
|
|
366
|
+
if (originalText !== undefined || originalHtml === undefined) {
|
|
367
|
+
body.text = [comment, headerLines, '', originalText ?? ''].filter((x) => x !== undefined).join('\n\n');
|
|
368
|
+
}
|
|
369
|
+
if (originalHtml !== undefined) {
|
|
370
|
+
const escapedHeader = escapeHtml(headerLines).replace(/\n/g, '<br>');
|
|
371
|
+
body.html =
|
|
372
|
+
(comment ? `<p>${escapeHtml(comment)}</p>` : '') +
|
|
373
|
+
`<p>${escapedHeader}</p><blockquote style="border-left:1px solid #ccc;padding-left:1ex;margin:0 0 0 0.8ex">${originalHtml}</blockquote>`;
|
|
374
|
+
}
|
|
375
|
+
return body;
|
|
376
|
+
}
|
|
377
|
+
function escapeHtml(s) {
|
|
378
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
379
|
+
}
|
|
380
|
+
//# sourceMappingURL=emailService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emailService.js","sourceRoot":"","sources":["../../src/service/emailService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type FluxmailDb } from './db.js';
|
|
2
|
+
export interface ApiKeyInfo {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
lastUsedAt: number | null;
|
|
7
|
+
/** Member the key was issued to; null means an unscoped admin key. */
|
|
8
|
+
memberId: string | null;
|
|
9
|
+
}
|
|
10
|
+
/** Returns the plaintext key exactly once; only its hash is stored. */
|
|
11
|
+
export declare function createApiKey(db: FluxmailDb, name: string, memberId?: string): {
|
|
12
|
+
key: string;
|
|
13
|
+
info: ApiKeyInfo;
|
|
14
|
+
};
|
|
15
|
+
export interface ApiKeyAuth {
|
|
16
|
+
/** Member the key was issued to; null for an unscoped admin key. */
|
|
17
|
+
memberId: string | null;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Verify a key, record its use, and return the scope it authorizes. Callers must
|
|
21
|
+
* honour `memberId`: a member-scoped key may only reach shared or owned mailboxes.
|
|
22
|
+
*/
|
|
23
|
+
export declare function authenticateApiKey(db: FluxmailDb, key: string): ApiKeyAuth | null;
|
|
24
|
+
export declare function verifyApiKey(db: FluxmailDb, key: string): boolean;
|
|
25
|
+
export declare function listApiKeys(db: FluxmailDb): ApiKeyInfo[];
|
|
26
|
+
export declare function revokeApiKey(db: FluxmailDb, id: string): boolean;
|
|
27
|
+
//# sourceMappingURL=apiKeys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeys.d.ts","sourceRoot":"","sources":["../../src/storage/apiKeys.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,KAAK,UAAU,EAAE,MAAM,SAAS,CAAC;AAGnD,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAMD,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAS/G;AAED,MAAM,WAAW,UAAU;IACzB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CASjF;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,UAAU,GAAG,UAAU,EAAE,CAMxD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAGhE"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { apiKeys } from './db.js';
|
|
4
|
+
import { getMember } from './members.js';
|
|
5
|
+
function hashKey(key) {
|
|
6
|
+
return createHash('sha256').update(key).digest('hex');
|
|
7
|
+
}
|
|
8
|
+
/** Returns the plaintext key exactly once; only its hash is stored. */
|
|
9
|
+
export function createApiKey(db, name, memberId) {
|
|
10
|
+
if (memberId)
|
|
11
|
+
getMember(db, memberId);
|
|
12
|
+
const id = `key_${randomBytes(8).toString('hex')}`;
|
|
13
|
+
const key = `fmk_${randomBytes(24).toString('hex')}`;
|
|
14
|
+
const createdAt = Date.now();
|
|
15
|
+
db.insert(apiKeys)
|
|
16
|
+
.values({ id, name, keyHash: hashKey(key), createdAt, memberId: memberId ?? null })
|
|
17
|
+
.run();
|
|
18
|
+
return { key, info: { id, name, createdAt, lastUsedAt: null, memberId: memberId ?? null } };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Verify a key, record its use, and return the scope it authorizes. Callers must
|
|
22
|
+
* honour `memberId`: a member-scoped key may only reach shared or owned mailboxes.
|
|
23
|
+
*/
|
|
24
|
+
export function authenticateApiKey(db, key) {
|
|
25
|
+
const row = db
|
|
26
|
+
.select()
|
|
27
|
+
.from(apiKeys)
|
|
28
|
+
.where(eq(apiKeys.keyHash, hashKey(key)))
|
|
29
|
+
.get();
|
|
30
|
+
if (!row)
|
|
31
|
+
return null;
|
|
32
|
+
db.update(apiKeys).set({ lastUsedAt: Date.now() }).where(eq(apiKeys.id, row.id)).run();
|
|
33
|
+
return { memberId: row.memberId };
|
|
34
|
+
}
|
|
35
|
+
export function verifyApiKey(db, key) {
|
|
36
|
+
return authenticateApiKey(db, key) !== null;
|
|
37
|
+
}
|
|
38
|
+
export function listApiKeys(db) {
|
|
39
|
+
return db
|
|
40
|
+
.select()
|
|
41
|
+
.from(apiKeys)
|
|
42
|
+
.all()
|
|
43
|
+
.map((r) => ({ id: r.id, name: r.name, createdAt: r.createdAt, lastUsedAt: r.lastUsedAt, memberId: r.memberId }));
|
|
44
|
+
}
|
|
45
|
+
export function revokeApiKey(db, id) {
|
|
46
|
+
const result = db.delete(apiKeys).where(eq(apiKeys.id, id)).run();
|
|
47
|
+
return result.changes > 0;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=apiKeys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeys.js","sourceRoot":"","sources":["../../src/storage/apiKeys.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/storage/crypto.ts"],"names":[],"mappings":"AAMA,mEAAmE;AACnE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAKpE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQjE"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
2
|
+
const ALGO = 'aes-256-gcm';
|
|
3
|
+
const IV_LENGTH = 12;
|
|
4
|
+
const TAG_LENGTH = 16;
|
|
5
|
+
/** AES-256-GCM. Output layout: base64(iv || tag || ciphertext). */
|
|
6
|
+
export function encryptString(key, plaintext) {
|
|
7
|
+
const iv = randomBytes(IV_LENGTH);
|
|
8
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
9
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
|
|
10
|
+
return Buffer.concat([iv, cipher.getAuthTag(), ciphertext]).toString('base64');
|
|
11
|
+
}
|
|
12
|
+
export function decryptString(key, packed) {
|
|
13
|
+
const buf = Buffer.from(packed, 'base64');
|
|
14
|
+
const iv = buf.subarray(0, IV_LENGTH);
|
|
15
|
+
const tag = buf.subarray(IV_LENGTH, IV_LENGTH + TAG_LENGTH);
|
|
16
|
+
const ciphertext = buf.subarray(IV_LENGTH + TAG_LENGTH);
|
|
17
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
18
|
+
decipher.setAuthTag(tag);
|
|
19
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/storage/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE5E,MAAM,IAAI,GAAG,aAAa,CAAC;AAC3B,MAAM,SAAS,GAAG,EAAE,CAAC;AACrB,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,mEAAmE;AACnE,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,SAAiB;IAC1D,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACrF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,MAAc;IACvD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC1C,MAAM,EAAE,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,GAAG,CAAC,QAAQ,CAAC,SAAS,GAAG,UAAU,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IACjD,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACzB,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACzF,CAAC"}
|