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
package/README.md
CHANGED
|
@@ -1,29 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fluxmail
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Fluxmail is a self-hosted MCP server that connects AI agents to Gmail and IMAP/SMTP mailboxes. It gives MCP clients a consistent set of tools to read, search, draft, send, and organize email.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
+
Fluxmail requires Node.js 20 or later.
|
|
8
|
+
|
|
7
9
|
```bash
|
|
8
10
|
npm install -g fluxmail
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Connect Gmail
|
|
14
|
+
|
|
15
|
+
Create a Google Cloud OAuth client with the Gmail API enabled, then save its credentials:
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
fluxmail
|
|
15
|
-
fluxmail
|
|
18
|
+
fluxmail config set GOOGLE_CLIENT_ID <your-client-id>.apps.googleusercontent.com
|
|
19
|
+
fluxmail config set GOOGLE_CLIENT_SECRET <your-client-secret>
|
|
20
|
+
fluxmail accounts add gmail
|
|
16
21
|
```
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
The last command opens Google's consent flow in your browser.
|
|
24
|
+
|
|
25
|
+
## Connect IMAP
|
|
19
26
|
|
|
20
27
|
```bash
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
fluxmail accounts add imap \
|
|
29
|
+
--email you@example.com \
|
|
30
|
+
--imap-host imap.example.com \
|
|
31
|
+
--smtp-host smtp.example.com
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
Fluxmail prompts for the password and stores it encrypted. Use `--imap-password-env <name>` for Docker or scripted setup.
|
|
35
|
+
|
|
36
|
+
## Connect an MCP client
|
|
37
|
+
|
|
38
|
+
Configure your client to launch `fluxmail stdio`. For example, with Claude Code:
|
|
26
39
|
|
|
27
40
|
```bash
|
|
28
|
-
|
|
41
|
+
claude mcp add fluxmail -- fluxmail stdio
|
|
29
42
|
```
|
|
43
|
+
|
|
44
|
+
Or with Codex CLI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
codex mcp add fluxmail -- fluxmail stdio
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Fluxmail also supports Streamable HTTP for Docker and remote deployments.
|
|
51
|
+
|
|
52
|
+
See the [setup guide](https://fluxmail.ai/docs/quickstart) for Google OAuth configuration, Docker deployment, supported MCP clients, environment variables, and the complete tool list.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
See the [repository](https://github.com/churichard/fluxmail-mcp) for license terms.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OAuth2Client, type Credentials } from 'google-auth-library';
|
|
2
|
+
import type { FluxmailConfig } from '../config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Full mail scope: the unified API includes permanent delete, which Gmail only
|
|
5
|
+
* allows with https://mail.google.com/ (gmail.modify cannot call messages.delete).
|
|
6
|
+
* Users bring their own OAuth app, so they are consenting to their own credentials.
|
|
7
|
+
*/
|
|
8
|
+
export declare const GMAIL_SCOPES: string[];
|
|
9
|
+
export declare function requireGoogleConfig(config: FluxmailConfig): {
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function createOAuthClient(config: FluxmailConfig, redirectUri: string): OAuth2Client;
|
|
14
|
+
export declare function buildAuthUrl(client: OAuth2Client, state: string): string;
|
|
15
|
+
/** Verify the OAuth id_token and extract the authenticated Google identity. */
|
|
16
|
+
export declare function identityFromIdToken(client: OAuth2Client, tokens: Credentials): Promise<{
|
|
17
|
+
email: string;
|
|
18
|
+
displayName?: string;
|
|
19
|
+
}>;
|
|
20
|
+
export interface OAuthResult {
|
|
21
|
+
email: string;
|
|
22
|
+
displayName?: string;
|
|
23
|
+
tokens: Credentials;
|
|
24
|
+
}
|
|
25
|
+
export declare function exchangeCode(client: OAuth2Client, code: string): Promise<OAuthResult>;
|
|
26
|
+
/**
|
|
27
|
+
* Loopback OAuth flow for the CLI: listens once on config.oauthPort, prints the
|
|
28
|
+
* consent URL, and resolves when Google redirects back with a code.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runLoopbackFlow<T = OAuthResult>(config: FluxmailConfig, onAuthUrl: (url: string) => void, onAuthorized?: (result: OAuthResult) => T | Promise<T>): Promise<T>;
|
|
31
|
+
//# sourceMappingURL=googleAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"googleAuth.d.ts","sourceRoot":"","sources":["../../src/accounts/googleAuth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD;;;;GAIG;AACH,eAAO,MAAM,YAAY,UAA6D,CAAC;AAEvF,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAStG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY,CAG3F;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxE;AAED,+EAA+E;AAC/E,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAYlD;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;CACrB;AAoCD,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAU3F;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,WAAW,EACnD,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,EAChC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACrD,OAAO,CAAC,CAAC,CAAC,CA4DZ"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
4
|
+
import { EmailError } from '@fluxmail/core';
|
|
5
|
+
/**
|
|
6
|
+
* Full mail scope: the unified API includes permanent delete, which Gmail only
|
|
7
|
+
* allows with https://mail.google.com/ (gmail.modify cannot call messages.delete).
|
|
8
|
+
* Users bring their own OAuth app, so they are consenting to their own credentials.
|
|
9
|
+
*/
|
|
10
|
+
export const GMAIL_SCOPES = ['https://mail.google.com/', 'openid', 'email', 'profile'];
|
|
11
|
+
export function requireGoogleConfig(config) {
|
|
12
|
+
if (!config.google) {
|
|
13
|
+
throw new EmailError('invalid_request', 'GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET are not set. Create OAuth credentials in Google Cloud ' +
|
|
14
|
+
'(see README "Google setup") and set both environment variables.');
|
|
15
|
+
}
|
|
16
|
+
return config.google;
|
|
17
|
+
}
|
|
18
|
+
export function createOAuthClient(config, redirectUri) {
|
|
19
|
+
const { clientId, clientSecret } = requireGoogleConfig(config);
|
|
20
|
+
return new OAuth2Client({ clientId, clientSecret, redirectUri });
|
|
21
|
+
}
|
|
22
|
+
export function buildAuthUrl(client, state) {
|
|
23
|
+
return client.generateAuthUrl({
|
|
24
|
+
access_type: 'offline',
|
|
25
|
+
prompt: 'consent',
|
|
26
|
+
scope: GMAIL_SCOPES,
|
|
27
|
+
state,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Verify the OAuth id_token and extract the authenticated Google identity. */
|
|
31
|
+
export async function identityFromIdToken(client, tokens) {
|
|
32
|
+
const idToken = tokens.id_token;
|
|
33
|
+
if (!idToken)
|
|
34
|
+
throw new EmailError('provider_unavailable', 'Google did not return an id_token');
|
|
35
|
+
if (!client._clientId)
|
|
36
|
+
throw new EmailError('provider_unavailable', 'Google OAuth client has no client id');
|
|
37
|
+
const ticket = await client.verifyIdToken({ idToken, audience: client._clientId });
|
|
38
|
+
const claims = ticket.getPayload();
|
|
39
|
+
if (!claims)
|
|
40
|
+
throw new EmailError('provider_unavailable', 'Google id_token has no claims');
|
|
41
|
+
if (!claims.email)
|
|
42
|
+
throw new EmailError('provider_unavailable', 'Google id_token has no email claim');
|
|
43
|
+
if (!claims.email_verified) {
|
|
44
|
+
throw new EmailError('provider_unavailable', 'Google did not verify the account email address');
|
|
45
|
+
}
|
|
46
|
+
return { email: claims.email, ...(claims.name ? { displayName: claims.name } : {}) };
|
|
47
|
+
}
|
|
48
|
+
function oauthListenerError(err, port) {
|
|
49
|
+
const code = err.code;
|
|
50
|
+
if (code !== 'EADDRINUSE')
|
|
51
|
+
return err;
|
|
52
|
+
return new Error(`OAuth callback port ${port} is already in use.\n\n` +
|
|
53
|
+
'If Fluxmail is already running with Docker Compose, connect the account inside the container:\n\n' +
|
|
54
|
+
' docker compose exec fluxmail fluxmail accounts add gmail\n\n' +
|
|
55
|
+
`Otherwise, stop the process using port ${port} and try again.`, { cause: err });
|
|
56
|
+
}
|
|
57
|
+
function escapeHtml(value) {
|
|
58
|
+
return value.replace(/[&<>"']/g, (char) => {
|
|
59
|
+
const entities = {
|
|
60
|
+
'&': '&',
|
|
61
|
+
'<': '<',
|
|
62
|
+
'>': '>',
|
|
63
|
+
'"': '"',
|
|
64
|
+
"'": ''',
|
|
65
|
+
};
|
|
66
|
+
return entities[char];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function callbackPage(title, message) {
|
|
70
|
+
return ('<html><body style="font-family: sans-serif">' +
|
|
71
|
+
`<h2>${escapeHtml(title)}</h2><p>${escapeHtml(message)}</p>` +
|
|
72
|
+
'<p>You can close this tab and return to the terminal.</p></body></html>');
|
|
73
|
+
}
|
|
74
|
+
export async function exchangeCode(client, code) {
|
|
75
|
+
const { tokens } = await client.getToken(code);
|
|
76
|
+
if (!tokens.refresh_token) {
|
|
77
|
+
throw new EmailError('invalid_request', "Google did not return a refresh token. Remove Fluxmail from the account's third-party access " +
|
|
78
|
+
'(myaccount.google.com/permissions) and try again.');
|
|
79
|
+
}
|
|
80
|
+
return { ...(await identityFromIdToken(client, tokens)), tokens };
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Loopback OAuth flow for the CLI: listens once on config.oauthPort, prints the
|
|
84
|
+
* consent URL, and resolves when Google redirects back with a code.
|
|
85
|
+
*/
|
|
86
|
+
export async function runLoopbackFlow(config, onAuthUrl, onAuthorized) {
|
|
87
|
+
const redirectUri = `http://localhost:${config.oauthPort}/oauth/callback`;
|
|
88
|
+
const client = createOAuthClient(config, redirectUri);
|
|
89
|
+
const state = randomBytes(16).toString('hex');
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
const server = createServer(async (req, res) => {
|
|
92
|
+
// Close the listener and destroy lingering keep-alive sockets (the browser
|
|
93
|
+
// holds its connection open after loading the page); otherwise the CLI
|
|
94
|
+
// process never exits.
|
|
95
|
+
const finish = (settle) => {
|
|
96
|
+
res.once('close', () => {
|
|
97
|
+
server.close();
|
|
98
|
+
server.closeAllConnections();
|
|
99
|
+
settle();
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
const url = new URL(req.url ?? '/', redirectUri);
|
|
104
|
+
if (url.pathname !== '/oauth/callback') {
|
|
105
|
+
res.writeHead(404).end('Not found');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (url.searchParams.get('state') !== state) {
|
|
109
|
+
res.writeHead(400).end('State mismatch. Restart the flow.');
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const error = url.searchParams.get('error');
|
|
113
|
+
if (error) {
|
|
114
|
+
finish(() => reject(new EmailError('invalid_request', `Google OAuth error: ${error}`)));
|
|
115
|
+
res.writeHead(400).end(`Google returned an error: ${error}. You can close this tab.`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const code = url.searchParams.get('code');
|
|
119
|
+
if (!code) {
|
|
120
|
+
res.writeHead(400).end('Missing code parameter.');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const result = await exchangeCode(client, code);
|
|
124
|
+
const accepted = onAuthorized ? await onAuthorized(result) : result;
|
|
125
|
+
finish(() => resolve(accepted));
|
|
126
|
+
res
|
|
127
|
+
.writeHead(200, { 'content-type': 'text/html; charset=utf-8' })
|
|
128
|
+
.end(callbackPage(`${result.email} is connected to Fluxmail`, 'The account is ready to use.'));
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
finish(() => reject(err));
|
|
132
|
+
const expected = err instanceof EmailError;
|
|
133
|
+
const message = expected
|
|
134
|
+
? err.message
|
|
135
|
+
: 'Fluxmail could not finish connecting this account. Check the terminal for details.';
|
|
136
|
+
res
|
|
137
|
+
.writeHead(expected ? 400 : 500, { 'content-type': 'text/html; charset=utf-8' })
|
|
138
|
+
.end(callbackPage('Fluxmail could not connect this account', message));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
server.on('error', (err) => reject(oauthListenerError(err, config.oauthPort)));
|
|
142
|
+
server.listen(config.oauthPort, config.oauthHost, () => {
|
|
143
|
+
onAuthUrl(buildAuthUrl(client, state));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=googleAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"googleAuth.js","sourceRoot":"","sources":["../../src/accounts/googleAuth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type Credentials } from 'google-auth-library';
|
|
2
|
+
import { type Account, type EmailProvider } from '@fluxmail/core';
|
|
3
|
+
import { type ImapCredentials } from '@fluxmail/provider-imap';
|
|
4
|
+
import type { FluxmailConfig } from '../config.js';
|
|
5
|
+
import { type FluxmailDb } from '../storage/db.js';
|
|
6
|
+
export declare class AccountRegistry {
|
|
7
|
+
private readonly db;
|
|
8
|
+
private readonly config;
|
|
9
|
+
private readonly providers;
|
|
10
|
+
constructor(db: FluxmailDb, config: FluxmailConfig);
|
|
11
|
+
private evictProvider;
|
|
12
|
+
listAccounts(): Account[];
|
|
13
|
+
getAccount(accountId: string): Account;
|
|
14
|
+
/** Fail before OAuth when a new account would exceed the current plan. */
|
|
15
|
+
assertCanAddAccount(): void;
|
|
16
|
+
/** Resolve an account id, defaulting to the sole account when only one exists. */
|
|
17
|
+
resolveAccountId(accountId?: string): string;
|
|
18
|
+
getProvider(accountId: string): EmailProvider;
|
|
19
|
+
private loadCredentialRow;
|
|
20
|
+
private loadTokens;
|
|
21
|
+
private writeCredentials;
|
|
22
|
+
private saveTokens;
|
|
23
|
+
private writeTokens;
|
|
24
|
+
private buildGmailProvider;
|
|
25
|
+
addGmailAccount(email: string, tokens: Credentials, displayName?: string, memberId?: string): Account;
|
|
26
|
+
testImapCredentials(email: string, credentials: ImapCredentials, displayName?: string): Promise<Array<{
|
|
27
|
+
message: string;
|
|
28
|
+
}>>;
|
|
29
|
+
addImapAccount(email: string, credentials: ImapCredentials, displayName?: string, memberId?: string, reauthorizeId?: string): Account;
|
|
30
|
+
loadImapCredentials(accountId: string): ImapCredentials;
|
|
31
|
+
saveImapCredentials(accountId: string, credentials: ImapCredentials): void;
|
|
32
|
+
/** Assign a mailbox to a member, or make it shared again (memberId = null). */
|
|
33
|
+
assignAccountMember(accountId: string, memberId: string | null): Account;
|
|
34
|
+
removeAccount(accountId: string): void;
|
|
35
|
+
markStatus(accountId: string, status: Account['status']): void;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/accounts/registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAc,KAAK,OAAO,EAAE,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAE7F,OAAO,EAAmC,KAAK,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAA6C,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAO9F,qBAAa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,EAAE;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgF;gBAGvF,EAAE,EAAE,UAAU,EACd,MAAM,EAAE,cAAc;IAGzC,OAAO,CAAC,aAAa;IAMrB,YAAY,IAAI,OAAO,EAAE;IAgBzB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAMtC,0EAA0E;IAC1E,mBAAmB,IAAI,IAAI;IAc3B,kFAAkF;IAClF,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAoB5C,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa;IAyB7C,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,UAAU;IAKlB,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,kBAAkB;IAyB1B,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAuC/F,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,eAAe,EAC5B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAgBtC,cAAc,CACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,eAAe,EAC5B,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,CAAC,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO;IAwCV,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe;IAQvD,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,GAAG,IAAI;IAM1E,+EAA+E;IAC/E,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO;IAOxE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAMtC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,IAAI;CAG/D"}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { eq } from 'drizzle-orm';
|
|
3
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
4
|
+
import { EmailError } from '@fluxmail/core';
|
|
5
|
+
import { GmailProvider, GMAIL_CAPABILITIES } from '@fluxmail/provider-gmail';
|
|
6
|
+
import { ImapProvider, IMAP_CAPABILITIES } from '@fluxmail/provider-imap';
|
|
7
|
+
import { accountCredentials, accounts, oauthTokens } from '../storage/db.js';
|
|
8
|
+
import { decryptString, encryptString } from '../storage/crypto.js';
|
|
9
|
+
import { assertAccountLimit, getEntitlements } from '../licensing/entitlements.js';
|
|
10
|
+
import { getMember } from '../storage/members.js';
|
|
11
|
+
import { requireGoogleConfig } from './googleAuth.js';
|
|
12
|
+
import { SqliteImapStateStore } from '../storage/imapState.js';
|
|
13
|
+
export class AccountRegistry {
|
|
14
|
+
db;
|
|
15
|
+
config;
|
|
16
|
+
providers = new Map();
|
|
17
|
+
constructor(db, config) {
|
|
18
|
+
this.db = db;
|
|
19
|
+
this.config = config;
|
|
20
|
+
}
|
|
21
|
+
evictProvider(accountId) {
|
|
22
|
+
const cached = this.providers.get(accountId)?.provider;
|
|
23
|
+
this.providers.delete(accountId);
|
|
24
|
+
void cached?.close?.();
|
|
25
|
+
}
|
|
26
|
+
listAccounts() {
|
|
27
|
+
return this.db
|
|
28
|
+
.select()
|
|
29
|
+
.from(accounts)
|
|
30
|
+
.all()
|
|
31
|
+
.map((row) => ({
|
|
32
|
+
id: row.id,
|
|
33
|
+
provider: row.provider,
|
|
34
|
+
email: row.email,
|
|
35
|
+
...(row.displayName ? { displayName: row.displayName } : {}),
|
|
36
|
+
status: row.status,
|
|
37
|
+
capabilities: row.provider === 'imap' ? IMAP_CAPABILITIES : GMAIL_CAPABILITIES,
|
|
38
|
+
...(row.memberId ? { memberId: row.memberId } : {}),
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
getAccount(accountId) {
|
|
42
|
+
const account = this.listAccounts().find((a) => a.id === accountId);
|
|
43
|
+
if (!account)
|
|
44
|
+
throw new EmailError('not_found', `No account with id "${accountId}"`);
|
|
45
|
+
return account;
|
|
46
|
+
}
|
|
47
|
+
/** Fail before OAuth when a new account would exceed the current plan. */
|
|
48
|
+
assertCanAddAccount() {
|
|
49
|
+
const existing = this.listAccounts();
|
|
50
|
+
try {
|
|
51
|
+
assertAccountLimit(existing.length, getEntitlements(this.db));
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (!(err instanceof EmailError))
|
|
55
|
+
throw err;
|
|
56
|
+
const reconnect = existing.length === 1
|
|
57
|
+
? `To reconnect ${existing[0].email}, rerun this command with ` + `"--reauthorize ${existing[0].id}".`
|
|
58
|
+
: 'To reconnect an existing account, rerun this command with "--reauthorize <account-id>".';
|
|
59
|
+
throw new EmailError(err.code, `${err.message} ${reconnect}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Resolve an account id, defaulting to the sole account when only one exists. */
|
|
63
|
+
resolveAccountId(accountId) {
|
|
64
|
+
if (accountId)
|
|
65
|
+
return this.getAccount(accountId).id;
|
|
66
|
+
const all = this.listAccounts();
|
|
67
|
+
if (all.length === 0) {
|
|
68
|
+
throw new EmailError('invalid_request', 'No email accounts are connected yet. Run "fluxmail accounts add gmail" or "fluxmail accounts add imap".');
|
|
69
|
+
}
|
|
70
|
+
if (all.length > 1) {
|
|
71
|
+
throw new EmailError('invalid_request', `Multiple accounts are connected; specify accountId. Available: ${all
|
|
72
|
+
.map((a) => `${a.id} (${a.email})`)
|
|
73
|
+
.join(', ')}`);
|
|
74
|
+
}
|
|
75
|
+
return all[0].id;
|
|
76
|
+
}
|
|
77
|
+
getProvider(accountId) {
|
|
78
|
+
const account = this.getAccount(accountId);
|
|
79
|
+
const credentialRow = this.loadCredentialRow(accountId);
|
|
80
|
+
const cached = this.providers.get(accountId);
|
|
81
|
+
if (cached?.encryptedCredentials === credentialRow.encryptedCredentials)
|
|
82
|
+
return cached.provider;
|
|
83
|
+
const stored = JSON.parse(decryptString(this.config.encryptionKey, credentialRow.encryptedCredentials));
|
|
84
|
+
const provider = account.provider === 'gmail'
|
|
85
|
+
? this.buildGmailProvider(accountId, account.email, stored, account.displayName)
|
|
86
|
+
: account.provider === 'imap'
|
|
87
|
+
? new ImapProvider({
|
|
88
|
+
accountId,
|
|
89
|
+
email: account.email,
|
|
90
|
+
...(account.displayName ? { displayName: account.displayName } : {}),
|
|
91
|
+
credentials: stored,
|
|
92
|
+
store: new SqliteImapStateStore(this.db, accountId),
|
|
93
|
+
})
|
|
94
|
+
: undefined;
|
|
95
|
+
if (!provider)
|
|
96
|
+
throw new EmailError('unsupported_capability', `Provider "${account.provider}" is not supported yet`);
|
|
97
|
+
this.providers.set(accountId, { provider, encryptedCredentials: credentialRow.encryptedCredentials });
|
|
98
|
+
return provider;
|
|
99
|
+
}
|
|
100
|
+
loadCredentialRow(accountId) {
|
|
101
|
+
const row = this.db.select().from(accountCredentials).where(eq(accountCredentials.accountId, accountId)).get();
|
|
102
|
+
if (!row)
|
|
103
|
+
throw new EmailError('auth_expired', `No stored credentials for account ${accountId}`);
|
|
104
|
+
return row;
|
|
105
|
+
}
|
|
106
|
+
loadTokens(accountId) {
|
|
107
|
+
const row = this.loadCredentialRow(accountId);
|
|
108
|
+
return JSON.parse(decryptString(this.config.encryptionKey, row.encryptedCredentials));
|
|
109
|
+
}
|
|
110
|
+
writeCredentials(db, accountId, credentials) {
|
|
111
|
+
const encrypted = encryptString(this.config.encryptionKey, JSON.stringify(credentials));
|
|
112
|
+
db.insert(accountCredentials)
|
|
113
|
+
.values({ accountId, encryptedCredentials: encrypted, updatedAt: Date.now() })
|
|
114
|
+
.onConflictDoUpdate({
|
|
115
|
+
target: accountCredentials.accountId,
|
|
116
|
+
set: { encryptedCredentials: encrypted, updatedAt: Date.now() },
|
|
117
|
+
})
|
|
118
|
+
.run();
|
|
119
|
+
return encrypted;
|
|
120
|
+
}
|
|
121
|
+
saveTokens(accountId, tokens) {
|
|
122
|
+
return this.writeTokens(this.db, accountId, tokens);
|
|
123
|
+
}
|
|
124
|
+
writeTokens(db, accountId, tokens) {
|
|
125
|
+
const encrypted = this.writeCredentials(db, accountId, tokens);
|
|
126
|
+
const updatedAt = Date.now();
|
|
127
|
+
db.insert(oauthTokens)
|
|
128
|
+
.values({ accountId, encryptedTokens: encrypted, updatedAt })
|
|
129
|
+
.onConflictDoUpdate({
|
|
130
|
+
target: oauthTokens.accountId,
|
|
131
|
+
set: { encryptedTokens: encrypted, updatedAt },
|
|
132
|
+
})
|
|
133
|
+
.run();
|
|
134
|
+
return encrypted;
|
|
135
|
+
}
|
|
136
|
+
buildGmailProvider(accountId, email, stored, displayName) {
|
|
137
|
+
const { clientId, clientSecret } = requireGoogleConfig(this.config);
|
|
138
|
+
const auth = new OAuth2Client({ clientId, clientSecret });
|
|
139
|
+
auth.setCredentials(stored);
|
|
140
|
+
const provider = new GmailProvider({
|
|
141
|
+
accountId,
|
|
142
|
+
email,
|
|
143
|
+
...(displayName ? { displayName } : {}),
|
|
144
|
+
auth,
|
|
145
|
+
});
|
|
146
|
+
// google-auth-library refreshes access tokens transparently; persist them so
|
|
147
|
+
// restarts don't need a refresh round-trip (and rotated refresh tokens survive).
|
|
148
|
+
auth.on('tokens', (fresh) => {
|
|
149
|
+
const encryptedCredentials = this.saveTokens(accountId, { ...this.loadTokens(accountId), ...fresh });
|
|
150
|
+
const cached = this.providers.get(accountId);
|
|
151
|
+
if (cached?.provider === provider)
|
|
152
|
+
cached.encryptedCredentials = encryptedCredentials;
|
|
153
|
+
});
|
|
154
|
+
return provider;
|
|
155
|
+
}
|
|
156
|
+
addGmailAccount(email, tokens, displayName, memberId) {
|
|
157
|
+
// Validate up front for a clean not_found instead of a FK constraint error.
|
|
158
|
+
if (memberId)
|
|
159
|
+
getMember(this.db, memberId);
|
|
160
|
+
const existing = this.db.select().from(accounts).all();
|
|
161
|
+
const duplicate = existing.find((a) => a.provider === 'gmail' && a.email === email);
|
|
162
|
+
if (duplicate) {
|
|
163
|
+
// Re-authenticating an existing account: refresh tokens, clear error
|
|
164
|
+
// state. Ownership is untouched; reassign with assignAccountMember.
|
|
165
|
+
this.db.transaction((tx) => {
|
|
166
|
+
this.writeTokens(tx, duplicate.id, tokens);
|
|
167
|
+
tx.update(accounts)
|
|
168
|
+
.set({ status: 'active', ...(displayName ? { displayName } : {}) })
|
|
169
|
+
.where(eq(accounts.id, duplicate.id))
|
|
170
|
+
.run();
|
|
171
|
+
});
|
|
172
|
+
this.evictProvider(duplicate.id);
|
|
173
|
+
return this.getAccount(duplicate.id);
|
|
174
|
+
}
|
|
175
|
+
const id = `acct_${randomBytes(6).toString('hex')}`;
|
|
176
|
+
this.db.transaction((tx) => {
|
|
177
|
+
const accountCount = tx.select().from(accounts).all().length;
|
|
178
|
+
assertAccountLimit(accountCount, getEntitlements(tx));
|
|
179
|
+
tx.insert(accounts)
|
|
180
|
+
.values({
|
|
181
|
+
id,
|
|
182
|
+
provider: 'gmail',
|
|
183
|
+
email,
|
|
184
|
+
displayName: displayName ?? null,
|
|
185
|
+
status: 'active',
|
|
186
|
+
createdAt: Date.now(),
|
|
187
|
+
memberId: memberId ?? null,
|
|
188
|
+
})
|
|
189
|
+
.run();
|
|
190
|
+
this.writeTokens(tx, id, tokens);
|
|
191
|
+
});
|
|
192
|
+
return this.getAccount(id);
|
|
193
|
+
}
|
|
194
|
+
async testImapCredentials(email, credentials, displayName) {
|
|
195
|
+
const provider = new ImapProvider({
|
|
196
|
+
accountId: 'imap_setup',
|
|
197
|
+
email,
|
|
198
|
+
...(displayName ? { displayName } : {}),
|
|
199
|
+
credentials,
|
|
200
|
+
store: new SqliteImapStateStore(this.db, 'imap_setup'),
|
|
201
|
+
});
|
|
202
|
+
try {
|
|
203
|
+
await provider.testConnection();
|
|
204
|
+
return await provider.getFolderWarnings();
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await provider.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
addImapAccount(email, credentials, displayName, memberId, reauthorizeId) {
|
|
211
|
+
if (memberId)
|
|
212
|
+
getMember(this.db, memberId);
|
|
213
|
+
const existingRows = this.db.select().from(accounts).all();
|
|
214
|
+
const duplicate = reauthorizeId
|
|
215
|
+
? existingRows.find((row) => row.id === reauthorizeId)
|
|
216
|
+
: existingRows.find((row) => row.provider === 'imap' && row.email === email);
|
|
217
|
+
if (duplicate) {
|
|
218
|
+
if (duplicate.provider !== 'imap' || duplicate.email !== email) {
|
|
219
|
+
throw new EmailError('invalid_request', `Account ${duplicate.id} does not match IMAP mailbox ${email}.`);
|
|
220
|
+
}
|
|
221
|
+
this.db.transaction((tx) => {
|
|
222
|
+
this.writeCredentials(tx, duplicate.id, credentials);
|
|
223
|
+
tx.update(accounts)
|
|
224
|
+
.set({ status: 'active', ...(displayName ? { displayName } : {}) })
|
|
225
|
+
.where(eq(accounts.id, duplicate.id))
|
|
226
|
+
.run();
|
|
227
|
+
});
|
|
228
|
+
this.evictProvider(duplicate.id);
|
|
229
|
+
return this.getAccount(duplicate.id);
|
|
230
|
+
}
|
|
231
|
+
const id = `acct_${randomBytes(6).toString('hex')}`;
|
|
232
|
+
this.db.transaction((tx) => {
|
|
233
|
+
assertAccountLimit(tx.select().from(accounts).all().length, getEntitlements(tx));
|
|
234
|
+
tx.insert(accounts)
|
|
235
|
+
.values({
|
|
236
|
+
id,
|
|
237
|
+
provider: 'imap',
|
|
238
|
+
email,
|
|
239
|
+
displayName: displayName ?? null,
|
|
240
|
+
status: 'active',
|
|
241
|
+
createdAt: Date.now(),
|
|
242
|
+
memberId: memberId ?? null,
|
|
243
|
+
})
|
|
244
|
+
.run();
|
|
245
|
+
this.writeCredentials(tx, id, credentials);
|
|
246
|
+
});
|
|
247
|
+
return this.getAccount(id);
|
|
248
|
+
}
|
|
249
|
+
loadImapCredentials(accountId) {
|
|
250
|
+
const account = this.getAccount(accountId);
|
|
251
|
+
if (account.provider !== 'imap')
|
|
252
|
+
throw new EmailError('invalid_request', `Account ${accountId} is not an IMAP account.`);
|
|
253
|
+
const row = this.loadCredentialRow(accountId);
|
|
254
|
+
return JSON.parse(decryptString(this.config.encryptionKey, row.encryptedCredentials));
|
|
255
|
+
}
|
|
256
|
+
saveImapCredentials(accountId, credentials) {
|
|
257
|
+
this.loadImapCredentials(accountId);
|
|
258
|
+
this.writeCredentials(this.db, accountId, credentials);
|
|
259
|
+
this.evictProvider(accountId);
|
|
260
|
+
}
|
|
261
|
+
/** Assign a mailbox to a member, or make it shared again (memberId = null). */
|
|
262
|
+
assignAccountMember(accountId, memberId) {
|
|
263
|
+
this.getAccount(accountId);
|
|
264
|
+
if (memberId)
|
|
265
|
+
getMember(this.db, memberId);
|
|
266
|
+
this.db.update(accounts).set({ memberId }).where(eq(accounts.id, accountId)).run();
|
|
267
|
+
return this.getAccount(accountId);
|
|
268
|
+
}
|
|
269
|
+
removeAccount(accountId) {
|
|
270
|
+
this.getAccount(accountId);
|
|
271
|
+
this.db.delete(accounts).where(eq(accounts.id, accountId)).run();
|
|
272
|
+
this.evictProvider(accountId);
|
|
273
|
+
}
|
|
274
|
+
markStatus(accountId, status) {
|
|
275
|
+
this.db.update(accounts).set({ status }).where(eq(accounts.id, accountId)).run();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/accounts/registry.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|