Users
Manage your end-users
Find a user, change what you know about them, and end their access.
The management App is where you and your support team work with the directory: find an end-user, read what Lessly Users knows about them, and act — invite, edit, ban, revoke a session, reset a lost second factor, delete or erase an account. Everything here is scoped to one product and needs no code on your side.
Every operation below is available to an agent as well, so a support workflow can be automated once you have done it by hand and know what it does.
Find a user
Search is one box, and it matches a substring of an email address or of the user’s name, case-insensitively. It does not match the user id: to land on a record from an id you hold in your own logs or in a token, read that user directly with users_users_get or go to its address in the App.
One filter narrows the list without a search term: status, as All, Active, Banned or Waitlisted. There is no filter by sign-in method, by verification state or by date, on either surface — users_users_list takes the search term and the status, and nothing else that narrows.
People who signed up to a waitlist are directory records too, and they are kept out of the default list so they do not drown the users who can actually sign in. The Waitlisted filter is what brings them back, or work with them in the funnel described in Run a waitlist. Soft-deleted users are hidden from the list the same way, and the App offers no control that shows them; users_users_list has includeDeleted for that.
Read a record
Opening a row from Users opens that user’s record. It carries six panels — Identifiers, Profile, Impersonation, Sessions, MFA factors and Audit trail — and the ban, delete and erase buttons sit in its header.
Identity. The stable opaque id — the one you store in your own database and the one that appears in every access token and event — plus the name and image if your product collects them.
Identifiers. The email addresses that reach this person. A user may hold several; exactly one is primary. Each address carries its own verification state, so a record can hold a verified address and an unverified one at the same time. “Is this user verified?” is always a question about an address. The panel lists them and marks the primary one; the only action on it is Set primary, described below.
Sign-in methods. Separately from the addresses, the ways this person can prove who they are: a password, a linked Google or GitHub account. The record shows neither. There is no panel for a linked provider account, no way to unlink one, and no way to clear a password — those are not operations this product offers on either surface today.
Sessions. One row per session, naming the device by what it reported and the address it connected from, with when it last refreshed and when it expires. Ended sessions stay in the list, marked as ended and without a revoke button, because a support case needs what happened and not only what is live.
Factors. Enrolled multi-factor methods: an authenticator app once its enrolment has been confirmed, and how many single-use backup codes remain.
Profile and metadata. Three bags of your own data hang off the record, and they differ in who may write them and who may read them.
| Bag | Written by | Read by | Notes |
|---|---|---|---|
| Public metadata | you, from your backend or an agent | your backend and the end-user | A size-capped projection of it travels in the access token |
| Private metadata | you, from your backend or an agent | your backend | Never leaves your side |
| Unsafe metadata | the end-user | everyone | Never base an authorization decision on it |
Use public metadata for anything your backend wants without a lookup — a plan name, a role, a feature flag. Because it rides in the token, it is capped, so an over-large write is refused rather than silently breaking that user’s next sign-in.
Security history. The record’s own audit trail, described at the end of this page.
Create and invite a user
These are two acts, and the App keeps them apart. Creating writes the record and mails nobody. Inviting writes the record too — the person appears in the directory at once, with an unverified address and no credential — and then mails a one-time link that sets their first password and verifies the address. That link is how invite-only sign-up works, and accepting it creates no session by itself.
An invitation is single-use and expires; its lifetime and the cooldown on re-inviting the same address are configuration fields (invite), with no screen. Inviting is idempotent by address: re-inviting returns the same user rather than creating a second, and whether it mails again is a cooldown decision, so pressing the button twice cannot flood a mailbox. Nothing is mailed at all until this product has a verified sender address. Neither surface revokes an invitation, so an invitation you regret is one you wait out or ban the record for. Accepting it applies everything your product’s configuration requires — a password that meets the policy, a second factor if you demand one.
Edit a record
The profile is the Profile panel on the record: the display name, the avatar URL, the waitlist flag and the must-reset-password flag. It is a form with an explicit Save profile, and it sends only the boxes you changed, so a value someone else changed while you had the record open is not clobbered by your save. Emptying a text box clears that field rather than leaving it alone.
Metadata is not on that form, or on any other. Write the bags with users_users_update. A change to public metadata reaches your backend on the user’s next access token, within the token lifetime.
The primary identifier is changed from Identifiers → Set primary, and it is not a field edit. It opens a dialog that asks for the new address and for a written reason — what identity proofing you did, under which ticket — and the reason is recorded in the audit row word for word. The swap is then scheduled rather than applied: the panel answers with a request id and the moment it takes effect, the old address is mailed immediately, and the delay is this product’s emailChange.overrideDelayHours, 72 hours by default. The primary address is what Lessly Users uses to reach the person and what your backend receives in the token’s email claim.
Adding or removing an address has no screen and no tool. A record’s addresses are the ones it was created with; plan for that when you import.
Reset a lost second factor
When a user loses both their authenticator app and their backup codes, an operator resets their factors from the record. The reset clears the enrolled factors, revokes all of the user’s sessions, and lets them enrol again on their next sign-in.
Revoke a session
From the sessions list you can end one session — that device signs out and nothing else changes — or end all of them at once. A revoked session’s refresh stops working immediately.
Because your backend verifies access tokens locally, a revoked session’s current access token remains valid for the rest of its lifetime — ten minutes by default. Where that matters, the server library can check a token against the live session on the calls that deserve it; Sessions and tokens covers both modes, and Configure authentication covers shortening the token lifetime.
Ban and unban
Banning refuses every future sign-in and revokes all existing sessions at once. The record, its identifiers and its metadata stay exactly as they were, so a ban is reversible: unbanning restores the account, and the person signs in again with the credentials they already had.
Use a ban for abuse and for accounts you may want back; use deletion for accounts that are leaving.
Impersonate a user for support
To reproduce what a user is seeing, you can open a session as them from the Impersonation panel on their record. It is the strongest thing the App can do, and it is fenced accordingly:
- the button is disabled until your product’s redirect URI allowlist has an entry in it, because an impersonated session has nowhere legal to land otherwise;
- the dialog will not submit without a written reason, and that reason is both recorded and quoted to the user in the email they receive;
- the session is capped at thirty minutes and cannot be refreshed;
- nothing about the account can be changed while you are inside it — no password, no factors, no addresses, no session revocations;
- your backend can tell: the session is marked as an impersonation and the server library exposes it as such, so you can hide sensitive screens or refuse destructive actions during one;
- it is recorded with your name as the actor, and — unless you turn the notice off for your product — the user is emailed that it happened.
What the App hands back is an access token, not a signed-in browser tab. The panel shows the session id, the moment it expires and the token itself, with a Copy access token button. Nothing redirects, and no session is transferred to the tab you are standing in. Your product’s own integration is what consumes the token, over whatever route it already has. There is no refresh token, so when the thirty minutes are up the session is over.
Delete or erase an account
Two different operations, for two different reasons.
| Delete | Erase | |
|---|---|---|
| What it is for | An account that is leaving. | A data-protection request. |
| Sessions | Revoked at once. | Revoked at once. |
| The record | Kept for a grace period, so a mistaken deletion can be undone and your own systems can reconcile. | Purged: addresses, profile, metadata and credentials are gone; a tombstone stands in for the identity. |
| The email address | Released for re-use after the grace period. | Free again immediately. |
| Undo | Yes, during the grace period. | None. Erasure is final. |
| Security-log entries | Kept. | Kept, with the identity in them pseudonymized. |
What erasure means for the data itself, and how it fits the platform’s retention and deletion model, is on Data and privacy.
Import users in bulk
Bringing an existing user base with you is a first-class operation in the App, not a support ticket. Import takes the file in three steps: paste or upload the rows, say which column is which, then read what will happen before anything is sent. The screen maps four columns — the email address, a display name, the password hash you hold today, and the algorithm that hash belongs to — and its preview counts the rows it will send and the rows it rejected, each rejection named with its row number and reason.
What happens on the run:
- each accepted row becomes a directory record with its address in the verification state you declared;
- a supplied password hash is stored as it is, and the password keeps working — it is verified in its original format the first time that person signs in, then transparently re-stored in argon2id;
- a hash that cannot be used marks the account as needing a password reset, so the user is asked to set a new password rather than being locked out;
- rows with no credential at all become invitable accounts, which you then invite from the App as above.
The whole import is recorded in the security history. Migrate users in and out has the file format, the supported hash formats, and the export path back out.
Read the audit trail
Two records are kept, for two audiences.
| The user’s security history | The product-wide log | |
|---|---|---|
| Organized by | whom it was done to | when it happened |
| Where you read it | the Audit trail panel on the record | users_audit_list, which has no screen |
| The question it answers | ”what has happened to this account?" | "what was done in this product last Tuesday?” |
| Covers agent actions | yes | yes |
Both read the same log; the record’s panel is that log filtered to one user. Entries are append-only: nothing in the App edits or removes one.
Every entry carries the actor, the action, what it targeted and when it happened. Note what the filters do not offer: users_audit_list narrows by action, by target and by time range, and there is no way to ask it what one member of your team did — an actor is something you read off an entry, not something you can filter for. Together with the notification emails Lessly Users sends on its own — a changed password, a new device, a requested email change with its revert link, a pending factor reset — this is what makes an operator override safe to offer: nothing sensitive happens quietly, either to your team or to your end-user.
Next steps
- Configure authentication: the sign-in methods, password policy and session lifetimes the operations above obey.
- Read the token contract: why a revoked session can outlive its revocation by one token lifetime, and how to close that gap.
- Migrate users in and out: the import file format, and getting your users back out.
- Run a waitlist: the directory before your product is open to everyone.