@rndmjoker/proton-mcp 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +405 -0
  3. package/dist/activity.d.ts +49 -0
  4. package/dist/activity.js +72 -0
  5. package/dist/activity.js.map +1 -0
  6. package/dist/bridge/certificate.d.ts +75 -0
  7. package/dist/bridge/certificate.js +176 -0
  8. package/dist/bridge/certificate.js.map +1 -0
  9. package/dist/bridge/connection.d.ts +99 -0
  10. package/dist/bridge/connection.js +244 -0
  11. package/dist/bridge/connection.js.map +1 -0
  12. package/dist/bridge/errors.d.ts +35 -0
  13. package/dist/bridge/errors.js +56 -0
  14. package/dist/bridge/errors.js.map +1 -0
  15. package/dist/config.d.ts +92 -0
  16. package/dist/config.js +142 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/credentials/availability.d.ts +62 -0
  19. package/dist/credentials/availability.js +107 -0
  20. package/dist/credentials/availability.js.map +1 -0
  21. package/dist/credentials/encrypted-file.d.ts +50 -0
  22. package/dist/credentials/encrypted-file.js +156 -0
  23. package/dist/credentials/encrypted-file.js.map +1 -0
  24. package/dist/credentials/keyring.d.ts +48 -0
  25. package/dist/credentials/keyring.js +97 -0
  26. package/dist/credentials/keyring.js.map +1 -0
  27. package/dist/credentials/plain-file.d.ts +24 -0
  28. package/dist/credentials/plain-file.js +78 -0
  29. package/dist/credentials/plain-file.js.map +1 -0
  30. package/dist/credentials/probe-keyring.d.ts +11 -0
  31. package/dist/credentials/probe-keyring.js +57 -0
  32. package/dist/credentials/probe-keyring.js.map +1 -0
  33. package/dist/credentials/session.d.ts +15 -0
  34. package/dist/credentials/session.js +21 -0
  35. package/dist/credentials/session.js.map +1 -0
  36. package/dist/credentials/store.d.ts +80 -0
  37. package/dist/credentials/store.js +94 -0
  38. package/dist/credentials/store.js.map +1 -0
  39. package/dist/in-flight.d.ts +33 -0
  40. package/dist/in-flight.js +78 -0
  41. package/dist/in-flight.js.map +1 -0
  42. package/dist/mail/actions.d.ts +116 -0
  43. package/dist/mail/actions.js +265 -0
  44. package/dist/mail/actions.js.map +1 -0
  45. package/dist/mail/attachments.d.ts +39 -0
  46. package/dist/mail/attachments.js +86 -0
  47. package/dist/mail/attachments.js.map +1 -0
  48. package/dist/mail/compose.d.ts +264 -0
  49. package/dist/mail/compose.js +395 -0
  50. package/dist/mail/compose.js.map +1 -0
  51. package/dist/mail/drafts.d.ts +138 -0
  52. package/dist/mail/drafts.js +449 -0
  53. package/dist/mail/drafts.js.map +1 -0
  54. package/dist/mail/ids.d.ts +78 -0
  55. package/dist/mail/ids.js +135 -0
  56. package/dist/mail/ids.js.map +1 -0
  57. package/dist/mail/labels.d.ts +61 -0
  58. package/dist/mail/labels.js +139 -0
  59. package/dist/mail/labels.js.map +1 -0
  60. package/dist/mail/markup.d.ts +91 -0
  61. package/dist/mail/markup.js +265 -0
  62. package/dist/mail/markup.js.map +1 -0
  63. package/dist/mail/messages.d.ts +147 -0
  64. package/dist/mail/messages.js +219 -0
  65. package/dist/mail/messages.js.map +1 -0
  66. package/dist/mail/quote.d.ts +65 -0
  67. package/dist/mail/quote.js +157 -0
  68. package/dist/mail/quote.js.map +1 -0
  69. package/dist/mail/search.d.ts +66 -0
  70. package/dist/mail/search.js +107 -0
  71. package/dist/mail/search.js.map +1 -0
  72. package/dist/mail/send.d.ts +61 -0
  73. package/dist/mail/send.js +132 -0
  74. package/dist/mail/send.js.map +1 -0
  75. package/dist/mime/parse.d.ts +110 -0
  76. package/dist/mime/parse.js +194 -0
  77. package/dist/mime/parse.js.map +1 -0
  78. package/dist/server.d.ts +13 -0
  79. package/dist/server.js +387 -0
  80. package/dist/server.js.map +1 -0
  81. package/dist/session.d.ts +86 -0
  82. package/dist/session.js +220 -0
  83. package/dist/session.js.map +1 -0
  84. package/dist/settings.d.ts +52 -0
  85. package/dist/settings.js +80 -0
  86. package/dist/settings.js.map +1 -0
  87. package/dist/tools/actions.d.ts +21 -0
  88. package/dist/tools/actions.js +133 -0
  89. package/dist/tools/actions.js.map +1 -0
  90. package/dist/tools/capabilities.d.ts +52 -0
  91. package/dist/tools/capabilities.js +96 -0
  92. package/dist/tools/capabilities.js.map +1 -0
  93. package/dist/tools/confirm.d.ts +84 -0
  94. package/dist/tools/confirm.js +212 -0
  95. package/dist/tools/confirm.js.map +1 -0
  96. package/dist/tools/drafts.d.ts +21 -0
  97. package/dist/tools/drafts.js +199 -0
  98. package/dist/tools/drafts.js.map +1 -0
  99. package/dist/tools/failures.d.ts +46 -0
  100. package/dist/tools/failures.js +157 -0
  101. package/dist/tools/failures.js.map +1 -0
  102. package/dist/tools/format.d.ts +55 -0
  103. package/dist/tools/format.js +220 -0
  104. package/dist/tools/format.js.map +1 -0
  105. package/dist/tools/guidance.d.ts +38 -0
  106. package/dist/tools/guidance.js +226 -0
  107. package/dist/tools/guidance.js.map +1 -0
  108. package/dist/tools/interface.d.ts +28 -0
  109. package/dist/tools/interface.js +63 -0
  110. package/dist/tools/interface.js.map +1 -0
  111. package/dist/tools/labels.d.ts +21 -0
  112. package/dist/tools/labels.js +92 -0
  113. package/dist/tools/labels.js.map +1 -0
  114. package/dist/tools/list-folders.d.ts +14 -0
  115. package/dist/tools/list-folders.js +70 -0
  116. package/dist/tools/list-folders.js.map +1 -0
  117. package/dist/tools/messages.d.ts +9 -0
  118. package/dist/tools/messages.js +221 -0
  119. package/dist/tools/messages.js.map +1 -0
  120. package/dist/tools/send.d.ts +29 -0
  121. package/dist/tools/send.js +236 -0
  122. package/dist/tools/send.js.map +1 -0
  123. package/dist/tools/sign-in-required.d.ts +94 -0
  124. package/dist/tools/sign-in-required.js +168 -0
  125. package/dist/tools/sign-in-required.js.map +1 -0
  126. package/dist/web/layout.d.ts +75 -0
  127. package/dist/web/layout.js +590 -0
  128. package/dist/web/layout.js.map +1 -0
  129. package/dist/web/pages.d.ts +79 -0
  130. package/dist/web/pages.js +284 -0
  131. package/dist/web/pages.js.map +1 -0
  132. package/dist/web/sections.d.ts +105 -0
  133. package/dist/web/sections.js +207 -0
  134. package/dist/web/sections.js.map +1 -0
  135. package/dist/web/security.d.ts +122 -0
  136. package/dist/web/security.js +180 -0
  137. package/dist/web/security.js.map +1 -0
  138. package/dist/web/server.d.ts +153 -0
  139. package/dist/web/server.js +622 -0
  140. package/dist/web/server.js.map +1 -0
  141. package/package.json +70 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * The pages that come before anybody is signed in.
3
+ *
4
+ * Rendered as strings rather than with a template engine: it keeps the
5
+ * dependency count at zero for something that handles credentials, and there are
6
+ * only a handful of pages.
7
+ *
8
+ * Everything that could come from outside goes through escapeHtml. The CSP would
9
+ * already stop injected script from doing anything, but not from making a mess of
10
+ * the page, and relying on a single layer is how holes happen.
11
+ *
12
+ * These three carry no navigation. There is nothing to navigate to yet, and a
13
+ * rail full of links that all lead back here would suggest otherwise. The
14
+ * sections that appear afterwards are in sections.ts, the frame they share in
15
+ * layout.ts.
16
+ */
17
+ import { layout, escapeHtml, errorBanner } from './layout.js';
18
+ import { EXPOSURE_LABELS, } from '../credentials/store.js';
19
+ export { escapeHtml, formatDuration } from './layout.js';
20
+ function exposureTag(exposure) {
21
+ return `<span class="tag exposure-${exposure}">${escapeHtml(EXPOSURE_LABELS[exposure])}</span>`;
22
+ }
23
+ /**
24
+ * One storage option, as a radio inside the sign-in form.
25
+ *
26
+ * It used to be a form of its own that posted the choice and re-rendered the
27
+ * page. That worked and read badly: picking one of four options reloaded
28
+ * everything, and a page that flashes on every click feels like something went
29
+ * wrong. Now the choice travels with the credentials in one submission, and
30
+ * nothing happens until Connect is pressed.
31
+ *
32
+ * The whole card is the label, so the click target is the card rather than a
33
+ * small circle. The radio itself stays a real radio: keyboard and screen readers
34
+ * get the ordinary control, and the styling is a ring around the card.
35
+ */
36
+ function storeOption(store, suggested, chosen) {
37
+ const classes = ['option'];
38
+ if (!store.available)
39
+ classes.push('unavailable');
40
+ const id = `store-${store.kind}`;
41
+ return `<label class="${classes.join(' ')}" for="${id}">
42
+ <input type="radio" name="store" id="${id}" value="${escapeHtml(store.kind)}"
43
+ ${chosen ? 'checked' : ''} ${store.available ? '' : 'disabled'}>
44
+ <div class="option-body">
45
+ <header>
46
+ <h3>${escapeHtml(store.title)}</h3>
47
+ ${exposureTag(store.exposure)}
48
+ <span class="tag">prompts: ${escapeHtml(store.prompts)}</span>
49
+ ${suggested ? '<span class="tag suggested">suggested</span>' : ''}
50
+ </header>
51
+ <p class="hint">${escapeHtml(store.summary)}</p>
52
+ <p class="pro">${escapeHtml(store.benefit)}</p>
53
+ <p class="con">${escapeHtml(store.cost)}</p>
54
+ <p class="who">${escapeHtml(store.bestFor)}</p>
55
+ ${store.reason ? `<p class="blocked">${escapeHtml(store.reason)}</p>` : ''}
56
+ </div>
57
+ </label>`;
58
+ }
59
+ /**
60
+ * The warning shown when the plain file is picked.
61
+ *
62
+ * The card already states the cost in one line, and one line is easy to read
63
+ * past when it sits between three other options. This one is in the way, which
64
+ * is the point: it is the only option that leaves a usable password where
65
+ * anything that can read your files can read it.
66
+ *
67
+ * It is not a refusal. The choice stays available and the warning closes, both
68
+ * because the trade is sometimes the right one and because nobody is served by
69
+ * a tool that decides for them. Shown and closed entirely in CSS, since there is
70
+ * no JavaScript here to open a dialog with.
71
+ */
72
+ function plainFileWarning() {
73
+ return `<div class="warning-layer">
74
+ <label for="warning-seen" class="warning-backdrop" aria-hidden="true"></label>
75
+ <div class="warning-box">
76
+ <h3>The password will be readable</h3>
77
+ <p>A plain file keeps your Bridge password as text. The file is created with mode
78
+ <code>600</code>, so only your user account can read it, and that is the whole of the
79
+ protection: <strong>anything running as you can read it too</strong>, including a program
80
+ you did not mean to run.</p>
81
+ <p class="hint">The other three options all avoid this. The encrypted file works everywhere
82
+ the plain one does and asks for a master password once per start.</p>
83
+ <label for="warning-seen" class="warning-close">I understand, keep this option</label>
84
+ </div>
85
+ </div>`;
86
+ }
87
+ /**
88
+ * The extra fields the encrypted file needs.
89
+ *
90
+ * Always in the document now, and shown by CSS when that option is selected.
91
+ * They used to be rendered only for that store, which meant selecting it had to
92
+ * reload the page to make them appear.
93
+ *
94
+ * Two fields rather than one: a mistyped master password that nothing anywhere
95
+ * can recover is worth one extra line of typing.
96
+ *
97
+ * They are not disabled while hidden, and that is deliberate. A hidden field
98
+ * submits its value, so a master password typed and then abandoned by switching
99
+ * options would still travel. The server ignores it for any other store, and
100
+ * ignoring it there is the one place that decision belongs.
101
+ */
102
+ function masterPasswordFields() {
103
+ return `<div class="master-fields">
104
+ <label for="master">Master password for the encrypted file</label>
105
+ <input id="master" name="master" type="password" autocomplete="new-password">
106
+ <p class="hint">Chosen by you, and asked for again every time this server starts. It is not stored
107
+ anywhere: <strong>if you forget it, the saved Bridge password is gone</strong> and you have to
108
+ fetch it from the Bridge again.</p>
109
+
110
+ <label for="masterRepeat">Repeat the master password</label>
111
+ <input id="masterRepeat" name="masterRepeat" type="password" autocomplete="new-password">
112
+ </div>`;
113
+ }
114
+ /**
115
+ * Where the Bridge is reached, folded away.
116
+ *
117
+ * Almost nobody needs it: the Bridge listens on 1143 and 1025 unless somebody
118
+ * changed that in the Bridge itself. But a wrong port looks exactly like a
119
+ * Bridge that is not running, and finding that out after signing in, in a
120
+ * different section, is a poor way to spend an evening. So it is here, prefilled
121
+ * with what the server will actually use, and closed.
122
+ *
123
+ * `<details>` rather than a scripted panel: it is the browser's own disclosure
124
+ * element and needs nothing this page cannot have.
125
+ *
126
+ * **The host is shown and not editable, and that is deliberate.** A wrong port
127
+ * means nothing works, which is obvious immediately. A wrong host means the
128
+ * Bridge password is handed to another machine at the next connection, which is
129
+ * obvious to nobody. Making it changeable is tracked in #19, where it will sit
130
+ * behind a key that never travels through a tool call, so that changing it stays
131
+ * a deliberate act by the person at the keyboard.
132
+ */
133
+ function advancedSettings(data) {
134
+ const portFields = data.portsLocked
135
+ ? `<p class="hint">Both ports come from the environment
136
+ (<code>BRIDGE_IMAP_PORT</code>, <code>BRIDGE_SMTP_PORT</code>), so they are not changed here.
137
+ An explicit setting stays explicit.</p>
138
+ <table>
139
+ <tr><th>IMAP port</th><td>${escapeHtml(String(data.bridgeImapPort))}</td></tr>
140
+ <tr><th>SMTP port</th><td>${escapeHtml(String(data.bridgeSmtpPort))}</td></tr>
141
+ </table>`
142
+ : `<label for="imapPort">IMAP port</label>
143
+ <input id="imapPort" name="imapPort" type="text" inputmode="numeric"
144
+ value="${escapeHtml(String(data.bridgeImapPort))}">
145
+
146
+ <label for="smtpPort">SMTP port</label>
147
+ <input id="smtpPort" name="smtpPort" type="text" inputmode="numeric"
148
+ value="${escapeHtml(String(data.bridgeSmtpPort))}">
149
+ <p class="hint">The defaults are what the Bridge uses unless you changed them in the Bridge
150
+ itself, where you also find the real ones under its settings. They are saved along with the
151
+ sign-in and can be changed later under Bridge.</p>`;
152
+ return `<details class="card advanced"${data.showAdvanced ? ' open' : ''}>
153
+ <summary>Advanced: where the Bridge is reached</summary>
154
+
155
+ <label for="bridgeHostShown">Address of the Bridge</label>
156
+ <input id="bridgeHostShown" type="text" value="${escapeHtml(data.bridgeHost)}" disabled>
157
+ <p class="hint">Set with <code>BRIDGE_HOST</code> and not editable here yet. A wrong port means
158
+ nothing works and you notice at once; a wrong address means your Bridge password goes to another
159
+ machine and nobody notices at all. Making it changeable in the browser is planned, behind a key
160
+ that no assistant can reach.</p>
161
+
162
+ ${portFields}
163
+ </details>`;
164
+ }
165
+ /**
166
+ * The sign-in form.
167
+ *
168
+ * One form for everything: the address, the password and where the password
169
+ * goes. Nothing is submitted until Connect is pressed, so choosing among the
170
+ * four options costs no round trip and the page does not move under the
171
+ * pointer.
172
+ *
173
+ * Two things appear and disappear with the selection, both in CSS: the master
174
+ * password fields for the encrypted file, and the warning for the plain file.
175
+ * See the stylesheet for how, and for what happens in a browser too old to do
176
+ * it.
177
+ */
178
+ export function loginPage(data) {
179
+ const effective = data.chosen ?? data.suggested;
180
+ const options = data.stores
181
+ .map((store) => storeOption(store, store.kind === data.suggested, store.kind === effective))
182
+ .join('\n');
183
+ return layout({
184
+ title: 'Sign in',
185
+ section: 'none',
186
+ token: data.token,
187
+ disclaimer: data.disclaimer,
188
+ body: `<h1>Connect to Proton Mail Bridge</h1>
189
+ <p class="lead">Enter the credentials the Bridge generated for your account. This page runs on your
190
+ own machine and the password is never sent anywhere else.</p>
191
+
192
+ ${errorBanner(data.error)}
193
+
194
+ <form method="post" action="/sign-in?token=${escapeHtml(data.token)}" class="sign-in">
195
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrf)}">
196
+ <input type="checkbox" id="warning-seen" class="offscreen">
197
+
198
+ <div class="card">
199
+ <label for="address">Proton address</label>
200
+ <input id="address" name="address" type="text" autocomplete="off" spellcheck="false"
201
+ value="${escapeHtml(data.address ?? '')}" placeholder="you@example.com">
202
+ <p class="hint">The address as it appears in the Bridge.</p>
203
+
204
+ <label for="password">Bridge password</label>
205
+ <input id="password" name="password" type="password" autocomplete="off">
206
+ <p class="hint"><strong>Not your Proton account password.</strong> The Bridge generates a separate
207
+ one per account. You find it in the Bridge application under the account, or by running
208
+ <code>protonmail-bridge --cli</code> and then <code>info</code>.</p>
209
+
210
+ ${masterPasswordFields()}
211
+ </div>
212
+
213
+ <h2>Where should the password be kept?</h2>
214
+ <p class="lead">${escapeHtml(data.suggestionReason)}</p>
215
+ ${options}
216
+
217
+ ${advancedSettings(data)}
218
+
219
+ <div class="card">
220
+ <button type="submit">Connect</button>
221
+ <p class="hint">Nothing is sent until you press this. The Bridge is asked whether the credentials
222
+ work before they are stored anywhere.</p>
223
+ </div>
224
+
225
+ ${plainFileWarning()}
226
+ </form>`,
227
+ });
228
+ }
229
+ /**
230
+ * Asked for when an encrypted credentials file exists but is not open yet.
231
+ *
232
+ * The counterpart to the sign-in form, and the reason the encrypted option is
233
+ * usable at all: after a restart the Bridge password is still on disk, so the
234
+ * only thing missing is the one password that decrypts it.
235
+ *
236
+ * It also offers the way out. Someone who forgot their master password cannot
237
+ * be left on a page that only accepts the thing they do not have.
238
+ */
239
+ export function unlockPage(data) {
240
+ return layout({
241
+ title: 'Unlock',
242
+ section: 'none',
243
+ token: data.token,
244
+ disclaimer: data.disclaimer,
245
+ body: `<h1>Unlock the stored credentials</h1>
246
+ <p class="lead">Your Bridge password is saved in an encrypted file. Enter the master password you
247
+ chose for it and the server can connect again.</p>
248
+
249
+ ${errorBanner(data.error)}
250
+
251
+ <div class="card">
252
+ <form method="post" action="/unlock?token=${escapeHtml(data.token)}">
253
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrf)}">
254
+ <label for="master">Master password</label>
255
+ <input id="master" name="master" type="password" autocomplete="current-password" autofocus>
256
+ <p class="hint">The file is at <code>${escapeHtml(data.path)}</code>. Nothing but this password
257
+ can open it, not even this server.</p>
258
+ <button type="submit">Unlock</button>
259
+ </form>
260
+ </div>
261
+
262
+ <div class="card">
263
+ <h2>Forgot the master password?</h2>
264
+ <p>Then the file cannot be opened, by anyone. Throwing it away is the only way forward. You will
265
+ have to look your Bridge password up in the Bridge again and sign in from scratch.</p>
266
+ <form method="post" action="/discard-encrypted?token=${escapeHtml(data.token)}">
267
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrfDiscard)}">
268
+ <button type="submit" class="danger">Delete the file and start over</button>
269
+ </form>
270
+ </div>`,
271
+ });
272
+ }
273
+ /** Shown when a request is rejected. Deliberately says little. */
274
+ export function rejectionPage() {
275
+ return layout({
276
+ title: 'Not available',
277
+ section: 'none',
278
+ body: `<h1>Not available</h1>
279
+ <p>This request was refused. Either the link is wrong or it has expired.</p>
280
+ <p class="hint">The address for this interface is printed by the server when it asks you to sign in,
281
+ and it changes every time the server restarts.</p>`,
282
+ });
283
+ }
284
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/web/pages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The sections shown once somebody is signed in.
3
+ *
4
+ * One page each, rather than one long page with headings. The split is not
5
+ * cosmetic: it decides what is on screen without being asked for. Mailbox names
6
+ * had that treatment already, for reasons written at mailboxSection, and the
7
+ * same reasoning applies more weakly to everything else here. A page is also a
8
+ * place to put a setting, and the settings that limit what the agent may do are
9
+ * arriving next.
10
+ *
11
+ * The counterpart is pages.ts, which holds what comes before signing in.
12
+ */
13
+ import { type DisclaimerState } from './layout.js';
14
+ import type { StoreKind } from '../credentials/store.js';
15
+ /** What every section needs to draw itself. */
16
+ interface Common {
17
+ token: string;
18
+ error?: string;
19
+ notice?: string;
20
+ /**
21
+ * Required rather than optional on purpose. Forgetting it would make the
22
+ * notice quietly disappear, and a legal statement is not something to lose by
23
+ * omission.
24
+ */
25
+ disclaimer: DisclaimerState;
26
+ }
27
+ export interface OverviewData extends Common {
28
+ connected: boolean;
29
+ address?: string;
30
+ storeTitle?: string;
31
+ readOnly: boolean;
32
+ webPort: number;
33
+ uptimeMs: number;
34
+ mailboxCount?: number;
35
+ messageCount?: number;
36
+ unseenCount?: number;
37
+ bridgeHost: string;
38
+ bridgeImapPort: number;
39
+ bridgeSmtpPort: number;
40
+ }
41
+ /**
42
+ * The first page, and the only one that answers "is this working".
43
+ *
44
+ * It carries state and no controls. Every button lives in the section it belongs
45
+ * to, so that a page somebody opens to check something cannot be the page where
46
+ * they change it by accident.
47
+ */
48
+ export declare function overviewSection(data: OverviewData): string;
49
+ export interface BridgeData extends Common {
50
+ csrfSettings: string;
51
+ csrfTest: string;
52
+ bridgeHost: string;
53
+ bridgeImapPort: number;
54
+ bridgeSmtpPort: number;
55
+ /** True when the port came from the environment and cannot be changed here. */
56
+ portsLocked: boolean;
57
+ certificate?: {
58
+ fingerprint: string;
59
+ subject: string;
60
+ firstSeen: string;
61
+ };
62
+ }
63
+ /** Ports, the pinned certificate, and the button that tries the Bridge. */
64
+ export declare function bridgeSection(data: BridgeData): string;
65
+ export interface CredentialsData extends Common {
66
+ csrfSignOut: string;
67
+ address?: string;
68
+ storeKind?: StoreKind;
69
+ storeTitle?: string;
70
+ }
71
+ /** Where the Bridge password is kept, and the way to take it back out. */
72
+ export declare function credentialsSection(data: CredentialsData): string;
73
+ export interface ActivityData extends Common {
74
+ /** Calls in progress, so a slow search is visibly a slow search. */
75
+ running: Array<{
76
+ tool: string;
77
+ forMs: number;
78
+ }>;
79
+ /** Finished calls, newest first. */
80
+ recent: Array<{
81
+ tool: string;
82
+ at: number;
83
+ durationMs: number;
84
+ outcome: 'ok' | 'failed';
85
+ }>;
86
+ }
87
+ /** What the server has been doing, in tool names and timings and nothing else. */
88
+ export declare function activitySection(data: ActivityData): string;
89
+ export interface MailboxData extends Common {
90
+ mailboxes: Array<{
91
+ path: string;
92
+ kind: 'system' | 'folder' | 'label';
93
+ selectable: boolean;
94
+ }>;
95
+ }
96
+ /**
97
+ * The folders and labels, on their own page.
98
+ *
99
+ * Its own page because of what these names are. They belong to the Proton
100
+ * account, not the address that signed in, so this is the full structure of the
101
+ * account even when the mail in it is invisible. Showing that as part of an
102
+ * answer nobody asked for would hand it over unasked.
103
+ */
104
+ export declare function mailboxSection(data: MailboxData): string;
105
+ export {};
@@ -0,0 +1,207 @@
1
+ /**
2
+ * The sections shown once somebody is signed in.
3
+ *
4
+ * One page each, rather than one long page with headings. The split is not
5
+ * cosmetic: it decides what is on screen without being asked for. Mailbox names
6
+ * had that treatment already, for reasons written at mailboxSection, and the
7
+ * same reasoning applies more weakly to everything else here. A page is also a
8
+ * place to put a setting, and the settings that limit what the agent may do are
9
+ * arriving next.
10
+ *
11
+ * The counterpart is pages.ts, which holds what comes before signing in.
12
+ */
13
+ import { layout, escapeHtml, formatDuration, errorBanner, noticeBanner, } from './layout.js';
14
+ /** A wall-clock time, local to whoever is looking at the page. */
15
+ function formatTime(at) {
16
+ return new Date(at).toLocaleTimeString();
17
+ }
18
+ /** A row of the definition tables that carry most of the state here. */
19
+ function row(key, value, raw = false) {
20
+ return `<tr><th>${escapeHtml(key)}</th><td>${raw ? value : escapeHtml(value)}</td></tr>`;
21
+ }
22
+ function frame(section, title, data, body) {
23
+ return layout({
24
+ title,
25
+ section,
26
+ token: data.token,
27
+ disclaimer: data.disclaimer,
28
+ body: `${errorBanner(data.error)}${noticeBanner(data.notice)}${body}`,
29
+ });
30
+ }
31
+ /**
32
+ * The first page, and the only one that answers "is this working".
33
+ *
34
+ * It carries state and no controls. Every button lives in the section it belongs
35
+ * to, so that a page somebody opens to check something cannot be the page where
36
+ * they change it by accident.
37
+ */
38
+ export function overviewSection(data) {
39
+ const connection = data.connected
40
+ ? '<span class="state good">connected</span>'
41
+ : '<span class="state bad">not connected</span>';
42
+ const rows = [
43
+ row('Connection', connection, true),
44
+ row('Address', data.address ?? 'not signed in'),
45
+ row('Credentials kept in', data.storeTitle ?? 'nowhere yet'),
46
+ row('Mode', data.readOnly ? 'read-only, nothing can be changed or sent' : 'read and write'),
47
+ row('Bridge IMAP', `${data.bridgeHost}:${data.bridgeImapPort}`),
48
+ row('Bridge SMTP', `${data.bridgeHost}:${data.bridgeSmtpPort}`),
49
+ row('This interface', `127.0.0.1:${data.webPort}`),
50
+ row('Server running for', formatDuration(data.uptimeMs)),
51
+ ];
52
+ const counters = [];
53
+ if (data.mailboxCount !== undefined)
54
+ counters.push(row('Mailboxes', String(data.mailboxCount)));
55
+ if (data.messageCount !== undefined) {
56
+ const unseen = data.unseenCount === undefined ? '' : ` (${data.unseenCount} unread)`;
57
+ counters.push(row('Messages in inbox', `${data.messageCount}${unseen}`));
58
+ }
59
+ return frame('overview', 'Overview', data, `<h1>Overview</h1>
60
+ <p class="lead">What this server currently knows. No message content is shown anywhere in this
61
+ interface: it is for configuration and state, not a mail client.</p>
62
+
63
+ <div class="card"><table>${rows.join('\n')}</table></div>
64
+ ${counters.length > 0 ? `<h2>Mailbox counts</h2>\n<div class="card"><table>${counters.join('\n')}</table></div>` : ''}`);
65
+ }
66
+ /** Ports, the pinned certificate, and the button that tries the Bridge. */
67
+ export function bridgeSection(data) {
68
+ const ports = data.portsLocked
69
+ ? `<p class="hint">The ports come from the environment (<code>BRIDGE_IMAP_PORT</code> or
70
+ <code>BRIDGE_SMTP_PORT</code>), so they are not changed here. An explicit setting stays
71
+ explicit.</p>`
72
+ : `<form method="post" action="/settings?token=${escapeHtml(data.token)}">
73
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrfSettings)}">
74
+ <label for="imapPort">IMAP port</label>
75
+ <input id="imapPort" name="imapPort" type="text" inputmode="numeric"
76
+ value="${escapeHtml(String(data.bridgeImapPort))}">
77
+ <label for="smtpPort">SMTP port</label>
78
+ <input id="smtpPort" name="smtpPort" type="text" inputmode="numeric"
79
+ value="${escapeHtml(String(data.bridgeSmtpPort))}">
80
+ <p class="hint">The Bridge lets you change these, so a wrong value here looks exactly like a Bridge
81
+ that is not running. You find the real ones in the Bridge under settings. The host is not
82
+ changeable here on purpose: it would be a field that sends your Bridge password to another
83
+ machine.</p>
84
+ <button type="submit">Save ports</button>
85
+ </form>`;
86
+ const certificate = data.certificate
87
+ ? `<div class="card">
88
+ <table>
89
+ ${row('Fingerprint (SHA-256)', `<code>${escapeHtml(data.certificate.fingerprint)}</code>`, true)}
90
+ ${row('Subject', data.certificate.subject)}
91
+ ${row('First seen', data.certificate.firstSeen.slice(0, 10))}
92
+ </table>
93
+ <p class="hint">The Bridge signs its own certificate, so it was recorded on first use and is checked
94
+ against that record every time. If it ever changes, the server refuses to connect rather than hand
95
+ your password to whatever is answering on that port.</p>
96
+ </div>`
97
+ : `<div class="card"><p class="hint">Nothing recorded yet. The certificate is read and pinned on the
98
+ first connection to the Bridge.</p></div>`;
99
+ return frame('bridge', 'Bridge', data, `<h1>Bridge</h1>
100
+ <p class="lead">Where the Proton Mail Bridge is reached, and the certificate this server holds it
101
+ to. The host stays at <code>${escapeHtml(data.bridgeHost)}</code> and cannot be changed here.</p>
102
+
103
+ <h2>Connection test</h2>
104
+ <div class="card">
105
+ <form method="post" action="/test-connection?token=${escapeHtml(data.token)}">
106
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrfTest)}">
107
+ <p>Checks that the Bridge answers on the ports below and accepts the stored credentials.</p>
108
+ <button type="submit">Test the connection</button>
109
+ </form>
110
+ </div>
111
+
112
+ <h2>Ports</h2>
113
+ <div class="card">${ports}</div>
114
+
115
+ <h2>Certificate</h2>
116
+ ${certificate}`);
117
+ }
118
+ /** Where the Bridge password is kept, and the way to take it back out. */
119
+ export function credentialsSection(data) {
120
+ return frame('credentials', 'Credentials', data, `<h1>Credentials</h1>
121
+ <p class="lead">The Bridge password this server signs in with, and where it is kept between
122
+ restarts.</p>
123
+
124
+ <div class="card"><table>
125
+ ${row('Signed in as', data.address ?? 'not signed in')}
126
+ ${row('Kept in', data.storeTitle ?? 'nowhere yet')}
127
+ </table></div>
128
+
129
+ <h2>Sign out</h2>
130
+ <div class="card">
131
+ <form method="post" action="/sign-out?token=${escapeHtml(data.token)}">
132
+ <input type="hidden" name="csrf" value="${escapeHtml(data.csrfSignOut)}">
133
+ <p>Removes the stored credentials from wherever they are kept, everywhere at once, and
134
+ disconnects.</p>
135
+ <button type="submit" class="danger">Sign out and forget credentials</button>
136
+ </form>
137
+ </div>`);
138
+ }
139
+ /** What the server has been doing, in tool names and timings and nothing else. */
140
+ export function activitySection(data) {
141
+ const running = data.running.length === 0
142
+ ? '<p class="hint">Nothing running right now.</p>'
143
+ : `<table>
144
+ <tr><th>In progress</th><th>Running for</th></tr>
145
+ ${data.running
146
+ .map((call) => `<tr><td><code>${escapeHtml(call.tool)}</code></td><td>${escapeHtml(formatDuration(call.forMs))}</td></tr>`)
147
+ .join('\n ')}
148
+ </table>
149
+ <p class="hint">A full-text search over a large mailbox takes a few seconds. This is how you tell
150
+ that apart from a server that has stopped answering.</p>`;
151
+ const recent = data.recent.length === 0
152
+ ? '<p class="hint">No tool has been called yet in this session.</p>'
153
+ : `<table>
154
+ <tr><th>Tool</th><th>When</th><th>Took</th><th>Result</th></tr>
155
+ ${data.recent
156
+ .map((call) => `<tr><td><code>${escapeHtml(call.tool)}</code></td><td>${escapeHtml(formatTime(call.at))}</td>` +
157
+ `<td>${escapeHtml(formatDuration(call.durationMs))}</td>` +
158
+ `<td>${call.outcome === 'ok' ? 'ok' : 'failed'}</td></tr>`)
159
+ .join('\n ')}
160
+ </table>`;
161
+ return frame('activity', 'Activity', data, `<h1>Activity</h1>
162
+ <p class="lead">Which tools were called and how long they took. This is the record of what an
163
+ assistant did with the connection.</p>
164
+
165
+ <h2>Right now</h2>
166
+ <div class="card">${running}</div>
167
+
168
+ <h2>Finished calls</h2>
169
+ <div class="card">
170
+ ${recent}
171
+ <p class="hint">Tool names and timings only. What was searched for, which message was read and which
172
+ mailbox was opened are deliberately not recorded: that would turn this page into a log of your mail
173
+ habits.</p>
174
+ </div>`);
175
+ }
176
+ /**
177
+ * The folders and labels, on their own page.
178
+ *
179
+ * Its own page because of what these names are. They belong to the Proton
180
+ * account, not the address that signed in, so this is the full structure of the
181
+ * account even when the mail in it is invisible. Showing that as part of an
182
+ * answer nobody asked for would hand it over unasked.
183
+ */
184
+ export function mailboxSection(data) {
185
+ const groups = [
186
+ ['system', 'System mailboxes'],
187
+ ['folder', 'Folders'],
188
+ ['label', 'Labels'],
189
+ ];
190
+ const sections = groups
191
+ .map(([kind, heading]) => {
192
+ const part = data.mailboxes.filter((m) => m.kind === kind && m.selectable);
193
+ if (part.length === 0)
194
+ return '';
195
+ return `<h2>${escapeHtml(heading)} (${part.length})</h2>
196
+ <div class="card"><table>
197
+ ${part.map((m) => `<tr><td><code>${escapeHtml(m.path)}</code></td></tr>`).join('\n')}
198
+ </table></div>`;
199
+ })
200
+ .filter(Boolean)
201
+ .join('\n');
202
+ return frame('mailboxes', 'Folders and labels', data, `<h1>Folders and labels</h1>
203
+ <p class="lead">Names only, never contents. Proton distinguishes folders, where a message lives in
204
+ exactly one, from labels, where it can carry any number.</p>
205
+ ${sections || '<div class="card"><p class="hint">No mailboxes were returned.</p></div>'}`);
206
+ }
207
+ //# sourceMappingURL=sections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../src/web/sections.ts"],"names":[],"mappings":"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"}