fluxmail 0.0.1 → 0.1.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/dist/accounts/googleAuth.d.ts +31 -0
- package/dist/accounts/googleAuth.d.ts.map +1 -0
- package/dist/accounts/googleAuth.js +143 -0
- package/dist/accounts/googleAuth.js.map +1 -0
- package/dist/accounts/registry.d.ts +28 -0
- package/dist/accounts/registry.d.ts.map +1 -0
- package/dist/accounts/registry.js +185 -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 +434 -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 +215 -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 +110 -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 +394 -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 +130 -0
- package/dist/scheduler/sendScheduler.js.map +1 -0
- package/dist/service/emailService.d.ts +138 -0
- package/dist/service/emailService.d.ts.map +1 -0
- package/dist/service/emailService.js +375 -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 +43 -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 +729 -0
- package/dist/storage/db.d.ts.map +1 -0
- package/dist/storage/db.js +143 -0
- package/dist/storage/db.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 +38 -15
- package/README.md +0 -29
- package/bin/fluxmail.js +0 -4
- package/src/cli.js +0 -36
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type FluxmailDb } from '../storage/db.js';
|
|
2
|
+
/** After a lease expires, paid limits are honored this much longer before the plan lapses. */
|
|
3
|
+
export declare const GRACE_PERIOD_MS: number;
|
|
4
|
+
export interface Entitlements {
|
|
5
|
+
/** 'personal' when unlicensed, otherwise the plan name from the signed lease. */
|
|
6
|
+
plan: string;
|
|
7
|
+
/** True while a lease grants paid limits (including the grace period). */
|
|
8
|
+
licensed: boolean;
|
|
9
|
+
/** True when the lease has expired but the grace period is still running. */
|
|
10
|
+
inGrace: boolean;
|
|
11
|
+
maxMembers: number;
|
|
12
|
+
maxAccounts: number;
|
|
13
|
+
/** Licensed only: when the lease expires (or expired) and the grace period starts. */
|
|
14
|
+
leaseExpiresAt?: string;
|
|
15
|
+
/** In grace only: when paid limits lapse to the Personal plan unless renewed. */
|
|
16
|
+
graceUntil?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare const PERSONAL_TIER: Entitlements;
|
|
19
|
+
/** Accepts a transaction as well as the root db, like the other storage helpers. */
|
|
20
|
+
type DbReader = Pick<FluxmailDb, 'select'>;
|
|
21
|
+
export declare function readLeaseRow(db: DbReader): {
|
|
22
|
+
token: string;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
} | undefined;
|
|
25
|
+
export declare function saveLeaseToken(db: Pick<FluxmailDb, 'insert'>, token: string): void;
|
|
26
|
+
export declare function clearLease(db: Pick<FluxmailDb, 'delete'>): void;
|
|
27
|
+
/**
|
|
28
|
+
* Effective entitlements: the plan and caps from the cached lease while it
|
|
29
|
+
* verifies against a pinned license-server key, Personal-plan limits otherwise.
|
|
30
|
+
* An expired lease keeps its paid limits through the grace period, so a
|
|
31
|
+
* renewal hiccup never interrupts a running server. Reads only local state;
|
|
32
|
+
* enforcement never waits on the network.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getEntitlements(db: DbReader, now?: Date): Entitlements;
|
|
35
|
+
export declare function assertAccountLimit(current: number, entitlements: Entitlements): void;
|
|
36
|
+
export declare function assertMemberLimit(current: number, entitlements: Entitlements): void;
|
|
37
|
+
export interface LicenseState {
|
|
38
|
+
entitlements: Entitlements;
|
|
39
|
+
accountCount: number;
|
|
40
|
+
memberCount: number;
|
|
41
|
+
/** Usage exceeds the entitled caps; only possible after a paid license lapsed. */
|
|
42
|
+
overQuota: boolean;
|
|
43
|
+
/** Renewal warning while in grace or after a lapse; undefined when all is well. */
|
|
44
|
+
warning?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Entitlements plus current usage, for quota enforcement and renewal warnings. */
|
|
47
|
+
export declare function checkLicenseState(db: DbReader, now?: Date): LicenseState;
|
|
48
|
+
/**
|
|
49
|
+
* Gate for MCP tool calls: throws once a lapsed license leaves the instance
|
|
50
|
+
* over the entitled caps. Returns the current state so callers can surface
|
|
51
|
+
* grace warnings without a second read.
|
|
52
|
+
*/
|
|
53
|
+
export declare function assertWithinQuota(db: DbReader, now?: Date): LicenseState;
|
|
54
|
+
export {};
|
|
55
|
+
//# sourceMappingURL=entitlements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/licensing/entitlements.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmC,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAGpF,8FAA8F;AAC9F,eAAO,MAAM,eAAe,QAA2B,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,aAAa,EAAE,YAM3B,CAAC;AAIF,oFAAoF;AACpF,KAAK,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AAE3C,wBAAgB,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAG3F;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMlF;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,GAAG,IAAI,CAE/D;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,OAAa,GAAG,YAAY,CAsB5E;AAcD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,IAAI,CAEpF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,GAAG,IAAI,CAEnF;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,SAAS,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,OAAa,GAAG,YAAY,CAsB9E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,GAAG,OAAa,GAAG,YAAY,CAa9E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { EmailError } from '@fluxmail/core';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { accounts, licenseLease, members } from '../storage/db.js';
|
|
4
|
+
import { licensePublicKeys, verifyLease } from './lease.js';
|
|
5
|
+
/** After a lease expires, paid limits are honored this much longer before the plan lapses. */
|
|
6
|
+
export const GRACE_PERIOD_MS = 21 * 24 * 60 * 60 * 1000;
|
|
7
|
+
export const PERSONAL_TIER = {
|
|
8
|
+
plan: 'personal',
|
|
9
|
+
licensed: false,
|
|
10
|
+
inGrace: false,
|
|
11
|
+
maxMembers: 1,
|
|
12
|
+
maxAccounts: 3,
|
|
13
|
+
};
|
|
14
|
+
const LEASE_ROW_ID = 'current';
|
|
15
|
+
export function readLeaseRow(db) {
|
|
16
|
+
const row = db.select().from(licenseLease).where(eq(licenseLease.id, LEASE_ROW_ID)).get();
|
|
17
|
+
return row ? { token: row.token, updatedAt: row.updatedAt } : undefined;
|
|
18
|
+
}
|
|
19
|
+
export function saveLeaseToken(db, token) {
|
|
20
|
+
const updatedAt = Date.now();
|
|
21
|
+
db.insert(licenseLease)
|
|
22
|
+
.values({ id: LEASE_ROW_ID, token, updatedAt })
|
|
23
|
+
.onConflictDoUpdate({ target: licenseLease.id, set: { token, updatedAt } })
|
|
24
|
+
.run();
|
|
25
|
+
}
|
|
26
|
+
export function clearLease(db) {
|
|
27
|
+
db.delete(licenseLease).where(eq(licenseLease.id, LEASE_ROW_ID)).run();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Effective entitlements: the plan and caps from the cached lease while it
|
|
31
|
+
* verifies against a pinned license-server key, Personal-plan limits otherwise.
|
|
32
|
+
* An expired lease keeps its paid limits through the grace period, so a
|
|
33
|
+
* renewal hiccup never interrupts a running server. Reads only local state;
|
|
34
|
+
* enforcement never waits on the network.
|
|
35
|
+
*/
|
|
36
|
+
export function getEntitlements(db, now = new Date()) {
|
|
37
|
+
const row = readLeaseRow(db);
|
|
38
|
+
if (!row)
|
|
39
|
+
return PERSONAL_TIER;
|
|
40
|
+
try {
|
|
41
|
+
const lease = verifyLease(row.token, licensePublicKeys(), now, { allowExpired: true });
|
|
42
|
+
const expiresAt = Date.parse(lease.expiresAt);
|
|
43
|
+
const graceUntil = expiresAt + GRACE_PERIOD_MS;
|
|
44
|
+
if (now.getTime() >= graceUntil)
|
|
45
|
+
return PERSONAL_TIER;
|
|
46
|
+
const inGrace = now.getTime() >= expiresAt;
|
|
47
|
+
return {
|
|
48
|
+
plan: lease.plan,
|
|
49
|
+
licensed: true,
|
|
50
|
+
inGrace,
|
|
51
|
+
maxMembers: lease.maxMembers,
|
|
52
|
+
maxAccounts: lease.maxAccounts,
|
|
53
|
+
leaseExpiresAt: lease.expiresAt,
|
|
54
|
+
...(inGrace ? { graceUntil: new Date(graceUntil).toISOString() } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Unverifiable lease: degrade to Personal-plan limits, never block.
|
|
59
|
+
return PERSONAL_TIER;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function assertLimit(kind, current, max, ent) {
|
|
63
|
+
if (current < max)
|
|
64
|
+
return;
|
|
65
|
+
const noun = `${kind === 'mailbox' ? 'connected mailbox' : 'member'}${max === 1 ? '' : kind === 'mailbox' ? 'es' : 's'}`;
|
|
66
|
+
throw new EmailError('entitlement_exceeded', ent.licensed
|
|
67
|
+
? `Your ${ent.plan} plan allows ${max} ${noun} (currently ${current}). Upgrade your plan to add more.`
|
|
68
|
+
: `The Personal plan allows ${max} ${noun} (currently ${current}). ` +
|
|
69
|
+
'A Fluxmail subscription unlocks more; see the README for details.');
|
|
70
|
+
}
|
|
71
|
+
export function assertAccountLimit(current, entitlements) {
|
|
72
|
+
assertLimit('mailbox', current, entitlements.maxAccounts, entitlements);
|
|
73
|
+
}
|
|
74
|
+
export function assertMemberLimit(current, entitlements) {
|
|
75
|
+
assertLimit('member', current, entitlements.maxMembers, entitlements);
|
|
76
|
+
}
|
|
77
|
+
/** Entitlements plus current usage, for quota enforcement and renewal warnings. */
|
|
78
|
+
export function checkLicenseState(db, now = new Date()) {
|
|
79
|
+
const entitlements = getEntitlements(db, now);
|
|
80
|
+
const accountCount = db.select().from(accounts).all().length;
|
|
81
|
+
const memberCount = db.select().from(members).all().length;
|
|
82
|
+
const overQuota = accountCount > entitlements.maxAccounts || memberCount > entitlements.maxMembers;
|
|
83
|
+
let warning;
|
|
84
|
+
if (entitlements.inGrace) {
|
|
85
|
+
warning =
|
|
86
|
+
`The Fluxmail license expired ${entitlements.leaseExpiresAt}; paid limits continue until ` +
|
|
87
|
+
`${entitlements.graceUntil}. Renew before then or this instance drops to the Personal plan.`;
|
|
88
|
+
}
|
|
89
|
+
else if (!entitlements.licensed && readLeaseRow(db)) {
|
|
90
|
+
warning =
|
|
91
|
+
`The Fluxmail license has lapsed; this instance is on the Personal plan ` +
|
|
92
|
+
`(${entitlements.maxAccounts} mailboxes, ${entitlements.maxMembers} member).` +
|
|
93
|
+
(overQuota
|
|
94
|
+
? ` Current usage (${accountCount} mailboxes, ${memberCount} members) exceeds that, so email tools are ` +
|
|
95
|
+
'blocked until you renew the license or remove mailboxes/members to fit.'
|
|
96
|
+
: '');
|
|
97
|
+
}
|
|
98
|
+
return { entitlements, accountCount, memberCount, overQuota, ...(warning ? { warning } : {}) };
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Gate for MCP tool calls: throws once a lapsed license leaves the instance
|
|
102
|
+
* over the entitled caps. Returns the current state so callers can surface
|
|
103
|
+
* grace warnings without a second read.
|
|
104
|
+
*/
|
|
105
|
+
export function assertWithinQuota(db, now = new Date()) {
|
|
106
|
+
const state = checkLicenseState(db, now);
|
|
107
|
+
if (state.overQuota) {
|
|
108
|
+
const { entitlements: ent } = state;
|
|
109
|
+
throw new EmailError('entitlement_exceeded', `This instance has ${state.accountCount} connected mailboxes and ${state.memberCount} members, but the ` +
|
|
110
|
+
`${ent.plan} plan allows ${ent.maxAccounts} and ${ent.maxMembers}. Renew the license ` +
|
|
111
|
+
'("fluxmail license activate <key>") or remove mailboxes/members ' +
|
|
112
|
+
'("fluxmail accounts remove", "fluxmail members remove") to fit the plan.');
|
|
113
|
+
}
|
|
114
|
+
return state;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=entitlements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.js","sourceRoot":"","sources":["../../src/licensing/entitlements.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlement lease issued by the hosted license server: a detached-signature
|
|
3
|
+
* token of the form base64url(payloadJSON) + "." + base64url(ed25519 signature).
|
|
4
|
+
* The signature covers the payload bytes exactly as transmitted, so we verify
|
|
5
|
+
* before parsing and never re-serialize the JSON.
|
|
6
|
+
*/
|
|
7
|
+
export interface LeasePayload {
|
|
8
|
+
/** Payload version; this client understands only 2. */
|
|
9
|
+
v: 2;
|
|
10
|
+
licenseId: string;
|
|
11
|
+
/** Lowercase plan name for display ("pro", "team", …); clients never switch on it. */
|
|
12
|
+
plan: string;
|
|
13
|
+
/** People who may use this instance. */
|
|
14
|
+
maxMembers: number;
|
|
15
|
+
/** Connected mailboxes on this instance. */
|
|
16
|
+
maxAccounts: number;
|
|
17
|
+
/** ISO 8601 */
|
|
18
|
+
issuedAt: string;
|
|
19
|
+
/** ISO 8601, issuedAt + ~7 days */
|
|
20
|
+
expiresAt: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* License-server signing keys (base64 SPKI DER), pinned at build time.
|
|
24
|
+
* Rotation: releases ship with both the old and new key pinned; the server
|
|
25
|
+
* switches its private key once instances have upgraded, then the old pin
|
|
26
|
+
* is removed.
|
|
27
|
+
*/
|
|
28
|
+
export declare const PINNED_LICENSE_PUBLIC_KEYS: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Pinned keys plus any keys from FLUXMAIL_LICENSE_PUBLIC_KEYS (comma-separated
|
|
31
|
+
* base64 SPKI DER), which exists for development and tests. Extra keys are
|
|
32
|
+
* additive so an env typo can never unpin the production key, and the override
|
|
33
|
+
* is ignored under NODE_ENV=production (as set by the Docker image) so a stock
|
|
34
|
+
* production instance trusts only the pinned keys.
|
|
35
|
+
*/
|
|
36
|
+
export declare function licensePublicKeys(): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Verify a lease token against the accepted public keys and return its payload.
|
|
39
|
+
* Throws with a human-readable reason on any failure; callers on enforcement
|
|
40
|
+
* paths catch and fall back to Personal-plan limits. With allowExpired, an
|
|
41
|
+
* expired-but-authentic lease is returned so the caller can apply the grace
|
|
42
|
+
* period; signature and shape failures still throw.
|
|
43
|
+
*/
|
|
44
|
+
export declare function verifyLease(token: string, publicKeys: readonly string[], now?: Date, opts?: {
|
|
45
|
+
allowExpired?: boolean;
|
|
46
|
+
}): LeasePayload;
|
|
47
|
+
//# sourceMappingURL=lease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.d.ts","sourceRoot":"","sources":["../../src/licensing/lease.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,CAAC,EAAE,CAAC,CAAC;IACL,SAAS,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,0BAA0B,EAAE,SAAS,MAAM,EAGvD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAU5C;AAYD;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,GAAG,OAAa,EAChB,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACpC,YAAY,CAwDd"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createPublicKey, verify as verifySignature } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* License-server signing keys (base64 SPKI DER), pinned at build time.
|
|
4
|
+
* Rotation: releases ship with both the old and new key pinned; the server
|
|
5
|
+
* switches its private key once instances have upgraded, then the old pin
|
|
6
|
+
* is removed.
|
|
7
|
+
*/
|
|
8
|
+
export const PINNED_LICENSE_PUBLIC_KEYS = [
|
|
9
|
+
'MCowBQYDK2VwAyEAoxmkS0uM1qUX6+Jzm3aoTPLFuZ8mcpyiEI3VE1qdM90=',
|
|
10
|
+
'MCowBQYDK2VwAyEA3ZAEKMPlNE1Ne0bTra438WMvI2Tle+C2aDOOAA8ETWM=',
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Pinned keys plus any keys from FLUXMAIL_LICENSE_PUBLIC_KEYS (comma-separated
|
|
14
|
+
* base64 SPKI DER), which exists for development and tests. Extra keys are
|
|
15
|
+
* additive so an env typo can never unpin the production key, and the override
|
|
16
|
+
* is ignored under NODE_ENV=production (as set by the Docker image) so a stock
|
|
17
|
+
* production instance trusts only the pinned keys.
|
|
18
|
+
*/
|
|
19
|
+
export function licensePublicKeys() {
|
|
20
|
+
const extra = process.env.NODE_ENV === 'production' ? undefined : process.env.FLUXMAIL_LICENSE_PUBLIC_KEYS;
|
|
21
|
+
const fromEnv = extra
|
|
22
|
+
? extra
|
|
23
|
+
.split(',')
|
|
24
|
+
.map((key) => key.trim())
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
: [];
|
|
27
|
+
return [...PINNED_LICENSE_PUBLIC_KEYS, ...fromEnv];
|
|
28
|
+
}
|
|
29
|
+
const BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
30
|
+
function decodePublicKey(base64Spki) {
|
|
31
|
+
try {
|
|
32
|
+
return createPublicKey({ key: Buffer.from(base64Spki, 'base64'), format: 'der', type: 'spki' });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify a lease token against the accepted public keys and return its payload.
|
|
40
|
+
* Throws with a human-readable reason on any failure; callers on enforcement
|
|
41
|
+
* paths catch and fall back to Personal-plan limits. With allowExpired, an
|
|
42
|
+
* expired-but-authentic lease is returned so the caller can apply the grace
|
|
43
|
+
* period; signature and shape failures still throw.
|
|
44
|
+
*/
|
|
45
|
+
export function verifyLease(token, publicKeys, now = new Date(), opts = {}) {
|
|
46
|
+
const parts = token.split('.');
|
|
47
|
+
if (parts.length !== 2 || !BASE64URL.test(parts[0]) || !BASE64URL.test(parts[1])) {
|
|
48
|
+
throw new Error('malformed lease token');
|
|
49
|
+
}
|
|
50
|
+
const payloadBytes = Buffer.from(parts[0], 'base64url');
|
|
51
|
+
const signature = Buffer.from(parts[1], 'base64url');
|
|
52
|
+
const signed = publicKeys.some((key) => {
|
|
53
|
+
const publicKey = decodePublicKey(key);
|
|
54
|
+
if (!publicKey)
|
|
55
|
+
return false;
|
|
56
|
+
try {
|
|
57
|
+
return verifySignature(null, payloadBytes, publicKey, signature);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (!signed) {
|
|
64
|
+
throw new Error('lease signature does not match any accepted license key');
|
|
65
|
+
}
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(payloadBytes.toString('utf8'));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error('lease payload is not valid JSON');
|
|
72
|
+
}
|
|
73
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
74
|
+
throw new Error('lease payload is not an object');
|
|
75
|
+
}
|
|
76
|
+
const payload = parsed;
|
|
77
|
+
if (payload.v !== 2) {
|
|
78
|
+
throw new Error(`unsupported lease version ${JSON.stringify(payload.v)}`);
|
|
79
|
+
}
|
|
80
|
+
if (typeof payload.licenseId !== 'string' || !payload.licenseId) {
|
|
81
|
+
throw new Error('lease payload has no licenseId');
|
|
82
|
+
}
|
|
83
|
+
if (typeof payload.plan !== 'string' || !payload.plan) {
|
|
84
|
+
throw new Error('lease payload has no plan');
|
|
85
|
+
}
|
|
86
|
+
for (const field of ['maxMembers', 'maxAccounts']) {
|
|
87
|
+
const value = payload[field];
|
|
88
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
|
|
89
|
+
throw new Error(`lease payload has an invalid ${field}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const field of ['issuedAt', 'expiresAt']) {
|
|
93
|
+
const value = payload[field];
|
|
94
|
+
if (typeof value !== 'string' || Number.isNaN(Date.parse(value))) {
|
|
95
|
+
throw new Error(`lease payload has an invalid ${field}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!opts.allowExpired && Date.parse(payload.expiresAt) <= now.getTime()) {
|
|
99
|
+
throw new Error(`lease expired at ${payload.expiresAt}`);
|
|
100
|
+
}
|
|
101
|
+
return payload;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../src/licensing/lease.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FluxmailConfig } from '../config.js';
|
|
2
|
+
import type { FluxmailDb } from '../storage/db.js';
|
|
3
|
+
import { type LeasePayload } from './lease.js';
|
|
4
|
+
/** Instances revalidate roughly daily; the lease itself is valid for ~7 days. */
|
|
5
|
+
export declare const VALIDATE_INTERVAL_MS: number;
|
|
6
|
+
export type RefreshResult = {
|
|
7
|
+
outcome: 'refreshed';
|
|
8
|
+
lease: LeasePayload;
|
|
9
|
+
} | {
|
|
10
|
+
outcome: 'invalid_key' | 'not_found' | 'inactive' | 'in_use' | 'bad_lease' | 'outage';
|
|
11
|
+
message: string;
|
|
12
|
+
/** True while a previously cached lease still grants paid limits. */
|
|
13
|
+
cachedLeaseActive: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Stable random id per install, persisted next to the database. Not a secret;
|
|
17
|
+
* it lets the license server spot one key shared across many instances.
|
|
18
|
+
*/
|
|
19
|
+
export declare function loadInstanceId(dataDir: string): string;
|
|
20
|
+
export interface RefreshOptions {
|
|
21
|
+
licenseKey: string;
|
|
22
|
+
serverUrl: string;
|
|
23
|
+
dataDir: string;
|
|
24
|
+
fetchImpl?: typeof fetch;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validate the license with the server and cache the returned lease. Every
|
|
28
|
+
* failure keeps the existing cached lease, per the contract: an outage or a
|
|
29
|
+
* lapsed subscription degrades to free-tier limits only once the cached lease
|
|
30
|
+
* expires, and never interrupts a running server.
|
|
31
|
+
*/
|
|
32
|
+
export declare function refreshLicense(db: FluxmailDb, opts: RefreshOptions): Promise<RefreshResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Background revalidation for long-running server modes: refresh immediately
|
|
35
|
+
* if the last validation is over a day old, then keep a daily cadence. Timers
|
|
36
|
+
* are unref'd and every failure path is caught, so licensing can never keep
|
|
37
|
+
* the process alive or take it down. Returns a stop function.
|
|
38
|
+
*/
|
|
39
|
+
export declare function startLicenseRefresher(deps: {
|
|
40
|
+
db: FluxmailDb;
|
|
41
|
+
config: FluxmailConfig;
|
|
42
|
+
log?: (line: string) => void;
|
|
43
|
+
/** Notify long-lived services that renewed entitlements are available. */
|
|
44
|
+
onRefreshed?: () => void;
|
|
45
|
+
}): () => void;
|
|
46
|
+
//# sourceMappingURL=refresher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresher.d.ts","sourceRoot":"","sources":["../../src/licensing/refresher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAkC,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/E,iFAAiF;AACjF,eAAO,MAAM,oBAAoB,QAAsB,CAAC;AAIxD,MAAM,MAAM,aAAa,GACrB;IAAE,OAAO,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,YAAY,CAAA;CAAE,GAC7C;IACE,OAAO,EAAE,aAAa,GAAG,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEN;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAStD;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA4DjG;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,EAAE,EAAE,UAAU,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,GAAG,MAAM,IAAI,CAoDb"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { validateLicense } from './client.js';
|
|
5
|
+
import { licensePublicKeys, verifyLease } from './lease.js';
|
|
6
|
+
import { getEntitlements, readLeaseRow, saveLeaseToken } from './entitlements.js';
|
|
7
|
+
/** Instances revalidate roughly daily; the lease itself is valid for ~7 days. */
|
|
8
|
+
export const VALIDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
/** After an outage, retry sooner than the daily cadence. */
|
|
10
|
+
const OUTAGE_RETRY_MS = 60 * 60 * 1000;
|
|
11
|
+
/**
|
|
12
|
+
* Stable random id per install, persisted next to the database. Not a secret;
|
|
13
|
+
* it lets the license server spot one key shared across many instances.
|
|
14
|
+
*/
|
|
15
|
+
export function loadInstanceId(dataDir) {
|
|
16
|
+
const file = path.join(dataDir, 'instance.id');
|
|
17
|
+
if (existsSync(file)) {
|
|
18
|
+
const id = readFileSync(file, 'utf8').trim();
|
|
19
|
+
if (id)
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
const id = randomBytes(16).toString('hex');
|
|
23
|
+
writeFileSync(file, id + '\n');
|
|
24
|
+
return id;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Validate the license with the server and cache the returned lease. Every
|
|
28
|
+
* failure keeps the existing cached lease, per the contract: an outage or a
|
|
29
|
+
* lapsed subscription degrades to free-tier limits only once the cached lease
|
|
30
|
+
* expires, and never interrupts a running server.
|
|
31
|
+
*/
|
|
32
|
+
export async function refreshLicense(db, opts) {
|
|
33
|
+
const result = await validateLicense({
|
|
34
|
+
serverUrl: opts.serverUrl,
|
|
35
|
+
licenseKey: opts.licenseKey,
|
|
36
|
+
instanceId: loadInstanceId(opts.dataDir),
|
|
37
|
+
...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
|
|
38
|
+
});
|
|
39
|
+
const cachedLeaseActive = () => getEntitlements(db).licensed;
|
|
40
|
+
switch (result.kind) {
|
|
41
|
+
case 'valid': {
|
|
42
|
+
let lease;
|
|
43
|
+
try {
|
|
44
|
+
lease = verifyLease(result.lease, licensePublicKeys());
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
return {
|
|
48
|
+
outcome: 'bad_lease',
|
|
49
|
+
message: `The license server returned a lease this build cannot verify ` +
|
|
50
|
+
`(${err instanceof Error ? err.message : String(err)}). ` +
|
|
51
|
+
'Update Fluxmail if a signing-key rotation was announced.',
|
|
52
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
saveLeaseToken(db, result.lease);
|
|
56
|
+
return { outcome: 'refreshed', lease };
|
|
57
|
+
}
|
|
58
|
+
case 'invalid_request':
|
|
59
|
+
return {
|
|
60
|
+
outcome: 'invalid_key',
|
|
61
|
+
message: 'The license server rejected the key as malformed; check FLUXMAIL_LICENSE_KEY.',
|
|
62
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
63
|
+
};
|
|
64
|
+
case 'license_not_found':
|
|
65
|
+
return {
|
|
66
|
+
outcome: 'not_found',
|
|
67
|
+
message: 'The license key is invalid; check it for typos.',
|
|
68
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
69
|
+
};
|
|
70
|
+
case 'license_inactive':
|
|
71
|
+
return {
|
|
72
|
+
outcome: 'inactive',
|
|
73
|
+
message: `The license is no longer active (${result.status}).`,
|
|
74
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
75
|
+
};
|
|
76
|
+
case 'license_in_use':
|
|
77
|
+
return {
|
|
78
|
+
outcome: 'in_use',
|
|
79
|
+
message: 'This license is already active on another Fluxmail instance. ' +
|
|
80
|
+
'Run "fluxmail license deactivate" there first, or manage instances from your Fluxmail account.',
|
|
81
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
82
|
+
};
|
|
83
|
+
case 'outage':
|
|
84
|
+
return {
|
|
85
|
+
outcome: 'outage',
|
|
86
|
+
message: `Could not reach the license server (${result.detail}); will retry.`,
|
|
87
|
+
cachedLeaseActive: cachedLeaseActive(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Background revalidation for long-running server modes: refresh immediately
|
|
93
|
+
* if the last validation is over a day old, then keep a daily cadence. Timers
|
|
94
|
+
* are unref'd and every failure path is caught, so licensing can never keep
|
|
95
|
+
* the process alive or take it down. Returns a stop function.
|
|
96
|
+
*/
|
|
97
|
+
export function startLicenseRefresher(deps) {
|
|
98
|
+
const { db, config } = deps;
|
|
99
|
+
const log = deps.log ?? (() => { });
|
|
100
|
+
const licenseKey = config.licenseKey;
|
|
101
|
+
if (!licenseKey)
|
|
102
|
+
return () => { };
|
|
103
|
+
let timer;
|
|
104
|
+
let stopped = false;
|
|
105
|
+
const schedule = (delayMs) => {
|
|
106
|
+
if (stopped)
|
|
107
|
+
return;
|
|
108
|
+
timer = setTimeout(() => void run(), delayMs);
|
|
109
|
+
timer.unref();
|
|
110
|
+
};
|
|
111
|
+
const run = async () => {
|
|
112
|
+
let result;
|
|
113
|
+
try {
|
|
114
|
+
result = await refreshLicense(db, {
|
|
115
|
+
licenseKey,
|
|
116
|
+
serverUrl: config.licenseServerUrl,
|
|
117
|
+
dataDir: config.dataDir,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
log(`License refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
122
|
+
schedule(OUTAGE_RETRY_MS);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (result.outcome === 'refreshed') {
|
|
126
|
+
log(`License validated; lease renewed until ${result.lease.expiresAt}`);
|
|
127
|
+
deps.onRefreshed?.();
|
|
128
|
+
schedule(VALIDATE_INTERVAL_MS);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
log(`License validation: ${result.message}` +
|
|
132
|
+
(result.cachedLeaseActive
|
|
133
|
+
? ' The cached lease keeps paid limits for now.'
|
|
134
|
+
: ' Running with Personal-plan limits.'));
|
|
135
|
+
schedule(result.outcome === 'outage' ? OUTAGE_RETRY_MS : VALIDATE_INTERVAL_MS);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const row = readLeaseRow(db);
|
|
139
|
+
const sinceLastValidation = row ? Date.now() - row.updatedAt : Number.POSITIVE_INFINITY;
|
|
140
|
+
if (sinceLastValidation >= VALIDATE_INTERVAL_MS)
|
|
141
|
+
void run();
|
|
142
|
+
else
|
|
143
|
+
schedule(VALIDATE_INTERVAL_MS - sinceLastValidation);
|
|
144
|
+
return () => {
|
|
145
|
+
stopped = true;
|
|
146
|
+
if (timer)
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=refresher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"refresher.js","sourceRoot":"","sources":["../../src/licensing/refresher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { EmailService, SendInput } from '../service/emailService.js';
|
|
3
|
+
type DraftArgs = {
|
|
4
|
+
accountId?: string;
|
|
5
|
+
to?: string[];
|
|
6
|
+
cc?: string[];
|
|
7
|
+
bcc?: string[];
|
|
8
|
+
subject?: string;
|
|
9
|
+
bodyText?: string;
|
|
10
|
+
bodyHtml?: string;
|
|
11
|
+
replyToMessageId?: string;
|
|
12
|
+
replyAll?: boolean;
|
|
13
|
+
attachments?: Array<{
|
|
14
|
+
filename: string;
|
|
15
|
+
mimeType: string;
|
|
16
|
+
content: string;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
export declare function toSendRequest(args: DraftArgs & {
|
|
20
|
+
draftId?: string;
|
|
21
|
+
}): SendInput | {
|
|
22
|
+
draftId: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function resolveAttachmentSavePath(savePath: string, filename: string): string;
|
|
25
|
+
export declare function saveAttachment(savePath: string, filename: string, content: Buffer): string;
|
|
26
|
+
export declare function buildMcpServer(service: EmailService): McpServer;
|
|
27
|
+
export {};
|
|
28
|
+
//# sourceMappingURL=buildServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildServer.d.ts","sourceRoot":"","sources":["../../src/mcp/buildServer.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAYpE,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAgE1E,KAAK,SAAS,GAAG;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9E,CAAC;AAyBF,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAsBrG;AAqDD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBpF;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAY1F;AAYD,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CA0T/D"}
|