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.
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:
| Situation | Result |
|---|---|
GOOGLE_CLIENT_ID unset | No Google button. The exchange endpoint answers google sign-in not configured (GOOGLE_CLIENT_ID unset). |
| Allowlist empty | Nobody signs in with Google. The user sees google sign-in is not available on this instance. |
| Email not on the allowlist | Refused with this account is not authorized for this instance. |
| First sign-in of an allowed email | A 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.
| Persona | Role |
|---|---|
admin, org_admin, owner | admin |
writer, editor | editor |
reader, viewer | viewer |
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:
| Deployment | What 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 ClientInstance | Refused, with a sentence saying where access is changed. A local change would be undone at the next start. |
| Neither | The 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.