Skip to content

Commit 8e20947

Browse files
committed
fix(webapp): stop stale login errors reappearing on the login page
The /login loader read the flashed auth error without committing the session, so the flash was never consumed: once any login attempt flashed an error (for example a rejected email address), it reappeared on every /login visit and made later, successful attempts look like they failed. Commit the session when an error was read, and read the error in the redirectTo branch too instead of dropping it. The /login/magic action also stored its validation and rate limit errors with session.set, which survives every later read. Switch those to session.flash so they render once and clear.
1 parent dad37ef commit 8e20947

3 files changed

Lines changed: 53 additions & 31 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Fixed stale login errors: an error from a previous login attempt (for example a rejected email address) no longer keeps reappearing on the login page and no longer makes later, successful attempts look like they failed.

apps/webapp/app/routes/login._index/route.tsx

Lines changed: 42 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ import { isGithubAuthSupported, isGoogleAuthSupported } from "~/services/auth.se
2323
import { getLastAuthMethod } from "~/services/lastAuthMethod.server";
2424
import { commitSession, setRedirectTo } from "~/services/redirectTo.server";
2525
import { getUserId } from "~/services/session.server";
26-
import { getUserSession } from "~/services/sessionStorage.server";
26+
import {
27+
commitSession as commitUserSession,
28+
getUserSession,
29+
} from "~/services/sessionStorage.server";
2730
import { ssoController } from "~/services/sso.server";
2831
import { flags as getGlobalFlags } from "~/v3/featureFlags.server";
2932
import { requestUrl } from "~/utils/requestUrl.server";
@@ -111,8 +114,31 @@ export async function loader({ request }: LoaderFunctionArgs) {
111114
showSsoAuth = (globalFlags as Record<string, unknown>).hasSso === true;
112115
}
113116

117+
// Read the flashed auth error and, when one exists, commit the session so
118+
// the flash is consumed. Reading without committing leaves the flash in the
119+
// cookie, so a stale error (e.g. a once-rejected email address) would
120+
// resurface on every future /login visit and make later, successful login
121+
// attempts look like they failed.
122+
const userSession = await getUserSession(request);
123+
const error = userSession.get("auth:error");
124+
125+
let authError: string | undefined;
126+
if (error) {
127+
if ("message" in error) {
128+
authError = error.message;
129+
} else {
130+
authError = JSON.stringify(error, null, 2);
131+
}
132+
}
133+
134+
const headers = new Headers();
135+
if (error) {
136+
headers.append("Set-Cookie", await commitUserSession(userSession));
137+
}
138+
114139
if (redirectTo) {
115140
const session = await setRedirectTo(request, redirectTo);
141+
headers.append("Set-Cookie", await commitSession(session));
116142

117143
return typedjson(
118144
{
@@ -121,39 +147,26 @@ export async function loader({ request }: LoaderFunctionArgs) {
121147
showGoogleAuth: isGoogleAuthSupported,
122148
showSsoAuth,
123149
lastAuthMethod,
124-
authError: null,
150+
authError,
125151
notice,
126152
isVercelMarketplace: redirectTo.startsWith("/vercel/callback"),
127153
},
128-
{
129-
headers: {
130-
"Set-Cookie": await commitSession(session),
131-
},
132-
}
154+
{ headers }
133155
);
134156
} else {
135-
const session = await getUserSession(request);
136-
const error = session.get("auth:error");
137-
138-
let authError: string | undefined;
139-
if (error) {
140-
if ("message" in error) {
141-
authError = error.message;
142-
} else {
143-
authError = JSON.stringify(error, null, 2);
144-
}
145-
}
146-
147-
return typedjson({
148-
redirectTo: null,
149-
showGithubAuth: isGithubAuthSupported,
150-
showGoogleAuth: isGoogleAuthSupported,
151-
showSsoAuth,
152-
lastAuthMethod,
153-
authError,
154-
notice,
155-
isVercelMarketplace: false,
156-
});
157+
return typedjson(
158+
{
159+
redirectTo: null,
160+
showGithubAuth: isGithubAuthSupported,
161+
showGoogleAuth: isGoogleAuthSupported,
162+
showSsoAuth,
163+
lastAuthMethod,
164+
authError,
165+
notice,
166+
isVercelMarketplace: false,
167+
},
168+
{ headers }
169+
);
157170
}
158171
}
159172

apps/webapp/app/routes/login.magic/route.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,9 @@ export async function action({ request }: ActionFunctionArgs) {
141141

142142
if (!result.success) {
143143
const session = await getUserSession(request);
144-
session.set("auth:error", {
144+
// flash, not set: a set() key survives every later read/commit, so the
145+
// error would reappear on each /login visit long after the failed attempt.
146+
session.flash("auth:error", {
145147
message: "Please enter a valid email address.",
146148
});
147149

@@ -191,7 +193,8 @@ export async function action({ request }: ActionFunctionArgs) {
191193
: "Failed sending magic link. Please try again shortly.";
192194

193195
const session = await getUserSession(request);
194-
session.set("auth:error", {
196+
// flash, not set — same one-shot semantics as the validation error above.
197+
session.flash("auth:error", {
195198
message: errorMessage,
196199
});
197200

0 commit comments

Comments
 (0)