Closes#345. Phase 6 of six, and the last one under #332.
The first phase a customer can see. AuthForm.tsx is the single sign-in implementation rendered by both the route modal and the cart prompt, so the button goes in one place and appears in both.
Where it sits, and why
Below the passkey button, which is below the password form. The order is deliberate and it is not about preference: a passkey is already on the device in front of the customer, Google is a round trip to somebody else's site, and passwords are how every existing customer signs in. Each step down that list asks more of the person using it.
Absent rather than disabled where it is not configured. Same call #41 made for a browser without WebAuthn, and it matters more here because unconfigured is the normal state: local development has no credentials, and QA cannot have any until #313.
The storefront learns this from a boolean on the existing public config, never the client id. The browser has no use for one — the whole flow is a redirect the server builds. There is a test that neither the id nor the secret appears in that payload.
Google's mark
Inlined as SVG with their published colours and geometry. A hand-drawn approximation of somebody else's trademark is a compliance problem rather than a style choice, and a second origin on the sign-in path is a second thing that can be down.
The return path
Supplied by the caller, because only the caller knows. The route modal renders over a backdrop location and its own path is /login, so reading the current URL there would send the customer back to the form they just left. The router builds it from the backdrop. The cart prompt uses the page it interrupted.
It cannot resume the interrupted action the way onSuccess does, because the redirect leaves the application. The customer lands back on the page and presses the button again.
Validated on the server, not in the browser. Anyone can type the URL, and doing it in one place beats doing it twice in two languages.
The end-to-end test asserts absence
Which is the behaviour local and QA actually have. It then signs in with the password form to show that the absence changes nothing — the point of putting the alternatives below rather than above.
docs/ops/google-sign-in.md
What has to be true outside the repository: the seven sections of the Google Auth Platform, the three scopes that keep publishing out of a verification review, the cutover checklist for #313, and the production smoke test.
It states plainly that QA on the Synology hostname is impossible rather than merely unconfigured. Google will not accept a redirect URI whose domain nobody can prove they own, and Synology owns the registrable domain. Same wall as #285.
The failure it warns about hardest: leaving the consent screen in Testing. Only listed test users can sign in, the refusal happens on Google's own page, and nothing reaches the storefront — so a customer reports a broken button and the logs are silent.
Testing
Four new integration tests on the config flag, one end-to-end test on the button's absence.
Verified locally: backend tsc clean for src and tests, 590 unit tests pass, lint at the seven warnings that predate this branch; frontend tsc, lint and build clean. The integration and end-to-end suites need a database this machine has no Docker for.
Closes #345. Phase 6 of six, and the last one under #332.
The first phase a customer can see. `AuthForm.tsx` is the single sign-in implementation rendered by both the route modal and the cart prompt, so the button goes in one place and appears in both.
## Where it sits, and why
Below the passkey button, which is below the password form. The order is deliberate and it is not about preference: a passkey is already on the device in front of the customer, Google is a round trip to somebody else's site, and passwords are how every existing customer signs in. Each step down that list asks more of the person using it.
**Absent rather than disabled where it is not configured.** Same call #41 made for a browser without WebAuthn, and it matters more here because unconfigured is the *normal* state: local development has no credentials, and QA cannot have any until #313.
The storefront learns this from a boolean on the existing public config, never the client id. The browser has no use for one — the whole flow is a redirect the server builds. There is a test that neither the id nor the secret appears in that payload.
## Google's mark
Inlined as SVG with their published colours and geometry. A hand-drawn approximation of somebody else's trademark is a compliance problem rather than a style choice, and a second origin on the sign-in path is a second thing that can be down.
## The return path
Supplied by the caller, because only the caller knows. The route modal renders over a backdrop location and its own path is `/login`, so reading the current URL there would send the customer back to the form they just left. The router builds it from the backdrop. The cart prompt uses the page it interrupted.
It cannot resume the interrupted action the way `onSuccess` does, because the redirect leaves the application. The customer lands back on the page and presses the button again.
**Validated on the server, not in the browser.** Anyone can type the URL, and doing it in one place beats doing it twice in two languages.
## The end-to-end test asserts absence
Which is the behaviour local and QA actually have. It then signs in with the password form to show that the absence changes nothing — the point of putting the alternatives below rather than above.
## docs/ops/google-sign-in.md
What has to be true outside the repository: the seven sections of the Google Auth Platform, the three scopes that keep publishing out of a verification review, the cutover checklist for #313, and the production smoke test.
It states plainly that **QA on the Synology hostname is impossible rather than merely unconfigured.** Google will not accept a redirect URI whose domain nobody can prove they own, and Synology owns the registrable domain. Same wall as #285.
The failure it warns about hardest: leaving the consent screen in **Testing**. Only listed test users can sign in, the refusal happens on Google's own page, and nothing reaches the storefront — so a customer reports a broken button and the logs are silent.
## Testing
Four new integration tests on the config flag, one end-to-end test on the button's absence.
Verified locally: backend `tsc` clean for src and tests, 590 unit tests pass, lint at the seven warnings that predate this branch; frontend `tsc`, lint and build clean. The integration and end-to-end suites need a database this machine has no Docker for.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The last of the six, and the first a customer can see. The auth form is the single sign-in implementation rendered by both the route modal and the cart prompt, so the button goes in one place and appears in both.
Below the passkey button, which is below the password form. The order is deliberate and it is not about preference: a passkey is already on the device in front of the customer, while Google is a round trip to somebody else's site, and passwords are how every existing customer signs in. Each step down that list asks more of the person using it.
Absent rather than disabled where it is not configured, which is the same call #41 made for a browser without WebAuthn. It matters more here, because being unconfigured is the normal state rather than the exception: local development has no credentials, and QA cannot have any until #313. The storefront advertises a boolean through the existing public config, never the client id — the browser has no use for one, since the whole flow is a redirect the server builds.
Google's mark is inlined as SVG with their published colours and geometry. A hand-drawn approximation of somebody else's trademark is a compliance problem rather than a style choice, and a second origin on the sign-in path is a second thing that can be down.
The button is a navigation rather than a fetch, which makes it unlike every other control on that form. The flow leaves the application entirely, so there is no promise to await and no error to catch — the callback decides and redirects.
Where to return to is supplied by the caller, because only the caller knows. The route modal renders over a backdrop location and its own path is /login, so reading the current URL there would send the customer back to the form they just left; the router builds it from the backdrop instead. The cart prompt uses the page it interrupted. It cannot resume the interrupted action the way onSuccess does — the redirect leaves the app — so the customer lands back on the page and presses the button again.
That value is validated on the server and not in the browser. It has to be, since anyone can type the URL, and doing it in one place beats doing it twice in two languages.
The end-to-end test asserts the button is ABSENT, which is the behaviour local and QA actually have, and then signs in with the password form to show that its absence changes nothing. That is the point of putting the alternatives below rather than above.
docs/ops/google-sign-in.md records what has to be true outside the repository: the seven sections of the Google Auth Platform, the three scopes that keep publishing out of a verification review, the cutover checklist for #313, and the production smoke test. It states plainly that QA on the Synology hostname is impossible rather than merely unconfigured, because Google will not accept a redirect URI whose domain nobody can prove they own — the same wall #285 hit with Cloudflare.
The failure that document warns about hardest is leaving the consent screen in Testing. Only listed test users can then sign in, the refusal happens on Google's own page, and nothing reaches the storefront at all — so a customer reports a broken button and the logs are silent.
Verified: backend tsc clean for src and tests, 590 unit tests pass, lint at the seven warnings that predate this branch, frontend tsc, lint and build clean. The integration and end-to-end suites need a database this machine has no Docker for.
Closes#345
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #345. Phase 6 of six, and the last one under #332.
The first phase a customer can see.
AuthForm.tsxis the single sign-in implementation rendered by both the route modal and the cart prompt, so the button goes in one place and appears in both.Where it sits, and why
Below the passkey button, which is below the password form. The order is deliberate and it is not about preference: a passkey is already on the device in front of the customer, Google is a round trip to somebody else's site, and passwords are how every existing customer signs in. Each step down that list asks more of the person using it.
Absent rather than disabled where it is not configured. Same call #41 made for a browser without WebAuthn, and it matters more here because unconfigured is the normal state: local development has no credentials, and QA cannot have any until #313.
The storefront learns this from a boolean on the existing public config, never the client id. The browser has no use for one — the whole flow is a redirect the server builds. There is a test that neither the id nor the secret appears in that payload.
Google's mark
Inlined as SVG with their published colours and geometry. A hand-drawn approximation of somebody else's trademark is a compliance problem rather than a style choice, and a second origin on the sign-in path is a second thing that can be down.
The return path
Supplied by the caller, because only the caller knows. The route modal renders over a backdrop location and its own path is
/login, so reading the current URL there would send the customer back to the form they just left. The router builds it from the backdrop. The cart prompt uses the page it interrupted.It cannot resume the interrupted action the way
onSuccessdoes, because the redirect leaves the application. The customer lands back on the page and presses the button again.Validated on the server, not in the browser. Anyone can type the URL, and doing it in one place beats doing it twice in two languages.
The end-to-end test asserts absence
Which is the behaviour local and QA actually have. It then signs in with the password form to show that the absence changes nothing — the point of putting the alternatives below rather than above.
docs/ops/google-sign-in.md
What has to be true outside the repository: the seven sections of the Google Auth Platform, the three scopes that keep publishing out of a verification review, the cutover checklist for #313, and the production smoke test.
It states plainly that QA on the Synology hostname is impossible rather than merely unconfigured. Google will not accept a redirect URI whose domain nobody can prove they own, and Synology owns the registrable domain. Same wall as #285.
The failure it warns about hardest: leaving the consent screen in Testing. Only listed test users can sign in, the refusal happens on Google's own page, and nothing reaches the storefront — so a customer reports a broken button and the logs are silent.
Testing
Four new integration tests on the config flag, one end-to-end test on the button's absence.
Verified locally: backend
tscclean for src and tests, 590 unit tests pass, lint at the seven warnings that predate this branch; frontendtsc, lint and build clean. The integration and end-to-end suites need a database this machine has no Docker for.🤖 Generated with Claude Code