Skip to content
Users and access

Users and access

PULSE keeps its own accounts in the users table. Each account has one role: admin, editor, or viewer. There are three ways an account comes to exist.

The break-glass admin

When PULSE_ADMIN_PASSWORD is set, the server creates or updates an account named admin with the admin role at every start. Change the username or email with PULSE_ADMIN_USERNAME and PULSE_ADMIN_EMAIL.

Without the variable, the log says No admin password injected (PULSE_ADMIN_PASSWORD/ADMIN_PASSWORD unset) — skipping admin seed; break-glass login unavailable until one is provided.

This is an emergency account. Give it a strong password from the secret store, and use Google sign-in and seeded roles for everyday access.

Google sign-in

Set GOOGLE_CLIENT_ID to turn on Sign in with Google, and set AUTH_ALLOWED_EMAILS to the people allowed to use it. Google sign-in fails closed:

SituationResult
GOOGLE_CLIENT_ID unsetNo Google button. The exchange endpoint answers google sign-in not configured (GOOGLE_CLIENT_ID unset).
Allowlist emptyNobody signs in with Google. The user sees google sign-in is not available on this instance.
Email not on the allowlistRefused with this account is not authorized for this instance.
First sign-in of an allowed emailA new viewer account with an unusable random password.

Set AUTH_GOOGLE_ONLY=true to hide the password form. The break-glass admin still exists, but the app no longer shows the form to reach it.

Seeded roles

AUTH_SEED_USERS grants roles by email, as a comma-separated list of email=persona pairs. CORE’s operator projects it from the ClientInstance’s user list. It is applied at every start.

PersonaRole
admin, org_admin, owneradmin
writer, editoreditor
reader, viewerviewer

An unknown persona grants nothing, and the entry is skipped with a warning. A seeded email that has no account yet gets one with an unusable password, so that person signs in with Google.

The Admin page

Admins see Admin in the navigation (/admin). It is the shared Runink members page (runink.ui.access.v1.AccessService): list members, invite, change a role, remove. The server reads the caller’s role fresh from the users table on every call, not from the token, and refuses anyone who is not an admin of this instance.

What a change does depends on where the roster lives:

DeploymentWhat happens to a change
PULSE may patch its own ClientInstance (PULSE_CLIENT_INSTANCE is set and the pod has permission)Written to the ClientInstance first, then to the users table. It is pending until the operator re-projects the roster and PULSE restarts.
AUTH_SEED_USERS is set, but PULSE cannot write the ClientInstanceRefused, with a sentence saying where access is changed. A local change would be undone at the next start.
NeitherThe users table is the roster. Changes apply at once.

When a role changes, PULSE ends that person’s sessions, so the new role takes effect at their next sign-in. There are no seats: invites are not limited by the subscription.

PULSE serves one instance per deployment. Its ID is PULSE_CLIENT_INSTANCE, else TENANT_ID, else pulse.

Sessions

Sessions live in the sealed app root, not in the database, so every replica sees the same state.

  • An access token lasts 24 hours. A refresh token lasts 7 days and works once. A session lasts at most 30 days.
  • Signing out revokes the session and every token issued in it.
  • The Account page can end all of a person’s sessions on every device.

Tokens issued before server-side sessions existed carry no session ID and are refused. Those users sign in again once.