import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse, } from '@simplewebauthn/server' import type { RegistrationResponseJSON, AuthenticationResponseJSON, } from '@simplewebauthn/server' export interface RpConfig { rpID: string rpName: string origin: string } export function getRpConfig(): RpConfig { const config = useRuntimeConfig() const rpID = (config as any).webauthn?.rpId const rpName = (config as any).webauthn?.rpName ?? 'LogShip ERP' const origin = (config as any).webauthn?.origin if (!rpID || !origin) { throw new Error('WEBAUTHN_RP_ID and WEBAUTHN_ORIGIN env vars must be set') } return { rpID, rpName, origin } } // numeric ad_user.id encoded as Uint8Array for the WebAuthn userHandle function encodeUserHandle(userId: number): Uint8Array { // Buffer.from(...) returns a Buffer (extends Uint8Array); the // TextEncoder path produces Uint8Array which TS rejects // for the simplewebauthn typing on userID. return Buffer.from(String(userId), 'utf8') } export function decodeUserHandle(handle: string | undefined | null): number | null { if (!handle) return null try { const decoded = Buffer.from(handle, 'base64url').toString('utf8') const n = Number(decoded) return Number.isFinite(n) && n > 0 ? n : null } catch { return null } } export async function buildRegistrationOptions(opts: { userId: number userName: string userDisplayName?: string excludeCredentialIds?: string[] }) { const { rpID, rpName } = getRpConfig() return generateRegistrationOptions({ rpName, rpID, userName: opts.userName, userID: encodeUserHandle(opts.userId) as any, userDisplayName: opts.userDisplayName ?? opts.userName, attestationType: 'none', authenticatorSelection: { residentKey: 'required', requireResidentKey: true, userVerification: 'preferred', }, excludeCredentials: (opts.excludeCredentialIds ?? []).map(id => ({ id })), }) } export async function verifyRegistration(opts: { response: RegistrationResponseJSON expectedChallenge: string }) { const { rpID, origin } = getRpConfig() return verifyRegistrationResponse({ response: opts.response, expectedChallenge: opts.expectedChallenge, expectedOrigin: origin, expectedRPID: rpID, requireUserVerification: false, }) } export async function buildAuthenticationOptions() { const { rpID } = getRpConfig() return generateAuthenticationOptions({ rpID, userVerification: 'preferred', // empty allowCredentials → discoverable (resident) credential flow }) } export async function verifyAuthentication(opts: { response: AuthenticationResponseJSON expectedChallenge: string credential: { id: string; publicKey: Uint8Array; counter: number; transports?: any[] } }) { const { rpID, origin } = getRpConfig() return verifyAuthenticationResponse({ response: opts.response, expectedChallenge: opts.expectedChallenge, expectedOrigin: origin, expectedRPID: rpID, credential: opts.credential as any, requireUserVerification: false, }) }