Skip to content

main: merge main into dev - #280

Merged
Kzoeps merged 48 commits into
devfrom
main
Nov 5, 2026
Merged

Kzoeps merged 48 commits into
devfrom
main

Conversation

@Kzoeps

@Kzoeps Kzoeps commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Syncs main into dev: #260 (Google sign-in hardening, server-side Google start, GitHub sign-in removed) and #279 (CodeRabbit follow-ups from #260).

DjimoSerodio and others added 30 commits October 15, 2026 08:35
…cial

The login page rendered the social buttons as links to
/api/auth/sign-in/social?provider=...&callbackURL=/auth/complete. A link
click is a GET, but better-auth 1.4.18 registers /sign-in/social as
POST-only, so every click ended on a 404 and the buttons could never start
a sign-in on an instance with GOOGLE_CLIENT_ID/SECRET or
GITHUB_CLIENT_ID/SECRET set.

Render them as <button type="button" data-social-provider="google|github">
and add one click handler to the existing page script: it POSTs
{ provider, callbackURL: '/auth/complete' } to authBasePath +
'/sign-in/social' (same origin, so already inside connect-src 'self') and
follows the provider URL better-auth returns. On any failure it shows the
page's standard error flash and re-enables the button. The button stays
disabled while the POST is in flight, because a second POST would replace
the first one's OAuth state cookie, and is re-enabled when the page comes
back from the back/forward cache.

No new endpoint, configuration or dependency; /auth/complete is unchanged.

Tests: new login-page-social-buttons.test.ts runs the page's real inline
script in a node:vm context against a stand-in DOM and clicks the buttons
(all 10 cases fail against main). The documentation-style assertions in
social-providers.test.ts that still described a GET link now describe the
POST.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A provider's button now renders only when the operator configured it and
the OAuth client lists it in epds_social_providers, the same way
epds_handle_login_url gates the ATProto/Bluesky button. Configuring Google
on a shared PDS therefore leaves the login page of every other client
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The login page script assigned window.location to whatever URL
/sign-in/social returned. Follow it only when it is an https URL whose
origin is that provider's authorization server (accounts.google.com for
Google, github.com for GitHub); any other response shows the standard
"Could not start sign-in" error and re-enables the button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tative

better-auth 1.4.18's Google provider copies the ID token's email_verified
claim into user.emailVerified unchanged, and handleOAuthUserInfo tests it
for truthiness when it decides whether to create a verified user or to
link the Google identity to an existing user with the same email. Two
problems follow:

- the string "false" is truthy, so a token carrying email_verified:
  "false" is treated as verified (and "true" is stored as a string);
- Google only vouches for addresses it hosts. For any other address the
  claim means "verified once, when the Google account was created", and
  Google's guidance is to treat it as unverified
  (https://developers.google.com/identity/gsi/web/guides/verify-google-id-token).
  Today such a sign-in links to, and so signs into, an existing user with
  that email.

Add lib/google-email-verified.ts and pass its mapper as the Google
provider's mapProfileToUser, which better-auth spreads over its own user
info. A Google sign-in now counts as email-verified only when the claim is
boolean true or the exact string "true" AND the address is Gmail
(@gmail.com / @googlemail.com) or the token carries an hd (Workspace)
claim. Everything else reaches better-auth as emailVerified: false, so it
is not linked to an existing user ("account not linked").

buildSocialProviders() is exported (as docs/design/testing-gaps.md
suggested) and social-providers.test.ts now tests it instead of a copy.
New better-auth-social-sign-in.test.ts drives a real better-auth instance
on in-memory SQLite through the ID-token sign-in path; 4 of its 8 cases
fail without the mapper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in page

The social buttons passed only callbackURL to better-auth, so any error in
the OAuth callback - the user pressing Cancel on Google's consent screen
(error=access_denied), better-auth refusing to link the identity to an
existing user (account_not_linked), the provider returning no email -
ended on better-auth's bare /api/auth/error page, outside the login flow,
with no way back to the client app.

- The page script now also sends errorCallbackURL: '/auth/complete'.
- /auth/complete checks ?error= right after loading the flow, before it
  reads any session (so a session an earlier sign-in left in the browser
  is never resumed by a cancelled one), reduces the code to one of
  cancelled / email_unverified / no_email / failed, and 303s to
  /oauth/authorize?request_uri=...&client_id=...&epds_social_error=<code>,
  the same login-page URL it already uses when it finds no session.
- GET /oauth/authorize maps epds_social_error to a fixed message and the
  page shows it in the standard flash region on load. Unknown values show
  nothing; request text is never reflected.
- With epds_social_error present, GET /oauth/authorize renders the login
  page even when the browser holds a device session. Handing the flow to
  pds-core's account chooser (session reuse) would drop the message, and
  could auto-select the RP's hinted account for someone who had just
  chosen "Another account" (whose prompt=login and epds_skip_par_hint the
  redirect cannot carry back). For the same reason the PAR login_hint is
  ignored on that return, so the email step renders, which is what the
  message asks for.

The mapping lives in lib/social-sign-in-error.ts. Tests: unit tests for the
mapping, route tests for /auth/complete?error= (all 5 fail without the
route change) and for the login page with epds_social_error, and the
page-script tests now pin errorCallbackURL and the on-load message. The
login page route test runs the script of the page it was served, so it
shows that each code's message reaches the flash region; for that the
node:vm page-script harness moves from login-page-social-buttons.test.ts
to __tests__/__helpers__/login-page-script.ts (with an explicit return
type, so tsc --build can emit it). With dev-id/ses-id cookies the route
test also shows the page instead of a 302 to pds-core, and the email step
when the PAR hint matches an account bound to the device; 2 of those 3
cases fail without the change (the third is the control without the
error).

State errors that better-auth raises before it can read the stored
errorCallbackURL (an expired or mismatched state cookie) still go to its
default error page; that would need onAPIError.errorURL, left out here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n account

ePDS keys a sign-in to a PDS account by email alone (/auth/complete calls
getDidByEmail on the session's address), and better-auth creates a user
for a social sign-in whatever its emailVerified says. With Google's claim
now strict, that leaves three ways an address nobody proved could still
act as its owner:

1. /auth/complete signed it into the existing PDS account for that
   address (it only withheld email_verified on the callback), or created
   a new account with email_verified=0. /auth/choose-handle could be
   POSTed directly to do the latter.
2. The unverified user row better-auth created stays linked to the social
   identity. When the address's real owner later signs in with an email
   code, better-auth's email-otp marks that existing user verified, and
   from then on the social identity signs straight in as them
   (pre-account-takeover).
3. Account settings accepted any better-auth session, so the same
   unverified session could manage the PDS account that owns the address.

Fixes:

- better-auth databaseHooks.user.create.before (refuseUnverifiedUser)
  refuses to create a user whose emailVerified is not true. Email OTP
  always creates verified users, so this only stops social sign-ins the
  provider did not vouch for; the redirect flow then ends on
  /auth/complete?error=email_not_verified (mapped to the email_unverified
  message) and the ID-token flow gets a 401.
- /auth/complete refuses a session whose email is not verified, for new
  and existing accounts alike, and returns to the login page with
  epds_social_error=email_unverified. /auth/choose-handle bounces such a
  session to /auth/complete.
- requireBetterAuth (account settings) and GET /account/login treat a
  session as signed out unless its email is verified.
- All of them, and the hook, use one check, hasVerifiedEmail() in
  lib/verified-email.ts: only a boolean true counts. An earlier release
  could store Google's claim as the text 'true' or 'false', which
  better-auth's SQLite adapter reads back as a string, and the text
  'false' is truthy; with a looser check in one route and a strict one in
  another, /account and /account/login would redirect to each other
  forever for such a row.

The /auth/complete, choose-handle and account guards are defence in depth
for user rows created before the hook existed. Two kinds of row an
earlier release left are beyond them: an unverified user with a linked
social identity, which comes back to life once its owner proves the
address (the next commit removes such links at that moment), and a
social identity an earlier release linked to a user who is verified,
which better-auth signs straight in without looking at the email again.
The docs commit therefore tells operators who had GOOGLE_* or GITHUB_*
set to delete the social links and unverified users an earlier release
left.

Tests: better-auth-social-sign-in.test.ts now runs with ePDS's
databaseHooks and also drives the real redirect flow (POST
/sign-in/social, GET /callback/google with Google's token endpoint
stubbed): a cancel lands on /auth/complete?error=access_denied, an
unverified address on ?error=email_not_verified with no user created, a
verified Gmail address on /auth/complete with a session. New route tests
cover /auth/complete, choose-handle and account settings with unverified
sessions, including emailVerified stored as the text 'true' or 'false'
(they fail without the route changes), and verified-email.test.ts pins
the check itself. callback-handle-mode's session fixture now carries
emailVerified: true, as an email-OTP session does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rified

The user-creation guard stops new unverified users, but an instance that
had Google or GitHub configured before it can hold unverified users an
earlier release created, each with a social identity linked. They cannot
sign in now. better-auth, however, marks such a user verified as soon as
the address's owner proves the address: email-otp's sign-in (and its
verify-email) does it for an email code, and handleOAuthUserInfo does it
when a social sign-in the provider vouches for (the owner's own Gmail
identity, say) links to the user. From then on better-auth signs the
linked identity straight in without looking at the email again, so it
signs in as the owner, and an old session of it starts counting as
signed in too.

Add databaseHooks.user.update before/after hooks: when an update marks a
user's email verified (in better-auth 1.4.18 only a user who was not
verified gets one; overrideUserInfoOnSignIn, which ePDS does not set,
would also write it for verified users), remove every social identity
linked to the user and sign out its sessions. This runs before the
sign-in that verified the address creates its own session, so that
sign-in completes as normal. A Gmail or Workspace identity links again by
itself on its next sign-in; the one a social sign-in has just linked is
dropped as well, which costs one extra link.

Still left to the operator cleanup in the docs: a social identity an
earlier release linked to a user who was already verified (this release
cannot tell it from a good link), and a user whose emailVerified an
earlier release stored as the text 'true' or 'false' (email-otp only
updates a falsy value, so that user's owner stays locked out until the
row is removed).

Tests: create-better-auth.test.ts runs createBetterAuth() itself, as
index.ts does (in-memory SQLite, Google's token endpoint stubbed, email
codes captured from a stub EmailSender, PDS lookups mocked), so it also
covers production wiring that no test reached before. It checks that the
hooks and the Google claim mapper are the ones passed to better-auth and
that a non-authoritative Google address is refused with no user, and it
drives both revival paths with rows seeded as an earlier release left
them: an unverified user with a Google link loses the link and its old
session when the owner signs in by email code, after which the Google
identity gets account_not_linked; an authoritative Google sign-in drops
a GitHub link from such a user and links itself again next time. A
verified user's links and sessions survive an email-code sign-in.
Removing databaseHooks from createBetterAuth fails 4 of its 6 tests,
removing the update hooks 3, removing the mapper 3. The Google
redirect-flow helpers move from better-auth-social-sign-in.test.ts to
__tests__/__helpers__/google-sign-in.ts so both files use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sted

docs/configuration.md's social providers section only listed the four
env vars. Add:

- that the buttons are instance-wide (every OAuth client of the ePDS);
- the redirect URIs to register, https://<AUTH_HOSTNAME>/api/auth/callback/google
  and .../callback/github, one per auth hostname;
- Google setup: a Web application OAuth client, a consent screen with only
  openid/email/profile, publishing status In production (Testing limits
  sign-in to at most 100 listed test users);
- which social sign-ins are trusted (Google: strict email_verified plus
  Gmail/Workspace; GitHub: the /user/emails verified flag, with the
  caveat that GitHub does not host addresses, so the flag can be stale)
  and what happens to the rest;
- for instances that had Google or GitHub configured before: which rows
  an earlier release left this release handles by itself (an unverified
  user's links and sessions go once its owner proves the address) and
  which still have to be removed by hand (a social identity linked to a
  verified user, which still signs in without the email being checked
  again; a user whose emailVerified was stored as text, which locks its
  owner out), a read-only query to look first, and the DELETEs. They use
  emailVerified IS NOT 1, which also catches the text 'true'/'false' an
  earlier release could store. Checked against the schema better-auth
  1.4.18 creates.

Both .env.example files now name the redirect URIs next to the variables.

features/social-login.feature (@pending) now matches: the "same email"
scenario uses a Gmail address, and new scenarios cover a Google address
Google does not host (back to the login page, asked for an email code),
a Workspace address (trusted), and Cancel at Google.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pnpm test:coverage on this branch: statements 64.62, branches 63.59,
functions 77.22, lines 63.51 (main: 61.68 / 60.64 / 74.39 / 60.49). Raise
the floors to 64 / 63 / 77 / 63 per the ratcheting policy, and record in
testing-gaps.md that better-auth.ts is now at ~76%, with
create-better-auth.test.ts running the production instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…per client

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isGoogleAuthoritativeFor treated any non-empty hd as proof that Google
hosts the address, so person@example.com with hd "*" or
"attacker.example" counted as verified. hd names a hosted domain: Google
is now authoritative only for @gmail.com / @googlemail.com, or when hd
equals the address's domain (both lower-cased). Workspace secondary
domains no longer count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hasVerifiedEmail accepted only a boolean true, so users an earlier
release stored with Google's claim as the text 'true' failed
/auth/complete, /auth/choose-handle and /account until an operator ran
the manual SQL cleanup. Accept the exact text 'true' as well; 'false'
and every other value still count as unverified, and the hook that drops
links made before verification is unchanged.

The cleanup in docs/configuration.md no longer removes 'true' users
(NOT IN (1, 'true') instead of IS NOT 1); it is still needed for social
links on verified users and for users stored as the text 'false', which
an email code cannot mark verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The social buttons only disabled the button that was clicked, so a click
on the other provider, an email code send, a verify or a handle hand-off
could start while the first sign-in was still in flight; a second
/sign-in/social replaces the first one's OAuth state.

One page-wide guard now covers the social buttons, sending and resending
an email code, verifying it, and the ATProto/Bluesky handle hand-off.
Each handler claims it before starting a request, releases it when the
attempt fails, and keeps it while the page navigates away. Sending a code
releases it once the code is sent, so the verify that follows can run.
The verify form's own double-submit latch is folded into it. A page
restored from the back/forward cache releases it.

The login page script harness moves to __tests__/__helpers__ so the new
guard tests and the social button tests share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also tell end users that only one sign-in runs at a time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/auth-service/src/__tests__/__helpers__/login-page-script.ts
#	packages/auth-service/src/__tests__/login-page-social-buttons.test.ts
Google and GitHub sign-in were never enabled on any deployment, so there
are no social links or sessions from an earlier release to repair.
Remove noteEmailVerification, dropLinksMadeBeforeVerification, their
context tracking and the user.update hooks, along with the earlier-release
fixtures and tests, the SQL clean-up section in docs/configuration.md and
the release-note text about it. The user-creation guard stays.

The update hook also fired on /email-otp/verify-email for a user who was
already verified, which signed out that user's existing (for example
Google) session. With it gone, a new test pins that re-verifying an
address keeps the session and the Google link, and that an email-code
sign-in after a Google sign-up lands on the same user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No release stored emailVerified as text, so hasVerifiedEmail is now
user?.emailVerified === true and the string-valued cases go from its
tests and the account route tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The redirect URI table, the trust notes and the .env.example comments
added for this change now cover Google only. The GitHub provider itself
and its existing variables are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sonar flagged 3.3% duplication on new code: complete-social-sign-in
copied the mocks and app setup of callback-handle-mode, and
login-page-social-error copied the config and server setup of
login-page-prompt-login. The /auth/complete social cases move into
callback-handle-mode, whose stand-in session now takes a user, and both
login page route tests start their app from __helpers__/login-page-app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sign-in guard and social button tests rendered the page with the same
option block; it now lives next to the script harness.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	packages/auth-service/src/__tests__/login-page-social-buttons.test.ts
features/social-login.feature was @pending and never ran. It is now
Google-only, tagged @fake-google, and runs in the private Atmosphere
stack:

- The browser's trip to accounts.google.com is intercepted with
  page.route and redirected straight back to /api/auth/callback/google,
  with the scenario's ID token claims as the authorization code (or
  error=access_denied for a cancel).
- With EPDS_TEST_HOOKS=1 and EPDS_TEST_FAKE_GOOGLE=1, auth-service
  answers better-auth's token request with an ID token for those claims
  (lib/test-google-token.ts). It refuses to install when
  NODE_ENV=production. Everything after it, including the Google claim
  check, runs as in production.
- The demo client lists google in epds_social_providers; the stack sets
  placeholder Google credentials and E2E_FAKE_GOOGLE=1 on the runner, and
  e2e/cucumber.mjs excludes @fake-google where that flag is unset.

Scenarios: an account created with Google is the same PDS account for a
later email-code sign-in; a Google sign-in refused for hd other.example
recovers with an email code to the existing account; a cancel at Google
returns to the login page. No session is created by the refused or
cancelled sign-ins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch had ratcheted the thresholds to 65/64/77/64 with the
earlier-release repair code and its tests included. Removing that code
leaves 64.76/63.75/77.31/63.61, so the floors become 64/63/77/63, still
above main's 61/60/74/60.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ding without a fake Google

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in flight

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DjimoSerodio and others added 16 commits October 21, 2026 12:46
The social button passes its provider in the errorCallbackURL, and
/auth/complete carries it back to the login page, so the message names
the provider that was used. Only names from a fixed list are shown; any
other value falls back to the neutral wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Extract the request-body parsing into requestForm() instead of a nested
ternary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Set prompt=select_account on the Google provider, so a browser already
signed in to another Google account cannot silently pick it. Also export
socialProviders as a const filled in place by createBetterAuth (Sonar
S6861), so importers still see the running instance's providers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A client can add epds_social_provider=google to /oauth/authorize (for
example from a "Sign in with Google" button on its own page). The login
page then hands off to Google on load and sends no email code; a
login_hint email becomes Google's login hint. The parameter only counts
for a provider the client offers, never on a social error return (which
carries it to name the failed provider), and, like prompt=login, skips
device-session reuse.

The login page's Google button also sends an email address typed into
the field as Google's login hint. The demo gets a "Sign in with Google"
button under "Sign in with ATProto/Bluesky", with an e2e scenario that
checks Google is asked for the account chooser and no code is sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Decide epds_social_provider against the client's offered providers
  before the device-session reuse check, so an unlisted or unconfigured
  provider no longer skips reuse (Greptile, CodeRabbit).
- Log why a social sign-in could not start instead of an empty catch.
- Demo: with social_provider, drop a malformed email instead of
  refusing the sign-in.
- Tests: the controls now assert the code is sent on load, and reuse
  still happens for an unoffered provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rdening

Brings in #265 (keep a handle/DID-resolved email server-side) and the
0.10.1 release. Conflicts:
- login-page.ts: keep main's renderTermsHtml/renderSocialButtonsHtml
  split, with the helper rendering this branch's POST buttons for the
  client's offered providers (main still rendered GET links, which 404);
  the auto-sent code keeps both the in-flight guard and main's
  flow-email fallback.
- vitest.config.ts, testing-gaps.md: main's numbers, both texts.
- login-page-prompt-login.test.ts: #265's new describe reuses the shared
  test app, which now exposes its context.
- The social auto-start loop only runs when a provider was requested, so
  main's flow-email page-script test (whose fake DOM has no getAttribute)
  passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Google is the only social provider. GITHUB_CLIENT_ID/SECRET are no longer
read, "github" in epds_social_providers is ignored, and the GitHub button,
provider origin, display name, docs, env examples and tests are gone.
The GitHub button never worked (its GET link was a 404), so no sign-in is
lost. A few tests keep "github" as input to show it stays ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With ?epds_social_provider=google, GET /oauth/authorize now starts the
sign-in on the server (better-auth signInSocial with returnHeaders) and
redirects to Google, instead of rendering the login page and pressing
its button from the page script. Nothing shows before Google's own page,
and Back returns to the app rather than restarting Google.

- The state cookie better-auth issues is appended to the response, next
  to the auth_flow cookie and any orphan device-cookie clears.
- Only an email hint the caller supplied goes to Google; an address
  resolved from a handle/DID never reaches the redirect.
- The provider URL gets the same https + origin check as the button.
- If the start fails, the login page renders with "Could not start
  sign-in" and no code is sent.
- The client-side auto-start (and the renderLoginPage option) is gone.
- Tests: route tests for the redirect, cookies, handle hints, failures
  and session reuse; create-better-auth runs the start against the real
  instance through to the callback. The e2e fake for Google follows the
  redirect chain from the demo's sign-in route, since Playwright routes
  only the first request of a chain, and the scenario now checks the
  auth service redirected straight to Google.
- Coverage thresholds ratcheted to 66/65/79/65.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How a client opts in (epds_social_providers), how to start Google from
its own page (epds_social_provider=google on the returned authorization
URL, with an optional email login_hint), what users and the callback see,
how to verify the redirect, and the two common failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only the first GET for a flow redirects to the provider. better-auth
  keeps one OAuth state cookie per browser, so a duplicate GET (extension
  re-request, prefetch, second tab) starting again would replace the
  state of the sign-in already at Google and make it fail. A duplicate
  gets the login page, which starts nothing on load, like the OTP path's
  otpAlreadySent.
- request_uri is narrowed to a string, like login_hint, so a repeated or
  bracketed query key gets the existing 400 instead of reaching the
  database lookup.
- Tests: the duplicate GET, a non-string request_uri, Google listed by a
  client on a server without Google configured, and direct cases for the
  server-side provider URL check (http, look-alike host, userinfo, port,
  relative, non-string, other providers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- remove-github-sign-in changeset: operators only; epds_social_providers
  was never released with "github".
- epds-login skill and verified-email changeset: Workspace addresses
  count only on the organisation's primary (hd) domain.
- e2e README: how the fake reaches Google when the auth service redirects.
- IMPLEMENTATION-PLAN: social-login.feature has 4 scenarios.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(auth): make social sign-in safe to switch on (verified emails only, errors back to the login page)
Send social callbacks that lost their state cookie to /auth/complete
instead of better-auth's bare error page, only treat known
epds_social_error codes as a sign-in return, bind the login-page test
server to 127.0.0.1, and tighten the related docs and changeset.

Closes #278
…-278

auth: address CodeRabbit follow-ups from #260
@changeset-bot

changeset-bot Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: dfb8e44

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
epds-demo Ready Ready Preview Nov 5, 2026 7:36am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 994d4b8e-fbbb-452c-9b08-d199804d1942

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@coveralls-official

coveralls-official Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

Coverage Report for CI Build 41988417676

Warning

No base build found for commit cc1a6a0 on dev.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 65.693%

Details

  • Patch coverage: 5 uncovered changes across 2 files (134 of 139 lines covered, 96.4%).

Uncovered Changes

File Changed Covered %
packages/auth-service/src/index.ts 3 0 0.0%
packages/auth-service/src/lib/test-google-token.ts 24 22 91.67%
Total (13 files) 139 134 96.4%

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 3418
Covered Lines: 2240
Line Coverage: 65.54%
Relevant Branches: 2199
Covered Branches: 1450
Branch Coverage: 65.94%
Branches in Coverage %: Yes
Coverage Strength: 14.21 hits per line

💛 - Coveralls

@sonarqubecloud

sonarqubecloud Bot commented Nov 5, 2026

Copy link
Copy Markdown

@Kzoeps
Kzoeps merged commit b335a9e into dev Nov 5, 2026
49 of 51 checks passed

This branch was successfully deployed

4 active deployments
Production — dfb8e44d Deployed Nov 5, 2026 by vercel[bot]
ePDS / test — dfb8e44d Deployed Nov 5, 2026 by railway-app[bot]
ePDS / pr-base — dfb8e44d Deployed Nov 5, 2026 by railway-app[bot]
Preview — dfb8e44d Deployed Nov 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants