Fix Google sign-in for existing email and ticket-buyer accounts.
Google sign-in only worked for people who already had a linked google row in auth_accounts. Anyone who first appeared another way -- a guest ticket purchase, or an email/password signup made after the Better Auth migration -- got a 401 "account not linked". trustedProviders: ['google'] defeats only one of better-auth's two linking gates. The second, requireLocalEmailVerified, defaults to true and refuses the link whenever the LOCAL users.email_verified is false, independently of whether the provider is trusted. That flag is false for every guest-booking row and for every post-migration signup, since requireEmailVerification is off and no verification mail is sent. Turn that gate off: the Google id_token is signature-verified against Google's JWKS with issuer/audience/max-age checks and carries its own email_verified, so the local column proves nothing extra here. Linking alone was not enough. getAuthUser() rejects any session whose user is not 'active', so a ticket buyer would link Google, receive a cookie, and still look logged out. A databaseHooks.account.create.after hook now promotes unclaimed rows to claimed/active when a google account is attached, scoped in the WHERE clause so a suspended account is never reactivated this way. Also normalize users.email. The unique index is case-sensitive while better-auth lowercases every lookup, so someone who booked as John@Gmail.com was invisible to sign-in and Google minted a SECOND user row, stranding their tickets on the first. normalizeEmail() covers the find-or-create sites in tickets.ts and door.ts plus the claim-eligibility lookup, and an idempotent migration lowercases existing rows -- skipping any that would collide and reporting those for manual merge, since merging two people's tickets and payments is not a migration's call. tickets.attendeeEmail still stores the address exactly as typed. Tests drive the real signInSocial id-token path with Google stubbed by signing tokens with a throwaway RS256 key and serving our own JWKS, so the actual verification runs without network or credentials. That also makes the deprecation risk loud: requireLocalEmailVerified is marked for removal upstream, and an upgrade that drops it now fails CI instead of silently locking ticket buyers out again. Frontend carries error.code through so OAUTH_LINK_ERROR renders an actionable message in both locales rather than a bare "account not linked". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3f7b2d51db
commit
87bf9a6151
@@ -114,7 +114,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
idToken: { token: credential },
|
||||
});
|
||||
if (error) {
|
||||
throw new Error(messageFrom(error, 'Google login failed'));
|
||||
// Carry the code through: GoogleSignInButton turns OAUTH_LINK_ERROR into
|
||||
// something actionable instead of showing better-auth's bare
|
||||
// "account not linked".
|
||||
const err = new Error(messageFrom(error, 'Google login failed'));
|
||||
(err as Error & { code?: string }).code = error.code;
|
||||
throw err;
|
||||
}
|
||||
await refreshUser();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user