Appearance
Users CRUD & Profile Management
Overview
User management is handled by UsersService (src/services/users.ts) and routed via src/routes/users.ts. Users are stored in odp_users.
Data Model
Table: odp_users
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
first_name | varchar | First name |
last_name | varchar | Last name |
email | varchar | Email (unique) |
password | varchar | Bcrypt hash |
token | varchar | Static API token (nullable) |
role | UUID | FK to odp_roles.id |
status | varchar | active, invited, suspended, archived |
avatar | UUID | FK to odp_files.id |
description | text | Profile bio |
language | varchar | Preferred UI language |
theme | varchar | UI theme preference |
appearance | varchar | Light/dark/auto |
tfa_secret | varchar | TOTP secret (nullable) |
tfa_enabled | boolean | Whether TFA is active |
last_access | timestamp | Last API request time |
last_page | varchar | Last app page visited |
provider | varchar | Auth provider (default: default) |
external_identifier | varchar | SSO provider's user ID |
auth_data | json | Provider-specific auth data |
Endpoints
POST /users
Create a new user.
Auth required: Admin
Request Body
json
{
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"password": "secret123",
"role": "role-uuid",
"status": "active"
}Response 200
json
{
"data": "new-user-uuid"
}GET /users
List all users.
Auth required: Yes (permissions apply)
Query Parameters — Standard query system applies.
Response 200
json
{
"data": [
{
"id": "uuid",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"role": "role-uuid",
"status": "active",
"last_access": "2026-03-26T10:00:00.000Z"
}
],
"meta": {
"total_count": 42,
"filter_count": 42
}
}GET /users/:id
Read a single user by UUID.
Auth required: Yes
Response 200
json
{
"data": {
"id": "uuid",
"first_name": "John",
"last_name": "Doe",
"email": "john@example.com",
"role": "role-uuid",
"status": "active"
}
}GET /users/me
Read the currently authenticated user.
Auth required: Yes (any authenticated user)
Response 200 — Same as GET /users/:id for the current user.
PATCH /users/:id
Update a user. Admin only.
Auth required: Admin
Request Body — Any user fields to update.
Response 200
json
{
"data": "user-uuid"
}PATCH /users/me
Update the current user's own profile.
Auth required: Yes (authenticated user)
Self-editable fields only:
first_name, last_name, email, password, avatar, description, language, theme, appearance, tokenOther fields in the payload are silently ignored.
Password change requires current_password and optionally otp:
json
{
"password": "new-password",
"current_password": "old-password",
"otp": "123456"
}Response 200
json
{
"data": "user-uuid"
}DELETE /users/:id
Delete a user. Admin only.
Auth required: Admin
Response 204
User Invitation
POST /users/invite
Invite a user by email. Sends an invitation email with a token link.
Auth required: Admin
Request Body
json
{
"email": "newuser@example.com",
"role": "role-uuid"
}Response 204
Creates a user with status invited and sends email. Token TTL is USER_INVITE_TOKEN_TTL (default: 7d).
POST /users/invite/accept
Accept an invitation and set a password.
Auth required: None (public)
Request Body
json
{
"token": "invite-token-from-email",
"password": "my-new-password"
}Response 204
Sets user status to active.
Two-Factor Authentication (TFA)
POST /users/me/tfa/enable
Enable TOTP-based TFA for the current user.
Auth required: Yes
Request Body
json
{
"password": "current-password"
}Password is optional for SSO-only users (no local password set).
Response 200
json
{
"data": {
"secret": "JBSWY3DPEHPK3PXP",
"otpauth_url": "otpauth://totp/ODP:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ODP"
}
}Display the otpauth_url as a QR code for the user to scan in their authenticator app. TFA is not fully enabled until the user confirms with a valid OTP (implementation may require a confirm step).
POST /users/me/tfa/disable
Disable TFA for the current user.
Auth required: Yes
Request Body
json
{
"otp": "123456"
}Response 204
POST /users/:id/tfa/disable
Admin force-disable TFA for any user (no OTP required).
Auth required: Admin
Response 204
Related Pages
- Sessions & Provider Linking — Full documentation of session management, static token management, SSO provider linking, and the set-password endpoint.
- App Permissions — Module-level access control beyond RBAC.
Business Logic Notes
- Password hashing: bcrypt with salt rounds configured by the system.
- Email uniqueness: Enforced at database level.
- User status flow:
invited→active(on invite accept) orsuspended/archived. - Deleted users:
deleteOneremoves the user row. Activity/audit logs retain the user UUID. PATCH /users/mewhitelist: Only the 9 self-editable fields are accepted. Any attempt to changerole,status,admin_access, etc., via self-edit is silently dropped.