diff --git a/CHANGELOG.md b/CHANGELOG.md index 44d9b297a..789364e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,7 +47,7 @@ Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML Id ### Changed -- **BREAKING — at-rest encryption key split from the JWT secret (`--encryption-key`).** The key used to encrypt secrets at rest (TOTP secrets, recovery codes) is now its own input and no longer derives from `--jwt-secret`. **A deployment using RS256/ES256 (`--jwt-private-key`/`--jwt-public-key`) without `--jwt-secret` will refuse to start until `--encryption-key` is set** — HMAC deployments (HS256/384/512) are unaffected, as the JWT secret still resolves the key. This is a security fix, not a preference: in **2.2.1 through 2.4.0-rc.13**, an asymmetric-JWT deployment with no `--jwt-secret` silently fell back to a **public constant** compiled into the source, so anything encrypted at rest was protected by a key any reader of the repository already had. Operators on those versions must treat existing TOTP enrollments and recovery codes as compromised: rotate `--encryption-key`, then have affected users re-enroll (existing ciphertext was written under the old key and will not decrypt). When no key can be resolved, TOTP is disabled with a startup warning rather than the server failing closed on every MFA path. There is no `ENCRYPTION_KEY` environment variable — v2 is flag-only ([#742](https://github.com/authorizerdev/authorizer/pull/742)). +- **BREAKING — at-rest encryption key split from the JWT secret (`--encryption-key`).** The key used to encrypt secrets at rest (TOTP secrets, and the OTP digests behind email/SMS verification and password reset) is now its own input and no longer derives from `--jwt-secret`. **A deployment using RS256/ES256 (`--jwt-private-key`/`--jwt-public-key`) without `--jwt-secret` will refuse to start until `--encryption-key` is set** — HMAC deployments (HS256/384/512) are unaffected, as the JWT secret still resolves the key. This is a security fix, not a preference: in **2.2.1 through 2.4.0-rc.13**, an asymmetric-JWT deployment with no `--jwt-secret` silently fell back to a **public constant** compiled into the source, so anything encrypted at rest was protected by a key any reader of the repository already had. Operators on those versions must treat existing TOTP enrollments as compromised: rotate `--encryption-key`, then have affected users re-enroll (existing ciphertext was written under the old key and will not decrypt). Recovery codes are unaffected by the rotation — they are stored as unkeyed SHA-256 digests. HMAC deployments that leave `--encryption-key` unset now log a startup warning: under the fallback, rotating `--jwt-secret` also changes the at-rest key, and there is no re-encryption path — every enrolled TOTP user is locked out. Set a distinct `--encryption-key` before rotating. There is no `ENCRYPTION_KEY` environment variable — v2 is flag-only ([#742](https://github.com/authorizerdev/authorizer/pull/742)). - **Admin dashboard UI migration from Chakra UI to shadcn/ui + Tailwind CSS**: Dashboard (`web/dashboard/`) completely modernized. Replaced Chakra UI v2 with shadcn/ui (Radix primitives) + Tailwind CSS v4. All TypeScript `any` types and `@ts-ignore` directives eliminated; full type safety on GraphQL responses, component props, and data models. Dead dependencies removed (react-draft-wysiwyg, @emotion, framer-motion, react-icons, focus-visible). 17 shadcn/ui-style components built on Radix; Authorizer branding (logo + blue-500) applied throughout. Cleaner tables, Sheet panels for forms, sonner toast notifications, skeleton loading states ([#605](https://github.com/authorizerdev/authorizer/pull/605)). - **BREAKING — MFA behavior completely redesigned: on by default, optional per user, withheld token until setup complete.** MFA methods (TOTP, Email OTP, SMS OTP, WebAuthn) are now enabled by default and opted out via new `--disable-totp-login`, `--disable-email-otp`, `--disable-sms-otp`, and `--disable-webauthn-mfa` flags; the old `--enable-totp-login`, `--enable-mfa`, `--enable-email-otp`, and `--enable-sms-otp` flags are removed. Email and SMS OTP only take effect when their provider (SMTP / Twilio) is configured. Whether MFA is available is now derived from the enabled methods rather than a standalone flag, which fixes the case where MFA appeared "enabled" while every method was unavailable. **New token-withholding behavior:** when MFA is optional (`--enforce-mfa` default `false`), first-time users who haven't set up MFA no longer receive an immediate token followed by a setup offer — the token is withheld until the user completes enrollment or explicitly skips (remembered as `has_skipped_mfa_setup_at`). This withheld-token model now applies uniformly to password login, passkey login, signup, and social login. When `--enforce-mfa` is set, MFA is mandatory and un-skippable. **Email/SMS OTP now require explicit enrollment** (new `email_otp_mfa_setup`/`sms_otp_mfa_setup` mutations) before they can be used for MFA verification, fixing the previous behavior where they fired automatically for any user with a phone/email on file. **Admin recovery:** new `reset_mfa` operation on `_update_user` clears all MFA state and enrolled factors across all storage backends. **User-initiated lockout:** new `lock_mfa` mutation prevents future MFA enrollment (admin-recoverable); lockout is refused if a verified Email/SMS OTP factor exists as a fallback. **`--disable-mfa` one-way kill switch** disables MFA entirely regardless of per-method flags (does not affect WebAuthn, which is a separate login recipe) ([#682](https://github.com/authorizerdev/authorizer/pull/682), [#684](https://github.com/authorizerdev/authorizer/pull/684), [#685](https://github.com/authorizerdev/authorizer/pull/685), [#686](https://github.com/authorizerdev/authorizer/pull/686)). - **License: relicensed from MIT to Apache License 2.0.** Per the CNCF IP Policy ([Charter §11(b)(iii)](https://github.com/cncf/foundation/blob/main/charter.md#11-ip-policy)), Authorizer's outbound code is now distributed under the Apache License 2.0. Existing copies distributed under the MIT License remain valid under their original grant; this change applies to the project's outbound license going forward. See [NOTICE](NOTICE) for attribution. diff --git a/MIGRATION.md b/MIGRATION.md index 29d2ea612..7d1d69f9b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -97,7 +97,8 @@ Pass all config as **CLI arguments** when starting the server binary (e.g. the v --client-secret=YOUR_CLIENT_SECRET \ --admin-secret=your-admin-secret \ --jwt-type=HS256 \ - --jwt-secret=your-jwt-secret + --jwt-secret=your-jwt-secret \ + --encryption-key=your-encryption-key ``` For local development (from repo root): @@ -106,7 +107,7 @@ For local development (from repo root): make dev # or go run main.go --database-type=sqlite --database-url=test.db \ - --jwt-type=HS256 --jwt-secret=test --admin-secret=admin \ + --jwt-type=HS256 --jwt-secret=test --encryption-key=test-encryption-key --admin-secret=admin \ --client-id=123456 --client-secret=secret ``` @@ -296,6 +297,7 @@ Use these v2 **CLI flags** instead of v1 env or dashboard config. Flag names use | `JWT_SECRET` | `--jwt-secret` | | `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY` | `--jwt-private-key`, `--jwt-public-key` | | `JWT_ROLE_CLAIM` | `--jwt-role-claim` | +| _(none — new in 2.4.0)_ | `--encryption-key` | | `CUSTOM_ACCESS_TOKEN_SCRIPT` | `--custom-access-token-script` | @@ -370,7 +372,7 @@ These mutations and queries exist for compatibility but **return an error** in v | -------------------- | ------------------------------------------------------------------------- | --------- | | `_update_env` | Returns error: *"deprecated. please configure env via cli args"* | Configure via CLI flags at startup. | | `_admin_signup` | Returns error: *"deprecated. please configure admin secret via cli args"* | Set admin secret with `--admin-secret` at startup. | -| `_generate_jwt_keys` | Returns error: *"deprecated. please configure jwt keys via cli args"* | Set JWT with `--jwt-type`, `--jwt-secret`, or `--jwt-private-key` / `--jwt-public-key` at startup. | +| `_generate_jwt_keys` | Returns error: *"deprecated. please configure jwt keys via cli args"* | Set JWT with `--jwt-type`, `--jwt-secret`, or `--jwt-private-key` / `--jwt-public-key` at startup. RSA/ECDSA also needs `--encryption-key` (2.4.0+). | | `mobile_signup` | Returns error: *"deprecated, use signup with mobile phone_number"* | Use the `signup` mutation with `phone_number` instead. | | `mobile_login` | Returns error: *"deprecated, use login with mobile phone_number"* | Use the `login` mutation with `phone_number` instead. | @@ -384,6 +386,7 @@ These mutations and queries exist for compatibility but **return an error** in v - **Admin secret:** set with `--admin-secret` at startup. - **JWT keys/type:** set with `--jwt-type`, `--jwt-secret`, or `--jwt-private-key` / `--jwt-public-key` at startup. +- **At-rest encryption key (2.4.0+):** `--encryption-key` protects TOTP secrets and OTP digests. Required with `RS*`/`ES*` — those have no `--jwt-secret` to fall back to, and the server refuses to start without it. HMAC deployments fall back automatically, but a distinct key keeps JWT-secret rotation from locking out enrolled TOTP users. - **All other env:** use the corresponding CLI flags when starting the server. If your app or dashboard calls any of the deprecated mutations or queries above, remove or replace those calls and use the migration guidance in the tables. @@ -486,7 +489,7 @@ The v2 repo ships with a `Makefile` that wraps the most common development and b - Copy all env configs from your v1 dashboard (client_id, client_secret, admin_secret, social provider configs, roles, JWT secrets, session/Redis config, email/SMTP config, allowed_origins, custom_access_token_script) and pass them as CLI args. - Set `**--client-id**` and `**--client-secret**` (required). -- Set `**--admin-secret**` and JWT options (`**--jwt-type**` and `**--jwt-secret**` or key pair) at startup. +- Set `**--admin-secret**` and JWT options (`**--jwt-type**` and `**--jwt-secret**` or key pair) at startup. With a key pair, also set `**--encryption-key**` (2.4.0+). - Stop calling `**_update_env**`, `**_admin_signup**`, and `**_generate_jwt_keys**`; remove or replace with startup config. - Update Docker/K8s/deployment to pass config as **CLI args** (or via a wrapper that maps env → args). - Upgrade **@authorizerdev/authorizer-js** to v3 and **@authorizerdev/authorizer-react** to v2; update type names and Node version as needed. diff --git a/README.md b/README.md index 66b3b95ce..1f4315704 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ This guide helps you practice using Authorizer to evaluate it before you use it --database-url=test.db \ --jwt-type=HS256 \ --jwt-secret=test \ + --encryption-key=test-encryption-key \ --admin-secret=admin \ --client-id=123456 \ --client-secret=secret @@ -168,7 +169,8 @@ The default image runs as **non-root** (UID `65532`). Writable mounts (SQLite un --client-secret=secret \ --admin-secret=admin \ --jwt-type=HS256 \ - --jwt-secret=test + --jwt-secret=test \ + --encryption-key=test-encryption-key ``` 2. **Keep non-root** and make the mount writable by `65532` (good for production-style bind mounts): @@ -268,6 +270,7 @@ Deploy / Try Authorizer using binaries. With each [Authorizer Release](https://g --database-url=test.db \ --jwt-type=HS256 \ --jwt-secret=test \ + --encryption-key=test-encryption-key \ --admin-secret=admin \ --client-id=123456 \ --client-secret=secret diff --git a/perf/README.md b/perf/README.md index 3d206c0cf..1ad8f0b3e 100644 --- a/perf/README.md +++ b/perf/README.md @@ -113,13 +113,14 @@ createdb authorizer_perf --jwt-type RS256 \ --jwt-private-key "$(cat perf/dev-jwt-private.pem)" \ --jwt-public-key "$(cat perf/dev-jwt-public.pem)" \ + --encryption-key perf-encryption-key-not-for-production \ --client-id kbyuFDidLLm280LIwVFiazOqjO3ty8KH \ --client-secret 60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa \ --allowed-origins localhost:8080 \ --http-port 8080 ``` -`--admin-secret`, `--jwt-type`/`--jwt-private-key`/`--jwt-public-key`, and +`--admin-secret`, `--jwt-type`/`--jwt-private-key`/`--jwt-public-key`, `--encryption-key`, and `--client-id`/`--client-secret` are all required — the server exits with `client ID missing in rootArgs` (or similar) without them. `--fga-store` can be omitted: it auto-reuses `--database-url` when the main DB is SQL-compatible