Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .agents/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ editing templates or values.
`server.configuration` file mount). Never mirror authup's config schema in
templates (Authelia's 714-line configMap treadmill is the cautionary tale).
`server.config` keys colliding with first-class env names fail the render.
The ONE mirrored schema is the theme manifest (`server.theme.title` /
`logo` / `tokens` / ... compose `theme.json`), and it earns the exception
on three counts: the file is a fixed 8-key document rather than a growing
config surface, authup fails the BOOT on an unknown key or a malformed
token so a typo has no cheaper detector, and the alternative is a JSON
blob inside a YAML string with no schema at all. It stays worth it only
while the manifest stays small: `files` remains the escape hatch, and a
hand-written `theme.json` there is still supported (the two are mutually
exclusive by validation).
15. **URL derivation is the chart's core UX.** `PUBLIC_URL`,
`NUXT_PUBLIC_API_URL`, `NUXT_PUBLIC_PUBLIC_URL` derive from the two
ingress blocks; the UI origin is auto-appended to `TRUSTED_ORIGINS`
Expand Down
24 changes: 21 additions & 3 deletions .agents/references/authup.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Repo: https://github.com/authup/authup (local checkout commonly at
`/opt/projects/authup/authup`). The chart encodes facts about the app; verify
against these sources when authup releases change behavior. Pinned against the
v1.0.0-beta.58 line (chart `appVersion`).
v1.0.0-beta.62 line (chart `appVersion`).

## Image / entrypoint contract

Expand All @@ -14,6 +14,7 @@ v1.0.0-beta.58 line (chart `appVersion`).
| Image runs as root; writable paths `/usr/src/app/writable` + npm cache | `Dockerfile` (`WRITABLE_DIRECTORY_PATH`, no `USER`) | emptyDir mounts + `npm_config_cache=/tmp/.npm-cache`; root securityContext default |
| `latest`/`<version>`/`beta`/`next` tags | `.github/workflows/release.yml`, `docker-nightly.yml` | `image.tag` defaults to `Chart.AppVersion` |
| `authup` CLI supervisor NOT routable through the entrypoint | `entrypoint.sh` case statement | chart never offers a combined pod |
| An unknown service arg EXITS 1 since beta.59 (it used to exit 0 and start nothing); `client/web` was renamed `client/admin-console` with no alias | `entrypoint.sh` `*)` branch | chart already passes `client/admin-console` |

## server-core env surface

Expand All @@ -29,9 +30,13 @@ Docs mirror: `docs/src/guide/deployment/configuration-server-core*.md`.
| `SMTP` (URL form; per-field SMTP is config-file-only) | `secret-smtp.yaml` |
| `PUBLIC_URL`, `TRUSTED_ORIGINS`, `TRUST_PROXY` (app default trusts every hop; chart pins "1") | `_urls.tpl` + `authup.server.configEnv` |
| `REGISTRATION_ENABLED`, `PASSWORD_RECOVERY_ENABLED`, `EMAIL_VERIFICATION_ENABLED`, `MFA_ENABLED`, `MFA_REQUIRED` (strict booleans: unparsable value crashes boot) | `server.features.*` / `server.mfa.*`, always quoted |
| `ACCOUNT_CONSOLE_ENABLED` (beta.62, default true): serves the `/account` self-service SPA off the IdP origin | `server.features.accountConsole` |
| `THEME_DIRECTORY_PATH` / `THEME_FRAGMENTS_ENABLED` (beta.59, EXPERIMENTAL): operator theme for the two served consoles; manifest at `<root>/theme.json`, HTTP mount root is `<root>/assets` only | `server.theme.*` (the chart composes theme.json) |
| `AUTH_CONSOLE_PATH` / `ACCOUNT_CONSOLE_PATH`: substitute a whole console package, boot-asserted `CONTRACT_VERSION` | deliberately NOT first-class; `server.config` + `extraVolumes` escape hatch |
| `USER_ADMIN_PASSWORD(_RESET)`, `CLIENT_SYSTEM_ENABLED/SECRET(_RESET)` | `auth.*` values + chart-managed secret |
| `SECRETS_ENCRYPTION_KEY` (base64 32 bytes; write-once, removal with wrapped rows fails loud) | `auth.secretsEncryptionKey(+Enabled)`, never generated, never optional |
| Cross-field boot validations (throttle needs event log, mfaRequired needs mfaEnabled, KEK length) | mirrored as render-time guards in `templates/validations.yaml` |
| `TRUSTED_ORIGINS` rejects `**` in a host at boot since beta.59 (`config/origins.ts`, `patternHasGlobstarInAuthority`); a single `*` is a supported host wildcard | `authup.assertTrustedOrigin` in `_urls.tpl`, asserted after tpl rendering |

Config file: `authup.server.core.conf` in the process cwd
(`app/modules/config/read/fs.ts`; env always wins) -> `server.configuration` /
Expand All @@ -47,8 +52,10 @@ and dead): `apps/client-admin-console/nuxt.config.ts`,
`NUXT_PUBLIC_API_URL` (browser-reachable server URL), `NUXT_PUBLIC_PUBLIC_URL`,
`NUXT_API_URL` (SSR-side override), `NUXT_PUBLIC_COOKIE_DOMAIN` (deliberately
never set by the chart: sharing a cookie domain with the server origin is
unsupported per `.agents/architecture.md` in the monorepo). Chart counterpart:
`_admin-console-env.tpl`.
unsupported per `.agents/architecture.md` in the monorepo),
`NUXT_PUBLIC_CLIENT_ID` (beta.59+, defaults to the per-realm built-in
`admin-console` client; fork-only override, reachable via
`adminConsole.config`). Chart counterpart: `_admin-console-env.tpl`.

## Operational contract

Expand All @@ -65,3 +72,14 @@ unsupported per `.agents/architecture.md` in the monorepo). Chart counterpart:
ServiceMonitor targets the Service; ingress warning in values/NOTES.
- In-process cron sweepers (oauth2-cleaner, event-cleaner) are idempotent
deletes; no leader election needed.
- Reserved client names: `admin-console` and `account-console` are provisioned
as built-in system clients in EVERY realm and take over a pre-existing client
of that name (beta.59). The shared per-realm `web` client was removed in the
same release; `TRUSTED_ORIGINS` now feeds the system clients' redirect
allowlists. NOTES warns against declaring either name in
`server.provisioning`.
- beta.60 ships a heavy migration (140 indexes, MySQL `varchar(36)` ->
`varchar(255)` table rewrites, three dropped tables) and beta.62 adds a
unique constraint on `auth_identity_provider_accounts` that ABORTS the boot
on pre-existing duplicates. Both are arguments for
`server.migration.enabled` on an upgrade, not just for multi-replica.
15 changes: 15 additions & 0 deletions .agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,23 @@ helm template t charts/authup --set server.publicUrl=auth.example.com # s
helm template t charts/authup --set postgresql.enabled=false --set externalDatabase.host=db # extdb w/o password
helm template t charts/authup --set server.ingress.enabled=true # ingress w/o hostname
helm template t charts/authup --set server.config.PUBLIC_URL=http://x # first-class collision
helm template t charts/authup --set server.theme.enabled=true # theme with no carrier
helm template t charts/authup --set server.theme.enabled=true --set server.theme.title=X --set server.theme.existingConfigMap=cm # manifest + existing CM
helm template t charts/authup --set server.theme.enabled=true --set server.theme.logo=logo.svg # asset outside assets/
helm template t charts/authup --set server.theme.enabled=true --set server.theme.logo=assets/logo.svg # asset missing from files
helm template t charts/authup --set server.theme.enabled=true --set 'server.theme.tokens.--authup-bg=url(x)' # token value authup rejects
helm template t charts/authup --set 'server.trustedOrigins[0]=https://**.x' # globstar host
```

A single `*` host wildcard (`https://*.example.com`) must still RENDER: authup
supports it, only `**` is the allow-any-origin trap.

The `server.theme` manifest guards assert the value AFTER `tpl` rendering, so
both directions need a case: a templated token or asset path must RENDER, and
one whose rendered result is illegal must FAIL. Validating the raw value gets
this backwards in a way that looks correct (every `{{ ... }}` contains `}`, so
the forbidden-character check rejects it for the wrong reason).

The generated `values.schema.json` must keep catching typos
(`--set server.replicaCountt=3` fails) while free-form maps stay open
(`--set server.config.X=y`, `--set server.resources.limits.cpu=1` succeed).
Expand Down
12 changes: 8 additions & 4 deletions charts/authup/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Authup is an authentication & authorization system. This chart
with optional built-in PostgreSQL, MySQL and Valkey instances.
type: application
version: 0.2.0
appVersion: "1.0.0-beta.58"
appVersion: "1.0.0-beta.62"
kubeVersion: ">=1.25.0-0"
home: https://authup.org
icon: https://raw.githubusercontent.com/authup/helm/master/assets/icon.svg
Expand All @@ -31,7 +31,11 @@ annotations:
- name: Source
url: https://github.com/authup/helm
artifacthub.io/changes: |
- kind: fixed
description: Chart license corrected to Apache-2.0
- kind: changed
description: appVersion tracks authup 1.0.0-beta.62
- kind: added
description: Chart icon
description: server.theme manifest values compose theme.json (title, logo, tokens)
- kind: added
description: server.features.accountConsole toggles the /account self-service console
- kind: added
description: Trusted origins carrying "**" now fail the render, as authup fails the boot
72 changes: 64 additions & 8 deletions charts/authup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
# authup

![Version](https://img.shields.io/badge/Version-0.2.0?style=flat-square&color=informational) <!-- x-release-please-version -->
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-beta.58](https://img.shields.io/badge/AppVersion-1.0.0--beta.58-informational?style=flat-square)
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 1.0.0-beta.62](https://img.shields.io/badge/AppVersion-1.0.0--beta.62-informational?style=flat-square)

Authup is an authentication & authorization system. This chart deploys the server-core IdP/API service and the client-admin-console admin UI, with optional built-in PostgreSQL, MySQL and Valkey instances. It deploys:

- **server-core** — the Authup IdP/API service: the OAuth2/OIDC protocol
surface plus the server-rendered auth pages (login, consent, registration,
password recovery). This is the identity origin.
surface, the server-rendered auth pages (login, consent, registration,
password recovery) and the `/account` self-service console
(`server.features.accountConsole`). This is the identity origin.
- **client-admin-console** — the Nuxt-based admin UI, an ordinary OAuth2 relying party
(optional; disable with `adminConsole.enabled=false` for a headless IdP).
- optionally, single-instance **PostgreSQL**, **MySQL** or **Valkey** built-in
Expand Down Expand Up @@ -88,6 +89,53 @@ Notable operational facts (enforced or warned about by the chart):
`server.configuration` file. See the
[Authup configuration reference](https://authup.org).

## Theming the served consoles

Both consoles server-core serves (the auth pages and `/account`) are rebranded
from a directory the chart mounts read-only. Set the manifest as values and the
chart composes `theme.json` for you; `files` carries the assets it references:

```yaml
server:
theme:
enabled: true
title: Sign in to ACME
logo: assets/logo.svg
stylesheet: assets/theme.css
tokens:
# the accent the whole primary palette is mixed from
--authup-periwinkle: "#c0392b"
--authup-surface-card: "#ffffff"
tokensDark:
--authup-surface-card: "#201e1d"
files:
assets/logo.svg: |
<svg xmlns="http://www.w3.org/2000/svg" ...></svg>
assets/theme.css: |
.a-auth-shell-card { border: 1px solid var(--authup-surface-border); }
```

A colour in `tokens` wins in dark mode too, so surface colours belong in both
maps. The chart rejects at render time what Authup rejects at boot or answers
with a 404: an asset outside `assets/`, an asset no file provides, a token name
that is not a lowercase custom property, and a token value carrying `url(` or
`;`. Only `assets/` is served over HTTP, so `theme.json` is unreachable by
construction.

`existingConfigMap` replaces the whole mechanism when you need binary assets
(`binaryData`); it is mounted whole, so it must carry `theme.json` itself and
cannot be combined with the manifest values. `fragmentsEnabled` splices
`fragments/head.html` into the console `<head>` verbatim: raw operator markup
on the origin that holds your users' session cookies, hence opt-in.

> The theme directory is as sensitive as the config file: CSS there can restyle
> or cover the OAuth2 consent buttons. Never source it from somewhere a tenant
> or a lower-privileged CI job can write.

Theming is experimental upstream: the directory layout and the `theme*` options
may change in an Authup minor release. See the
[Authup theming guide](https://authup.org/guide/deployment/theming.html).

## GitOps / ArgoCD

The generate-once-keep-forever behavior of empty passwords relies on helm's
Expand Down Expand Up @@ -297,7 +345,7 @@ Kubernetes: `>=1.25.0-0`
| server.autoscaling.hpa.targetCPU | int | `75` | Target CPU utilization percentage |
| server.autoscaling.hpa.targetMemory | string | `""` | Target memory utilization percentage |
| server.command | list | `[]` | Override the container command |
| server.config | object | `{}` | Extra environment variables rendered literally into the env ConfigMap (map of NAME: value) for options without first-class values |
| server.config | object | `{}` | Extra environment variables rendered literally into the env ConfigMap (map of NAME: value) for options without first-class values, e.g. AUTH_CONSOLE_PATH / ACCOUNT_CONSOLE_PATH, which replace a served console with your own build (pair them with extraVolumes; the substituted package owns the login flow, so use server.theme for branding instead) |
| server.configuration | string | `""` | Content of an authup.server.core.conf mounted into the working directory for file-only options (middleware objects, per-field SMTP, CORS allowlist). Environment variables always win over file values. |
| server.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"enabled":true,"readOnlyRootFilesystem":false,"runAsNonRoot":false,"runAsUser":0,"seccompProfile":{"type":"RuntimeDefault"}}` | Container security context. The upstream image runs as root and needs a writable npm cache; the chart mounts emptyDirs at /usr/src/app/writable and /tmp to keep readOnlyRootFilesystem viable. |
| server.customLivenessProbe | object | `{}` | Custom liveness probe |
Expand All @@ -311,6 +359,7 @@ Kubernetes: `>=1.25.0-0`
| server.extraEnvVarsSecret | string | `""` | Extra Secret with environment variables (tpl-rendered name) |
| server.extraVolumeMounts | list | `[]` | Extra volume mounts (tpl-rendered) |
| server.extraVolumes | list | `[]` | Extra volumes (tpl-rendered) |
| server.features.accountConsole | bool | `true` | Serve the account self-service console at <publicUrl>/account (profile, password, authenticators, sessions, applications). ACCOUNT_CONSOLE_ENABLED; disable it when you run your own portal |
| server.features.emailVerification | bool | `false` | Enable email verification (EMAIL_VERIFICATION_ENABLED; requires SMTP) |
| server.features.passwordRecovery | bool | `false` | Enable password recovery (PASSWORD_RECOVERY_ENABLED; requires SMTP) |
| server.features.registration | bool | `false` | Enable self-service user registration (REGISTRATION_ENABLED) |
Expand Down Expand Up @@ -406,15 +455,22 @@ Kubernetes: `>=1.25.0-0`
| server.startupProbe.successThreshold | int | `1` | |
| server.startupProbe.timeoutSeconds | int | `5` | |
| server.terminationGracePeriodSeconds | int | `30` | Pod termination grace period (server-core tears down within ~10s after signal) |
| server.theme.enabled | bool | `false` | Mount an operator theme for the served consoles (the auth console and the account console). Requires an authup image that supports THEME_DIRECTORY_PATH; older images ignore it |
| server.theme.existingConfigMap | string | `""` | Existing ConfigMap holding the theme (tpl-rendered name). Use for binary assets, which cannot be expressed in files |
| server.theme.enabled | bool | `false` | Mount an operator theme for the served consoles (the auth console and the account console). Requires an authup image that supports THEME_DIRECTORY_PATH; older images ignore it. Experimental upstream: the directory layout and the theme* options may change in a minor release |
| server.theme.existingConfigMap | string | `""` | Existing ConfigMap holding the theme (tpl-rendered name). Use for binary assets, which cannot be expressed in files. Mounted whole, so it must carry theme.json itself and excludes the manifest values above |
| server.theme.existingConfigMapItems | list | `[]` | Key -> path projection for existingConfigMap, so its keys can land in subdirectories (e.g. [{key: theme-css, path: assets/theme.css}]). Empty mounts every key flat at the theme root |
| server.theme.files | object | `{}` | Map of path -> file content, relative to the theme root (tpl-rendered). Keys may carry a "/" ("assets/theme.css") and are projected into subdirectories. Only assets/ is served over HTTP. Text only — use existingConfigMap with binaryData for images |
| server.theme.favicon | string | `""` | Favicon path, relative to the theme root and under assets/ (e.g. assets/favicon.svg). Must be a key of files |
| server.theme.files | object | `{}` | Map of path -> file content, relative to the theme root (tpl-rendered). Keys may carry a "/" ("assets/theme.css") and are projected into subdirectories. Only assets/ is served over HTTP. Text only — use existingConfigMap with binaryData for images. Set theme.json here only when writing the manifest by hand instead of using the values above |
| server.theme.fragmentsEnabled | bool | `false` | Read fragments/head.html and splice it into the console <head>. Raw, unsanitized markup on the identity provider origin, so it is opt-in |
| server.theme.logo | string | `""` | Logo replacing the built-in mark on both consoles, under assets/. Painted into the existing mark's box, so it needs no sizing |
| server.theme.logoDark | string | `""` | Dark-mode logo variant, under assets/. Without it dark mode reuses logo, which disappears when the mark is drawn dark-on-light |
| server.theme.stylesheet | string | `""` | Stylesheet path, under assets/ and ending in .css. Linked last, so it beats the token block; it is unlayered, so set dark colors explicitly |
| server.theme.title | string | `""` | Document title of both served consoles ("" = authup's own) |
| server.theme.tokens | object | `{}` | authup-periwinkle alone recolors buttons, focus rings and links. A color set here also wins in dark mode: put surface colors in both tokens and tokensDark |
| server.theme.tokensDark | object | `{}` | CSS custom properties applied in dark mode only (tpl-rendered) |
| server.tolerations | list | `[]` | Tolerations |
| server.topologySpreadConstraints | list | `[]` | Topology spread constraints (a missing labelSelector is filled with the pod's selector labels) |
| server.trustProxy | string | `"1"` | TRUST_PROXY setting. The chart defaults to one trusted hop (the ingress), not authup's spoofable trust-everything default |
| server.trustedOrigins | list | `[]` | Additional trusted first-party app origins (TRUSTED_ORIGINS). Each listed origin can obtain full-permission tokens via the per-realm web client. List or comma-separated string; tpl-rendered. |
| server.trustedOrigins | list | `[]` | Additional trusted first-party app origins (TRUSTED_ORIGINS). Each entry is added to the redirect allowlist of the per-realm built-in system clients (admin-console, account-console), so any listed origin can complete a login and obtain a full-permission token. A host may carry a single "*" (https://*.example.com); "**" in a host is rejected by authup at boot. List or comma-separated string; tpl-rendered. |
| server.trustedOriginsAppendAdminConsole | bool | `true` | Automatically append the client-admin-console UI origin to TRUSTED_ORIGINS (removes the most common dead-login misconfiguration) |
| server.updateStrategy | object | `{"type":"RollingUpdate"}` | Deployment update strategy |
| serviceAccount.annotations | object | `{}` | ServiceAccount annotations (tpl-rendered) |
Expand Down
Loading
Loading