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
package/dist/config.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DEFAULT_LICENSE_SERVER_URL } from './licensing/client.js';
|
|
6
|
+
function unquoteEnvValue(value) {
|
|
7
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
8
|
+
try {
|
|
9
|
+
const parsed = JSON.parse(value);
|
|
10
|
+
return typeof parsed === 'string' ? parsed : value.slice(1, -1);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return value.slice(1, -1);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
if (value.startsWith("'") && value.endsWith("'"))
|
|
17
|
+
return value.slice(1, -1);
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
function parseEnvValue(raw) {
|
|
21
|
+
// A fully quoted value may contain "#" verbatim.
|
|
22
|
+
if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) {
|
|
23
|
+
return unquoteEnvValue(raw);
|
|
24
|
+
}
|
|
25
|
+
// Otherwise strip an inline comment (whitespace + #), like dotenv and docker compose do.
|
|
26
|
+
const quoted = raw.match(/^("(?:[^"\\]|\\.)*"|'[^']*')\s+#/);
|
|
27
|
+
if (quoted?.[1])
|
|
28
|
+
return unquoteEnvValue(quoted[1]);
|
|
29
|
+
const comment = raw.search(/\s#/);
|
|
30
|
+
return comment === -1 ? raw : raw.slice(0, comment).trimEnd();
|
|
31
|
+
}
|
|
32
|
+
function parseEnvContent(content) {
|
|
33
|
+
const out = {};
|
|
34
|
+
for (const line of content.split('\n')) {
|
|
35
|
+
const trimmed = line.trim();
|
|
36
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
37
|
+
continue;
|
|
38
|
+
const eq = trimmed.indexOf('=');
|
|
39
|
+
if (eq === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const key = trimmed.slice(0, eq).trim().replace(/^export\s+/, '');
|
|
42
|
+
if (key)
|
|
43
|
+
out[key] = parseEnvValue(trimmed.slice(eq + 1).trim());
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function applyDotEnvFile(file) {
|
|
48
|
+
if (!existsSync(file))
|
|
49
|
+
return;
|
|
50
|
+
for (const [key, value] of Object.entries(parseEnvContent(readFileSync(file, 'utf8')))) {
|
|
51
|
+
if (process.env[key] === undefined)
|
|
52
|
+
process.env[key] = value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Load .env.local and .env from the working directory. Real environment
|
|
57
|
+
* variables always win; .env.local wins over .env.
|
|
58
|
+
*/
|
|
59
|
+
export function loadDotEnv(cwd = process.cwd()) {
|
|
60
|
+
applyDotEnvFile(path.join(cwd, '.env.local'));
|
|
61
|
+
applyDotEnvFile(path.join(cwd, '.env'));
|
|
62
|
+
}
|
|
63
|
+
/** Expand a leading "~" (e.g. FLUXMAIL_DATA_DIR=~/.fluxmail), which Node does not do. */
|
|
64
|
+
export function expandHome(p) {
|
|
65
|
+
if (p === '~')
|
|
66
|
+
return homedir();
|
|
67
|
+
if (p.startsWith('~/'))
|
|
68
|
+
return path.join(homedir(), p.slice(2));
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
/** Resolve (and create) the data dir, honoring cwd .env files and FLUXMAIL_DATA_DIR. */
|
|
72
|
+
export function resolveDataDir() {
|
|
73
|
+
loadDotEnv();
|
|
74
|
+
const fromEnv = process.env.FLUXMAIL_DATA_DIR;
|
|
75
|
+
const dataDir = fromEnv ? expandHome(fromEnv) : path.join(homedir(), '.fluxmail');
|
|
76
|
+
mkdirSync(dataDir, { recursive: true });
|
|
77
|
+
return dataDir;
|
|
78
|
+
}
|
|
79
|
+
export function configFilePath(dataDir) {
|
|
80
|
+
return path.join(dataDir, 'config.env');
|
|
81
|
+
}
|
|
82
|
+
/** Best-effort permission tightening: chmod fails on files owned by another user, but the read may still work. */
|
|
83
|
+
function tryRestrictPermissions(file) {
|
|
84
|
+
try {
|
|
85
|
+
chmodSync(file, 0o600);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Reading (or a later write) will surface a real permission problem.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Settings persisted by `fluxmail config set`, e.g. GOOGLE_CLIENT_ID. */
|
|
92
|
+
export function readStoredConfig(dataDir) {
|
|
93
|
+
const file = configFilePath(dataDir);
|
|
94
|
+
if (!existsSync(file))
|
|
95
|
+
return {};
|
|
96
|
+
tryRestrictPermissions(file);
|
|
97
|
+
return parseEnvContent(readFileSync(file, 'utf8'));
|
|
98
|
+
}
|
|
99
|
+
const CONFIG_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
100
|
+
export function setStoredConfig(dataDir, key, value) {
|
|
101
|
+
if (!CONFIG_KEY_PATTERN.test(key)) {
|
|
102
|
+
throw new Error(`Invalid key "${key}": use UPPER_SNAKE_CASE, e.g. GOOGLE_CLIENT_ID`);
|
|
103
|
+
}
|
|
104
|
+
if (key === 'FLUXMAIL_DATA_DIR') {
|
|
105
|
+
throw new Error('FLUXMAIL_DATA_DIR cannot be stored in the data dir itself; set it in your shell or a .env file');
|
|
106
|
+
}
|
|
107
|
+
const stored = readStoredConfig(dataDir);
|
|
108
|
+
stored[key] = value;
|
|
109
|
+
writeStoredConfig(dataDir, stored);
|
|
110
|
+
}
|
|
111
|
+
export function unsetStoredConfig(dataDir, key) {
|
|
112
|
+
const stored = readStoredConfig(dataDir);
|
|
113
|
+
if (!(key in stored))
|
|
114
|
+
return false;
|
|
115
|
+
delete stored[key];
|
|
116
|
+
writeStoredConfig(dataDir, stored);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
export function maskStoredConfigValue(key, value) {
|
|
120
|
+
if (!/SECRET|KEY|TOKEN|PASSWORD/i.test(key))
|
|
121
|
+
return value;
|
|
122
|
+
return value.length > 8 ? `${value.slice(0, 4)}…${value.slice(-4)}` : '********';
|
|
123
|
+
}
|
|
124
|
+
function writeStoredConfig(dataDir, values) {
|
|
125
|
+
const file = configFilePath(dataDir);
|
|
126
|
+
const lines = Object.entries(values).map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
127
|
+
writeFileSync(file, lines.join('\n') + (lines.length ? '\n' : ''), { mode: 0o600 });
|
|
128
|
+
tryRestrictPermissions(file);
|
|
129
|
+
}
|
|
130
|
+
function decodeEncryptionKey(value, errorMessage) {
|
|
131
|
+
if (!/^[0-9a-fA-F]{64}$/.test(value))
|
|
132
|
+
throw new Error(errorMessage);
|
|
133
|
+
return Buffer.from(value, 'hex');
|
|
134
|
+
}
|
|
135
|
+
function readPort(name, fallback) {
|
|
136
|
+
const raw = process.env[name];
|
|
137
|
+
if (raw === undefined)
|
|
138
|
+
return fallback;
|
|
139
|
+
const value = Number(raw);
|
|
140
|
+
if (!Number.isInteger(value) || value < 1 || value > 65_535) {
|
|
141
|
+
throw new Error(`${name} must be an integer between 1 and 65535, got "${raw}"`);
|
|
142
|
+
}
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
function readBaseUrl(port) {
|
|
146
|
+
const value = (process.env.FLUXMAIL_BASE_URL ?? `http://localhost:${port}`).replace(/\/+$/, '');
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = new URL(value);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
throw new Error(`FLUXMAIL_BASE_URL must be a valid HTTP or HTTPS URL, got "${value}"`);
|
|
153
|
+
}
|
|
154
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
155
|
+
throw new Error(`FLUXMAIL_BASE_URL must use HTTP or HTTPS, got "${parsed.protocol}"`);
|
|
156
|
+
}
|
|
157
|
+
if (parsed.username || parsed.password) {
|
|
158
|
+
throw new Error('FLUXMAIL_BASE_URL cannot contain embedded credentials');
|
|
159
|
+
}
|
|
160
|
+
if (parsed.search || parsed.hash) {
|
|
161
|
+
throw new Error('FLUXMAIL_BASE_URL cannot contain a query string or fragment');
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
function loadEncryptionKey(dataDir) {
|
|
166
|
+
const fromEnv = process.env.FLUXMAIL_ENCRYPTION_KEY;
|
|
167
|
+
if (fromEnv) {
|
|
168
|
+
return decodeEncryptionKey(fromEnv, 'FLUXMAIL_ENCRYPTION_KEY must be exactly 64 hex characters (32 bytes)');
|
|
169
|
+
}
|
|
170
|
+
// Auto-generate on first run so getting started requires zero key management.
|
|
171
|
+
const keyPath = path.join(dataDir, 'encryption.key');
|
|
172
|
+
if (existsSync(keyPath)) {
|
|
173
|
+
tryRestrictPermissions(keyPath);
|
|
174
|
+
const key = decodeEncryptionKey(readFileSync(keyPath, 'utf8').trim(), `Corrupt encryption key at ${keyPath}`);
|
|
175
|
+
return key;
|
|
176
|
+
}
|
|
177
|
+
const key = randomBytes(32);
|
|
178
|
+
writeFileSync(keyPath, key.toString('hex') + '\n', { mode: 0o600 });
|
|
179
|
+
return key;
|
|
180
|
+
}
|
|
181
|
+
export function loadConfig() {
|
|
182
|
+
// Precedence: shell env > cwd .env.local > cwd .env > data-dir config.env.
|
|
183
|
+
const dataDir = resolveDataDir();
|
|
184
|
+
applyDotEnvFile(configFilePath(dataDir));
|
|
185
|
+
const port = readPort('FLUXMAIL_PORT', 8977);
|
|
186
|
+
const oauthPort = readPort('FLUXMAIL_OAUTH_PORT', 8976);
|
|
187
|
+
const baseUrl = readBaseUrl(port);
|
|
188
|
+
const authModeEnv = process.env.FLUXMAIL_AUTH ?? 'apikey';
|
|
189
|
+
if (authModeEnv !== 'apikey' && authModeEnv !== 'none') {
|
|
190
|
+
throw new Error(`FLUXMAIL_AUTH must be "apikey" or "none", got "${authModeEnv}"`);
|
|
191
|
+
}
|
|
192
|
+
const config = {
|
|
193
|
+
dataDir,
|
|
194
|
+
dbPath: process.env.FLUXMAIL_DB_PATH
|
|
195
|
+
? expandHome(process.env.FLUXMAIL_DB_PATH)
|
|
196
|
+
: path.join(dataDir, 'fluxmail.db'),
|
|
197
|
+
encryptionKey: loadEncryptionKey(dataDir),
|
|
198
|
+
port,
|
|
199
|
+
baseUrl,
|
|
200
|
+
oauthPort,
|
|
201
|
+
oauthHost: process.env.FLUXMAIL_OAUTH_HOST ?? '127.0.0.1',
|
|
202
|
+
authMode: authModeEnv,
|
|
203
|
+
licenseServerUrl: (process.env.FLUXMAIL_LICENSE_SERVER_URL ?? DEFAULT_LICENSE_SERVER_URL).replace(/\/+$/, ''),
|
|
204
|
+
};
|
|
205
|
+
const licenseKey = process.env.FLUXMAIL_LICENSE_KEY?.trim();
|
|
206
|
+
if (licenseKey)
|
|
207
|
+
config.licenseKey = licenseKey;
|
|
208
|
+
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
209
|
+
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
210
|
+
if (clientId && clientSecret) {
|
|
211
|
+
config.google = { clientId, clientSecret };
|
|
212
|
+
}
|
|
213
|
+
return config;
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FluxmailConfig } from './config.js';
|
|
2
|
+
import { type FluxmailDb } from './storage/db.js';
|
|
3
|
+
import { AccountRegistry } from './accounts/registry.js';
|
|
4
|
+
import { EmailService } from './service/emailService.js';
|
|
5
|
+
import { SendScheduler } from './scheduler/sendScheduler.js';
|
|
6
|
+
export interface AppContext {
|
|
7
|
+
config: FluxmailConfig;
|
|
8
|
+
db: FluxmailDb;
|
|
9
|
+
registry: AccountRegistry;
|
|
10
|
+
service: EmailService;
|
|
11
|
+
/** Inert until start(); only the long-lived serve/stdio commands start it. */
|
|
12
|
+
scheduler: SendScheduler;
|
|
13
|
+
}
|
|
14
|
+
export declare function createContext(): AppContext;
|
|
15
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,cAAc,CAAC;IACvB,EAAE,EAAE,UAAU,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,YAAY,CAAC;IACtB,8EAA8E;IAC9E,SAAS,EAAE,aAAa,CAAC;CAC1B;AAED,wBAAgB,aAAa,IAAI,UAAU,CAQ1C"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { loadConfig } from './config.js';
|
|
2
|
+
import { openDb } from './storage/db.js';
|
|
3
|
+
import { AccountRegistry } from './accounts/registry.js';
|
|
4
|
+
import { EmailService } from './service/emailService.js';
|
|
5
|
+
import { SendScheduler } from './scheduler/sendScheduler.js';
|
|
6
|
+
export function createContext() {
|
|
7
|
+
const config = loadConfig();
|
|
8
|
+
const db = openDb(config.dbPath);
|
|
9
|
+
const registry = new AccountRegistry(db, config);
|
|
10
|
+
const service = new EmailService(registry, db);
|
|
11
|
+
const scheduler = new SendScheduler(db, service);
|
|
12
|
+
service.onScheduleChanged = () => scheduler.wake();
|
|
13
|
+
return { config, db, registry, service, scheduler };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAuB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,MAAM,EAAmB,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAW7D,MAAM,UAAU,aAAa;IAC3B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,IAAI,aAAa,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IACjD,OAAO,CAAC,iBAAiB,GAAG,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IACnD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import type { HttpBindings } from '@hono/node-server';
|
|
3
|
+
import type { FluxmailConfig } from '../config.js';
|
|
4
|
+
import type { FluxmailDb } from '../storage/db.js';
|
|
5
|
+
import type { AccountRegistry } from '../accounts/registry.js';
|
|
6
|
+
import type { EmailService } from '../service/emailService.js';
|
|
7
|
+
export interface AppDeps {
|
|
8
|
+
config: FluxmailConfig;
|
|
9
|
+
db: FluxmailDb;
|
|
10
|
+
registry: AccountRegistry;
|
|
11
|
+
service: EmailService;
|
|
12
|
+
}
|
|
13
|
+
export declare function createApp(deps: AppDeps): Hono<{
|
|
14
|
+
Bindings: HttpBindings;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ5E,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,cAAc,CAAC;IACvB,EAAE,EAAE,UAAU,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IAAE,QAAQ,EAAE,YAAY,CAAA;CAAE,CAAC,CA8GzE"}
|
package/dist/http/app.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { Hono } from 'hono';
|
|
3
|
+
import { isEmailError } from '@fluxmail/core';
|
|
4
|
+
import { RESPONSE_ALREADY_SENT } from '@hono/node-server/utils/response';
|
|
5
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
6
|
+
import { authenticateApiKey } from '../storage/apiKeys.js';
|
|
7
|
+
import { buildMcpServer } from '../mcp/buildServer.js';
|
|
8
|
+
import { buildAuthUrl, createOAuthClient, exchangeCode } from '../accounts/googleAuth.js';
|
|
9
|
+
import { VERSION } from '../version.js';
|
|
10
|
+
const OAUTH_STATE_TTL_MS = 10 * 60 * 1000;
|
|
11
|
+
export function createApp(deps) {
|
|
12
|
+
const { config, db, registry, service } = deps;
|
|
13
|
+
const app = new Hono();
|
|
14
|
+
const oauthStates = new Map();
|
|
15
|
+
// Connecting a mailbox is instance administration: only unscoped admin keys
|
|
16
|
+
// qualify; member-scoped keys are confined to mailbox access over MCP.
|
|
17
|
+
const adminAuthorized = (c, allowQueryKey = false) => {
|
|
18
|
+
if (config.authMode === 'none')
|
|
19
|
+
return true;
|
|
20
|
+
const bearer = c.req.header('authorization')?.replace(/^Bearer\s+/i, '');
|
|
21
|
+
const key = bearer ?? (allowQueryKey ? c.req.query('key') : undefined);
|
|
22
|
+
if (!key)
|
|
23
|
+
return false;
|
|
24
|
+
return authenticateApiKey(db, key)?.memberId === null;
|
|
25
|
+
};
|
|
26
|
+
// Authenticate an MCP request and resolve the mailbox scope its key authorizes.
|
|
27
|
+
// A member-scoped key is confined to shared and owned mailboxes; an admin key
|
|
28
|
+
// (or authMode 'none') gets unscoped access.
|
|
29
|
+
const scopeForRequest = (c) => {
|
|
30
|
+
if (config.authMode === 'none')
|
|
31
|
+
return { memberId: null };
|
|
32
|
+
const bearer = c.req.header('authorization')?.replace(/^Bearer\s+/i, '');
|
|
33
|
+
return bearer ? authenticateApiKey(db, bearer) : null;
|
|
34
|
+
};
|
|
35
|
+
app.get('/healthz', (c) => c.json({ ok: true, name: 'fluxmail', version: VERSION }));
|
|
36
|
+
// Stateless Streamable HTTP: a fresh server+transport pair per request.
|
|
37
|
+
app.post('/mcp', async (c) => {
|
|
38
|
+
const scope = scopeForRequest(c);
|
|
39
|
+
if (!scope) {
|
|
40
|
+
return c.json({ jsonrpc: '2.0', error: { code: -32001, message: 'Unauthorized: pass an API key as a Bearer token' }, id: null }, 401);
|
|
41
|
+
}
|
|
42
|
+
let body;
|
|
43
|
+
try {
|
|
44
|
+
body = await c.req.json();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return c.json({ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error' }, id: null }, 400);
|
|
48
|
+
}
|
|
49
|
+
const server = buildMcpServer(service.withScope(scope));
|
|
50
|
+
const transport = new StreamableHTTPServerTransport({
|
|
51
|
+
sessionIdGenerator: undefined,
|
|
52
|
+
enableJsonResponse: true,
|
|
53
|
+
});
|
|
54
|
+
c.env.outgoing.on('close', () => {
|
|
55
|
+
void transport.close();
|
|
56
|
+
void server.close();
|
|
57
|
+
});
|
|
58
|
+
await server.connect(transport);
|
|
59
|
+
await transport.handleRequest(c.env.incoming, c.env.outgoing, body);
|
|
60
|
+
return RESPONSE_ALREADY_SENT;
|
|
61
|
+
});
|
|
62
|
+
const methodNotAllowed = (c) => c.json({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed: this server runs in stateless mode' }, id: null }, 405);
|
|
63
|
+
app.get('/mcp', methodNotAllowed);
|
|
64
|
+
app.delete('/mcp', methodNotAllowed);
|
|
65
|
+
// Server-hosted OAuth flow (for remote deployments; the CLI uses a loopback flow instead).
|
|
66
|
+
app.get('/auth/google', (c) => {
|
|
67
|
+
if (!adminAuthorized(c, true)) {
|
|
68
|
+
return c.text('Unauthorized: append ?key=<an admin API key>', 401);
|
|
69
|
+
}
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
for (const [state, expiry] of oauthStates) {
|
|
72
|
+
if (expiry < now)
|
|
73
|
+
oauthStates.delete(state);
|
|
74
|
+
}
|
|
75
|
+
const state = randomBytes(16).toString('hex');
|
|
76
|
+
oauthStates.set(state, now + OAUTH_STATE_TTL_MS);
|
|
77
|
+
const client = createOAuthClient(config, `${config.baseUrl}/auth/google/callback`);
|
|
78
|
+
return c.redirect(buildAuthUrl(client, state));
|
|
79
|
+
});
|
|
80
|
+
app.get('/auth/google/callback', async (c) => {
|
|
81
|
+
const state = c.req.query('state');
|
|
82
|
+
const expiry = state ? oauthStates.get(state) : undefined;
|
|
83
|
+
if (!state || !expiry || expiry < Date.now()) {
|
|
84
|
+
return c.text('Invalid or expired OAuth state. Restart from /auth/google.', 400);
|
|
85
|
+
}
|
|
86
|
+
oauthStates.delete(state);
|
|
87
|
+
const error = c.req.query('error');
|
|
88
|
+
if (error)
|
|
89
|
+
return c.text(`Google returned an error: ${error}`, 400);
|
|
90
|
+
const code = c.req.query('code');
|
|
91
|
+
if (!code)
|
|
92
|
+
return c.text('Missing code parameter.', 400);
|
|
93
|
+
const client = createOAuthClient(config, `${config.baseUrl}/auth/google/callback`);
|
|
94
|
+
try {
|
|
95
|
+
const { email, displayName, tokens } = await exchangeCode(client, code);
|
|
96
|
+
const account = registry.addGmailAccount(email, tokens, displayName);
|
|
97
|
+
return c.html(`<html><body style="font-family: sans-serif"><h2>${email} is connected to Fluxmail</h2>` +
|
|
98
|
+
`<p>Account id: <code>${account.id}</code>. You can close this tab.</p></body></html>`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// Surface why connecting failed (expired code, missing refresh token,
|
|
102
|
+
// account limit) instead of a blank 500 the user cannot act on.
|
|
103
|
+
if (isEmailError(err))
|
|
104
|
+
return c.text(`Could not connect the account: ${err.message}`, 400);
|
|
105
|
+
return c.text('Could not connect the account; check the server logs.', 500);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return app;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/http/app.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { createContext, type AppContext } from './context.js';
|
|
2
|
+
export { createApp } from './http/app.js';
|
|
3
|
+
export { buildMcpServer } from './mcp/buildServer.js';
|
|
4
|
+
export { EmailService, buildForwardBody, type SendInput, type ForwardInput } from './service/emailService.js';
|
|
5
|
+
export { AccountRegistry } from './accounts/registry.js';
|
|
6
|
+
export { loadConfig, loadDotEnv, resolveDataDir, configFilePath, maskStoredConfigValue, readStoredConfig, setStoredConfig, unsetStoredConfig, type FluxmailConfig, } from './config.js';
|
|
7
|
+
export { openDb } from './storage/db.js';
|
|
8
|
+
export { encryptString, decryptString } from './storage/crypto.js';
|
|
9
|
+
export { getEntitlements, checkLicenseState, assertAccountLimit, assertMemberLimit, assertWithinQuota, readLeaseRow, saveLeaseToken, clearLease, PERSONAL_TIER, GRACE_PERIOD_MS, type Entitlements, type LicenseState, } from './licensing/entitlements.js';
|
|
10
|
+
export { addMember, getMember, findMember, listMembers, removeMember, type MemberInfo, } from './storage/members.js';
|
|
11
|
+
export { verifyLease, licensePublicKeys, PINNED_LICENSE_PUBLIC_KEYS, type LeasePayload, } from './licensing/lease.js';
|
|
12
|
+
export { validateLicense, releaseLicense, DEFAULT_LICENSE_SERVER_URL, LICENSE_KEY_PATTERN, type ValidateOutcome, } from './licensing/client.js';
|
|
13
|
+
export { refreshLicense, startLicenseRefresher, loadInstanceId, type RefreshResult, } from './licensing/refresher.js';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,0BAA0B,EAC1B,KAAK,YAAY,GAClB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,EACf,cAAc,EACd,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { createContext } from './context.js';
|
|
2
|
+
export { createApp } from './http/app.js';
|
|
3
|
+
export { buildMcpServer } from './mcp/buildServer.js';
|
|
4
|
+
export { EmailService, buildForwardBody } from './service/emailService.js';
|
|
5
|
+
export { AccountRegistry } from './accounts/registry.js';
|
|
6
|
+
export { loadConfig, loadDotEnv, resolveDataDir, configFilePath, maskStoredConfigValue, readStoredConfig, setStoredConfig, unsetStoredConfig, } from './config.js';
|
|
7
|
+
export { openDb } from './storage/db.js';
|
|
8
|
+
export { encryptString, decryptString } from './storage/crypto.js';
|
|
9
|
+
export { getEntitlements, checkLicenseState, assertAccountLimit, assertMemberLimit, assertWithinQuota, readLeaseRow, saveLeaseToken, clearLease, PERSONAL_TIER, GRACE_PERIOD_MS, } from './licensing/entitlements.js';
|
|
10
|
+
export { addMember, getMember, findMember, listMembers, removeMember, } from './storage/members.js';
|
|
11
|
+
export { verifyLease, licensePublicKeys, PINNED_LICENSE_PUBLIC_KEYS, } from './licensing/lease.js';
|
|
12
|
+
export { validateLicense, releaseLicense, DEFAULT_LICENSE_SERVER_URL, LICENSE_KEY_PATTERN, } from './licensing/client.js';
|
|
13
|
+
export { refreshLicense, startLicenseRefresher, loadInstanceId, } from './licensing/refresher.js';
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAqC,MAAM,2BAA2B,CAAC;AAC9G,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EACL,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,GAElB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EACL,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,GAGhB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,WAAW,EACX,YAAY,GAEb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,0BAA0B,GAE3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,eAAe,EACf,cAAc,EACd,0BAA0B,EAC1B,mBAAmB,GAEpB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,cAAc,GAEf,MAAM,0BAA0B,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { FluxmailDb } from '../storage/db.js';
|
|
2
|
+
import { type RefreshOptions, type RefreshResult } from './refresher.js';
|
|
3
|
+
/** Validate a replacement key before allowing it to overwrite stored config. */
|
|
4
|
+
export declare function activateLicense(db: FluxmailDb, opts: RefreshOptions): Promise<RefreshResult>;
|
|
5
|
+
//# sourceMappingURL=activation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../../src/licensing/activation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAkB,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEzF,gFAAgF;AAChF,wBAAsB,eAAe,CACnC,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,aAAa,CAAC,CAMxB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { setStoredConfig } from '../config.js';
|
|
2
|
+
import { refreshLicense } from './refresher.js';
|
|
3
|
+
/** Validate a replacement key before allowing it to overwrite stored config. */
|
|
4
|
+
export async function activateLicense(db, opts) {
|
|
5
|
+
const result = await refreshLicense(db, opts);
|
|
6
|
+
if (result.outcome === 'refreshed' || result.outcome === 'outage') {
|
|
7
|
+
setStoredConfig(opts.dataDir, 'FLUXMAIL_LICENSE_KEY', opts.licenseKey);
|
|
8
|
+
}
|
|
9
|
+
return result;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=activation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activation.js","sourceRoot":"","sources":["../../src/licensing/activation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,OAAO,EAAE,cAAc,EAA2C,MAAM,gBAAgB,CAAC;AAEzF,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,EAAc,EACd,IAAoB;IAEpB,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAClE,eAAe,CAAC,IAAI,CAAC,OAAO,EAAE,sBAAsB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the hosted license server's validate and deactivate
|
|
3
|
+
* endpoints. The contract of record is docs/license-validation-contract.md in
|
|
4
|
+
* this repo, implemented by the private license-server repo; request/response
|
|
5
|
+
* shapes here must match it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_LICENSE_SERVER_URL = "https://fluxmail.ai";
|
|
8
|
+
export declare const LICENSE_KEY_PATTERN: RegExp;
|
|
9
|
+
export type ValidateOutcome =
|
|
10
|
+
/** 200: `lease` is the signed token; verify it before trusting anything in it. */
|
|
11
|
+
{
|
|
12
|
+
kind: 'valid';
|
|
13
|
+
lease: string;
|
|
14
|
+
}
|
|
15
|
+
/** 400: the server considered the request malformed — a config error to surface. */
|
|
16
|
+
| {
|
|
17
|
+
kind: 'invalid_request';
|
|
18
|
+
}
|
|
19
|
+
/** 404: key doesn't exist (typo or deleted). Keep any cached lease until it expires. */
|
|
20
|
+
| {
|
|
21
|
+
kind: 'license_not_found';
|
|
22
|
+
}
|
|
23
|
+
/** 403: subscription ended ("canceled" | "revoked" | "expired"). Degrade when the cached lease runs out. */
|
|
24
|
+
| {
|
|
25
|
+
kind: 'license_inactive';
|
|
26
|
+
status: string;
|
|
27
|
+
}
|
|
28
|
+
/** 409: the license is bound to a different instance; deactivate it there first. */
|
|
29
|
+
| {
|
|
30
|
+
kind: 'license_in_use';
|
|
31
|
+
}
|
|
32
|
+
/** 5xx, network failure, or an unintelligible response: keep the cached lease and retry later. */
|
|
33
|
+
| {
|
|
34
|
+
kind: 'outage';
|
|
35
|
+
detail: string;
|
|
36
|
+
};
|
|
37
|
+
export interface ValidateOptions {
|
|
38
|
+
serverUrl: string;
|
|
39
|
+
licenseKey: string;
|
|
40
|
+
/** Stable random id per install; the server binds each license to one instance. */
|
|
41
|
+
instanceId: string;
|
|
42
|
+
fetchImpl?: typeof fetch;
|
|
43
|
+
timeoutMs?: number;
|
|
44
|
+
}
|
|
45
|
+
export declare function validateLicense(opts: ValidateOptions): Promise<ValidateOutcome>;
|
|
46
|
+
/**
|
|
47
|
+
* Best-effort release of this instance's license binding, so the key can be
|
|
48
|
+
* activated elsewhere. Returns false when the server could not be reached or
|
|
49
|
+
* declined; callers proceed with local deactivation either way.
|
|
50
|
+
*/
|
|
51
|
+
export declare function releaseLicense(opts: ValidateOptions): Promise<boolean>;
|
|
52
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/licensing/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,0BAA0B,wBAAwB,CAAC;AAEhE,eAAO,MAAM,mBAAmB,QAAgC,CAAC;AAEjE,MAAM,MAAM,eAAe;AACzB,kFAAkF;AAChF;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE;AAClC,oFAAoF;GAClF;IAAE,IAAI,EAAE,iBAAiB,CAAA;CAAE;AAC7B,wFAAwF;GACtF;IAAE,IAAI,EAAE,mBAAmB,CAAA;CAAE;AAC/B,4GAA4G;GAC1G;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC9C,oFAAoF;GAClF;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE;AAC5B,kGAAkG;GAChG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,mFAAmF;IACnF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CA6CrF;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC,CAc5E"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP client for the hosted license server's validate and deactivate
|
|
3
|
+
* endpoints. The contract of record is docs/license-validation-contract.md in
|
|
4
|
+
* this repo, implemented by the private license-server repo; request/response
|
|
5
|
+
* shapes here must match it.
|
|
6
|
+
*/
|
|
7
|
+
export const DEFAULT_LICENSE_SERVER_URL = 'https://fluxmail.ai';
|
|
8
|
+
export const LICENSE_KEY_PATTERN = /^fluxmail_lic_[0-9a-f]{40}$/;
|
|
9
|
+
export async function validateLicense(opts) {
|
|
10
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
11
|
+
const url = `${opts.serverUrl.replace(/\/+$/, '')}/api/v1/licenses/validate`;
|
|
12
|
+
let response;
|
|
13
|
+
try {
|
|
14
|
+
response = await doFetch(url, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'content-type': 'application/json' },
|
|
17
|
+
body: JSON.stringify({ licenseKey: opts.licenseKey, instanceId: opts.instanceId }),
|
|
18
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 10_000),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return { kind: 'outage', detail: err instanceof Error ? err.message : String(err) };
|
|
23
|
+
}
|
|
24
|
+
let body;
|
|
25
|
+
try {
|
|
26
|
+
body = await response.json();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
body = undefined;
|
|
30
|
+
}
|
|
31
|
+
const field = (name) => typeof body === 'object' && body !== null ? body[name] : undefined;
|
|
32
|
+
switch (response.status) {
|
|
33
|
+
case 200: {
|
|
34
|
+
const lease = field('lease');
|
|
35
|
+
if (typeof lease !== 'string' || !lease) {
|
|
36
|
+
return { kind: 'outage', detail: 'validation response is missing the lease token' };
|
|
37
|
+
}
|
|
38
|
+
return { kind: 'valid', lease };
|
|
39
|
+
}
|
|
40
|
+
case 400:
|
|
41
|
+
return { kind: 'invalid_request' };
|
|
42
|
+
case 404:
|
|
43
|
+
return { kind: 'license_not_found' };
|
|
44
|
+
case 403: {
|
|
45
|
+
const status = field('status');
|
|
46
|
+
return { kind: 'license_inactive', status: typeof status === 'string' ? status : 'inactive' };
|
|
47
|
+
}
|
|
48
|
+
case 409:
|
|
49
|
+
return { kind: 'license_in_use' };
|
|
50
|
+
default:
|
|
51
|
+
return { kind: 'outage', detail: `license server returned HTTP ${response.status}` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Best-effort release of this instance's license binding, so the key can be
|
|
56
|
+
* activated elsewhere. Returns false when the server could not be reached or
|
|
57
|
+
* declined; callers proceed with local deactivation either way.
|
|
58
|
+
*/
|
|
59
|
+
export async function releaseLicense(opts) {
|
|
60
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
61
|
+
const url = `${opts.serverUrl.replace(/\/+$/, '')}/api/v1/licenses/deactivate`;
|
|
62
|
+
try {
|
|
63
|
+
const response = await doFetch(url, {
|
|
64
|
+
method: 'POST',
|
|
65
|
+
headers: { 'content-type': 'application/json' },
|
|
66
|
+
body: JSON.stringify({ licenseKey: opts.licenseKey, instanceId: opts.instanceId }),
|
|
67
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 10_000),
|
|
68
|
+
});
|
|
69
|
+
return response.ok;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/licensing/client.ts"],"names":[],"mappings":"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"}
|