Skip to content

User Sessions & Provider Linking

Overview

ODP uses session-based refresh tokens. After login, a session record is created in odp_sessions and the session token is used as the refresh token. Session data is cached in Redis/memory for fast access token validation.

Each session can optionally be delivered via an HttpOnly cookie (SESSION_COOKIE_ENABLED).


Session Management Endpoints

GET /users/me/sessions

List all active sessions for the current authenticated user.

Auth required: Yes

Response:

json
{
  "data": [
    {
      "id": "3f4a5b6c7d8e",
      "token": "full-session-token",
      "ip": "192.168.1.100",
      "user_agent": "Mozilla/5.0...",
      "expires": "2024-01-08T00:00:00.000Z",
      "current": true
    },
    {
      "id": "9a8b7c6d5e4f",
      "token": "another-session-token",
      "ip": "10.0.0.1",
      "user_agent": "curl/7.79.1",
      "expires": "2024-01-07T12:00:00.000Z",
      "current": false
    }
  ]
}

Session ID: The id field is the first 16 hex characters of SHA-256(session_token) — a non-reversible short identifier for display purposes.

Current Session: Determined by comparing the session token stored in the current JWT's sid claim.


DELETE /users/me/sessions/:sid

Revoke a specific session.

Auth required: Yes

URL Parameters:

  • sid — Session identifier (either the 16-char hashed ID or the raw token)

Response: 204 No Content

Restrictions:

  • Cannot revoke the current session (use POST /auth/logout instead)
  • Ownership verified — only the session owner can revoke their sessions

Business Logic:

  1. Finds session by hashed id or raw token
  2. Validates it belongs to the authenticated user
  3. Checks it is not the current session
  4. Deletes from session cache (Redis/memory)
  5. Deletes from odp_sessions DB table

DELETE /users/me/sessions

Revoke all sessions except the current one.

Auth required: Yes

Response: 204 No Content

Business Logic:

  1. Determines current session sid from JWT
  2. Deletes all session data from cache for the user
  3. Deletes all sessions from DB EXCEPT the current one
  4. Re-caches the current session so it remains valid

Static Token Management

A static API token is a simple hex string stored directly on the user record. Unlike sub-tokens, it has no scopes, no expiry, and no audit trail. It is intended for simple integrations.

POST /users/me/token

Generate a new static API token (replaces any existing one).

Auth required: Yes

Response:

json
{
  "data": {
    "token": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456"
  }
}

Note: Generates a new crypto.randomBytes(32).toString('hex') and saves it to odp_users.token. Any previous static token is invalidated.


DELETE /users/me/token

Revoke the static API token.

Auth required: Yes

Response: 204 No Content

Sets odp_users.token = null.

Usage: Authenticate with static token via Authorization: Bearer <token>.


Provider Linking

Provider linking connects an external SSO identity to an existing ODP user account, allowing login via that provider without creating a separate account.

GET /users/me/providers

List linked SSO providers for the current user.

Auth required: Yes

Response:

json
{
  "data": [
    {
      "id": "link-uuid",
      "user_id": "user-uuid",
      "provider": "google",
      "external_identifier": "116634877...",
      "provider_email": "user@gmail.com",
      "created_at": "2024-01-01T00:00:00.000Z"
    }
  ]
}

auth_data is stripped from the response for security.


POST /users/me/providers/link

Link an OAuth2/OpenID SSO provider to the current user.

Auth required: Yes

Request Body:

json
{
  "provider": "github",
  "code": "authorization-code",
  "redirect_uri": "https://app.example.com/settings/providers",
  "code_verifier": "optional-pkce"
}

Response: 204 No Content

Business Logic:

  1. Exchanges authorization code with the provider
  2. Resolves SSOUserInfo (identifier, email, etc.)
  3. Checks allowUnverifiedEmail — rejects if false and email is unverified
  4. Checks email collision — SSO identity email must not belong to a different user
  5. Creates odp_user_providers record linking the authenticated user to the provider identity

DELETE /users/me/providers/:id

Unlink a specific provider.

Auth required: Yes

URL Parameters:

  • id — Provider link UUID (from odp_user_providers)

Response: 204 No Content

Note: Validates ownership before deletion.


Two-Factor Authentication (TFA)

POST /users/me/tfa/enable

Generate TFA secret and QR code for the current user.

Auth required: Yes

Request Body:

json
{
  "password": "current-password"
}

password is optional for SSO-only users who have no password set.

Response:

json
{
  "data": {
    "secret": "JBSWY3DPEHPK3PXP",
    "otpauth_url": "otpauth://totp/ODP:user@example.com?secret=..."
  }
}

POST /users/me/tfa/disable

Disable TFA for the current user.

Auth required: Yes

Request Body:

json
{
  "otp": "123456"
}

Response: 204 No Content


POST /users/:id/tfa/disable (Admin)

Force-disable TFA for any user without requiring OTP.

Auth required: Admin

Response: 204 No Content


Set Initial Password

POST /users/me/set-password

Set a password for users who signed up via SSO and have no password.

Auth required: Yes

Request Body:

json
{
  "password": "new-secure-password"
}

Response: 204 No Content

Note: Only valid if user does not already have a password (SSO-only accounts).


Session Data Model

odp_sessions

ColumnTypeDescription
tokenstring(64) PKSession token (used as refresh token)
userUUID FK → odp_usersSession owner (CASCADE delete)
expirestimestampSession expiry (REFRESH_TOKEN_TTL)
ipstring(255)Client IP
user_agenttextBrowser/client user-agent
shareUUID FK → odp_sharesFor share-based sessions
originstring(255)Request origin
next_tokenstring(64)Next token (for rolling sessions)

Session Cache

Session data is cached in Redis (or in-memory) with the session token as key. Cache entry TTL matches REFRESH_TOKEN_TTL.

Cached session data structure:

json
{
  "user": "user-uuid",
  "role": "role-uuid",
  "roles": ["role-uuid"],
  "admin": false,
  "tech": false,
  "app": true
}

Configuration

VariableDefaultDescription
SESSION_COOKIE_ENABLEDtrueEnable HttpOnly session cookie
SESSION_COOKIE_NAMEodp_session_tokenCookie name
ACCESS_TOKEN_TTL15mAccess token lifetime
REFRESH_TOKEN_TTL7dSession/refresh token lifetime
CACHE_ENABLEDfalseEnable Redis/memory cache for sessions
REDIS_ENABLEDfalseUse Redis for session cache

ODP Internal API Documentation