Accounts System

Built-in per-namespace user account management for the end-users of your application. Every verb shares the ACCOUNT prefix — ACCOUNT REGISTER, LOGIN, LOGOUT, SESSION, REFRESH, REVOKE, VERIFY, COMPARE, DELETE, UPDATE EMAIL, UPDATE PASSWORD — no external auth service required.

Implementation pending. The Accounts System is designed and specified below. Runtime implementation is in progress. STATUS, used in several examples, is documented on HTTP & Network.
Architecture note. These verbs manage accounts for your end-users — the people who sign up to the app you build with OcaltQL. They are entirely separate from your own OcaltQL API credentials, supplied in every request's identity/password. OcaltQL manages the storage internally; you never write SQL for this.
The account and the session are two different things, and OcaltQL already has the second. ?uid is the permanent identifier for the account — it never changes for the lifetime of that account, and it is what you store wherever you need a durable reference. The session is the browser, handled by START SESSION exactly as it is everywhere else in the language. ACCOUNT LOGIN joins the two: it authenticates, then places the account in the current session. There is no separate token to carry, no cookie to set by hand, and no second session system to keep in step with the first.

Verb Reference

Verb Description Returns
ACCOUNT REGISTER "email" WITH "password" SET ?uidCreate a user accountUID (string, permanent) or null if the email is already registered
ACCOUNT LOGIN "email" WITH "password" SET ?uidAuthenticate and place the account in the current sessionUID (string) or null on failure
ACCOUNT REVOKE "email" SET ?countInvalidate all sessions for a userNumber of sessions revoked
ACCOUNT VERIFY "email" SET ?okMark a user account as verified (e.g. after email confirmation)Boolean
ACCOUNT COMPARE "a" WITH "b" SET ?validConstant-time string comparison — safe for comparing tokens and secretsBoolean
ACCOUNT DELETE "email" SET ?okPermanently delete a user account and all its sessionsBoolean
ACCOUNT UPDATE EMAIL "old" TO "new" SET ?okChange the email address on an accountBoolean
ACCOUNT UPDATE PASSWORD "email" WITH "new_password" SET ?okChange the password on an accountBoolean

ACCOUNT REGISTER

Register a New Account
ACCOUNT REGISTER "alice@example.com" WITH "pass1234" SET ?uid
AFTER IF ?uid IS NULL
OPEN
  STATUS 409
  AFTER EMIT "Email already registered"
CLOSE
OR
OPEN
  EMIT "User created: " & ?uid
CLOSE

ACCOUNT LOGIN

Authenticates, and on success places the account in the current session. Start the session first — there is nowhere to put the account otherwise.

Authenticate and Sign In
START SESSION
AFTER ACCOUNT LOGIN "alice@example.com" WITH "pass1234" SET ?uid
AFTER IF ?uid IS NULL
OPEN
  STATUS 401
  AFTER EMIT "Invalid credentials"
CLOSE
OR
OPEN
  REDIRECT "/dashboard" AS "302"
CLOSE

Protecting a Route

Who is signed in is a session value, read like any other: !SESSION('account'). There is no account-specific verb for this, because the session system already answers the question.

A Page Only a Signed-In Account May See
START SESSION
AFTER SET !SESSION('account') AS ?uid
AFTER IF ?uid IS NULL
OPEN
  REDIRECT "/login" AS "302"
CLOSE
OR
OPEN
  EMIT "Welcome back, account " & ?uid
CLOSE
Signing out is KILL SESSION. It ends the browser's session, which is what signing out means. There is no ACCOUNT LOGOUT — it would have been a second way to do the same thing. To end every session a person has anywhere, on every device, see ACCOUNT REVOKE below.

Handling Auth Errors with CATCH ERROR

ACCOUNT LOGIN and ACCOUNT REGISTER throw an error on failure (wrong credentials, duplicate email) in addition to returning null. CATCH ERROR captures the error message into a variable; the chain continues normally afterward regardless of whether an error occurred.

CATCH ERROR — Recommended
START SESSION
AFTER ACCOUNT LOGIN "alice@example.com" WITH "wrongpass" SET ?uid
OR CATCH ERROR SET ?err
AFTER IF ?err IS NOT NULL
OPEN
  STATUS 401
  AFTER EMIT ?err
CLOSE
OR
OPEN
  EMIT "Welcome"
CLOSE
Simple Null Check — Loses the Error Message
START SESSION
AFTER ACCOUNT LOGIN "alice@example.com" WITH "pass1234" SET ?uid
AFTER IF ?uid IS NULL
OPEN
  STATUS 401
  AFTER EMIT "Invalid credentials"
CLOSE
OR
OPEN
  EMIT "Signed in"
CLOSE

Signing Out

End the Session
KILL SESSION
AFTER REDIRECT "/login" AS "302"

ACCOUNT REVOKE — Invalidate All Sessions

Revoking Every Session for an Account
ACCOUNT REVOKE "alice@example.com" SET ?count
AFTER EMIT "Revoked " & ?count & " sessions"

ACCOUNT VERIFY — Mark Account as Verified

After Confirming an Email Token
ACCOUNT VERIFY "alice@example.com" SET ?ok
AFTER EMIT ?ok

ACCOUNT COMPARE — Constant-Time Token Check

Always use ACCOUNT COMPARE instead of IS IDENTICAL TO when comparing secrets. It prevents timing attacks.

Safe Secret Comparison
ACCOUNT COMPARE ?submitted_token WITH ?expected_token SET ?valid
AFTER IF ?valid IS EQUAL TO true
OPEN
  EMIT "accepted"
CLOSE
OR
OPEN
  STATUS 401
CLOSE

ACCOUNT DELETE

Permanently deletes the account and every session associated with it. This cannot be undone.

Deleting an Account
ACCOUNT DELETE "alice@example.com" SET ?ok
AFTER IF ?ok IS EQUAL TO true
OPEN
  EMIT "Account deleted"
CLOSE
OR
OPEN
  STATUS 404
  AFTER EMIT "Account not found"
CLOSE

ACCOUNT UPDATE EMAIL

Changes the email address on an existing account. The account's permanent ?uid does not change — only the email used to log in.

Changing an Account's Email
ACCOUNT UPDATE EMAIL "alice@example.com" TO "alice.new@example.com" SET ?ok
AFTER IF ?ok IS EQUAL TO true
OPEN
  EMIT "Email updated"
CLOSE
OR
OPEN
  STATUS 409
  AFTER EMIT "New email already in use"
CLOSE

ACCOUNT UPDATE PASSWORD

Changing an Account's Password
ACCOUNT UPDATE PASSWORD "alice@example.com" WITH "new_secure_pass" SET ?ok
AFTER EMIT ?ok

Complete Registration + Login Flow (Site Mode)

POST /api/register
PARSE JSON !REQUEST('body') SET ?data
AFTER ACCOUNT REGISTER ?data("email") WITH ?data("password") SET ?uid
AFTER IF ?uid IS NULL
OPEN
  STATUS 409
  AFTER HEADER "Content-Type" AS "application/json"
  AFTER EMIT `{"error":"Email already registered"}`
CLOSE
OR
OPEN
  ACCOUNT LOGIN ?data("email") WITH ?data("password") SET ?signed_in
CLOSE
AFTER STATUS 201
AFTER HEADER "Content-Type" AS "application/json"
AFTER EMIT `{"uid":"` & ?uid & `"}`