Administrator accounts and login sessions

QilbeeDB supports explicitly provisioned username/password accounts in the 0.11.0 platform contract. A human account authenticates to a short-lived bearer session. An account never receives its underlying permanent API key through login.

Use this contract for installation operators and company administrators. There is no public signup, default password, email verification, password-reset email, MFA, refresh token, or built-in login form. An application can provide its own interface over these endpoints and keep its broader identity lifecycle separate. All production authentication traffic must use HTTPS.

Understand the authority boundary#

Account Provisioning authority Effective access
Global operator Installation master only The bound global credential's capabilities
Company administrator credential_admin in the same company The bound tenant credential's capabilities and exact resource grants
Application user Company administrator Only the bound credential's explicit permissions

A master account bound to the installation's bootstrap master can register companies, appoint their administrators and administer global credentials. It cannot use a global session directly on tenant memory endpoints. A company account cannot create a global account, register another company or bind a credential belonging to another company.

Each username is exact and case-sensitive within either the global authority or one tenant. Identical usernames in different tenants are independent. Login requires the tenant ID explicitly; the server verifies the account's stored binding. No requested capability, tenant change or role is accepted by login.

Create accounts#

First bootstrap the installation master through the trusted local operator command described in Global administration. Register a company and retain its tenant administrator credential. These remain distinct permanent secrets.

The master creates a global account with POST /api/v1/admin/login-accounts. A company administrator uses POST /api/v1/login-accounts with a tenant bearer credential. Both accept:

{
  "contract_version": 1,
  "username": "[email protected]",
  "password": "replace-with-a-private-password",
  "credential_id": "00000000-0000-0000-0000-000000000001"
}

Replace the example ID with the actual credential to bind. The global endpoint requires is_master: true; a delegated service key does not gain account administration merely by holding all four global capabilities. The tenant endpoint derives its company from the authenticated administrator and rejects a credential from any other tenant.

HTTP 201 returns contract_version and a sanitized account, including its ID, username, authority, underlying credential ID, revision, disabled timestamp and administrative history. Duplicate names return 409 without replacing the existing account. This operation does not return or rotate an API key.

Passwords accept 8–1,024 UTF-8 bytes and are never truncated or normalized. Use long, unique passwords; the lower bound is a compatibility limit, not a password strength assessment. QilbeeDB stores only an Argon2id v19 verifier with a random salt, 19 MiB memory, two iterations and one lane. These parameters follow the OWASP password storage guidance. The password and verifier are absent from public account views and audit history.

Sign in#

For a company account, send POST /api/v1/login:

{
  "contract_version": 1,
  "tenant_id": "example-company",
  "username": "[email protected]",
  "password": "replace-with-a-private-password"
}

For a global account, send POST /api/v1/admin/login with the same fields except tenant_id, which must be omitted. Both return HTTP 200:

{
  "contract_version": 1,
  "session": {
    "token": "<one-time-delivered-bearer-session>",
    "token_type": "Bearer",
    "expires_at_millis": 1800000900000,
    "account": {
      "id": "00000000-0000-0000-0000-000000000002",
      "username": "[email protected]",
      "authority": {"kind": "tenant", "tenant_id": "example-company"},
      "credential_id": "00000000-0000-0000-0000-000000000001",
      "revision": 1,
      "disabled_at_millis": null,
      "history": [{
        "revision": 1,
        "action": "login_account_created",
        "actor_id": "00000000-0000-0000-0000-000000000001",
        "at_millis": 1800000000000
      }]
    }
  }
}

The token above is a placeholder, not a valid credential. Actual tenant session tokens start with qdbst1_; global sessions start with qdbsg1_. Send the returned token in Authorization: Bearer <token>. Never include it in a URL or log. All responses carry Cache-Control: no-store. The API does not set cookies.

Sessions expire after at most 15 minutes, capped by the bound credential's expiry. The server reads the current account, session verifier and underlying credential on every authentication. /identity reports the underlying credential; the session's separate expiry is the value returned by login. A session cannot extend that deadline. Sign in again for a new session.

Each account retains at most 32 active session verifiers. Login removes expired or obsolete sessions and evicts the oldest retained session if the limit is full. Only verifiers are stored; tokens contain 256 random bits. Sessions survive a server restart until their normal expiry or explicit invalidation.

Password changes, disablement and logout#

Operation Company route Global route
Inspect account GET /api/v1/login-accounts/{id} GET /api/v1/admin/login-accounts/{id}
Replace password POST /api/v1/login-accounts/{id}/password POST /api/v1/admin/login-accounts/{id}/password
Disable account POST /api/v1/login-accounts/{id}/disable POST /api/v1/admin/login-accounts/{id}/disable

Password replacement accepts contract_version, expected_revision and password. Disablement accepts contract_version and expected_revision. Both require the same administrative authority as account creation and increment an auditable revision. A stale revision returns 409. There is no anonymous reset or account reactivation operation.

Changing a password or disabling an account atomically invalidates all its sessions. Rotating the bound API key invalidates existing sessions; the account can then sign in using the unchanged password and the current credential revision. Revoking or expiring the bound credential also prevents new login. Disabling a human account does not revoke separately issued API keys. Revoke those keys explicitly when ending their authority.

POST /api/v1/logout, authenticated with a session, invalidates only that session and returns {"contract_version":1,"logged_out":true}. A permanent API key is not a logout token. Expired or already logged-out sessions return 401. Administrative writes made through a session also compare its current account and parent credential in the storage transaction. Already-authorized data operations may finish during revocation; revocation does not cancel an in-flight request.

Limits and failure handling#

Unknown usernames, incorrect passwords, disabled accounts, accounts in cooldown and revoked underlying credentials return the same 401 invalid_login response. Unknown usernames incur Argon2 work without creating durable records. This follows OWASP authentication guidance on generic errors and login throttling; it is not a claim of constant network timing.

  • Five failed attempts cause a durable five-minute account cooldown. Attempts during the cooldown do not extend it. An authorized password replacement clears the cooldown; restarting the server does not.
  • Per process, each username/authority pair gets at most eight attempts per minute, with 120 attempts per minute across all names. This admission limit returns 429 login_rate_limited; wait at least one minute. Admission counters reset on process restart; the account cooldown remains durable.
  • At most two password operations run concurrently, including account creation and password changes. Occupied capacity returns 503 login_busy; retry with backoff. This bounds password-hashing work independently of retrieval admission.
  • Login and account routes accept at most 8 KiB of JSON. Invalid fields return 400, oversized bodies 413, missing credentials 401, denied authority 403 and storage or concurrent revision conflicts 409. Never overwrite newer account metadata merely to retry a conflict; inspect the current revision first.

Account-specific cooldowns can temporarily block a legitimate user targeted by repeated failures. Keep the separately secured operator API key for administrative recovery. Internet-facing deployments should also enforce appropriate edge abuse controls. This native password contract does not provide MFA or federation.

Use the administrative directories to list authorized accounts and credentials with bounded pagination.