feat: implement auth system, passkeys, and user management
Add PostgreSQL and Redis integration for users and sessions Implement password and WebAuthn passkey login flows Add Docker stack, super-admin seeding, and protected routes
This commit is contained in:
74
server/api/auth/passkey/register/verify.post.ts
Normal file
74
server/api/auth/passkey/register/verify.post.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { verifyRegistrationResponse, type RegistrationResponseJSON } from '@simplewebauthn/server'
|
||||
|
||||
import { requireAuth } from '../../../../utils/auth'
|
||||
import { createUserPasskey, getUserById, listUserPasskeys } from '../../../../utils/user-repository'
|
||||
import { buildPasskeyLabel, consumeRegistrationChallenge, getWebAuthnConfig } from '../../../../utils/webauthn'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const auth = await requireAuth(event)
|
||||
const body = await readBody<{
|
||||
response?: RegistrationResponseJSON
|
||||
}>(event)
|
||||
|
||||
if (!body.response) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Passkey registration response is required'
|
||||
})
|
||||
}
|
||||
|
||||
const expectedChallenge = await consumeRegistrationChallenge(auth.user.id)
|
||||
|
||||
if (!expectedChallenge) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Passkey registration challenge expired. Try again.'
|
||||
})
|
||||
}
|
||||
|
||||
const config = getWebAuthnConfig(event)
|
||||
const verification = await verifyRegistrationResponse({
|
||||
response: body.response,
|
||||
expectedChallenge,
|
||||
expectedOrigin: config.origin,
|
||||
expectedRPID: config.rpID
|
||||
})
|
||||
|
||||
if (!verification.verified || !verification.registrationInfo) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Passkey registration could not be verified'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
await createUserPasskey({
|
||||
userId: auth.user.id,
|
||||
credentialId: verification.registrationInfo.credential.id,
|
||||
publicKey: verification.registrationInfo.credential.publicKey,
|
||||
counter: verification.registrationInfo.credential.counter,
|
||||
deviceType: verification.registrationInfo.credentialDeviceType,
|
||||
backedUp: verification.registrationInfo.credentialBackedUp,
|
||||
transports: body.response.response.transports || [],
|
||||
label: buildPasskeyLabel()
|
||||
})
|
||||
} catch (error: any) {
|
||||
if (error?.code === '23505') {
|
||||
throw createError({
|
||||
statusCode: 409,
|
||||
statusMessage: 'This passkey is already registered'
|
||||
})
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
const updatedUser = await getUserById(auth.user.id)
|
||||
const passkeys = await listUserPasskeys(auth.user.id)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
user: updatedUser,
|
||||
passkeys
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user