@agenticmail/enterprise 0.5.293 → 0.5.295

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.
@@ -20,6 +20,16 @@ export function LoginPage({ onLogin }) {
20
20
  var [challengeToken, setChallengeToken] = useState('');
21
21
  var [totpCode, setTotpCode] = useState('');
22
22
 
23
+ // Forgot password state
24
+ var [forgotMode, setForgotMode] = useState(false); // show forgot password form
25
+ var [forgotEmail, setForgotEmail] = useState('');
26
+ var [forgotCode, setForgotCode] = useState('');
27
+ var [forgotNewPw, setForgotNewPw] = useState('');
28
+ var [forgotNewPw2, setForgotNewPw2] = useState('');
29
+ var [forgotStep, setForgotStep] = useState('email'); // 'email' | 'code' | 'no2fa' | 'done'
30
+ var [forgotLoading, setForgotLoading] = useState(false);
31
+ var [forgotError, setForgotError] = useState('');
32
+
23
33
  useEffect(function() {
24
34
  fetch('/auth/sso/providers').then(function(r) { return r.ok ? r.json() : null; }).then(function(d) {
25
35
  if (d && d.providers && d.providers.length > 0) setSsoProviders(d.providers);
@@ -59,6 +69,43 @@ export function LoginPage({ onLogin }) {
59
69
  setLoading(false);
60
70
  };
61
71
 
72
+ var submitForgotEmail = async function() {
73
+ setForgotLoading(true); setForgotError('');
74
+ try {
75
+ // Check if user has 2FA by attempting reset without code
76
+ var d = await authCall('/reset-password-self', { method: 'POST', body: JSON.stringify({ email: forgotEmail, newPassword: 'check__only__12', totpCode: '' }) });
77
+ if (d.has2fa) { setForgotStep('code'); }
78
+ else if (d.no2fa) { setForgotStep('no2fa'); }
79
+ else { setForgotStep('code'); }
80
+ } catch (err) {
81
+ var msg = err.message || '';
82
+ if (msg.indexOf('not enabled') >= 0 || msg.indexOf('administrator') >= 0) {
83
+ setForgotStep('no2fa');
84
+ } else {
85
+ setForgotStep('code');
86
+ }
87
+ }
88
+ setForgotLoading(false);
89
+ };
90
+
91
+ var submitForgotReset = async function() {
92
+ if (forgotNewPw !== forgotNewPw2) { setForgotError('Passwords do not match'); return; }
93
+ if (forgotNewPw.length < 8) { setForgotError('Password must be at least 8 characters'); return; }
94
+ setForgotLoading(true); setForgotError('');
95
+ try {
96
+ var d = await authCall('/reset-password-self', { method: 'POST', body: JSON.stringify({ email: forgotEmail, totpCode: forgotCode, newPassword: forgotNewPw }) });
97
+ if (d.ok) { setForgotStep('done'); }
98
+ else if (d.no2fa) { setForgotStep('no2fa'); setForgotError(d.error); }
99
+ else if (d.error) { setForgotError(d.error); }
100
+ } catch (err) { setForgotError(err.message); }
101
+ setForgotLoading(false);
102
+ };
103
+
104
+ var cancelForgot = function() {
105
+ setForgotMode(false); setForgotStep('email'); setForgotEmail(''); setForgotCode('');
106
+ setForgotNewPw(''); setForgotNewPw2(''); setForgotError('');
107
+ };
108
+
62
109
  var cancel2fa = function() {
63
110
  setNeeds2fa(false);
64
111
  setChallengeToken('');
@@ -110,6 +157,103 @@ export function LoginPage({ onLogin }) {
110
157
  );
111
158
  }
112
159
 
160
+ // ─── Forgot Password Screen ──────────────────────────
161
+
162
+ if (forgotMode) {
163
+ return h('div', { className: 'login-page', style: _brandBg ? { backgroundImage: 'url(' + _brandBg + ')', backgroundSize: 'cover', backgroundPosition: 'center' } : {} },
164
+ h('div', { className: 'login-card' },
165
+ h('div', { className: 'login-logo' },
166
+ h('img', { src: _brandLogo, alt: 'AgenticMail', style: { width: 48, height: 48, objectFit: 'contain' } }),
167
+ h('h1', null, 'Reset Password'),
168
+ h('p', null, forgotStep === 'email' ? 'Enter your email address' : forgotStep === 'code' ? 'Verify with your authenticator app' : forgotStep === 'done' ? 'Password updated' : 'Contact your administrator')
169
+ ),
170
+
171
+ // Step: enter email
172
+ forgotStep === 'email' && h('div', null,
173
+ h('div', { className: 'form-group' },
174
+ h('label', { className: 'form-label' }, 'Email Address'),
175
+ h('input', { className: 'input', type: 'email', value: forgotEmail, onChange: function(e) { setForgotEmail(e.target.value); }, placeholder: 'you@company.com', autoFocus: true })
176
+ ),
177
+ forgotError && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 12 } }, forgotError),
178
+ h('button', { className: 'btn btn-primary', onClick: submitForgotEmail, disabled: forgotLoading || !forgotEmail, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, forgotLoading ? 'Checking...' : 'Continue'),
179
+ h('div', { style: { textAlign: 'center', marginTop: 16 } },
180
+ h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: cancelForgot }, 'Back to login')
181
+ )
182
+ ),
183
+
184
+ // Step: enter 2FA code + new password
185
+ forgotStep === 'code' && h('div', null,
186
+ h('div', { style: { background: 'var(--info-soft, rgba(59,130,246,0.1))', borderRadius: 8, padding: 12, marginBottom: 16, fontSize: 12, color: 'var(--text-secondary)' } },
187
+ 'Enter the 6-digit code from your authenticator app (or a backup code) along with your new password.'
188
+ ),
189
+ h('div', { className: 'form-group' },
190
+ h('label', { className: 'form-label' }, '2FA Code'),
191
+ h('input', {
192
+ className: 'input', type: 'text', inputMode: 'numeric', autoComplete: 'one-time-code',
193
+ value: forgotCode, onChange: function(e) { setForgotCode(e.target.value.replace(/[^0-9A-Za-z]/g, '').slice(0, 8)); },
194
+ placeholder: '000000', autoFocus: true, maxLength: 8,
195
+ style: { textAlign: 'center', fontSize: 20, letterSpacing: '0.2em', fontFamily: 'var(--font-mono)' }
196
+ })
197
+ ),
198
+ h('div', { className: 'form-group' },
199
+ h('label', { className: 'form-label' }, 'New Password'),
200
+ h('input', { className: 'input', type: 'password', value: forgotNewPw, onChange: function(e) { setForgotNewPw(e.target.value); }, placeholder: 'Min 8 characters' })
201
+ ),
202
+ h('div', { className: 'form-group' },
203
+ h('label', { className: 'form-label' }, 'Confirm Password'),
204
+ h('input', { className: 'input', type: 'password', value: forgotNewPw2, onChange: function(e) { setForgotNewPw2(e.target.value); }, placeholder: 'Confirm new password' })
205
+ ),
206
+ forgotError && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 12 } }, forgotError),
207
+ h('button', { className: 'btn btn-primary', onClick: submitForgotReset, disabled: forgotLoading || !forgotCode || !forgotNewPw || !forgotNewPw2, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, forgotLoading ? 'Resetting...' : 'Reset Password'),
208
+ h('div', { style: { textAlign: 'center', marginTop: 16 } },
209
+ h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: cancelForgot }, 'Back to login')
210
+ )
211
+ ),
212
+
213
+ // Step: no 2FA — contact admin
214
+ forgotStep === 'no2fa' && h('div', null,
215
+ h('div', { style: { textAlign: 'center', padding: '12px 0' } },
216
+ h('div', { style: { width: 48, height: 48, borderRadius: '50%', background: 'var(--danger-soft, rgba(220,38,38,0.1))', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' } },
217
+ h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--danger, #dc2626)', strokeWidth: 2, strokeLinecap: 'round' },
218
+ h('path', { d: 'M12 9v4m0 4h.01M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z' })
219
+ )
220
+ ),
221
+ h('h3', { style: { fontSize: 16, fontWeight: 600, marginBottom: 8 } }, 'Cannot Reset Password'),
222
+ h('p', { style: { fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6, maxWidth: 320, margin: '0 auto' } },
223
+ 'Two-factor authentication is not enabled on this account. Without 2FA, you cannot reset your password yourself.'
224
+ ),
225
+ h('div', { style: { marginTop: 16, padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, fontSize: 13 } },
226
+ h('strong', null, 'What to do:'), h('br', null),
227
+ 'Contact your organization administrator and ask them to reset your password from the Users page.'
228
+ ),
229
+ h('div', { style: { marginTop: 16, padding: 10, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 8, fontSize: 12, color: 'var(--text-secondary)' } },
230
+ 'Tip: Once you regain access, enable 2FA immediately so you can reset your own password in the future.'
231
+ )
232
+ ),
233
+ h('div', { style: { textAlign: 'center', marginTop: 16 } },
234
+ h('button', { type: 'button', className: 'btn btn-primary', onClick: cancelForgot, style: { width: '100%', justifyContent: 'center' } }, 'Back to Login')
235
+ )
236
+ ),
237
+
238
+ // Step: done
239
+ forgotStep === 'done' && h('div', null,
240
+ h('div', { style: { textAlign: 'center', padding: '12px 0' } },
241
+ h('div', { style: { width: 48, height: 48, borderRadius: '50%', background: 'var(--success-soft, rgba(21,128,61,0.1))', display: 'flex', alignItems: 'center', justifyContent: 'center', margin: '0 auto 12px' } },
242
+ h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--success, #15803d)', strokeWidth: 2, strokeLinecap: 'round' },
243
+ h('path', { d: 'M20 6L9 17l-5-5' })
244
+ )
245
+ ),
246
+ h('h3', { style: { fontSize: 16, fontWeight: 600, marginBottom: 8 } }, 'Password Reset Successfully'),
247
+ h('p', { style: { fontSize: 13, color: 'var(--text-muted)' } }, 'You can now sign in with your new password.')
248
+ ),
249
+ h('div', { style: { textAlign: 'center', marginTop: 16 } },
250
+ h('button', { type: 'button', className: 'btn btn-primary', onClick: cancelForgot, style: { width: '100%', justifyContent: 'center' } }, 'Sign In')
251
+ )
252
+ )
253
+ )
254
+ );
255
+ }
256
+
113
257
  // ─── Main Login Screen ────────────────────────────────
114
258
 
115
259
  return h('div', { className: 'login-page', style: _brandBg ? { backgroundImage: 'url(' + _brandBg + ')', backgroundSize: 'cover', backgroundPosition: 'center' } : {} },
@@ -138,7 +282,10 @@ export function LoginPage({ onLogin }) {
138
282
  h('input', { className: 'input', type: 'password', value: password, onChange: function(e) { setPassword(e.target.value); }, placeholder: 'Enter password', required: true })
139
283
  ),
140
284
  error && h('div', { style: { color: 'var(--danger)', fontSize: 13, marginBottom: 16 } }, error),
141
- h('button', { className: 'btn btn-primary', type: 'submit', disabled: loading, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, loading ? 'Signing in...' : 'Sign In')
285
+ h('button', { className: 'btn btn-primary', type: 'submit', disabled: loading, style: { width: '100%', justifyContent: 'center', padding: '8px' } }, loading ? 'Signing in...' : 'Sign In'),
286
+ h('div', { style: { textAlign: 'center', marginTop: 12 } },
287
+ h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: function() { setForgotMode(true); setForgotEmail(email); setError(''); }, style: { fontSize: 12, color: 'var(--text-muted)' } }, 'Forgot Password?')
288
+ )
142
289
  ),
143
290
 
144
291
  // ── API Key Tab ─────────────────────────────────
@@ -216,7 +216,7 @@ export function UsersPage() {
216
216
  var { toast } = useApp();
217
217
  var [users, setUsers] = useState([]);
218
218
  var [creating, setCreating] = useState(false);
219
- var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer' });
219
+ var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*' });
220
220
  var [resetTarget, setResetTarget] = useState(null);
221
221
  var [newPassword, setNewPassword] = useState('');
222
222
  var [resetting, setResetting] = useState(false);
@@ -230,10 +230,22 @@ export function UsersPage() {
230
230
  apiCall('/page-registry').then(function(d) { setPageRegistry(d); }).catch(function() {});
231
231
  }, []);
232
232
 
233
+ var generateCreatePassword = function() {
234
+ var chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789!@#$%';
235
+ var pw = '';
236
+ for (var i = 0; i < 16; i++) pw += chars[Math.floor(Math.random() * chars.length)];
237
+ setForm(function(f) { return Object.assign({}, f, { password: pw }); });
238
+ };
239
+
240
+ var [showCreatePerms, setShowCreatePerms] = useState(false);
241
+
233
242
  var create = async function() {
234
243
  try {
235
- await apiCall('/users', { method: 'POST', body: JSON.stringify(form) });
236
- toast('User created', 'success'); setCreating(false); setForm({ email: '', password: '', name: '', role: 'viewer' }); load();
244
+ var body = { email: form.email, password: form.password, name: form.name, role: form.role };
245
+ if (form.permissions !== '*') body.permissions = form.permissions;
246
+ await apiCall('/users', { method: 'POST', body: JSON.stringify(body) });
247
+ toast('User created. They will be prompted to set a new password on first login.', 'success');
248
+ setCreating(false); setForm({ email: '', password: '', name: '', role: 'viewer', permissions: '*' }); setShowCreatePerms(false); load();
237
249
  } catch (e) { toast(e.message, 'error'); }
238
250
  };
239
251
 
@@ -324,13 +336,48 @@ export function UsersPage() {
324
336
  ),
325
337
 
326
338
  // Create user modal
327
- creating && h(Modal, { title: 'Add User', onClose: function() { setCreating(false); }, footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: function() { setCreating(false); } }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.email || !form.password }, 'Create')) },
328
- h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { name: e.target.value }); }); } })),
339
+ creating && h(Modal, { title: 'Add User', onClose: function() { setCreating(false); setShowCreatePerms(false); }, width: 520, footer: h(Fragment, null, h('button', { className: 'btn btn-secondary', onClick: function() { setCreating(false); setShowCreatePerms(false); } }, 'Cancel'), h('button', { className: 'btn btn-primary', onClick: create, disabled: !form.email || !form.password }, 'Create User')) },
340
+ h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Name'), h('input', { className: 'input', value: form.name, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { name: e.target.value }); }); }, autoFocus: true })),
329
341
  h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Email *'), h('input', { className: 'input', type: 'email', value: form.email, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { email: e.target.value }); }); } })),
330
- h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Password *'), h('input', { className: 'input', type: 'password', value: form.password, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { password: e.target.value }); }); } })),
342
+ h('div', { className: 'form-group' },
343
+ h('label', { className: 'form-label' }, 'Initial Password *'),
344
+ h('div', { style: { display: 'flex', gap: 8 } },
345
+ h('input', { className: 'input', type: 'text', value: form.password, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { password: e.target.value }); }); }, placeholder: 'Min 8 characters', style: { flex: 1, fontFamily: 'var(--font-mono)', fontSize: 13 } }),
346
+ h('button', { type: 'button', className: 'btn btn-secondary btn-sm', onClick: generateCreatePassword, title: 'Generate random password', style: { whiteSpace: 'nowrap' } }, I.refresh(), ' Generate')
347
+ ),
348
+ form.password && h('div', { style: { marginTop: 6, padding: 8, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 6, fontSize: 11, color: 'var(--text-secondary)' } },
349
+ 'The user will be required to change this password on their first login. Share it securely.'
350
+ )
351
+ ),
331
352
  h('div', { className: 'form-group' }, h('label', { className: 'form-label' }, 'Role'), h('select', { className: 'input', value: form.role, onChange: function(e) { setForm(function(f) { return Object.assign({}, f, { role: e.target.value }); }); } }, h('option', { value: 'viewer' }, 'Viewer'), h('option', { value: 'member' }, 'Member'), h('option', { value: 'admin' }, 'Admin'), h('option', { value: 'owner' }, 'Owner'))),
332
- (form.role === 'member' || form.role === 'viewer') && h('div', { style: { marginTop: 8, padding: 10, background: 'var(--info-soft)', borderRadius: 'var(--radius)', fontSize: 12, color: 'var(--info)' } },
333
- 'After creating this user, click the shield icon to set their page permissions. By default, new Member/Viewer users have full access.'
353
+ // Inline permissions for member/viewer
354
+ (form.role === 'member' || form.role === 'viewer') && h('div', { style: { marginTop: 4 } },
355
+ h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
356
+ h('label', { className: 'form-label', style: { marginBottom: 0 } }, 'Page Permissions'),
357
+ h('button', { type: 'button', className: 'btn btn-ghost btn-sm', onClick: function() { setShowCreatePerms(!showCreatePerms); }, style: { fontSize: 11 } }, showCreatePerms ? 'Hide' : 'Customize')
358
+ ),
359
+ !showCreatePerms && h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 4 } }, 'Full access (default). Click "Customize" to restrict.'),
360
+ showCreatePerms && pageRegistry && h('div', { style: { maxHeight: 200, overflowY: 'auto', border: '1px solid var(--border)', borderRadius: 6, marginTop: 8 } },
361
+ Object.keys(pageRegistry).map(function(pid) {
362
+ var page = pageRegistry[pid];
363
+ var grants = form.permissions === '*' ? null : form.permissions;
364
+ var checked = !grants || (grants && grants[pid]);
365
+ return h('div', { key: pid, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 10px', fontSize: 12, cursor: 'pointer' }, onClick: function() {
366
+ setForm(function(f) {
367
+ var current = f.permissions === '*' ? (function() { var a = {}; Object.keys(pageRegistry).forEach(function(p) { a[p] = true; }); return a; })() : Object.assign({}, f.permissions);
368
+ if (current[pid]) { delete current[pid]; } else { current[pid] = true; }
369
+ if (Object.keys(current).length === Object.keys(pageRegistry).length) return Object.assign({}, f, { permissions: '*' });
370
+ return Object.assign({}, f, { permissions: current });
371
+ });
372
+ } },
373
+ h('input', { type: 'checkbox', checked: checked, readOnly: true, style: { width: 14, height: 14, accentColor: 'var(--primary)' } }),
374
+ h('span', null, page.label)
375
+ );
376
+ })
377
+ )
378
+ ),
379
+ (form.role === 'owner' || form.role === 'admin') && h('div', { style: { marginTop: 8, padding: 8, background: 'var(--info-soft)', borderRadius: 'var(--radius)', fontSize: 11, color: 'var(--info)' } },
380
+ 'Owner and Admin roles always have full access to all pages.'
334
381
  )
335
382
  ),
336
383
 
@@ -0,0 +1,9 @@
1
+ import {
2
+ createAdapter,
3
+ getSupportedDatabases
4
+ } from "./chunk-KWW53O2B.js";
5
+ import "./chunk-KFQGP6VL.js";
6
+ export {
7
+ createAdapter,
8
+ getSupportedDatabases
9
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ createAdapter,
3
+ getSupportedDatabases
4
+ } from "./chunk-DYARH3NM.js";
5
+ import "./chunk-KFQGP6VL.js";
6
+ export {
7
+ createAdapter,
8
+ getSupportedDatabases
9
+ };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  provision,
3
3
  runSetupWizard
4
- } from "./chunk-I3AODI5Z.js";
4
+ } from "./chunk-3YLLWCUC.js";
5
5
  import {
6
6
  AgenticMailManager,
7
7
  GoogleEmailProvider,
@@ -42,7 +42,7 @@ import {
42
42
  requireRole,
43
43
  securityHeaders,
44
44
  validate
45
- } from "./chunk-64SXJMJI.js";
45
+ } from "./chunk-HGSWCMB7.js";
46
46
  import "./chunk-OF4MUWWS.js";
47
47
  import {
48
48
  PROVIDER_REGISTRY,
@@ -113,7 +113,7 @@ import {
113
113
  import {
114
114
  createAdapter,
115
115
  getSupportedDatabases
116
- } from "./chunk-NPR5DIPX.js";
116
+ } from "./chunk-KWW53O2B.js";
117
117
  import {
118
118
  AGENTICMAIL_TOOLS,
119
119
  ALL_TOOLS,