diff --git a/src/module.ts b/src/module.ts index b5103e66..66a3967c 100644 --- a/src/module.ts +++ b/src/module.ts @@ -3,7 +3,7 @@ import { defineNuxtModule, addPlugin, createResolver, - addImportsDir, + addImports, addServerHandler, addServerPlugin, addServerImportsDir, @@ -60,9 +60,17 @@ export default defineNuxtModule({ './runtime/types/index', ) + const composables = [ + { name: 'useUserSession', from: resolver.resolve('./runtime/app/composables/session') }, + ] + + if (options.webAuthn) { + composables.push({ name: 'useWebAuthn', from: resolver.resolve('./runtime/app/composables/webauthn') }) + } + // App addComponentsDir({ path: resolver.resolve('./runtime/app/components') }) - addImportsDir(resolver.resolve('./runtime/app/composables')) + addImports(composables) addPlugin(resolver.resolve('./runtime/app/plugins/session.server')) addPlugin(resolver.resolve('./runtime/app/plugins/session.client')) // Server diff --git a/src/runtime/server/lib/webauthn/authenticate.ts b/src/runtime/server/lib/webauthn/authenticate.ts index c8ba0c9b..c3453495 100644 --- a/src/runtime/server/lib/webauthn/authenticate.ts +++ b/src/runtime/server/lib/webauthn/authenticate.ts @@ -2,21 +2,11 @@ import { eventHandler, H3Error, createError, getRequestURL, readBody } from 'h3' import type { GenerateAuthenticationOptionsOpts } from '@simplewebauthn/server' import { generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server' import defu from 'defu' -import type { AuthenticationResponseJSON } from '@simplewebauthn/types' import { getRandomValues } from 'uncrypto' import { base64URLStringToBuffer, bufferToBase64URLString } from '@simplewebauthn/browser' import { useRuntimeConfig } from '#imports' import type { WebAuthnAuthenticateEventHandlerOptions, WebAuthnCredential } from '#auth-utils' - -type AuthenticationBody = { - verify: false - userName?: string -} | { - verify: true - attemptId: string - userName?: string - response: AuthenticationResponseJSON -} +import type { AuthenticationBody } from '~/src/runtime/types/webauthn' export function defineWebAuthnAuthenticateEventHandler({ storeChallenge, @@ -30,20 +20,20 @@ export function defineWebAuthnAuthenticateEventHandler { const url = getRequestURL(event) const body = await readBody(event) - const _config = defu(await getOptions?.(event) ?? {}, useRuntimeConfig(event).webauthn.authenticate, { + const _config = defu(await getOptions?.(event, body) ?? {}, useRuntimeConfig(event).webauthn.authenticate, { rpID: url.hostname, } satisfies GenerateAuthenticationOptionsOpts) - if (allowCredentials && body.userName) { - _config.allowCredentials = await allowCredentials(event, body.userName) - } - if (!storeChallenge) { _config.challenge = '' } try { if (!body.verify) { + if (allowCredentials && body.userName) { + _config.allowCredentials = await allowCredentials(event, body.userName) + } + const options = await generateAuthenticationOptions(_config as GenerateAuthenticationOptionsOpts) const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32))) @@ -71,6 +61,7 @@ export function defineWebAuthnAuthenticateEventHandler = { - user: T - verify: false -} | { - user: T - verify: true - attemptId: string - response: RegistrationResponseJSON -} +import type { RegistrationBody } from '~/src/runtime/types/webauthn' export function defineWebAuthnRegisterEventHandler({ storeChallenge, getChallenge, getOptions, validateUser, + excludeCredentials, onSuccess, onError, }: WebAuthnRegisterEventHandlerOptions) { @@ -41,7 +32,7 @@ export function defineWebAuthnRegisterEventHandler({ user = await validateUserData(body.user, validateUser) } - const _config = defu(await getOptions?.(event) ?? {}, useRuntimeConfig(event).webauthn.register, { + const _config = defu(await getOptions?.(event, body) ?? {}, useRuntimeConfig(event).webauthn.register, { rpID: url.hostname, rpName: url.hostname, userName: user.userName, @@ -57,6 +48,10 @@ export function defineWebAuthnRegisterEventHandler({ try { if (!body.verify) { + if (excludeCredentials) { + _config.excludeCredentials = await excludeCredentials(event, user.userName) + } + const options = await generateRegistrationOptions(_config as GenerateRegistrationOptionsOpts) const attemptId = bufferToBase64URLString(getRandomValues(new Uint8Array(32))) diff --git a/src/runtime/types/webauthn.ts b/src/runtime/types/webauthn.ts index 6de5bdda..c746a085 100644 --- a/src/runtime/types/webauthn.ts +++ b/src/runtime/types/webauthn.ts @@ -1,4 +1,4 @@ -import type { AuthenticatorTransportFuture } from '@simplewebauthn/types' +import type { AuthenticationResponseJSON, AuthenticatorTransportFuture, RegistrationResponseJSON } from '@simplewebauthn/types' import type { Ref } from 'vue' import type { H3Event, H3Error, ValidateFunction } from 'h3' import type { @@ -23,7 +23,7 @@ export interface WebAuthnUser { [key: string]: unknown } -type AllowCredentials = NonNullable +type CredentialsList = NonNullable // Using a discriminated union makes it such that you can only define both storeChallenge and getChallenge or neither type WebAuthnEventHandlerBase> = { @@ -38,22 +38,43 @@ type WebAuthnEventHandlerBase> = { onError?: (event: H3Event, error: H3Error) => void | Promise } +export type RegistrationBody = { + user: T + verify: false +} | { + user: T + verify: true + attemptId: string + response: RegistrationResponseJSON +} + export type WebAuthnRegisterEventHandlerOptions = WebAuthnEventHandlerBase<{ user: T credential: WebAuthnCredential registrationInfo: Exclude }> & { - getOptions?: (event: H3Event) => GenerateRegistrationOptionsOpts | Promise + getOptions?: (event: H3Event, body: RegistrationBody) => Partial | Promise> validateUser?: ValidateFunction + excludeCredentials?: (event: H3Event, userName: string) => CredentialsList | Promise +} + +export type AuthenticationBody = { + verify: false + userName?: string +} | { + verify: true + attemptId: string + userName?: string + response: AuthenticationResponseJSON } export type WebAuthnAuthenticateEventHandlerOptions = WebAuthnEventHandlerBase<{ credential: T authenticationInfo: Exclude }> & { - getOptions?: (event: H3Event) => Partial | Promise> + getOptions?: (event: H3Event, body: AuthenticationBody) => Partial | Promise> getCredential: (event: H3Event, credentialID: string) => T | Promise - allowCredentials?: (event: H3Event, userName: string) => AllowCredentials | Promise + allowCredentials?: (event: H3Event, userName: string) => CredentialsList | Promise } export interface WebAuthnComposable {