@agenticmail/enterprise 0.5.296 → 0.5.297
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/chunk-A5LURBEY.js +1519 -0
- package/dist/chunk-WQRGOMQJ.js +4008 -0
- package/dist/chunk-Y3WLLVOK.js +48 -0
- package/dist/cli-agent-WSCDFYMU.js +1778 -0
- package/dist/cli-recover-NXARYVSH.js +487 -0
- package/dist/cli-serve-GTTDOLB7.js +143 -0
- package/dist/cli-verify-RKB6KQN4.js +149 -0
- package/dist/cli.js +5 -5
- package/dist/dashboard/pages/users.js +120 -12
- package/dist/factory-M6E7YAKW.js +9 -0
- package/dist/index.js +3 -3
- package/dist/postgres-LJSV5YUF.js +771 -0
- package/dist/server-VTMQY7VU.js +15 -0
- package/dist/setup-HK4WDXUL.js +20 -0
- package/dist/sqlite-E5KKAJ24.js +503 -0
- package/package.json +1 -1
- package/src/admin/routes.ts +59 -3
- package/src/auth/routes.ts +5 -0
- package/src/dashboard/pages/users.js +120 -12
- package/src/db/adapter.ts +1 -0
- package/src/db/postgres.ts +2 -0
- package/src/db/sqlite.ts +1 -0
|
@@ -295,7 +295,8 @@ function InlinePermissionPicker({ permissions, pageRegistry, onChange }) {
|
|
|
295
295
|
// ─── Users Page ────────────────────────────────────
|
|
296
296
|
|
|
297
297
|
export function UsersPage() {
|
|
298
|
-
var
|
|
298
|
+
var app = useApp();
|
|
299
|
+
var toast = app.toast;
|
|
299
300
|
var [users, setUsers] = useState([]);
|
|
300
301
|
var [creating, setCreating] = useState(false);
|
|
301
302
|
var [form, setForm] = useState({ email: '', password: '', name: '', role: 'viewer', permissions: '*' });
|
|
@@ -343,22 +344,39 @@ export function UsersPage() {
|
|
|
343
344
|
setResetting(false);
|
|
344
345
|
};
|
|
345
346
|
|
|
346
|
-
var
|
|
347
|
+
var toggleActive = async function(user) {
|
|
348
|
+
var action = user.isActive === false ? 'reactivate' : 'deactivate';
|
|
347
349
|
var ok = await showConfirm({
|
|
348
|
-
title: '
|
|
349
|
-
message:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
350
|
+
title: action === 'deactivate' ? 'Deactivate User' : 'Reactivate User',
|
|
351
|
+
message: action === 'deactivate'
|
|
352
|
+
? 'Deactivate "' + (user.name || user.email) + '"? They will be unable to log in and will see a message to contact their organization.'
|
|
353
|
+
: 'Reactivate "' + (user.name || user.email) + '"? They will be able to log in again.',
|
|
354
|
+
danger: action === 'deactivate',
|
|
355
|
+
confirmText: action === 'deactivate' ? 'Deactivate' : 'Reactivate'
|
|
353
356
|
});
|
|
354
357
|
if (!ok) return;
|
|
355
358
|
try {
|
|
356
|
-
await apiCall('/users/' + user.id, { method: '
|
|
357
|
-
toast('User
|
|
359
|
+
await apiCall('/users/' + user.id + '/' + action, { method: 'POST' });
|
|
360
|
+
toast('User ' + action + 'd', 'success');
|
|
358
361
|
load();
|
|
359
362
|
} catch (e) { toast(e.message, 'error'); }
|
|
360
363
|
};
|
|
361
364
|
|
|
365
|
+
var [deleteStep, setDeleteStep] = useState(0);
|
|
366
|
+
var [deleteTarget, setDeleteTarget] = useState(null);
|
|
367
|
+
var [deleteTyped, setDeleteTyped] = useState('');
|
|
368
|
+
|
|
369
|
+
var startDelete = function(user) { setDeleteTarget(user); setDeleteStep(1); setDeleteTyped(''); };
|
|
370
|
+
var cancelDelete = function() { setDeleteTarget(null); setDeleteStep(0); setDeleteTyped(''); };
|
|
371
|
+
|
|
372
|
+
var confirmDelete = async function() {
|
|
373
|
+
try {
|
|
374
|
+
await apiCall('/users/' + deleteTarget.id, { method: 'DELETE', body: JSON.stringify({ confirmationToken: 'DELETE_USER_' + deleteTarget.email }) });
|
|
375
|
+
toast('User permanently deleted', 'success');
|
|
376
|
+
cancelDelete(); load();
|
|
377
|
+
} catch (e) { toast(e.message, 'error'); }
|
|
378
|
+
};
|
|
379
|
+
|
|
362
380
|
var openPermissions = async function(user) {
|
|
363
381
|
try {
|
|
364
382
|
var d = await apiCall('/users/' + user.id + '/permissions');
|
|
@@ -485,18 +503,100 @@ export function UsersPage() {
|
|
|
485
503
|
onClose: function() { setPermTarget(null); }
|
|
486
504
|
}),
|
|
487
505
|
|
|
506
|
+
// 5-step delete confirmation modal
|
|
507
|
+
deleteTarget && h(Modal, {
|
|
508
|
+
title: 'Delete User — Step ' + deleteStep + ' of 5',
|
|
509
|
+
onClose: cancelDelete,
|
|
510
|
+
width: 480,
|
|
511
|
+
footer: h(Fragment, null,
|
|
512
|
+
h('button', { className: 'btn btn-secondary', onClick: deleteStep === 1 ? cancelDelete : function() { setDeleteStep(deleteStep - 1); } }, deleteStep === 1 ? 'Cancel' : 'Back'),
|
|
513
|
+
deleteStep < 5
|
|
514
|
+
? h('button', { className: 'btn btn-' + (deleteStep >= 3 ? 'danger' : 'primary'), onClick: function() { setDeleteStep(deleteStep + 1); } }, 'Continue')
|
|
515
|
+
: h('button', { className: 'btn btn-danger', onClick: confirmDelete, disabled: deleteTyped !== deleteTarget.email }, 'Permanently Delete')
|
|
516
|
+
)
|
|
517
|
+
},
|
|
518
|
+
// Step 1: Warning
|
|
519
|
+
deleteStep === 1 && h('div', null,
|
|
520
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 12, padding: 16, background: 'var(--danger-soft, rgba(220,38,38,0.08))', borderRadius: 8, marginBottom: 16 } },
|
|
521
|
+
h('svg', { width: 24, height: 24, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--danger)', strokeWidth: 2 }, h('path', { d: 'M12 9v4m0 4h.01M10.29 3.86l-8.6 14.86A2 2 0 0 0 3.4 21h17.2a2 2 0 0 0 1.71-2.98L13.71 3.86a2 2 0 0 0-3.42 0z' })),
|
|
522
|
+
h('div', null,
|
|
523
|
+
h('strong', null, 'Permanent Deletion'),
|
|
524
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 2 } }, 'This action cannot be undone.')
|
|
525
|
+
)
|
|
526
|
+
),
|
|
527
|
+
h('p', { style: { fontSize: 13 } }, 'You are about to permanently delete the user account for:'),
|
|
528
|
+
h('div', { style: { padding: 12, background: 'var(--bg-tertiary)', borderRadius: 8, marginTop: 8 } },
|
|
529
|
+
h('strong', null, deleteTarget.name || 'Unnamed'), h('br'),
|
|
530
|
+
h('span', { style: { fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' } }, deleteTarget.email)
|
|
531
|
+
),
|
|
532
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 12 } }, 'Consider deactivating instead — deactivated users can be reactivated later.')
|
|
533
|
+
),
|
|
534
|
+
// Step 2: Data loss
|
|
535
|
+
deleteStep === 2 && h('div', null,
|
|
536
|
+
h('h4', { style: { marginBottom: 12 } }, 'Data That Will Be Lost'),
|
|
537
|
+
h('ul', { style: { paddingLeft: 20, fontSize: 13, lineHeight: 1.8 } },
|
|
538
|
+
h('li', null, 'All login sessions will be terminated immediately'),
|
|
539
|
+
h('li', null, 'Audit log entries will be orphaned (no user reference)'),
|
|
540
|
+
h('li', null, 'Any API keys created by this user will be revoked'),
|
|
541
|
+
h('li', null, 'Permission grants and role assignments will be removed'),
|
|
542
|
+
h('li', null, '2FA configuration and backup codes will be destroyed')
|
|
543
|
+
)
|
|
544
|
+
),
|
|
545
|
+
// Step 3: Impact
|
|
546
|
+
deleteStep === 3 && h('div', null,
|
|
547
|
+
h('h4', { style: { marginBottom: 12 } }, 'Impact Assessment'),
|
|
548
|
+
h('div', { style: { padding: 12, background: 'var(--warning-soft, rgba(245,158,11,0.08))', borderRadius: 8, fontSize: 13, lineHeight: 1.6 } },
|
|
549
|
+
h('p', null, 'If this user manages or supervises any agents, those agents will lose their manager assignment.'),
|
|
550
|
+
h('p', { style: { marginTop: 8 } }, 'If this user created approval workflows, pending approvals may become orphaned.'),
|
|
551
|
+
h('p', { style: { marginTop: 8 } }, 'Any scheduled tasks or cron jobs created by this user will continue to run but cannot be modified.')
|
|
552
|
+
)
|
|
553
|
+
),
|
|
554
|
+
// Step 4: Alternative
|
|
555
|
+
deleteStep === 4 && h('div', null,
|
|
556
|
+
h('h4', { style: { marginBottom: 12 } }, 'Are You Sure?'),
|
|
557
|
+
h('div', { style: { padding: 16, background: 'var(--success-soft, rgba(21,128,61,0.08))', borderRadius: 8, marginBottom: 16 } },
|
|
558
|
+
h('strong', null, 'Recommended alternative: Deactivate'),
|
|
559
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 } }, 'Deactivating blocks login while preserving all data. The user can be reactivated at any time. This is the safe option.')
|
|
560
|
+
),
|
|
561
|
+
h('div', { style: { padding: 16, background: 'var(--danger-soft, rgba(220,38,38,0.08))', borderRadius: 8 } },
|
|
562
|
+
h('strong', null, 'Permanent deletion'),
|
|
563
|
+
h('p', { style: { fontSize: 12, color: 'var(--text-secondary)', marginTop: 4 } }, 'Removes the user and all associated data forever. There is no recovery.')
|
|
564
|
+
)
|
|
565
|
+
),
|
|
566
|
+
// Step 5: Type email to confirm
|
|
567
|
+
deleteStep === 5 && h('div', null,
|
|
568
|
+
h('h4', { style: { marginBottom: 12, color: 'var(--danger)' } }, 'Final Confirmation'),
|
|
569
|
+
h('p', { style: { fontSize: 13, marginBottom: 12 } }, 'Type the user\'s email address to confirm permanent deletion:'),
|
|
570
|
+
h('div', { style: { padding: 8, background: 'var(--bg-tertiary)', borderRadius: 6, fontFamily: 'var(--font-mono)', fontSize: 13, textAlign: 'center', marginBottom: 12 } }, deleteTarget.email),
|
|
571
|
+
h('input', {
|
|
572
|
+
className: 'input', type: 'text', value: deleteTyped,
|
|
573
|
+
onChange: function(e) { setDeleteTyped(e.target.value); },
|
|
574
|
+
placeholder: 'Type email to confirm',
|
|
575
|
+
autoFocus: true,
|
|
576
|
+
style: { fontFamily: 'var(--font-mono)', fontSize: 13, borderColor: deleteTyped === deleteTarget.email ? 'var(--danger)' : 'var(--border)' }
|
|
577
|
+
}),
|
|
578
|
+
deleteTyped && deleteTyped !== deleteTarget.email && h('div', { style: { fontSize: 11, color: 'var(--danger)', marginTop: 4 } }, 'Email does not match')
|
|
579
|
+
)
|
|
580
|
+
),
|
|
581
|
+
|
|
488
582
|
// Users table
|
|
489
583
|
h('div', { className: 'card' },
|
|
490
584
|
h('div', { className: 'card-body-flush' },
|
|
491
585
|
users.length === 0 ? h('div', { style: { padding: 24, textAlign: 'center', color: 'var(--text-muted)' } }, 'No users')
|
|
492
586
|
: h('table', null,
|
|
493
|
-
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width:
|
|
587
|
+
h('thead', null, h('tr', null, h('th', null, 'Name'), h('th', null, 'Email'), h('th', null, 'Role'), h('th', null, 'Status'), h('th', null, 'Access'), h('th', null, '2FA'), h('th', null, 'Created'), h('th', { style: { width: 200 } }, 'Actions'))),
|
|
494
588
|
h('tbody', null, users.map(function(u) {
|
|
495
589
|
var isRestricted = u.role === 'member' || u.role === 'viewer';
|
|
496
|
-
|
|
590
|
+
var isDeactivated = u.isActive === false;
|
|
591
|
+
var isSelf = u.id === ((app || {}).user || {}).id;
|
|
592
|
+
return h('tr', { key: u.id, style: isDeactivated ? { opacity: 0.6 } : {} },
|
|
497
593
|
h('td', null, h('strong', null, u.name || '-')),
|
|
498
594
|
h('td', null, h('span', { style: { fontFamily: 'var(--font-mono)', fontSize: 12 } }, u.email)),
|
|
499
595
|
h('td', null, h('span', { className: 'badge badge-' + (u.role === 'owner' ? 'warning' : u.role === 'admin' ? 'primary' : 'neutral') }, u.role)),
|
|
596
|
+
h('td', null, isDeactivated
|
|
597
|
+
? h('span', { className: 'badge badge-danger', style: { fontSize: 10 } }, 'Deactivated')
|
|
598
|
+
: h('span', { className: 'badge badge-success', style: { fontSize: 10 } }, 'Active')
|
|
599
|
+
),
|
|
500
600
|
h('td', null, permBadge(u)),
|
|
501
601
|
h('td', null, u.totpEnabled ? h('span', { className: 'badge badge-success' }, 'On') : h('span', { className: 'badge badge-neutral' }, 'Off')),
|
|
502
602
|
h('td', { style: { fontSize: 12, color: 'var(--text-muted)' } }, u.createdAt ? new Date(u.createdAt).toLocaleDateString() : '-'),
|
|
@@ -509,7 +609,15 @@ export function UsersPage() {
|
|
|
509
609
|
style: !isRestricted ? { opacity: 0.4 } : {}
|
|
510
610
|
}, I.shield()),
|
|
511
611
|
h('button', { className: 'btn btn-ghost btn-sm', title: 'Reset Password', onClick: function() { setResetTarget(u); setNewPassword(''); } }, I.lock()),
|
|
512
|
-
|
|
612
|
+
// Deactivate / Reactivate
|
|
613
|
+
!isSelf && h('button', {
|
|
614
|
+
className: 'btn btn-ghost btn-sm',
|
|
615
|
+
title: isDeactivated ? 'Reactivate User' : 'Deactivate User',
|
|
616
|
+
onClick: function() { toggleActive(u); },
|
|
617
|
+
style: { color: isDeactivated ? 'var(--success, #15803d)' : 'var(--warning, #f59e0b)' }
|
|
618
|
+
}, isDeactivated ? I.check() : I.pause()),
|
|
619
|
+
// Delete (owner only)
|
|
620
|
+
!isSelf && h('button', { className: 'btn btn-ghost btn-sm', title: 'Delete User Permanently', onClick: function() { startDelete(u); }, style: { color: 'var(--danger)' } }, I.trash())
|
|
513
621
|
)
|
|
514
622
|
)
|
|
515
623
|
);
|
package/src/db/adapter.ts
CHANGED
|
@@ -67,6 +67,7 @@ export interface User {
|
|
|
67
67
|
totpBackupCodes?: string; // JSON array of hashed backup codes
|
|
68
68
|
permissions?: any; // '*' or { pageId: true | string[] }
|
|
69
69
|
mustResetPassword?: boolean;
|
|
70
|
+
isActive?: boolean;
|
|
70
71
|
createdAt: Date;
|
|
71
72
|
updatedAt: Date;
|
|
72
73
|
lastLoginAt?: Date;
|
package/src/db/postgres.ts
CHANGED
|
@@ -191,6 +191,7 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
191
191
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS totp_backup_codes TEXT;
|
|
192
192
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS permissions JSONB DEFAULT '"*"';
|
|
193
193
|
ALTER TABLE users ADD COLUMN IF NOT EXISTS must_reset_password BOOLEAN DEFAULT FALSE;
|
|
194
|
+
ALTER TABLE users ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE;
|
|
194
195
|
`).catch(() => {});
|
|
195
196
|
await client.query('COMMIT');
|
|
196
197
|
} catch (err) {
|
|
@@ -709,6 +710,7 @@ export class PostgresAdapter extends DatabaseAdapter {
|
|
|
709
710
|
totpSecret: r.totp_secret, totpEnabled: !!r.totp_enabled, totpBackupCodes: r.totp_backup_codes,
|
|
710
711
|
permissions: r.permissions != null ? (typeof r.permissions === 'string' ? (() => { try { return JSON.parse(r.permissions); } catch { return '*'; } })() : r.permissions) : '*',
|
|
711
712
|
mustResetPassword: !!r.must_reset_password,
|
|
713
|
+
isActive: r.is_active !== false && r.is_active !== 0, // default true
|
|
712
714
|
createdAt: new Date(r.created_at), updatedAt: new Date(r.updated_at),
|
|
713
715
|
lastLoginAt: r.last_login_at ? new Date(r.last_login_at) : undefined,
|
|
714
716
|
};
|
package/src/db/sqlite.ts
CHANGED
|
@@ -62,6 +62,7 @@ export class SqliteAdapter extends DatabaseAdapter {
|
|
|
62
62
|
// Add permissions column if missing
|
|
63
63
|
try { this.db.exec(`ALTER TABLE users ADD COLUMN permissions TEXT DEFAULT '"*"'`); } catch { /* exists */ }
|
|
64
64
|
try { this.db.exec(`ALTER TABLE users ADD COLUMN must_reset_password INTEGER DEFAULT 0`); } catch { /* exists */ }
|
|
65
|
+
try { this.db.exec(`ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1`); } catch { /* exists */ }
|
|
65
66
|
});
|
|
66
67
|
tx();
|
|
67
68
|
}
|