HarperFast / HarperFast/studio

OAuth sign-in error redirects carry error/reason params; check-oauth drops them and shows one generic "try again" toast

Open
#1,674 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.