Skip to content

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

ColumnTypeDescription
idUUIDPrimary key
first_namevarcharFirst name
last_namevarcharLast name
emailvarcharEmail (unique)
passwordvarcharBcrypt hash
tokenvarcharStatic API token (nullable)
roleUUIDFK to odp_roles.id
statusvarcharactive, invited, suspended, archived
avatarUUIDFK to odp_files.id
descriptiontextProfile bio
languagevarcharPreferred UI language
themevarcharUI theme preference
appearancevarcharLight/dark/auto
tfa_secretvarcharTOTP secret (nullable)
tfa_enabledbooleanWhether TFA is active
last_accesstimestampLast API request time
last_pagevarcharLast app page visited
providervarcharAuth provider (default: default)
external_identifiervarcharSSO provider's user ID
auth_datajsonProvider-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, token

Other 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


  • 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: invitedactive (on invite accept) or suspended/archived.
  • Deleted users: deleteOne removes the user row. Activity/audit logs retain the user UUID.
  • PATCH /users/me whitelist: Only the 9 self-editable fields are accepted. Any attempt to change role, status, admin_access, etc., via self-edit is silently dropped.

ODP Internal API Documentation