App user authentication
Covers 6 API operations in the app-auth category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs/{orgSlug}/projects/{projectName}/auth | End-user auth status (runlot auth) |
| POST | /v1/orgs/{orgSlug}/projects/{projectName}/auth | Turns on auth (runlot deploy calls this after reading the "auth": true declaration) |
| PATCH | /v1/orgs/{orgSlug}/projects/{projectName}/auth | Change settings (runlot auth set) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/auth | Disables auth (runlot auth delete) |
| PUT | /v1/orgs/{orgSlug}/projects/{projectName}/auth/providers/{provider} | Register or replace a social provider (runlot auth provider set) |
| DELETE | /v1/orgs/{orgSlug}/projects/{projectName}/auth/providers/{provider} | Disconnect a login provider (runlot auth provider rm) |
GET /v1/orgs/{orgSlug}/projects/{projectName}/auth
Whether granted, configured, registered providers, and the host to point callbacks at (docs/auth.md §9).
The subscriber table is not part of this API. User information is stored in the
runlot_auth schema of the project's Postgres (docs/auth.md §6), and CP does not query
this table. You can check the current user list via the worker's env.auth.users.* and
runlot pg shell.
providers carries only the client id. The client secret is sealed and never appears in
any response (the secret-surface rule).
viewer or above — whether this app has login is a fact anyone in the org should be able to know.
operationId getAuth
| Status code | Description | Response body |
|---|---|---|
| 200 | Auth status | AuthStatus |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core — no connection to cp-core | Error |
POST /v1/orgs/{orgSlug}/projects/{projectName}/auth
This is idempotent — calling it twice leaves the settings and login providers unchanged. Because the sole representation of enablement is the record's existence (migration 0024), there is no "on but disabled" state.
A database is required. Without one, it returns 409 database_required. Because the
subscriber table lives inside the project DB (docs/auth.md §1), enabling auth alone
leaves the worker without the table it needs at startup. This is why declaring "auth": true also enables the database, and deploys grant it in that order, so this 409 is
never seen there.
Once enabled, the node starts the auth system worker as the socket's entry service on
the next convergence. As with env.db and env.storage, no redeploy is needed. Table
initialization is idempotent, so enabling it twice performs it only once.
There is no request body. Settings start at their defaults (the defaults of
AuthSettings), and changing them is done with PATCH.
member and above. Audited as auth.grant.
operationId grantAuth
| Status code | Description | Response body |
|---|---|---|
| 200 | Grant status (existing value if it was already there) | AuthStatus |
| 403 | — | — |
| 404 | — | — |
| 409 | database_required — this project has no database. Set "database": true in runlot.json and deploy. | Error |
| 503 | no_core | Error |
PATCH /v1/orgs/{orgSlug}/projects/{projectName}/auth
Is a partial update. Only the fields you send change, and the rest stay as they are. If the UI sends all three brand settings every time a single switch changes, a later save can overwrite an earlier one when two users edit the same settings.
Returns 404 not_granted if the usage setting does not exist. You cannot save settings
alone ahead of time, because the setting values are stored together with the usage
setting record.
sessionDays is a setting that also determines the size of the session table
(docs/auth.md §13 — it stays within the 150 MB cap).
Requires member or higher. Audited as auth.settings.
operationId updateAuthSettings
Request body: application/json · AuthSettingsPatch
| Status code | Description | Response body |
|---|---|---|
| 200 | The status that changed | AuthStatus |
| 400 | — | — |
| 403 | — | — |
| 404 | not_granted — this project has no auth. When the project itself does not exist, the code is not_found, not this one. | Error |
| 503 | no_core | Error |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/auth
Subscriber data is not deleted. The runlot_auth table stays in the project DB as
is (docs/auth.md §9) — the data belongs to the DB, and deleting it is runlot pg's job.
Turning it back on lets the same people log in as before.
Starting from the next convergence, the auth system worker goes down, and
/__runlot/auth/* passes straight through to the user worker — if that path has no
route, it's a 404.
Disabling is also idempotent. It returns 204 even when auth was not enabled.
admin and above — enabling it takes member, but disabling it signs out everyone who is
logged in. Audited as auth.revoke.
operationId revokeAuth
| Status code | Description | Response body |
|---|---|---|
| 204 | detached | — |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |
PUT /v1/orgs/{orgSlug}/projects/{projectName}/auth/providers/{provider}
A project registers its own OAuth app. There is no shared platform app because the consent screen, review, and privacy are all owned by the app's owner (docs/auth.md §12).
The callback URL to register with the provider is per host:
https://<host>/__runlot/auth/callback/<provider>. The host list is in
AuthStatus.hosts.
clientSecret is sealed with the same DEK and the same AAD as
project_secrets (named with the reserved prefix auth/<provider>), and
never appears in any response again. Rotation uses the same PUT — it is
idempotent, so retries are safe.
Requires admin or higher — this is a credential. Audit auth.provider.set
(the secret is not recorded).
operationId setAuthProvider
Request body: application/json · AuthProviderPut
| Status code | Description | Response body |
|---|---|---|
| 200 | The status that changed | AuthStatus |
| 400 | invalid_provider — the name is outside github, google, kakao, or clientId/clientSecret is empty. | Error |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |
DELETE /v1/orgs/{orgSlug}/projects/{projectName}/auth/providers/{provider}
Anyone who used only that login provider will no longer be able to log
in. The user record remains, and only the matching entry in identities
is deleted. Registering it again restores login with the same account.
Deleting a login provider that does not exist also returns 204.
admin or above. Audit auth.provider.delete.
operationId deleteAuthProvider
| Status code | Description | Response body |
|---|---|---|
| 204 | detached | — |
| 403 | — | — |
| 404 | — | — |
| 503 | no_core | Error |