HarperFast / HarperFast/studio
OAuth sign-in error redirects carry error/reason params; check-oauth drops them and shows one generic "try again" toast
- Dominant language
- TypeScript
- Stars
- 5
- Forks
- 4
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 40
Description
## Summary
When an OAuth sign-in fails after the browser has left the studio, `@harperfast/oauth` redirects back to `postLoginRedirect` (`/#/check-oauth` for the studio) with `error` and `reason` query params that say why. The studio never reads them. `CheckOAuth` calls `getCurrentUser()` and, on failure, shows the same toast for every case:
> We were not able to verify your sign-in. Please try signing in again.
For several of the reasons the plugin sends, retrying is exactly the wrong advice.
## What the plugin sends today
Callback-side failures in `handleCallback` ([oauth `src/lib/handlers.ts` @ 0aa719cc2d640db618295f9c2f63ce6b3e714d6d](https://github.com/HarperFast/oauth/blob/0aa719cc2d640db618295f9c2f63ce6b3e714d6d/src/lib/handlers.ts#L182-L532)) redirect to the original URL with:
| Redirect | When |
|---|---|
| `error=auth_failed&reason=csrf` | state / browser-binding mismatch |
| `error=auth_failed&reason=token_exchange\|user_mapping\|user_info\|login_hook\|unknown` | thrown during code exchange, userinfo, or the `onLogin` hook |
| `error=oauth_failed&reason=` | the IdP returned an error, e.g. `access_denied` |
| `error=access_denied&reason=` | the application's `onLogin` hook denied the login; the Fabric control plane's hook returns `email_not_verified`, `provider_not_authorized`, `login_not_allowed`, `internal_error` |
| `error=invalid_request` | missing state or code |
Login-side storage failures (the CSRF state write 503ing during a core write wedge, HarperFast/harper#2450) currently surface as a raw JSON error page instead of a redirect; HarperFast/oauth#227 tracks turning those into a redirect too, so the studio will start receiving `error=server_error&reason=state_storage` as well.
## Why the studio can't see them even if it wanted to
The plugin appends the params with the URL API, which puts them before the hash: `/?error=auth_failed&reason=csrf#/check-oauth`. The studio router is `createHashHistory()` ([`src/router/useNewRouter.ts:13` @ 037a2fdb60ab25b334a2fb87c850bd6834e57e0c](https://github.com/HarperFast/studio/blob/037a2fdb60ab25b334a2fb87c850bd6834e57e0c/src/router/useNewRouter.ts#L13)), so `useSearch` only sees the hash's own query string, which is empty. Reading them needs `window.location.search`, the pattern [`ProcessSetupIntent.tsx:14`](https://github.com/HarperFast/studio/blob/037a2fdb60ab25b334a2fb87c850bd6834e57e0c/src/features/organization/billing/confirm/ProcessSetupIntent.tsx#L14) already uses.
## Where it shows
[`src/features/auth/CheckOAuth.tsx:26-31` @ 037a2fdb60ab25b334a2fb87c850bd6834e57e0c](https://github.com/HarperFast/studio/blob/037a2fdb60ab25b334a2fb87c850bd6834e57e0c/src/features/auth/CheckOAuth.tsx#L26-L31): `getCurrentUser().catch(() => null)` → generic toast → navigate to `/sign-in`. `SignIn.tsx` only reads `me` from search.
## Proposed fix
1. In `CheckOAuth` (or the auth layout), parse `window.location.search` for `error` / `reason` before calling `getCurrentUser`, and pick the message from the reason:
- `email_not_verified` → "Verify your email address before signing in with this provider."
- `provider_not_authorized`, `login_not_allowed` → "This sign-in method isn't allowed for your account."
- `csrf` → "Your sign-in session expired. Please try again."
- `server_error`, `internal_error`, `unknown`, `token_exchange`, `user_info` → "Sign-in is temporarily unavailable. Please try again in a few minutes."
- IdP `access_denied` → "Sign-in was cancelled."
2. Strip the params from the URL after reading them (as `clearUtmParamsFromUrl` does) so a reload doesn't re-toast.
3. Keep the existing generic toast as the fallback when there is no `error` param but `getCurrentUser` still fails.
## Context
Found while looking at a sign-in failure during a control-plane write wedge (HarperFast/harper#2450). The raw 503 the user saw on `/oauth/google/login` was at least honest; any redirect-with-reason from the plugin would have collapsed into the generic "try again" toast. Making sign-in degrade legibly is therefore a studio change first.
Contributor guide
Research direction
Read src/features/auth/CheckOAuth.tsx and the window.location.search pattern in src/features/organization/billing/confirm/ProcessSetupIntent.tsx. Parse and clear the OAuth error parameters before the user lookup, show the specified reason-based messages, retain the generic fallback, and navigate to sign-in without re-showing the message on reload.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authentication, frontend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100