Pattern 01
Authentication error paths that do not fail closed
What we see
The login function returns the same error for an invalid email and an invalid password — correct, that prevents user enumeration. But the same function does not handle a database timeout, a connection failure, or an unexpected exception the same way. Under those conditions it may return a generic 500 (acceptable), return a stack trace (unacceptable), or — worst case — allow the login to proceed because the exception was caught before the return statement.
Why an assistant produces it
The prompt was "add authentication". The success path and the wrong-password path are both in the training data thousands of times over. The path where Postgres stops answering mid-request is not, and nothing in the prompt asked for it.
The pattern
export async function login(email: string, password: string) {
let user;
try {
user = await db.user.findUnique({ where: { email } });
if (!user) return { error: "Invalid credentials" };
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return { error: "Invalid credentials" };
} catch (err) {
logger.error("login failed", err);
// falls through — no return, no throw
}
// reached even when the try block threw
await createSession(user!.id);
return { ok: true };
}The fix
export async function login(email: string, password: string) {
try {
const user = await db.user.findUnique({ where: { email } });
if (!user) return { error: "Invalid credentials" };
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) return { error: "Invalid credentials" };
// Session creation is INSIDE the guarded path, and is the last
// thing that happens — nothing authenticated exists before here.
await createSession(user.id);
return { ok: true };
} catch (err) {
// Anything unexpected is a rejection, logged as an incident, not a
// credential failure — an operator needs to be able to tell these apart.
logger.error({ event: "auth.error" }, err);
return { error: "Authentication unavailable" };
}
}The correct implementation
If anything unexpected happens during authentication, the correct outcome is a clear, logged rejection. No session is created, no authenticated state is set, and the log line distinguishes "wrong password" from "the auth system is broken" so the second one pages somebody. The error handling on the unhappy path must be as deliberate as the logic on the happy path.
How to test it
Interrupt the database connection after the user lookup but before session creation — a proxy that drops the connection, or a mocked client that throws on the second call. The application should return an error and create no session. Assert on the session store, not on the HTTP status: a 500 with a valid session cookie attached is the failure this test exists to catch.