User Management
View and manage users across your organizations
{/* Search + filter bar */}
Users
{!isLoading && {total} }
Click a user to view details and manage their role or SSH keys
{isLoading ? (
) : filteredUsers.length === 0 ? (
{debouncedSearch ? "No users match your search" : "No users found"}
) : (
{filteredUsers.map((user) => (
openUserDrawer(user)}
>
{user.full_name || user.email}
{user.email}
{isSuspended(user.status) && (
Suspended
)}
{user.activated === false && (
Not activated
)}
))}
)}
{/* Pagination */}
{pages > 1 && (
Page {page} of {pages} · {total} total
setPage((p) => Math.max(1, p - 1))}
disabled={page === 1}
>
Previous
setPage((p) => Math.min(pages, p + 1))}
disabled={page === pages}
>
Next
)}
{/* ── User detail drawer ─────────────────────────────────────────────────── */}
{ if (!open) setSelectedUser(null); }}>
{selectedUser && (
<>
{selectedUser.full_name || selectedUser.email}
{selectedUser.email}
{/* Basic info */}
Status
{isSuspended(selectedUser.status) ? (
<>Suspended{selectedUser.status === "compliance_suspended" ? " (compliance)" : ""} >
) : (
<>Active >
)}
Joined
{formatDate(selectedUser.created_at)}
Activated
{selectedUser.activated === false ? (
<> No>
) : (
<> Yes>
)}
Last login
{formatDate(selectedUser.last_login_at)}
{/* Suspend / Unsuspend — only for other users */}
{selectedUser.id !== currentUser?.id && (
Account Access
{/* Unverified / inactive email block */}
{(!selectedUser.email_verified || selectedUser.status === "inactive") && (
{selectedUser.status === "inactive"
? "This account is inactive — the user has not verified their email and cannot log in, set up OAuth, or configure MFA."
: "This user's email address is not verified."}
{isVerifyingEmail ? : }
Verify email & activate account
)}
{isSuspended(selectedUser.status) ? (
{selectedUser.status === "compliance_suspended"
? "This account is suspended due to MFA compliance. The user cannot log in or request certificates."
: "This account is suspended. The user cannot log in or request certificates."}
{isSuspending ? : }
Restore account
) : (
Suspending blocks this user from logging in and requesting SSH certificates.
setShowSuspendConfirm(true)}
disabled={isSuspending}
className="text-red-600 border-red-300 hover:bg-red-50"
>
Suspend account
)}
)}
{/* Role management — only if not viewing yourself and user has org_id */}
{selectedUser.org_id && selectedUser.id !== currentUser?.id && (
Organization Role
Member
Admin
Owner
{isUpdatingRole && }
{(selectedUser.org_role || "").toLowerCase() === "owner" && (
Owner role cannot be changed here. Transfer ownership from the Members page.
)}
)}
{/* ── MFA Methods section ────────────────────────────────────────── */}
{selectedUser.id !== currentUser?.id && (
MFA Methods
{userMfaMethods.length > 1 && (
setShowRemoveAllMfa(true)}
className="text-red-600 border-red-300 hover:bg-red-50 text-xs"
>
Remove all
)}
{isMfaLoading ? (
) : userMfaMethods.length === 0 ? (
No MFA methods configured.
) : (
{userMfaMethods.map((method) => (
{method.type === "totp" ? (
) : (
)}
{method.name}
{method.last_used_at && (
Last used: {formatDate(method.last_used_at)}
)}
handleRemoveMfaMethod(method)}
disabled={removingMfaId === method.id}
className="text-red-600 hover:bg-red-50 flex-shrink-0 ml-2"
title={`Remove ${method.name}`}
>
{removingMfaId === method.id ? (
) : (
)}
))}
)}
Remove an MFA method if the user has lost access (e.g. lost phone or passkey).
The user will be able to re-enroll after removal.
)}
{/* ── Linked Accounts section ────────────────────────────────── */}
{selectedUser.id !== currentUser?.id && (
Linked OAuth Accounts
{userLinkedAccounts.length === 0 ? (
No OAuth providers linked.
) : (
{userLinkedAccounts.map((account) => {
const isOnlyMethod = totalAuthMethods <= 1;
return (
{account.provider_type}
{account.email && (
{account.email}
)}
{account.linked_at && (
Linked: {formatDate(account.linked_at)}
)}
handleUnlinkProvider(account)}
disabled={unlinkingProvider === account.id || isOnlyMethod}
className="text-red-600 hover:bg-red-50 flex-shrink-0 ml-2"
title={
isOnlyMethod
? "Cannot unlink — this is the user's only sign-in method"
: `Unlink ${account.provider_type}`
}
>
{unlinkingProvider === account.id ? (
) : (
)}
);
})}
)}
Unlink an OAuth provider to prevent sign-in via that provider.
Cannot unlink if it is the user's only sign-in method.
)}
{/* ── Admin Password Reset section ──────────────────────────── */}
{selectedUser.id !== currentUser?.id && (
Password
Set a new password for this user. Use this when a user is locked out or needs a password added to their account.
{ setPasswordResetError(null); setNewPassword(""); setNewPasswordConfirm(""); setShowPasswordReset(true); }}
>
Set password
)}
{/* SSH Keys section */}
SSH Keys
setShowAddKey(true)}>
Add key
{isDrawerLoading ? (
) : userSshKeys.length === 0 ? (
No SSH keys registered
) : (
{userSshKeys.map((k) => (
{k.description || No description }
{k.verified ? (
Verified
) : (
Unverified
)}
{k.fingerprint ?? k.public_key.slice(0, 64) + "…"}
Added {formatDate(k.created_at)}
))}
)}
{/* Danger zone — Hard delete */}
{selectedUser.id !== currentUser?.id && (
Danger Zone
Permanently delete this account. This cannot be undone — all SSH keys and certificates will be revoked immediately.
{ setHardDeleteConfirmEmail(""); setShowHardDelete(true); }}
className="text-destructive border-destructive/40 hover:bg-destructive/10"
>
Permanently delete account
)}
>
)}
{/* ── Admin add SSH key dialog ───────────────────────────────────────────── */}
{ setShowAddKey(open); setAddKeyError(null); }}>
Add SSH Key for {selectedUser?.email}
Add an SSH public key on behalf of this user (admin action).
{addKeyError && (
{addKeyError}
)}
Public key
Description (optional)
setAddKeyDescription(e.target.value)}
disabled={isAddingKey}
/>
setShowAddKey(false)} disabled={isAddingKey}>
Cancel
{isAddingKey && }
Add key
{/* ── Suspend confirmation dialog ───────────────────────────────────────── */}
Suspend account?
{selectedUser?.full_name || selectedUser?.email} will be blocked from logging in and requesting SSH certificates. You can restore their access at any time.
setShowSuspendConfirm(false)} disabled={isSuspending}>
Cancel
{isSuspending && }
Suspend
{/* ── Remove All MFA confirmation ───────────────────────────────────────── */}
Remove all MFA methods?
All MFA methods for{" "}
{selectedUser?.full_name || selectedUser?.email} will
be removed. They will be able to re-enroll after this action. Use this
when the user has lost access to their authenticator app or passkey.
setShowRemoveAllMfa(false)} disabled={isRemovingAllMfa}>
Cancel
{isRemovingAllMfa && }
Remove all MFA
{/* ── Hard delete confirmation ──────────────────────────────────────────── */}
{ setShowHardDelete(open); if (!open) setHardDeleteConfirmEmail(""); }}
>
Permanently delete account?
This will permanently delete{" "}
{selectedUser?.full_name || selectedUser?.email} ,
revoke all their SSH certificates, and remove all their SSH keys. This action cannot be undone.
Type {selectedUser?.email} to confirm
setHardDeleteConfirmEmail(e.target.value)}
placeholder={selectedUser?.email ?? ""}
disabled={isHardDeleting}
className="font-mono"
/>
setShowHardDelete(false)}
disabled={isHardDeleting}
>
Cancel
{isHardDeleting && }
Delete permanently
{/* ── Admin password reset dialog ───────────────────────────────────── */}
{
setShowPasswordReset(open);
if (!open) { setNewPassword(""); setNewPasswordConfirm(""); setPasswordResetError(null); }
}}
>
Set password for {selectedUser?.email}
The user will be able to log in with this password immediately. This does not affect their existing OAuth logins.
setShowPasswordReset(false)}
disabled={isResettingPassword}
>
Cancel
{isResettingPassword && }
Set password
);
}