import type { H3Event } from 'h3' import type { AuthenticatorTransportFuture, WebAuthnCredential } from '@simplewebauthn/server' import { getRequestURL } from 'h3' import { decodeBase64Url, randomToken } from './base64url' import { getRedisClient } from './redis' import type { PasskeyRecord } from './user-repository' const CHALLENGE_TTL_SECONDS = 60 * 5 function getAppOrigin(event: H3Event) { const config = useRuntimeConfig() if (config.public.appUrl) { return new URL(config.public.appUrl).origin } const url = getRequestURL(event) return `${url.protocol}//${url.host}` } export function getWebAuthnConfig(event: H3Event) { const config = useRuntimeConfig() const origin = getAppOrigin(event) const originUrl = new URL(origin) return { origin, rpID: originUrl.hostname, rpName: config.public.rpName || 'Dinner Ticket System' } } function getRegistrationChallengeKey(userId: string) { return `webauthn:register:${userId}` } function getLoginChallengeKey(token: string) { return `webauthn:login:${token}` } export async function storeRegistrationChallenge(userId: string, challenge: string) { const redis = await getRedisClient() await redis.set(getRegistrationChallengeKey(userId), challenge, { expiration: { type: 'EX', value: CHALLENGE_TTL_SECONDS } }) } export async function consumeRegistrationChallenge(userId: string) { const redis = await getRedisClient() const key = getRegistrationChallengeKey(userId) const challenge = await redis.get(key) if (challenge) { await redis.del(key) } return challenge } export async function createLoginChallenge(challenge: string) { const token = randomToken(24) const redis = await getRedisClient() await redis.set(getLoginChallengeKey(token), challenge, { expiration: { type: 'EX', value: CHALLENGE_TTL_SECONDS } }) return token } export async function consumeLoginChallenge(token: string) { const redis = await getRedisClient() const key = getLoginChallengeKey(token) const challenge = await redis.get(key) if (challenge) { await redis.del(key) } return challenge } export function toWebAuthnCredential(passkey: PasskeyRecord): WebAuthnCredential { return { id: passkey.credentialId, publicKey: decodeBase64Url(passkey.publicKey), counter: passkey.counter, transports: passkey.transports as AuthenticatorTransportFuture[] } } export function buildPasskeyLabel() { return `Passkey ${new Date().toISOString().slice(0, 16).replace('T', ' ')}` }