You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d23d966
Browse filesBrowse the repository at this point in the historyBrowse files
fix(auth): an invitation reads the invitee's own sys_user.locale when the address already holds a row
The two invitation sends kept the deployment rung while the other four auth
sends gained a per-recipient one in #14762. The recorded reason — an invitee
has no `sys_user` row until acceptance — covers only one of the two
populations an invitation reaches, so both sends now take a two-branch shape:
1. an address / phone number that ALREADY carries a row (an existing user
invited into a second organization, a re-invitation, or an imported
phone-only account) uses that row's `locale`;
2. a genuinely new invitee with no row keeps the deployment default.
The inviter direction stays rejected on both branches, now pinned against a
manager that HAS the top rung wired rather than one with no rung at all.
Reachability of branch 1 is measured, not assumed: better-auth's
`create-invitation` rejects only an address already a member of THIS org
(`routes/crud-invites.mjs`, installed 1.7.2), and the SMS invite's one in-repo
caller creates the account before it sends.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
fix(auth): an invitation is written in the invitee's own `sys_user.locale` when the address already holds a row, and keeps the deployment default when it does not (#14641)
6
+
7
+
The four auth sends whose requester IS the recipient gained a per-recipient
8
+
language rung in #14762 (`sys_user.locale`, ruled on #13881). The two
9
+
**invitation** sends did not, and the recorded reason was structural rather
10
+
than an oversight: an invitee generally has no `sys_user` row until they accept,
11
+
so there is no stored language to read, and the *inviter's*`Accept-Language` is
12
+
the wrong authority — an English-speaking admin would silently send English
13
+
invitations to a Chinese-language workspace's new hires.
14
+
15
+
That reason covers only one of the two populations an invitation reaches. This
16
+
change gives both invitation sends the same top rung the other four already
17
+
read, on a **two-branch** shape:
18
+
19
+
1. the address (or phone number) **already carries** a `sys_user` row — an
20
+
existing platform user invited into a second organization, a re-invitation,
21
+
or an imported phone-only account — the row's `locale` wins;
22
+
2. a genuinely **new** invitee with **no** row keeps the deployment default,
23
+
because their language is still truly unknown at invitation time.
24
+
25
+
⛔ The inviter direction stays rejected on both branches, and is now pinned
26
+
against a manager that has the top rung wired rather than against one with no
27
+
rung at all. #13881's ruling item 3 fixes the chain as **recipient** locale →
28
+
deployment default; what opened here is the invitee's own column, never the
29
+
inviter's header.
30
+
31
+
**Both branches are reachable, measured rather than assumed.**
32
+
`sendInvitationEmail`: better-auth's `create-invitation` route rejects only an
33
+
address that is already a member of *this* organization
34
+
(`USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION`, `routes/crud-invites.mjs` in
35
+
the installed 1.7.2), so an existing account invited elsewhere — and the
36
+
`resend` branch — reach the callback normally. `sendPhoneInviteSms` is the
37
+
branch-1 case in its purest form: its one in-repo caller, the identity import
38
+
endpoint's `invite` policy, **creates** the account and only then sends the SMS,
39
+
so the row exists before the send does.
40
+
41
+
**Matching is exact, and that is safe rather than merely tolerable here.**
42
+
better-auth lowercases the invitee address on the invite route and the stored
43
+
`user.email` on sign-up, so both sides of the predicate are already in the same
44
+
case; `email` and `phone_number` are both `unique: true` in the `user` table
45
+
`sys_user` is backed by. An address that resolves no row lands on the deployment
46
+
default, which is the documented floor rather than a failure — and, as
47
+
everywhere else on this ladder, a failing recipient read never blocks a send.
48
+
49
+
**Docs.**`permissions/authentication.mdx` said "The **invitation** SMS reads
50
+
the deployment default alone"; that sentence is now false and is corrected. No
51
+
shipped page states the invitation *email* locale rule (the auth email ladder is
0 commit comments