Add database tables and repository for managing bookings Create API endpoints for booking submission and capacity management Update landing page to persist bookings before WhatsApp redirection
93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import type { H3Event } from 'h3'
|
|
|
|
import type { AuthenticatorTransportFuture, WebAuthnCredential } from '@simplewebauthn/server'
|
|
|
|
import { decodeBase64Url, randomToken } from './base64url'
|
|
import { getAppOrigin } from './app-url'
|
|
import { getRedisClient } from './redis'
|
|
import type { PasskeyRecord } from './user-repository'
|
|
|
|
const CHALLENGE_TTL_SECONDS = 60 * 5
|
|
|
|
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', ' ')}`
|
|
}
|