Skip to content

0.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 05 Sep 15:30
· 37 commits to develop since this release

[0.4.0] - 2026-09-05

Features

  • POST /certify: a new authenticated (session-cookie) endpoint that certifies a caller-supplied verification key under the caller's own realm_id/sub/auth_scheme, returning a long-lived, ES256-signed certificate. It is signed with a dedicated certificate signing key (certificate_jwt_params), entirely separate from the session JWT key, so it can never be presented back as a session cookie/token even if algorithms collide.
  • Added GET /.well-known/certificate-jwks.json, a JWKS document for the new certificate signing key, kept separate from the existing session /.well-known/jwks.json for the same isolation reason.
  • Added a per-realm certificate_max_age_seconds field controlling how long issued certificates remain valid (defaults to one year), following the same pattern as the existing session_max_age_seconds.
  • POST /login's per-IP rate limiter is now configurable via ServerParams::login_rate_limit_per_second / login_rate_limit_burst (both optional, default to the previous hardcoded values: 5 req/s, burst of 10).
  • UserPass gained extra_claims, arbitrary claims a realm admin sets at enrollment that are merged into the session JWT on username/password login (rejected with 400 on collision with a typed claim or if serialized size exceeds 4 KiB), and POST /certify gained a claims field (plus exclude_sub) to selectively copy a session's extra claims into an isolated, separately-signed certificate.
  • UserPass gained password_input, a PasswordInput enum (Plaintext(String) or Hashed(String)) letting a caller provision a credential either from a plaintext password or from an already-computed Argon2id PHC string without ever sending its plaintext to this server; the latter is rejected with 400 unless it uses exactly this server's own algorithm/version/cost parameters.
  • CredentialModal (create mode): added a plaintext/pre-hashed password toggle (PasswordFields) so an admin can provision a credential from an already-computed Argon2 PHC string instead of a plaintext password, and a key/value extra-claims editor (ExtraClaimsEditor) for UserPass.extra_claims, matching the new server-side password_input/extra_claims fields.

Bug Fixes

  • POST /admins, POST /admins/realms, and POST /realms/{realm_id}/userpass used to leak a raw 500 with database internals on a duplicate ID/username; all three now return a clean 409 Conflict (the userpass case unconditionally, since distinguishing a genuine conflict from a byte-for-byte resubmission would require checking the submitted password against the stored hash on this unrate-limited, admin-authenticated endpoint, turning it into a password-guessing oracle).
  • CredentialModal's roles-fetch effect had no unmount guard: if the list() call resolved after the modal/component was torn down, the resulting setAvailableRoles call could fire against a dead environment. Added the same cancelled guard already used by the form-validity effect right below it.
  • Column migrations (e.g. userpass.roles) now use the same atomic ADD COLUMN IF NOT EXISTS on PostgreSQL as other columns instead of a SELECT-then-ALTER check missing a schema filter, and all three backends (PostgreSQL, MySQL, SQLite) now propagate a failure of that check instead of silently treating it as "column missing".
  • Plaintext passwords are now wrapped in Zeroizing through the HTTP Basic Auth extraction path and the userpass create/update handlers, reducing how long they remain in cleartext in application-held memory.
  • Raised the server's Argon2id parameters to RFC 9106 §4's recommended t=3, m=64 MiB (65536), p=4 (was t=3, m=4 MiB (4096), p=1, a wrongly-documented value inherited from the crate's own default) and now pin/validate the algorithm version (v=19) in addition to the variant and cost parameters when accepting a pre-hashed password_input: { hashed: ... }.
  • Bumped h2 from 0.4.13 to 0.4.16 (hyper/reqwest instance) to fix RUSTSEC-2026-0258 (unbounded empty DATA frames, low severity); the other instance, pulled in transitively via actix-http 3.13.1 (pinned to h2 ^0.3, no patched 0.3.x release exists), is now explicitly ignored in deny.toml. Also removed a stale RUSTSEC-2023-0071 ignore entry.
  • Bumped eight admin-ui dependencies flagged by Dependabot to pick up upstream fixes: antd, react-router, @testing-library/react, @types/react-dom, @types/node, eslint, eslint-plugin-react-refresh, and typescript-eslint.

Refactor

  • Removed public_key_pem from POST /login and the as_pk private claim it populated on the session JWT: this was dead weight (no caller ever set it) and the wrong shape for the job — VELO, the intended consumer, now uses /certify instead, which produces a purpose-built, long-lived, cryptographically isolated certificate rather than piggybacking on the short-lived session token.
  • UserPass.password/hashed_password (two separate, manually-mutually-exclusive fields) replaced by a single password_input: Option<PasswordInput> field, enforcing the plaintext-vs-pre-hashed exclusivity at the type level instead of at runtime — a request providing both is now structurally impossible rather than a 400 caught after the fact.
  • Renamed UserPass.password to password_hash and changed its type from Vec<u8> to String: the stored value has always been the full Argon2id PHC string, so the old name/type obscured what it actually holds and forced an unnecessary byte/string round-trip. The three database backends still store it as a binary column (BYTEA/BLOB, unchanged — no migration needed) and convert at the Rust boundary.

Tests

  • Added certify_tests.rs: session requirement, empty-key rejection (400), missing server configuration (500), certificate JWKS availability, and cryptographic isolation (a certificate signed with the certificate key is rejected by the session JWT decoding key).
  • Added a userpass duplicate-creation regression test and renamed a misleading pre-existing duplicate-admin test for clarity.
  • Fixed a flaky admin-ui unit test (window is not defined, RealmContext.test.tsx) by guarding RealmContext's fetchRealms against post-unmount setState and extending the scheduler-drain loop in afterEach.

Docs

  • Added SECURITY.md, a hand-maintained security policy and vulnerability-disclosure ledger, so the auth server has the same advisory-tracking surface as the KMS repository.
  • Documented the advisory-ledger lifecycle in AGENTS.md §11 and mirrored a short pointer in .github/copilot-instructions.md, defining the COSMIAN-AUTH-<YYYY>-NNN ID scheme, the released-vs-unreleased rule, and the three-part internal-consistency requirement.

CI

  • Regenerated the Nix admin-ui pnpm dependencies hash (nix/expected-hashes/admin-ui.pnpm.*.sha256) for all three platforms after several Dependabot bumps to admin-ui/pnpm-lock.yaml went unmatched by a hash regeneration, breaking the Nix Packaging CI job; also corrected a stale darwin hash.
  • Fixed the admin-ui package-update pipeline: regenerated pnpm-lock.yaml to record the pnpm.overrides block (was causing ERR_PNPM_LOCKFILE_CONFIG_MISMATCH), added a browser User-Agent to Nix's fetchurl/importCargoLock calls to stop crates.io HTTP 403s, and pinned npm_config_node_version=22.12.0 so pnpm's engine check picks up the @rolldown/binding-linux-x64-gnu optional dependency.

Security

  • Recorded previously shipped and fixed vulnerabilities in SECURITY.md: COSMIAN-AUTH-2026-001 and COSMIAN-AUTH-2026-002 (plaintext password storage via the create_userpass/update_userpass endpoints), COSMIAN-AUTH-2026-003 (TLS private key baked into the Docker image), and COSMIAN-AUTH-2026-004 (vulnerable admin UI transitive dependencies).