This commit is contained in:
Jack Mechem 2026-05-01 16:21:50 -07:00
parent c991fe7b6d
commit e6b5fed399
8 changed files with 6647 additions and 6623 deletions

View file

@ -13,7 +13,8 @@ export async function POST(req: NextRequest) {
});
if (!res.ok) {
return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
const text = await res.text();
return NextResponse.json({ error: text }, { status: 401 });
}
// Returns { session_id, challenge } — browser completes the WebAuthn step

View file

@ -1,6 +1,11 @@
import { NextRequest, NextResponse } from "next/server";
const ENROLLMENT_OPEN = process.env.ENROLLMENT_OPEN === "true";
export async function POST(req: NextRequest) {
if (!ENROLLMENT_OPEN) {
return new NextResponse(null, { status: 404 });
}
const body = await req.json();
const res = await fetch("http://localhost:3001/auth/register/finish", {

View file

@ -1,6 +1,11 @@
import { NextRequest, NextResponse } from "next/server";
const ENROLLMENT_OPEN = process.env.ENROLLMENT_OPEN === "true";
export async function POST(req: NextRequest) {
if (!ENROLLMENT_OPEN) {
return new NextResponse(null, { status: 404 });
}
const { username, password } = await req.json();
const res = await fetch("http://localhost:3001/auth/register/start", {

View file

@ -69,11 +69,13 @@ export default function AuthPage() {
setStatus("waiting_yubikey");
const opts = challenge.publicKey;
opts.challenge = b64uToBuf(opts.challenge);
opts.userVerification = "discouraged";
if (opts.allowCredentials) {
opts.allowCredentials = opts.allowCredentials.map(
(c: { id: string; type: string; transports?: string[] }) => ({
...c,
type: c.type,
id: b64uToBuf(c.id),
transports: ["usb", "nfc", "ble", "hybrid"],
}),
);
}

View file

@ -50,6 +50,14 @@ export default function EnrollPage() {
const opts = challenge.publicKey;
opts.challenge = b64uToBuf(opts.challenge);
opts.user.id = b64uToBuf(opts.user.id);
// Force security key UI — residentKey/UV discouraged so Android
// Chrome doesn't suppress the NFC option in favour of biometrics
opts.authenticatorSelection = {
authenticatorAttachment: "cross-platform",
residentKey: "discouraged",
requireResidentKey: false,
userVerification: "discouraged",
};
if (opts.excludeCredentials) {
opts.excludeCredentials = opts.excludeCredentials.map(
(c: { id: string; type: string; transports?: string[] }) => ({
@ -89,9 +97,7 @@ export default function EnrollPage() {
response: {
attestationObject: bufToB64u(attestation.attestationObject),
clientDataJSON: bufToB64u(attestation.clientDataJSON),
transports: attestation.getTransports
? attestation.getTransports()
: [],
transports: ["usb", "nfc", "ble", "hybrid"],
},
extensions: {},
},

View file

@ -29,7 +29,12 @@ export default function RootLayout({
lang="en"
className={`${dmSans.variable} ${playfair.variable} h-full antialiased`}
>
<body className="min-h-full h-full flex flex-col">{children}</body>
<body className="min-h-full h-full flex flex-col">
{children}
<span className="fixed bottom-3 right-4 text-[10px] text-gray-300 select-none pointer-events-none">
v0.1.0
</span>
</body>
</html>
);
}