This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/ICD/profile.md
gobha b7746c3004 Changeset 0.37.14 (#226)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-23 16:47:48 +00:00

6.4 KiB
Raw Blame History

User Profile & Settings

User identity, preferences, and credentials.

All endpoints require: Auth() middleware (JWT bearer token).


Get Profile

GET /profile

Auth: Authenticated user

Returns the current user's profile.

{
  "id": "uuid",
  "username": "jdoe",
  "email": "jdoe@example.com",
  "display_name": "Jane Doe",
  "role": "user",
  "avatar": "data:image/png;base64,...",
  "settings": { "theme": "dark" },
  "created_at": "2025-06-15T14:30:00Z",
  "last_login_at": "2025-06-15T14:30:00Z"
}
Field Type Notes
id string UUIDv4
username string Unique login name
email string Unique, lowercased
display_name string | null Optional friendly name
role "user" | "admin" Platform role
avatar string | null Data URI (PNG, 128×128). Omitted if unset.
settings object User preferences (theme, keybindings, etc.)
created_at string ISO 8601 timestamp
last_login_at string | null ISO 8601 timestamp. Null if never logged in.

Note: The profile endpoint returns avatar (not avatar_url) as a curated response shape. The User model uses avatar_url internally.


Update Profile

PUT /profile

Auth: Authenticated user

{
  "display_name": "New Name",
  "email": "new@example.com"
}

Both fields are optional (partial update). Returns the full profile (same shape as GET /profile).

Errors:

  • 409 — email already taken

Upload Avatar

POST /profile/avatar

Auth: Authenticated user

Content-Type: application/json

{
  "image": "data:image/png;base64,..."
}

Accepts a base64 data URI or raw base64 string. The server decodes, resizes to 128×128 PNG, and stores as a data URI. Max input: 2 MB.

Response:

{
  "avatar": "data:image/png;base64,..."
}

Errors:

  • 400 — missing image field, invalid base64, unsupported format, too large

Delete Avatar

DELETE /profile/avatar

Auth: Authenticated user

Response:

{
  "message": "avatar removed"
}

Change Password

POST /profile/password

Auth: Authenticated user (builtin auth only)

{
  "current_password": "old-pass",
  "new_password": "new-pass-min-8"
}

Validates current_password against stored bcrypt hash. new_password must be 8128 characters.

On success, the UEK (User Encryption Key) is re-wrapped with the new password so BYOK-encrypted provider keys remain accessible.

Response:

{
  "message": "password updated"
}

Errors:

  • 400 — missing fields, password too short/long
  • 401 — current password incorrect

Get Settings

GET /settings

Auth: Authenticated user

Returns user-level preferences wrapped in a settings key.

{
  "settings": {
    "theme": "dark",
    "editor_keybindings": "vim",
    "default_model": "claude-sonnet-4-5"
  }
}

This is a composite response (named key wrapping a single object), not a bare object. The settings key is always present; its value is {} if the user has never saved preferences.


Update Settings

PUT /settings

Auth: Authenticated user

{
  "theme": "light",
  "new_key": "new_value"
}

Shallow merge: incoming keys overwrite existing, unmentioned keys are preserved. Handles edge cases: SQL NULL, JSON null, and corrupted array values are all normalized to {} before merge.

Returns the updated settings (same shape as GET /settings).


Get My Permissions

GET /profile/permissions

Auth: Authenticated user

Returns the current user's resolved permission set, contributing group IDs, team memberships, and UI-relevant policies. Admin users receive all permissions by definition.

Response:

{
  "permissions": ["model.use", "kb.read", "channel.create"],
  "groups": ["group-id-1", "00000000-0000-0000-0000-000000000001"],
  "teams": [
    { "id": "uuid", "name": "Engineering", "my_role": "member" }
  ],
  "policies": {
    "allow_user_byok": false,
    "allow_user_personas": false,
    "allow_raw_model_access": true,
    "kb_direct_access": true
  }
}
Field Type Description
permissions string[] Resolved permission set (union of all group permissions)
groups string[] Contributing group IDs (always includes Everyone)
teams object[] User's active team memberships with my_role
policies object Boolean policy flags relevant to UI feature gating

permissions is sorted alphabetically. teams includes only active teams the user is a member of.

This endpoint is the SDK's bootstrap source for sw.can(permission). Called once at login and on each token refresh.


Bootstrap (v0.37.15)

GET /profile/bootstrap

Auth: Authenticated user

Single-call boot payload for the SDK. Collapses what previously required 34 sequential requests (profile, permissions, teams/mine, settings) into one call. The SDK calls this at startup and on token refresh.

Response:

{
  "user": {
    "id": "uuid",
    "username": "jdoe",
    "display_name": "Jane Doe",
    "email": "jdoe@example.com",
    "role": "user",
    "avatar": "data:image/png;base64,..."
  },
  "permissions": ["channel.create", "kb.read", "model.use"],
  "groups": ["group-id-1", "00000000-0000-0000-0000-000000000001"],
  "teams": [
    { "id": "uuid", "name": "Engineering", "my_role": "member" }
  ],
  "policies": {
    "allow_user_byok": false,
    "allow_user_personas": false,
    "allow_raw_model_access": true,
    "kb_direct_access": true
  },
  "settings": {
    "theme": "dark",
    "editor_keybindings": "vim"
  }
}
Field Type Description
user object Curated profile (same fields as GET /profile minus timestamps)
permissions string[] Resolved permission set (sorted). Admins get all.
groups string[] Contributing group IDs (always includes Everyone)
teams object[] Active team memberships with my_role
policies object Boolean policy flags for UI feature gating
settings object User preferences ({} if never saved)

user.avatar is omitted when unset (not null).

This is a superset of GET /profile/permissions — it includes everything that endpoint returns plus user and settings. Clients that only need permissions can continue using the narrower endpoint.