Users and permissions
Lumaft authorizes every request against its own accounts, roles, and permissions. External identity providers can vouch for who a person is; they never decide what that person may do.
The model
account ──has exactly one──▶ fixed role ──resolves to──▶ permissions ──checked by──▶ every route
(administrator | viewer | transcript-reader)
integration token ───────────────────────────────────▶ operations:evidence:write only
- Accounts are local users. Each holds exactly one fixed role. Accounts are disabled, never deleted, so audit and evidence attribution always resolve to a real identity.
- Roles resolve to a set of permissions. Controllers check permissions, never role names.
- Integration tokens are not accounts. They carry one permission that no role has, and they cannot open the console.
- Server-side authorization is authoritative. Browser capability checks are presentation only.
| Capability | Edition |
|---|---|
| Local accounts with three fixed roles | Community |
| Custom roles composed from the permission catalog | Business |
| Stack read scopes | Enterprise |
| OIDC, SAML, trusted-proxy sign-in, SCIM | Enterprise |
Bootstrap the first administrator
On first startup, with no local identity in the database, Lumaft reads one password file and creates one administrator. It never bootstraps a second account from a file.
- Create a password of 12–4096 characters in a file below a protected directory: parent mode
0700, file mode0600, owned by UID 1000, no symlinks. - Set
LUMAFT_LOCAL_ADMIN_PASSWORD_FILEto its absolute path. - Optionally set
LUMAFT_LOCAL_ADMIN_USERNAMEbefore first startup. The default isadmin. A username is a lowercase letter followed by up to 63 lowercase letters, digits, periods, underscores, or hyphens. - Start Lumaft and sign in.
- Unset
LUMAFT_LOCAL_ADMIN_PASSWORD_FILEand securely delete the file.
Lumaft stores an Argon2id-derived credential, never the plaintext. Leaving or changing the file after bootstrap does nothing; it does not rotate the password.
If the database is deleted or replaced with an empty one, the next startup is a new bootstrap and requires the file again.
Recover a lost administrator password
Do not delete the database to regain access; that destroys the evidence Lumaft exists to keep. Use the one-shot file rotation instead:
- Create a new password file under the same protection rules.
- Set
LUMAFT_LOCAL_ADMIN_PASSWORD_FILEto its path andLUMAFT_LOCAL_ADMIN_PASSWORD_ROTATE=true. - Restart once. Startup replaces the credential of the account named by
LUMAFT_LOCAL_ADMIN_USERNAME, revokes all of its sessions in the same transaction, and logs one sanitized event. It changes no roles and does not re-enable a disabled account. - Unset both variables and securely delete the file.
While the flag stays set, every startup re-applies the file and revokes sessions again, and logs a warning telling you to remove it. A missing or malformed file fails startup rather than keeping the old credential silently.
For any other account, an administrator resets the password from Users & Access.
Roles and permissions
Fixed roles
| Role | Holds | Does not hold |
|---|---|---|
administrator |
Every permission except operations:evidence:write and operations:transcripts:read |
Transcript bodies; evidence submission |
viewer |
backends:read, observations:read, operations:read |
Any administration; transcript bodies |
transcript-reader |
The viewer's three read permissions plus operations:transcripts:read |
Any administration |
An administrator does not implicitly read transcript bodies. Assign the reader role to a separate account or change an existing account's fixed role deliberately.
Permission catalog
| Permission | Allows |
|---|---|
backends:read |
Read configured backend metadata |
observations:read |
Read projects, stacks, resources, redacted outputs, locks, update history |
operations:read |
Read the operation timeline and safe resource-change fields |
operations:transcripts:read |
View and download authorized retained transcript bodies |
operations:export |
Create bounded evidence exports |
operations:retention:manage |
Change retention policy; inspect expiry health |
operations:purge |
Perform confirmed scoped deletion |
accounts:manage |
Administer accounts, roles, identity providers, and stack grants |
integrations:admin |
Issue, scope, and revoke integration tokens |
backends:diagnostics:read |
Read backend diagnostic results |
storage:health:read |
Read storage-health, observation-polling, and object-store traffic reports |
operations:evidence:write |
Submit operation evidence — integration tokens only |
Add and manage accounts
Requires accounts:manage. There is no self-service signup, invitation flow, or password change
for non-administrators.
Add a user from the console
- Open Administration → Users & Access.
- Under Create account, enter a username (bootstrap rules above), a password of 12–4096 characters, and a role.
- Save. Share the password out of band; the user has no way to change it themselves.
Add a user from the API
curl -fsS -X POST https://lumaft.example.com/api/v1/accounts \
-H 'Content-Type: application/json' \
--data '{"username":"m.okafor","password":"<12+ characters>","role":"viewer"}'
| Route | Purpose |
|---|---|
GET /api/v1/accounts |
List local accounts |
POST /api/v1/accounts |
Create an account by name, password, and role |
PUT /api/v1/accounts/:userId/role |
Assign the account's single fixed role |
POST /api/v1/accounts/:userId/password |
Reset the password and revoke the account's sessions |
PUT /api/v1/accounts/:userId/status |
Disable or enable the account |
All account routes are session-authenticated. A bearer token presented to them is refused.
Rules
- A role change takes effect on the target's next request; sessions re-read roles at verification.
- Disabling an account or resetting its password revokes its sessions in the same transaction.
- An administrator cannot demote or disable their own account. The installation always keeps a working administrator.
- Offboarding is one action: disable the account. Its history and attribution remain.
Integration tokens
Integration tokens are the credential for machine evidence submission — deployment runners and
developer machines running lumaft exec. They are administered under integrations:admin
(administrators only).
Issue a token
- Open Administration → Users & Access and find Issue token.
- Provide:
- Name — for the audit trail, for example
ci-deploysorlaptop-mokafor. - Authority kind —
deployment-runnerordeveloper-workflow. - Scope — explicit backend, project, and stack patterns.
*matches every project (legacy-layout stacks included) or every stack. - Expiry —
developer-workflowdefaults to 90 days,deployment-runnerto none; an explicit 1–365 days overrides either.
- Name — for the audit trail, for example
- Copy the secret (
lumaft_it_…). It is shown once. Lumaft stores only its SHA-256 digest.
Store the secret in a CI secret store or a user-home credential file, never in project files. The wrapper enforces that rule by refusing a token file outside the home directory.
Lifecycle
- Revocation is immediate and tokens are revoked, never deleted.
- One active token per name. A revoked token frees its name for a successor.
- Rotation is issue-successor-then-revoke.
- Use is audited (last-use time) and failed presentations throttle per client like local sign-in. An authenticated token is limited to 60 requests per minute.
| Route | Purpose |
|---|---|
GET /api/v1/integrations |
List tokens, including revoked ones |
POST /api/v1/integrations |
Issue a named, scoped token |
POST /api/v1/integrations/:tokenId/revoke |
Revoke immediately |
A token authenticates exactly one surface, /api/v1/ingestion/operations, with
Authorization: Bearer lumaft_it_…. It never reads state, history, or transcripts, and never
opens a browser surface. How a runner uses it is in
Runner integration.
Custom roles (Business)
Custom roles let you grant a subset of the catalog — an auditor who can export but not purge, an operator who can manage retention but not accounts — over the same local accounts.
- A role composes only from the implemented catalog, minus
operations:evidence:write. - A role cannot grant a permission its author's own session does not hold.
- Assigning a custom role sets the account's fixed role to
viewerin the same transaction, so the fallback whenever custom roles are not entitled is always the viewer baseline. - The last enabled fixed administrator cannot take a custom role.
- A role still assigned cannot be removed.
- Every act, including refusals, writes one audit row.
| Route | Purpose |
|---|---|
GET /api/v1/roles |
List custom roles, assignments, and the gate state |
POST /api/v1/roles |
Create a named role from the catalog |
PUT /api/v1/roles/:name |
Replace the role's grant set |
DELETE /api/v1/roles/:name |
Remove an unassigned role |
PUT /api/v1/accounts/:userId/custom-role |
Assign; the fixed role becomes viewer |
DELETE /api/v1/accounts/:userId/custom-role |
Remove the assignment |
On license lapse, definitions and assignments are retained read-only and every assigned account operates as a viewer. Re-licensing restores the grants without touching a stored row.
Stack read scopes (Enterprise)
A grant names userId, backendId, projectName (null for a legacy-layout stack), and
stackName. There are no wildcards and no deny rules; each user has at most 1,000 grants.
Grants narrow retained-history reads: operations, resource history, reliability, retention audits, exports, and the scope used for transcript reads. They do not grant a permission the role lacks, and live observation pages remain installation-wide — this is not tenant isolation. A user with no grants reads the whole installation. Fixed administrators cannot be scoped.
| Route | Purpose |
|---|---|
GET /api/v1/authorization |
List grants |
POST /api/v1/authorization/grants |
Add a grant |
DELETE /api/v1/authorization/grants/:grantId |
Remove a grant |
Existing grants stay enforced on license lapse.
Federated sign-in (Enterprise)
Federation is additive. Keep a working local administrator and its recovery procedure; a provider outage never removes local sign-in. This section is the reference; step-by-step walkthroughs for Okta and Microsoft Entra ID, plus SCIM setup, are in Federated identity.
Register a provider
From Users & Access → Identity or POST /api/v1/identity/providers, supply:
| Field | Value |
|---|---|
issuerKey |
1–64 lowercase letters, digits, or dashes; permanently bound to the issuer |
issuer |
The exact OIDC issuer, SAML entity ID, or proxy realm |
provider |
oidc, saml, or trusted-proxy |
provisioningMode |
administered or on-first-login |
Identity is the registered issuer plus the provider's immutable subject. Email, display name, group, and role claims never select, link, or authorize an account.
administered— pre-link each subject to an existing user withPOST /api/v1/identity/links(issuerKey,subject,userId). An unknown subject fails without creating anything.on-first-login— a verified first sign-in creates a new viewer. It never creates an administrator.
Protocol settings
These are deployment environment variables, separate from the registration. <KEY> is the
issuer key uppercased with dashes changed to underscores (corp-idp → CORP_IDP). Set
LUMAFT_PUBLIC_ORIGIN to the externally reachable HTTPS origin for OIDC and SAML, and apply the
same settings to every replica.
| Provider | Variables | Provider-side configuration |
|---|---|---|
| OIDC | LUMAFT_OIDC_<KEY>_CLIENT_ID, LUMAFT_OIDC_<KEY>_CLIENT_SECRET |
Authorization-code client; redirect URI <origin>/api/v1/auth/oidc/<issuerKey>/callback; issuer discovery, PKCE, nonce |
| SAML | LUMAFT_SAML_<KEY>_SSO_URL, LUMAFT_SAML_<KEY>_IDP_CERT |
HTTP-Redirect SSO, signed responses, persistent NameID; SP entity ID <origin>/api/v1/auth/saml/<issuerKey>, ACS at …/acs |
| Trusted proxy | LUMAFT_TRUSTED_PROXY_<KEY>_SUBJECT_HEADER, LUMAFT_TRUSTED_PROXY_<KEY>_SHARED_SECRET |
Proxy forwards an immutable subject and proves itself with x-lumaft-trusted-proxy-secret (≥ 32 characters) |
SAML is SP-initiated only; unsolicited responses and encrypted assertions are refused. OIDC and
SAML round trips are single-use and expire after ten minutes. Sign-in starts at
/api/v1/auth/<provider>/<issuerKey>/start or from the console's provider action.
For trusted proxy, strip client-supplied identity and proof headers at the proxy, inject verified values, and restrict direct access to the application.
Offboarding a federated user
Disable or unlink the identity to revoke its sessions. Disabling a provider registration stops new sign-ins but does not revoke existing sessions. Do not use license expiry as an offboarding control; configured sign-in paths remain available on lapse.
SCIM provisioning
Issue a registration-bound SCIM token from Users & Access or
POST /api/v1/identity/scim-tokens (issuerKey, name). The base URL is
<origin>/api/v1/scim/v2/<issuerKey>.
- Supported: user create, read, replace, patch, delete;
ServiceProviderConfig,ResourceTypes,Schemas; filtering by subject equality. externalIdmust be the same immutable subject used at sign-in. If omitted,userNameis the subject, not an email lookup.- Creation assigns exactly the viewer role. Delete means deactivate; rows remain.
- Not supported: groups, bulk, sorting, ETags, password management. Group and role attributes never grant permissions.
Sessions and sign-in protection
- The session cookie is
__Host-lumaft-session: Secure, HttpOnly, SameSite=Strict. Lumaft stores only a SHA-256 digest of the 32-byte token. - Sessions have a twelve-hour absolute lifetime. Logout revokes immediately.
- Five failed attempts from one client address, or for one username/client pair, block further attempts for fifteen minutes. The throttle is process-local; enforce installation-wide limits at your ingress.
- Authentication failures on the ingestion API return one sanitized
401.
| Route | Purpose |
|---|---|
GET /api/v1/auth/session |
Read the current authentication state |
POST /api/v1/auth/login |
Verify local credentials and create a session |
POST /api/v1/auth/logout |
Revoke the current session |
Securing the deployment
- TLS everywhere off loopback. The Secure cookie will not survive plain HTTP. Never set
LUMAFT_ALLOW_HTTPon a network-reachable deployment. - Retire the bootstrap file. Unset
LUMAFT_LOCAL_ADMIN_PASSWORD_FILEafter first sign-in. - Give people their own accounts. Do not share the bootstrap administrator. Create a named administrator per operator and keep the bootstrap account as the recovery path.
- Default to viewer. Grant
administratoronly to people who administer. On Business, use custom roles to hand outoperations:exportoroperations:retention:managewithout the rest. - Separate transcript readers. Transcript bodies are sensitive; the reader role exists so that access is a deliberate, auditable assignment.
- Scope tokens narrowly. One token per pipeline, scoped to the stacks it deploys, with an expiry where the runner is not permanent. Revoke on rotation; never reuse a secret.
- Keep the observer identity read-only. Never add
operations:evidence:writesemantics to the S3 identity or S3 write permissions to the observer. They are different credentials on purpose. - Protect the database and its backups like credentials. They contain every account's credential hash, every token digest, and every retained observation.
- Alert on administration. Account, role, token, retention, purge, and hold actions each write audit rows. On Enterprise, forward the administration core-event feed to your external audit sink.