OriginChainDB docs
dashboard · users & access

Database users and access.

Access in OriginChainDB has two separate layers. Console access decides who can sign in to the dashboard and what they can do there — that's org members and roles, covered on Roles and permissions. Database access decides who can reach the database over the wire and what rows and columns they get back. That's this page.

The two never overlap. Adding a teammate to your org does not give them a database login, and creating a database user does not give anyone a console account.

The Security group.

Everything on this page and the next lives under Security in the console sidebar. Nine entries, in this order:

sidebar entry path what it is
API keys /keys The one shared bearer token per database. Rotate it here.
Access tokens /tokens Your own personal bearer tokens, one set per database. Rotate and revoke independently of everyone else's.
Members & teams /members Who can sign in to the console, and their org role. Covered on the RBAC page.
Roles & permissions /roles The org capability matrix, plus custom roles. Covered on the RBAC page.
Database users /db/users Principals that connect to the database itself and run queries.
Database RBAC /db/rbac Database roles and the privilege matrix that grants them access to tables.
Row-level security /db/rls Per-role row filters on a table, plus a tester for “what would this user see?”.
Cluster audit /db/audit The hash-chained log of every database user / role / grant / policy change.
Audit log /audit Org-level activity for the account (members, databases, billing).

The four /db/* pages act on whichever database is selected in the top-bar picker. Pick it once; every page follows.

Database users.

A database user is a principal that connects to the database and runs queries. Open Security → Database users. The table lists every user with its authentication kind, the roles attached to it, how many API keys it holds, and when it was created. A disabled user is flagged inline.

The Database users page for a database named acme, listing four users: analyst_ro with the analyst role, app_rw with the app_writer role, reporting authenticating by password, and a disabled etl_legacy user. Each row shows its auth kind, role pills, API key count, creation date, and plus key and delete actions. The Security group in the left sidebar lists API keys, Access tokens, Members and teams, Roles and permissions, Database users, Database RBAC, Row-level security, Cluster audit, and Audit log.
Security → Database users. Roles decide what a user can read; the RBAC page decides what those roles mean.

Create a user.

+ Create user opens a dialog with exactly three inputs. There is no read-only switch here — what a user can do comes from the roles you attach and the grants those roles hold, which you set on Database RBAC.

  • Username — free text, e.g. analyst_ro.
  • AuthAPI key or Password (SCRAM). Picking Password reveals a password field beside the dropdown.
  • Roles — comma-separated role names that already exist on the database.
The Create database user dialog with the username field set to analyst_ro, the auth dropdown set to API key, and the roles field set to analyst. Close and Create buttons sit at the bottom.
Three fields. Switching auth to Password (SCRAM) adds a password input next to the dropdown.

An API-key user's key is returned once, in the dialog, under a API KEY — COPY NOW, SHOWN ONCE banner. Copy it before you close the dialog; the console cannot show it again, because only a hash is stored.

The same dialog after creating the user. A highlighted panel reads API key, copy now, shown once, above an example key value of oc_key_EXAMPLE0000000000, with a created confirmation beneath it.
The key is shown once. The value pictured here is a placeholder, not a real credential.

Rotating and removing.

Each row carries two actions. + key issues an additional API key for that user and shows it once, in a browser prompt — useful for handing a second consumer its own credential, or for rolling one over before you retire the old one. delete asks for confirmation and then removes the user outright.

There is no session list in the console, and nothing to "sign out". Deleting a user removes it from the database's authorization store, so the next request that identifies as that user is rejected as unauthorized rather than served. In-flight requests already executing are not interrupted.

how a client becomes a database user

Database users are opt-in on the data plane. A request identifies one by sending X-OC-DB-User and X-OC-DB-Key alongside the usual bearer. A request that sends neither runs with the database's own administrative identity, which is a superuser: no role checks, no column masking, no row filters. That is the behaviour every SDK and every quickstart example gets by default, and it is the single most important thing to understand on this page — creating users and grants does not restrict anything until your clients actually present those headers.

password users need a key too

A user created with Password (SCRAM) stores SCRAM-SHA-256 credentials, but the HTTP data plane authenticates database users by API key today. Give a password user a key with + key if it needs to issue queries over HTTP.

API keys and access tokens.

The console issues three different credentials, from three different places. They are not interchangeable, and mixing them up is the usual cause of a confusing 401.

credential looks like issued from scope
Shared database bearer oc_live_… API keys One per database, shared by everyone and every deployed client. Rotate replaces it. Every client still using the old value stops working.
Personal access token oc_u_… Access tokens Yours alone, one or many per database. Carries read/write or read-only. Rotate or revoke affects only that token — nobody else's breaks.
Database-user API key issued per database user Database users Belongs to a database user, so queries run as that user with its roles and grants. “+ key” issues another; deleting the user removes them all.

Personal access tokens.

Security → Access tokens lists one card per database, and inside it your own tokens for that database. Give a token a name, optionally tick read-only, and press Create token. Existing tokens can be rotated or revoked one at a time, and each row shows a truncated prefix plus a READ/WRITE or READ-ONLY badge.

The Access tokens page showing one card for the acme database with three personal tokens: laptop marked read/write, ci marked read-only, and a revoked old-laptop token. Each active row has Rotate and Revoke buttons. A create row at the bottom has a token name field, a read-only checkbox, and a Create token button.
Personal tokens are per-member. Rotating or revoking one of yours leaves everyone else's alone.

A token's access level is derived from your org role rather than chosen freely: owner, admin and cluster operator mint read/write tokens; every other role, including custom ones, mints read-only. Ticking read-only steps down from that ceiling but never up. The database enforces the restriction: a read-only token is refused on write requests. The level is also re-derived from your current role whenever that database's token set is rebuilt, so a demotion narrows the tokens you already hold.

shown once, always

Every credential the console mints is displayed exactly once, at creation or rotation, and stored only as a hash afterwards. That is why the Connection tab on a database shows a literal Bearer <your-token> placeholder in its example request instead of your real value — there is nothing to display. If you lose a token, rotate it; you cannot recover it.

If a credential leaks.

  • A personal token — revoke it on Access tokens. Only that token stops working; deployed clients using the shared bearer are untouched.
  • The shared bearer — rotate it on API keys, then redeploy every client that carries it. This one is disruptive by design: the old value stops working immediately.
  • A database-user key — on Database users, delete the user (removing all its keys) or issue a fresh key with + key and move consumers over.
  • Then tighten the blast radius: put the source addresses on the IP access list below, and check Cluster audit for authorization changes you did not make.

Network access.

Credentials decide who; the IP access list decides from where. Open your database from Databases and pick the Network access tab.

The default is an empty list, which allows any address. Add a single entry and the rule flips: only the listed ranges can connect, over both HTTPS and the Postgres wire protocol. The status line above the editor spells out which state you are in.

The Network access tab of the acme database, with IP Access List, Peering and Private Endpoint sub-tabs. The IP Access List panel reads two addresses allowed, everything else is blocked, above an add-entry row and a table listing 203.0.113.4/32 labelled Office VPN and 198.51.100.0/24 labelled Application subnet, each with Edit and Remove actions, plus Save changes and Discard buttons.
Edits are staged. Nothing changes on the database until you press Save changes.
  1. 1. Type an address or range into IP address. A bare IPv4 address is stored as a single host (/32); a bare IPv6 address becomes a /128. Explicit CIDR ranges are accepted as typed.
  2. 2. Add an optional Description so the entry still means something in six months, then press Add entry.
  3. 3. The shortcuts do the obvious things: + Add my current IP address fills in the address you're browsing from, and + Allow access from anywhere appends 0.0.0.0/0.
  4. 4. Press Save changes to apply, or Discard to drop the staged edits. The console says changes take effect within a few seconds.
you can lock yourself out

If your own address isn't on the list you lose direct access to the endpoint. The console keeps working, so you can re-open access by clearing the list. Traffic between the nodes of a multi-node database is never affected by this list.

On a dedicated database the tab carries two more sub-tabs, Peering and Private Endpoint. Both are request forms rather than self-serve toggles: you submit your network details and our team completes the connection and sends back the acceptance details. A free database has neither — the IP access list is its only network control, and the console hides the other two entirely.

Row-level security.

Security → Row-level security attaches a USING predicate to a (table, role) pair. When a query reads that table as a user holding that role, rows that fail the predicate are dropped from the result. With no policies on a table, every role reads every row.

The predicate language the console documents on the page itself is: =, !=, < <= > >=, IN (…), IS [NOT] NULL, AND / OR / NOT, and CURRENT_USER, which resolves to the acting database user's name.

The Row-level security page listing two policies: shop.orders for the analyst role using region IN quote in quote comma quote sg quote, and shop.customers for the analyst role using owner equals CURRENT_USER AND deleted_at IS NULL, both for the SELECT command. The effective-policy tester panel on the right has analyst_ro and shop.orders selected and reports a RESTRICTED verdict with the matching predicate underneath.
Policies on the left; the tester on the right answers “what would this user actually see?” without running a query as them.

+ Policy asks for a table, a role and the predicate; all three are required. The CMD column shows which command the policy covers — policies created from this page cover reads. The effective-policy tester takes a database user and a table and reports one of three verdicts: superuser — sees all, restricted, or unrestricted, listing the policies that apply.

scope of a policy

Policies apply to reads made by a request that identifies a database user. A request that carries only the database bearer is the administrative superuser and sees every row — the tester says so in as many words. Row filtering runs before column masking, so a policy sees real cell values even on columns that will be masked in the response.

Cluster audit.

Security → Cluster audit is the append-only, hash-chained record of authorization changes on the database. Each entry carries the previous entry's hash, so altering or deleting anything in the past invalidates every hash after it.

The Cluster audit page showing six chained entries with sequence numbers, timestamps, an ll_admin actor, and actions role.create, grant.put, user.create, rls.put, user.rotate_key and user.delete, each with a target, a JSON detail column and a truncated hash. A green banner above the table reads chain intact, six entries, chain head verified.
Verify chain recomputes every link. Export NDJSON hands the same records to your log pipeline.

Every row is one authorization change. The actions you will see are user.create, user.delete, user.rotate_key, role.create, role.delete, grant.put, grant.revoke, rls.put and rls.delete. The target names what changed — a username, or role@object for a grant or policy — and detail holds the change itself as JSON. Hover a truncated hash or detail cell to see it in full.

Verify chain recomputes every link and reports either chain intact or a tamper verdict naming the first broken sequence number. Export NDJSON downloads the whole chain, one JSON object per line, for a SIEM or log pipeline.

This log covers the database's authorization surface only. Account-level activity — members joining, databases being created, billing changes — lives in the separate org Audit log at the bottom of the Security group.

What's enforced, and what isn't.

Access-control screens are easy to over-read, so here is the honest boundary of the database layer as the console ships it today. Treat everything not on the enforced list as a record of intent — useful for review and audit, not as a barrier.

control applies when
IP access list Always, on every connection to the endpoint, once the list is non-empty. Independent of any credential.
Read-only personal token Always, for that token. Write requests are refused; the level follows your current org role.
Database-user identity Only when the request sends the database-user headers. Without them the caller is the administrative superuser.
SELECT grants, column masking, row-level policies On reads by an identified database user. A user without SELECT on a table is refused; masked columns come back as •••••; policy predicates drop rows.
Insert / update / delete / references grants Recorded on the grant and shown in the privilege matrix. They are not a write barrier today — to stop writes, use a read-only token or a separate database.

If you need a guarantee stronger than the row above, say so before you design around it — write us at support@originchain.ai.