Skip to content
Merged
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
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,34 @@ All notable changes to Authorizer will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.4.4] - 2026-10-30

Four of the SQL backends in the supported list could not create their own schema,
and PostgreSQL was rewriting four tables on every restart. **Upgrading is
recommended for every SQL deployment.** The first boot after this release emits a
small, one-time set of `ALTER COLUMN` statements and nothing on every boot after;
see the upgrade notes at the end.

### Fixed

- **`char(36)` columns were retyped on every single boot, and that was fatal on CockroachDB** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). GORM's postgres driver reads these columns back from `information_schema` as `bpchar`, which never prefix-matches the declared `char(36)`, so AutoMigrate re-emitted the same no-op type change at every startup: `ALTER TABLE "authorizer_sessions" ALTER COLUMN "user_id" TYPE char(36) USING "user_id"::char(36)`. On PostgreSQL that is a silent table rewrite under `ACCESS EXCLUSIVE` on four tables at every restart. On CockroachDB it is fatal — `ALTER COLUMN TYPE` sits behind an experimental flag (SQLSTATE XCEXF) — so the server started once and never started again. Five non-PK columns now declare `varchar(36)`, which is what the catalog reports; values are 36-character uuids either way. Measured on PostgreSQL 18 with `log_statement=all`: 5 alters on the upgrade boot, 0 on every boot after.
- **CockroachDB aborted at startup dropping a constraint it never had** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). CockroachDB lists unique *indexes* in `information_schema.table_constraints`, and MySQL reports `COLUMN_KEY='UNI'` for them, so GORM saw every `gorm:"uniqueIndex"` field as a constraint the model no longer wants and issued `DROP CONSTRAINT "uni_<table>_<column>"` — a name it only ever creates from a `unique` tag. Startup died on `constraint "uni_authorizer_oauth_states_state_key" ... does not exist (SQLSTATE 42704)`, and on MySQL on `Error 1091: Can't DROP`. Those indexes are declared by the models and are meant to be there, so the drop is skipped; the create direction is delegated untouched.
- **The pre-2.3.0 uniqueness cleanup silently did nothing on CockroachDB** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). `ALTER TABLE ... DROP CONSTRAINT` on a UNIQUE is rejected outright there (`unimplemented: ... use DROP INDEX CASCADE instead`, SQLSTATE 0A000), and the failure was logged at debug level and swallowed. Releases before 2.3.0 declared `authorizer_users.email` and `authorizer_otps.phone_number` as UNIQUE; v2 keeps plain indexes and enforces uniqueness in the application layer, writing the empty string into `phone_number` for every email OTP — so on an upgraded CockroachDB database the **second email OTP was rejected** and email OTP login was broken. The drop now goes through `DROP INDEX ... CASCADE`.
- **MySQL and MariaDB had never worked at all, on three separate aborts** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). Nothing had ever run the storage suite against them. (1) GORM's mysql driver widens an unsized string to `varchar(191)` when the field carries `index` or `unique`, but it checks only those two tags — a field tagged `uniqueIndex`, named or composite, is missed and becomes `longtext`, which MySQL refuses to index: `Error 1170: BLOB/TEXT column 'client_id' used in key specification without a key length`, so the schema could not be created. (2) Then the phantom `DROP CONSTRAINT` above, as `Error 1091`. (3) Then `DeleteSessionTokensByUserID` used a backslash as its `LIKE` escape character, which MySQL rejects as `Error 1064`; the escape character is now `!` and the escaping is portable across engines, pinned by a new metacharacter test case.
- **SQL Server could not create its schema since 2.3.0-rc.10** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). `gorm.io/driver/sqlserver` has the identical two-tag gap as the mysql driver, so ten unsized `uniqueIndex` strings became `nvarchar(MAX)`: `mssql: Column 'client_id' in table 'authorizer_clients' is of a type that is invalid for use as a key column in an index.` Fixed at the driver's own indexed-string width. Two upsert tests remain red on an unrelated driver bug — `MergeCreate` builds its `MERGE` predicate from the primary key and ignores `clause.OnConflict.Columns`, so an upsert keyed on a composite unique index inserts a second row instead of replacing one. `make test-sqlserver` therefore exists but is deliberately outside `make test-all-db`.
- **`TestDeleteUserCascadesAllUserOwnedRows` could not be run twice against a persistent database** ([#826](https://github.com/authorizerdev/authorizer/pull/826)). Fixed fixture ids and no cleanup, so a second run collided on rows the first left behind. Pre-existing — it failed identically on PostgreSQL — and it masked real failures behind a false one on every re-run.

### Changed

- **`make test-all-db` now covers 12 databases**, with new `make test-cockroachdb`, `test-mysql`, `test-mariadb` and `test-sqlserver` targets ([#826](https://github.com/authorizerdev/authorizer/pull/826)). CI runs SQLite only, so a backend with no target had most likely never been executed — which is how four of them shipped unable to boot. Readiness is gated by a new `scripts/wait-for-sql.sh`, because `nc -z` connects to Docker Desktop's published-port proxy the instant `docker run -p` returns, and MySQL binds its port, runs its init scripts and then *restarts* — `mysqladmin ping` answers during that window, so the probe now runs a real query against the test database instead.

### Upgrade notes

- **One-time DDL on the first boot.** Five columns move from `char(36)` to `varchar(36)`: `audit_logs.actor_id`, `audit_logs.resource_id`, `authenticators.user_id`, `sessions.user_id` and `webhook_logs.webhook_id`. Stored values are unchanged — a 36-character uuid is identical in both types — and nothing is narrowed. On PostgreSQL this is five rewrites on the upgrade boot and zero thereafter; take the usual care if `authorizer_audit_logs` is large.
- **MySQL/MariaDB and SQL Server column widths.** An unsized string column under a `uniqueIndex` is now created as `varchar(191)` / `nvarchar(256)` instead of `longtext` / `nvarchar(MAX)`. Every affected column lives in a table introduced in 2.3.0-rc.10 (`clients`, `organizations`, `org_memberships`, `federated_identities`, `scim_endpoints`, `trusted_issuers`, `webauthn_credentials`), so no existing data is narrowed.
- **A SQL Server database left behind by a failed boot must be dropped.** The unsized `uniqueIndex` columns and the composite index over `authenticators.user_id` both arrived in 2.3.0-rc.10, so the two never coexist in a database that completed AutoMigrate. A database touched by a 2.3.0-rc.10 or later SQL Server boot has `user_id` as `char(36)` *inside* that index, and SQL Server will not retype an indexed column. No boot there ever finished, so such a database holds no data. Databases from 2.0.0 through 2.3.0 did boot, carry no index over any widened column, and upgrade in one boot with rows intact.
- **PlanetScale and Yugabyte were not executed.** PlanetScale is hosted MySQL-wire and shares the MySQL code path verbatim; Yugabyte keeps the unchanged `postgres.Open` path and the same `bpchar` catalog, so the `varchar(36)` change covers it.

## [2.4.3] - 2026-10-29

### Fixed
Expand Down
Loading