Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
generateRegistrationOptions,
|
||||
verifyRegistrationResponse,
|
||||
generateAuthenticationOptions,
|
||||
verifyAuthenticationResponse,
|
||||
} from "@simplewebauthn/server";
|
||||
import type {
|
||||
RegistrationResponseJSON,
|
||||
AuthenticationResponseJSON,
|
||||
AuthenticatorTransportFuture,
|
||||
} from "@simplewebauthn/types";
|
||||
|
||||
/**
|
||||
* SEC3-b: WebAuthn/Passkey-Basis. Kapselt die RP-/Origin-Konfiguration (aus AUTH_URL bzw.
|
||||
* WEBAUTHN_*-Overrides) und die vier @simplewebauthn/server-Zeremonien mit unserer
|
||||
* base64url-Kodierung. Höhere Schichten (Actions/Route) kümmern sich um Challenge-Store
|
||||
* (Cookie) und Persistenz.
|
||||
*/
|
||||
|
||||
export const RP_NAME = "Certvia";
|
||||
/** Cookie-Name der kurzlebigen Login-Challenge (pre-session Passkey-Login). */
|
||||
export const LOGIN_CHALLENGE_COOKIE = "wa_login_challenge";
|
||||
|
||||
function authUrl(): URL {
|
||||
return new URL(process.env.WEBAUTHN_ORIGIN || process.env.AUTH_URL || "http://localhost:3000");
|
||||
}
|
||||
/** Relying-Party-ID = registrierbare Domain (ohne Protokoll/Port). */
|
||||
export function rpID(): string {
|
||||
return process.env.WEBAUTHN_RP_ID || authUrl().hostname;
|
||||
}
|
||||
/** Erwarteter Origin der Zeremonie (Protokoll + Host + Port). */
|
||||
export function rpOrigin(): string {
|
||||
return authUrl().origin;
|
||||
}
|
||||
|
||||
export function toB64Url(bytes: Uint8Array): string {
|
||||
return Buffer.from(bytes).toString("base64url");
|
||||
}
|
||||
export function fromB64Url(s: string): Uint8Array {
|
||||
return new Uint8Array(Buffer.from(s, "base64url"));
|
||||
}
|
||||
|
||||
type StoredCred = { credentialId: string; transports: string[] };
|
||||
|
||||
/** Registrierungs-Optionen (Challenge) für einen angemeldeten Nutzer erzeugen. */
|
||||
export function buildRegistrationOptions(params: {
|
||||
userId: string;
|
||||
userName: string;
|
||||
userDisplayName: string;
|
||||
existing: StoredCred[];
|
||||
}) {
|
||||
return generateRegistrationOptions({
|
||||
rpName: RP_NAME,
|
||||
rpID: rpID(),
|
||||
userID: params.userId,
|
||||
userName: params.userName,
|
||||
userDisplayName: params.userDisplayName,
|
||||
attestationType: "none",
|
||||
excludeCredentials: params.existing.map((c) => ({
|
||||
id: fromB64Url(c.credentialId),
|
||||
type: "public-key",
|
||||
transports: c.transports as AuthenticatorTransportFuture[],
|
||||
})),
|
||||
authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyReg(response: RegistrationResponseJSON, expectedChallenge: string) {
|
||||
return verifyRegistrationResponse({
|
||||
response,
|
||||
expectedChallenge,
|
||||
expectedOrigin: rpOrigin(),
|
||||
expectedRPID: rpID(),
|
||||
requireUserVerification: false,
|
||||
});
|
||||
}
|
||||
|
||||
/** Authentifizierungs-Optionen (Challenge) — allowCredentials optional (Discoverable/Resident). */
|
||||
export function buildAuthenticationOptions(allow: StoredCred[]) {
|
||||
return generateAuthenticationOptions({
|
||||
rpID: rpID(),
|
||||
allowCredentials: allow.map((c) => ({
|
||||
id: fromB64Url(c.credentialId),
|
||||
type: "public-key",
|
||||
transports: c.transports as AuthenticatorTransportFuture[],
|
||||
})),
|
||||
userVerification: "preferred",
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyAuth(params: {
|
||||
response: AuthenticationResponseJSON;
|
||||
expectedChallenge: string;
|
||||
credential: { credentialId: string; publicKey: string; counter: bigint };
|
||||
}) {
|
||||
return verifyAuthenticationResponse({
|
||||
response: params.response,
|
||||
expectedChallenge: params.expectedChallenge,
|
||||
expectedOrigin: rpOrigin(),
|
||||
expectedRPID: rpID(),
|
||||
authenticator: {
|
||||
credentialID: fromB64Url(params.credential.credentialId),
|
||||
credentialPublicKey: fromB64Url(params.credential.publicKey),
|
||||
counter: Number(params.credential.counter),
|
||||
},
|
||||
requireUserVerification: false,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user