Changelog
v1.3.0 (2026-08-23)
French and Italian, for the interface and for outbound email. Unlike the last two releases, this one changes what existing users see and receive — three behaviour changes take effect on upgrade without any config change on your side. They are listed under Changed below; read them before you pull. An English-only instance is unaffected: rendered English email is byte-identical to v1.2.0.
Added
- French and Italian, bringing the bundled locales to four. Both cover the full interface and the built-in emails — one file per locale under
app/i18n/, holding that language's UI strings plus anemailsubtree. Tone follows each language rather than being copied from German: French uses formal vous, German and Italian informal du / tu. - Built-in email translations. Previously the outbound templates existed only in English — there was no German set either, just per-locale
NUXT_EMAIL_*_<LOCALE>env overrides. All five emails now ship translated in all four locales, sitting underneath the same override cascade as before:NUXT_EMAIL_*_<LOCALE>→NUXT_EMAIL_*→ built-in. - Shipping a catalogue does not enable it.
NUXT_SUPPORTED_LOCALESstill defaults toen; list a locale to let users store it and receive mail in it. - A
:nextcontainer image on every push tomain, plus an immutable:main-<short-sha>for the same build so a pre-release can be pinned or rolled back.:latestis untouched by these and still means "most recent release". See Deployment → Which tag to pull.
Changed
Three changes to behaviour, all of which apply on upgrade with no config change:
- Users with a German, French or Italian locale now receive translated mail where they previously received English. If you had worked around this with
NUXT_EMAIL_*_<LOCALE>overrides, they still win — nothing to undo. - Users with no stored locale now get
NUXT_DEFAULT_LOCALEinstead of English for the verification resend, the 2FA login code and the new-sign-in alert. Those four send paths bypassed the locale cascade entirely, which also meant a locale-suffixed env override never applied to them.users.localeis empty for accounts created before the column existed, so this can affect a meaningful share of an older instance. - Anonymous visitors now see the login page in
NUXT_DEFAULT_LOCALE. The interface's last-resort fallback was hard-coded English, so aNUXT_DEFAULT_LOCALE=deinstance greeted visitors in English while mailing them in German. If you relied on the English login page, setNUXT_DEFAULT_LOCALE=en.
The new-sign-in alert deliberately ignores the triggering request's Accept-Language, unlike every other send: that request is the suspicious one, and its headers must not choose the language of the warning the account owner has to read.
Fixed
- The logo disappeared once you signed in. The account layout never rendered a brand block, so the mark vanished at the moment a user landed on
/account. - The admin sidebar and mobile header rendered the logo and the app name side by side, which put the bundled mark next to an operator's own brand. Both now render one or the other, as the login and account pages already did.
Internal
- The email copy moved out of a separate server-side module into the shared i18n catalogues, so a locale is one file rather than two. Note the two placeholder syntaxes this creates: UI strings use
{single}braces, email atomsones, matching the operator-facing env-override syntax. - Locale parity is now enforced in CI, not only by the type-checker: every bundled locale must carry exactly the English key set, no empty strings, and the same placeholder tokens per key.
- Added the missing regression test for email escaping — the HTML body must escape
firstName/ip/devicewhile the text body keeps them raw.
v1.2.0 (2026-08-21)
Visual identity: the app finally looks like itself. Nothing to configure and nothing to migrate — an operator who has already set their own name, logo or theme keeps exactly what they had.
Changed
- A stock install now shows a logo instead of the word "Schleuse". The mark — the two gate leaves of a canal lock, which is what Schleuse means — ships with the app and is used on the login pages, in the admin sidebar, and as the browser-tab icon, which previously did not exist at all: the favicon was only registered when a logo URL was configured.
- The UI now ships its own typeface. Geist (and Geist Mono for code, identifiers and audit output), bundled with the app as two variable woff2 files, Latin subset, ~52 KB together. Previously no typeface was set at all, so the UI rendered in whatever the visitor's operating system supplied. Deliberately not loaded from a CDN: the login page makes no third-party request, which keeps it working air-gapped and stops visitors' IP addresses reaching a font host. Both faces are SIL OFL 1.1; the licence ships at
/fonts/OFL.txt. The typeface is not configurable — say so if you need it to be. NUXT_PUBLIC_THEME_LOGO_URLis unchanged and still wins when set. If you setNUXT_PUBLIC_THEME_NAMEto your own brand without a logo, you keep seeing your name as text — the bundled mark is never shown above someone else's branding. Email is unaffected: the mail header still uses only an explicitly configured logo, and still falls back to the app name as text.
v1.1.1 (2026-08-21)
Internal only — nothing here changes how Schleuse behaves. No config, no API, no OIDC behaviour, no migration. Upgrading needs no action.
Fixed
pnpm db:migrateand its:make/:rollback/:down/:statussiblings now run. They never had, since the first release — extensionless relative imports don't resolve under Node ESM, and the sharedhelpers.tssat inside the migrations directory, where Knex loads every.tsas a migration. Development tooling only: the server has always applied migrations itself at startup, which is why this went unnoticed. Requires Node ≥ 22.18 (the published image runs 22.23).
Internal
- Removed dead code: an unused error-logging helper, two unused validation helpers and two unused schemas, and a repository method with no callers.
- Simplified the database write helpers — both carried a SQLite branch that ran the write and then a second SELECT to read the row back, which SQLite has not needed since it gained
RETURNING. Verified to return byte-identical rows on SQLite and PostgreSQL, including booleans and timestamps. - Dropped the
db:seedscripts, which pointed at a directory that never existed.
v1.1.0 (2026-08-21)
Added
- SQLite Coolify template —
docker-compose.coolify-sqlite.yml, the single-container variant: no database service, one volume,NUXT_DATABASE_URL=sqlite:/app/data/schleuse.db. For a small instance with no separate database to run or back up. Note that/app/datathen holds the database as well as the auto-generated keys, so losing it loses users, audit log, TOTP enrolments and passkeys at once — andtar/docker cpof a live WAL database is not a safe backup. The Postgres template keeps its filename; both cross-reference each other.
Changed
- Email addresses are matched case-insensitively. Addresses are stored canonically (trimmed, lowercased) and every lookup canonicalises its input, so
A@example.comanda@example.comare one account. Previously they were two, and a user who registered with one casing and typed the other got a generic "invalid email or password" — indistinguishable from a wrong password. Subaddressing (user+tag@…) and dots are preserved. - The OIDC
emailandpreferred_usernameclaims are now always lowercase. For users whose stored address was mixed-case the claim value changes once.subis unaffected; a relying party keying onsubneeds no migration.
Fixed
- Corrected the public docs URL in the README (five links).
Upgrading
A migration canonicalises existing rows at first start. If two accounts hold addresses that differ only by case or whitespace they cannot both survive — the migration refuses to guess, aborts, and the container will not start until you reconcile them. Run the pre-flight query before upgrading; see Upgrading → duplicate email addresses.
v1.0.0 (2026-08-20)
First release.
A self-hosted identity provider: password and WebAuthn/passkey authentication, email verification, TOTP and emailed two-factor codes, and an OIDC provider (Authorization Code Flow with PKCE) for downstream applications.
Includes an admin panel (user management, audit log, integration guide, env configurator, SMTP and email diagnostics), a bearer-gated Prometheus endpoint, a provisioning API for migrating an existing realm without forcing a password reset, GDPR export and erasure, per-application OIDC roles, back-channel and RP-initiated logout, and a bilingual (en/de) interface.
See docs/implementation/v1.0.md for the design constraints and the decisions behind them.