Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

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:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+108
View File
@@ -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,
});
}