Skip to content

Login & Auth

scarecr0w12 edited this page Jun 24, 2026 · 1 revision

Login & Auth

CortexPrism supports two authentication methods: username+password login for the web UI, and API token authentication for programmatic access. Both flow through the extractIdentity() function in src/server/auth.ts and produce a RequestIdentity used by authorization guards.

Authentication Flow

┌─────────────────────────────────────────────────────┐
│                   Incoming Request                   │
└────────────────────────┬────────────────────────────┘
                         │
                         ▼
              ┌─────────────────────┐
              │   extractIdentity() │
              └─────────┬───────────┘
                        │
          ┌─────────────┼─────────────┐
          ▼             │             ▼
   Authorization:        │        Session Cookie
   Bearer <token>        │        cortex_session
          │              │             │
          ▼              │             ▼
   validateApiToken()    │     validateSession()
   SHA-256 hash match    │     in-memory Map lookup
          │              │             │
          └──────────────┼─────────────┘
                         │
                         ▼
              ┌─────────────────────┐
              │  getUserTeams()     │
              │  isInstanceAdmin()  │
              └─────────┬───────────┘
                        │
                        ▼
              ┌─────────────────────┐
              │  RequestIdentity    │
              │  { type, userId,    │
              │    username,         │
              │    teamIds,          │
              │    isInstanceAdmin } │
              └─────────────────────┘

Username + Password Login

Web UI Login

The login page at /login (src/server/ui/pages/login.ts) presents a form with username and password fields. On submit, it calls POST /api/auth/login.

API Login

POST /api/auth/login
Content-Type: application/json

{
  "username": "alice",
  "password": "mypassword"
}

Multi-user path: When username is provided, verifyUserPassword() queries the users table, retrieves the stored PBKDF2-SHA-256 hash and salt, recomputes the hash, and compares using constant-time comparison. On success, a session is created with userId and username set.

Legacy path: When username is omitted, the system falls back to the vault-stored single password (__cortex_web_password) for backward compatibility.

Rate limiting: Both paths are rate-limited by client IP via checkAuthRateLimit(), returning HTTP 429 after too many attempts.

Response (200):

{
  "success": true,
  "sessionId": "550e8400-...",
  "user": {
    "id": "usr_550e8400-...",
    "username": "alice"
  }
}

The response includes a Set-Cookie header with the cortex_session cookie.

Password Complexity

Passwords must be at least 8 characters with at least 2 of:

  • Lowercase letters (a-z)
  • Uppercase letters (A-Z)
  • Numbers (0-9)
  • Symbols (non-alphanumeric)

Password Hashing

Passwords are hashed using PBKDF2 with:

  • Hash: SHA-256
  • Iterations: 200,000
  • Salt: 16 bytes of cryptographically random data
  • Key length: 32 bytes (256 bits)
// src/server/auth.ts
const PBKDF2_ITERATIONS = 200_000;
const KEY_LENGTH = 32;
const SALT_LENGTH = 16;

API Token Auth

Include the token in the Authorization header:

Authorization: Bearer cortex_token_550e8400-...

The extractIdentity() function:

  1. Parses the Authorization header
  2. Strips the Bearer prefix
  3. Computes SHA-256 hash of the token
  4. Looks up the hash in the user_tokens table
  5. Validates expiration and revocation status
  6. Fetches the user's teams and admin status
  7. Returns a RequestIdentity with type: 'user'

See API Tokens for full token management documentation.

Sessions

Sessions are stored in an in-memory Map<string, Session> with a 7-day TTL:

const SESSION_DURATION_MS = 7 * 24 * 60 * 60 * 1000; // 7 days

interface Session {
  id: string;
  userId?: string;
  username?: string;
  createdAt: string;
  expiresAt: string;
  lastActivity: string;
  ipAddress?: string;
  userAgent?: string;
}

Session Cookie

The cortex_session cookie is set with:

  • HttpOnly: prevents JavaScript access
  • Secure: when the request is over HTTPS
  • Path: /
  • Max-Age: 7 days
  • SameSite: Strict

Session Validation

validateSession() checks the session ID against the in-memory map and verifies the expiry. On each validation, lastActivity is updated. Expired sessions are automatically removed from the map.

Logout

POST /api/auth/logout

Destroys the session from the in-memory map and clears the session cookie.

Identity Extraction

extractIdentity() in src/server/auth.ts extracts identity from either an API token or session cookie:

async function extractIdentity(req: Request): Promise<RequestIdentity> {
  // 1. Check Authorization: Bearer header
  // 2. Validate API token (SHA-256 hash lookup)
  // 3. Fall back to session cookie (cortex_session)
  // 4. Return anonymous if neither is valid
}

The returned RequestIdentity includes:

  • type: 'user' | 'instance' | 'anonymous'
  • userId, username: user details
  • teamIds: all teams the user belongs to
  • currentTeamId: from x-cortex-team header or first team
  • isInstanceAdmin: whether the user is an instance admin
  • sessionId: web session ID (when authenticated via cookie)

Auth Middleware

requireAuth() in src/server/auth.ts is the main auth guard used by the router:

  1. If webAuth.requireAuth is false, allow all requests as instance identity
  2. If vault key is not configured, return 503
  3. If no users exist (first run), allow as instance identity
  4. Extract identity via extractIdentity()
  5. If identity is user or instance, allow
  6. Otherwise, return 401 with { error: "Authentication required", loginUrl: "/login" }

Auth Status

GET /api/auth/status

Returns the current authentication state:

{
  "authenticated": true,
  "user": {
    "id": "usr_550e8400-...",
    "username": "alice",
    "teams": ["team_abc123"],
    "currentTeam": "team_abc123",
    "isInstanceAdmin": false
  },
  "hasPassword": true,
  "requireAuth": true
}

When not authenticated:

{
  "authenticated": false,
  "hasPassword": true,
  "requireAuth": true
}

CLI Commands

cortex login                   # Interactive login with username + password
cortex login --token <token>   # Login with an API token directly
cortex login --host <url>      # Specify server URL (default: http://localhost:11434)
cortex logout                  # Clear stored auth token from ~/.cortex/auth.json
cortex whoami                  # Show current authenticated user and teams

cortex login

Prompts for username and password, then calls POST /api/auth/login. On success, displays the authenticated user. Note: web sessions are ephemeral — use cortex login --token <token> with an API token for persistent CLI authentication.

cortex login --token

Saves the API token to ~/.cortex/auth.json for persistent authentication across CLI sessions. The server URL can be overridden with --host or the CORTEX_API_URL environment variable.

cortex whoami

Calls GET /api/auth/status with the stored token and displays:

  • Username and user ID
  • Team memberships
  • Instance admin status (if applicable)

cortex logout

Removes ~/.cortex/auth.json, clearing the stored API token.

Web UI Page

The Login page (/login) is rendered by src/server/ui-auth.ts using the page template in src/server/ui/pages/login.ts. It provides:

  • Username and password fields
  • Submit button that calls POST /api/auth/login
  • Redirect to the main UI on success
  • Error display on failed login

The header (src/server/ui/shell.ts) includes a team selector dropdown for switching between teams, rendered when the user belongs to multiple teams.

Security Considerations

  • Passwords are never stored in plaintext — only PBKDF2-derived hashes
  • Session cookies are HttpOnly and SameSite=Strict to prevent XSS and CSRF attacks
  • API token hashes cannot be reversed to recover the plaintext token
  • Rate limiting is applied to login endpoints by client IP
  • Disabled users cannot authenticate even with correct credentials
  • Revoked API tokens are rejected by validateApiToken()

See Also

Clone this wiki locally