Upgrading 0.21 to 0.23
Esta página aún no está disponible en tu idioma.
Upgrading 0.21 → 0.23
Section titled “Upgrading 0.21 → 0.23”These are the ### Breaking entries 0.23 carries. 0.22 declares no breaking
change, so an app on 0.21 or 0.22 reads this one page to reach 0.23.
Three of these refuse at boot, naming the setting, rather than change behaviour quietly. Four change who can sign in or what a key can reach: read those first if your app has API keys on nested paths, a custom auth repository, or guests that become accounts. The last changes the contract of a custom job store.
33. An API key on a nested path needs read on every parent collection
Section titled “33. An API key on a nested path needs read on every parent collection”A key was checked against the last segment of a nested URL, which names a
relation rather than a collection, and the parent collection was never checked
at all. Now the route’s operation is checked against the collection the path
reaches, and every collection the path passes through needs read. A key scoped
only to posts that calls /api/data/authors/1/posts gets
403 API_KEY_FORBIDDEN naming authors until it can read authors too.
What to change: give each key that uses nested paths read on the parents
(PUT /api/admin/api-keys/:id), or have it call the child collection’s own
route with a filter instead.
[ { "collection": "posts", "operations": ["read", "write"] }, { "collection": "authors", "operations": ["read"] }]See API keys. Since 0.24 the same rule is written in scopes: each parent needs data:read.
34. A numeric environment variable that is not a number fails the boot
Section titled “34. A numeric environment variable that is not a number fails the boot”PORT, SMTP_PORT, REBASE_MAX_BODY_SIZE and the DB_POOL_* variables used to
pass NaN on, or read a blank value as 0. A blank REBASE_MAX_BODY_SIZE
removed the body limit from every route, and a blank PORT made a production
server listen on a random port. A blank value now means unset and takes the
default, an explicit 0 still counts, and anything that is not a number stops
the boot, naming the variable.
What to change: nothing, unless the boot now names a variable. Then fix its value, or unset it.
grep -nE "^(PORT|SMTP_PORT|REBASE_MAX_BODY_SIZE|DB_POOL_[A-Z_]+)=" .env*35. A session lifetime the token signer cannot read refuses to boot
Section titled “35. A session lifetime the token signer cannot read refuses to boot”auth.accessExpiresIn and auth.refreshExpiresIn (JWT_ACCESS_EXPIRES_IN,
JWT_REFRESH_EXPIRES_IN) were checked against ^\d+[dhms]$ while the token
signer read them another way, and anything in between fell back silently:
1w, 2 weeks or 30 days became a 400-day refresh token. Both now take a
positive number and a unit, such as 15m, 1h, 30d or 2 weeks. A bare
number, zero, a negative value, or a value the signer cannot read stops the boot
and names the setting.
What to change: give every lifetime a unit. 3600 is not an hour — the
signer reads a bare number as milliseconds — so write 1h.
grep -rnE "(EXPIRES_IN|ExpiresIn)" .env* config36. A custom auth repository needs unlinkUserIdentity, or some sign-ins are refused
Section titled “36. A custom auth repository needs unlinkUserIdentity, or some sign-ins are refused”The first time someone proves an unverified account’s address, by magic link,
email code or password reset, Rebase removes every linked sign-in identity whose
provider did not verify that address (37).
It does that through the repository’s new optional unlinkUserIdentity. A
repository without it answers such a sign-in with 409 UNVERIFIED_IDENTITIES
rather than leave the identity in place. updatePassword must also accept
null, which removes the password.
What to change: in a custom AuthRepository, implement
unlinkUserIdentity(uid, provider, providerId) and let updatePassword take
null. The Postgres store implements both. The MongoDB store has no
unlinkUserIdentity yet, so on MongoDB such a sign-in is refused.
37. An unverified account loses what nobody proved the first time its address is proven
Section titled “37. An unverified account loses what nobody proved the first time its address is proven”Nothing verifies the address an account is registered with, so anyone could
register someone else’s address with a password, or sign in as it through a
provider that does not vouch for it, and wait for the owner. The first magic
link, email code or password reset on an unverified account now removes its
password (a reset sets the new one) and every linked identity whose provider did
not verify that address, and ends all its sessions, before marking it verified.
OAuth sign-in no longer links onto an unverified account at all: it answers
403 EMAIL_NOT_VERIFIED, with details.reason naming which side was
unverified.
What to change: nothing in code. Expect that someone who registered with a
password, never verified, and then signs in by magic link or code has to set a
password again, and is signed out on their other devices. Accounts created with
POST /api/admin/users are stored verified, so invitees can still use “Sign in
with Google”. See
Account linking.
38. With captcha protecting register, upgrading a guest needs a solved challenge
Section titled “38. With captcha protecting register, upgrading a guest needs a solved challenge”POST /auth/anonymous/link turns a guest into an account with an email and a
password, which is registration, and it skipped both the register captcha and
beforeUserCreate. It now takes the register challenge and runs the hook, as
/auth/register does. register is one of the default captcha routes. Guest
sign-in itself (POST /auth/anonymous) still takes no challenge.
What to change: an app that turns guests into accounts sends the widget’s
token on /auth/anonymous/link too — as captchaToken in the body, or in the
cf-turnstile-response / h-captcha-response header — the way it does for
/auth/register. See Bot protection.
39. Email refuses to boot without an absolute password-reset base URL
Section titled “39. Email refuses to boot without an absolute password-reset base URL”Verification and magic links fall back to auth.email.resetPasswordUrl, and
nothing falls back to verifyEmailUrl or magicLinkUrl. A config that set only
those booted, and sent a dead relative link in every password-reset email. Now
email refuses to start unless every emailed link kind resolves to an absolute
http(s) base, and says which one does not.
What to change: set FRONTEND_URL, or auth.email.resetPasswordUrl, to an
absolute URL such as https://app.example.com.
grep -rnE "(FRONTEND_URL|resetPasswordUrl|verifyEmailUrl|magicLinkUrl)" .env* config40. A custom job store takes a claim when it completes or fails a job
Section titled “40. A custom job store takes a claim when it completes or fails a job”A queue handler that outlived the visibility timeout was reclaimed while it was
still running and started a second time beside itself. complete and fail
found the job by its id alone, so the first attempt’s success marked the job
done while the second ran, and the second’s failure sent the finished job back
for a third run. The worker now renews its claim while a handler runs, and only
the attempt that still holds a job may record its outcome.
JobStore.complete(id, claim) and fail(id, error, retryAt, claim) receive a
JobClaim, { workerId, attempt }. The new optional heartbeat(id, claim)
renews a running job’s claim and resolves with whether it still held. The
built-in store implements all three, so an app that uses it has nothing to do.
What to change: in a custom JobStore, make complete and fail change the
row only while the claim still holds it — running, locked by claim.workerId,
on attempt claim.attempt — and do nothing otherwise. Add heartbeat, renewing
the lock time under the same condition. A store without it still works, but a
handler that outlives the visibility timeout is run twice. See
Background Jobs.