Skip to content
File

Blob: src/client/lib/sign-in-oauth.ts

typescript48 lines
1// Pure decisions for the /sign-in OAuth flow. Extracted so the
2// signed-query ordering and the signed-in render/redirect contract can
3// be unit-tested without React/DOM. The page consumes these helpers; do
4// not duplicate the logic in `pages/sign-in.tsx`.
5 
6// tessera-local UI params that may appear next to a signed RP-initiated
7// /authorize query (e.g. `?error=social_unavailable&<signed>`). The
8// extractor skips them so the rebuilt slice is byte-equal to what
9// Better Auth signed.
10const SIGNED_QUERY_LOCAL_KEYS = new Set(["error", "redirect"]);
11 
12// Returns the original signed parameter sequence through `sig`, with
13// tessera-local UI params filtered out. Returns null when the URL is
14// not an RP-initiated /authorize (no `client_id` + `sig` pair).
15export const signedOAuthQueryFromParams = (params: URLSearchParams): string | null => {
16 if (!params.has("client_id") || !params.has("sig")) return null;
17 const signed = new URLSearchParams();
18 for (const [key, value] of params.entries()) {
19 if (SIGNED_QUERY_LOCAL_KEYS.has(key)) continue;
20 signed.append(key, value);
21 if (key === "sig") return signed.toString();
22 }
23 return null;
24};
25 
26// Builds the social `errorCallbackURL`. Signed params come first so the
27// extractor's stop-at-sig invariant is preserved end-to-end even if a
28// stale tab loads the URL: `?error=...&<signed>` would also recover via
29// the extractor's blocklist, but constructing it signed-first removes
30// the dependency on that hardening for current code paths.
31export const socialErrorCallbackURL = (signedOAuthQuery: string | null): string =>
32 signedOAuthQuery ? `/sign-in?${signedOAuthQuery}&error=social_unavailable` : "/sign-in?error=social_unavailable";
33 
34// Better Auth reaches /sign-in even with an active session when the RP
35// requests `prompt=login` or `prompt=create`. Auto-navigating away on
36// the basis of "user has a session" alone would drop the RP's signed
37// authorization request. Redirect only when no signed query is present.
38export const shouldRedirectSignedInFromSignIn = (input: {
39 sessionPending: boolean;
40 signedInUserId: string | null;
41 signedOAuthQuery: string | null;
42}): boolean => {
43 if (input.sessionPending) return false;
44 if (!input.signedInUserId) return false;
45 if (input.signedOAuthQuery) return false;
46 return true;
47};