Files
HealthLog/src/lib/validations/client-transport.ts
T
Marc-André Bombeck b100c73a62 fix(auth): refuse a sign-in over plain http before it spends a factor (#1097)
A server that issues Secure session cookies cannot sign anyone in on a
plain http:// page: the browser drops the cookie. The login request was
still verified, the MFA ticket minted and then claimed, and the next
attempt answered "Invalid or expired challenge".

The web client now sends an optional clientTransport on the browser
sign-in requests (password login, passkey start, both MFA completions).
When the server issues Secure cookies and the page is plain http on a
non-loopback host, the request is refused with 409
auth.session.insecure_transport before the password is checked, a
ticket is minted or claimed, a code is spent, or a passkey challenge is
issued. A request without the field is never refused. The SSO start
takes client_proto/client_host and sends such a page back to the login
page instead of to the provider.

GET /api/auth/registration-status publishes sessionCookieSecure, and the
login page shows the reason with both fixes and disables the sign-in
controls before anything is sent; on localhost it shows a note only.
SESSION_COOKIE_SECURE keeps its meaning; the header heuristic log line
stays.
2026-10-03 18:21:19 +02:00

24 lines
1.0 KiB
TypeScript

import { z } from "zod/v4";
/**
* The optional `clientTransport` field the web client sends on the browser
* sign-in requests. Optional everywhere: a client that does not send it (the
* iOS app, scripts) is never refused because of it. It can only ever cause a
* refusal of its own request, so a forged value harms nobody but its sender.
*/
export const clientTransportSchema = z
.object({
protocol: z
.enum(["http", "https"])
.describe("The scheme of the page the request comes from."),
host: z
.string()
.max(255)
.describe("`location.host` of that page: hostname and optional port."),
})
.meta({
id: "ClientTransport",
description:
"Sent by the web client only. When the server issues `Secure` session cookies and this says the page is on plain `http://` (and not a loopback host), the sign-in is refused with 409 `auth.session.insecure_transport` before any credential, ticket or code is checked or used, because the browser would drop the session cookie.",
});