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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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
```

Expand Down Expand Up @@ -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` |


Expand Down Expand Up @@ -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. |

Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion perf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down