Skip to content

Commit 2f2c156

Browse files
committed
webAuthN authentication challenge and attestation endpoints added to API docs generation
1 parent 534789a commit 2f2c156

File tree

1 file changed

+122
-43
lines changed

1 file changed

+122
-43
lines changed

lib/api/2fa/webauthn.js

+122-43
Original file line numberDiff line numberDiff line change
@@ -383,22 +383,68 @@ module.exports = (db, server, userHandler) => {
383383

384384
// Get webauthn challenge
385385
server.post(
386-
'/users/:user/2fa/webauthn/authentication-challenge',
386+
{
387+
path: '/users/:user/2fa/webauthn/authentication-challenge',
388+
tags: ['TwoFactorAuth'],
389+
summary: 'Begin WebAuthN authentication challenge',
390+
description: 'This method retrieves the WebAuthN PublicKeyCredentialRequestOptions object to use it for authentication',
391+
validationObjs: {
392+
requestBody: {
393+
origin: Joi.string().empty('').uri().required().description('Origin domain'),
394+
authenticatorAttachment: Joi.string()
395+
.valid('platform', 'cross-platform')
396+
.example('cross-platform')
397+
.default('cross-platform')
398+
.description(
399+
'Indicates whether authenticators should be part of the OS ("platform"), or can be roaming authenticators ("cross-platform")'
400+
),
401+
402+
rpId: Joi.string().hostname().empty('').description('Relaying party ID. Domain'),
403+
404+
sess: sessSchema,
405+
ip: sessIPSchema
406+
},
407+
queryParams: {},
408+
pathParams: { user: userId },
409+
response: {
410+
200: {
411+
description: 'Success',
412+
model: Joi.object({
413+
success: successRes,
414+
authenticationOptions: Joi.object({
415+
challenge: Joi.string().hex().required().description('Challenge as hex string'),
416+
allowCredentials: Joi.array()
417+
.items(
418+
Joi.object({
419+
rawId: Joi.string().hex().required().description('RawId of the credential as hex string'),
420+
type: Joi.string().required().description('Credential type')
421+
})
422+
)
423+
.required()
424+
.description('Allowed credential(s) based on the request'),
425+
rpId: Joi.string().description('Relaying Party ID. Domain'),
426+
rawChallenge: Joi.string().description('Raw challenge bytes. ArrayBuffer'),
427+
attestation: Joi.string().description('Attestation string. `direct`/`indirect`/`none`'),
428+
extensions: Joi.object({}).description('Any credential extensions'),
429+
userVerification: Joi.string().description('User verification type. `required`/`preferred`/`discouraged`'),
430+
timeout: Joi.number().description('Timeout in milliseconds (ms)')
431+
})
432+
.required()
433+
.description('PublicKeyCredentialRequestOptions object')
434+
})
435+
}
436+
}
437+
}
438+
},
387439
tools.responseWrapper(async (req, res) => {
388440
res.charSet('utf-8');
389-
const schema = Joi.object().keys({
390-
user: Joi.string().hex().lowercase().length(24).required(),
391-
origin: Joi.string().empty('').uri().required(),
392-
authenticatorAttachment: Joi.string()
393-
.valid('platform', 'cross-platform')
394-
.example('cross-platform')
395-
.default('cross-platform')
396-
.description('Indicates whether authenticators should be part of the OS ("platform"), or can be roaming authenticators ("cross-platform")'),
397-
398-
rpId: Joi.string().hostname().empty(''),
399-
400-
sess: sessSchema,
401-
ip: sessIPSchema
441+
442+
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;
443+
444+
const schema = Joi.object({
445+
...pathParams,
446+
...requestBody,
447+
...queryParams
402448
});
403449

404450
const result = schema.validate(req.params, {
@@ -433,37 +479,70 @@ module.exports = (db, server, userHandler) => {
433479
);
434480

435481
server.post(
436-
'/users/:user/2fa/webauthn/authentication-assertion',
482+
{
483+
path: '/users/:user/2fa/webauthn/authentication-assertion',
484+
tags: ['TwoFactorAuth'],
485+
summary: 'WebAuthN authentication Assertion',
486+
description: 'Assert WebAuthN authentication request and actually authenticate the user',
487+
validationObjs: {
488+
requestBody: {
489+
challenge: Joi.string().empty('').hex().max(2048).required().description('Challenge of the credential as hex string'),
490+
rawId: Joi.string().empty('').hex().max(2048).required().description('RawId of the credential'),
491+
clientDataJSON: Joi.string()
492+
.empty('')
493+
.hex()
494+
.max(1024 * 1024)
495+
.required()
496+
.description('Client data JSON as hex string'),
497+
authenticatorData: Joi.string()
498+
.empty('')
499+
.hex()
500+
.max(1024 * 1024)
501+
.required()
502+
.description('Authentication data as hex string'),
503+
504+
signature: Joi.string()
505+
.empty('')
506+
.hex()
507+
.max(1024 * 1024)
508+
.required()
509+
.description('Private key encrypted signature to verify with public key on the server. Hex string'),
510+
511+
rpId: Joi.string().hostname().empty('').description('Relaying party ID. Domain'),
512+
513+
token: booleanSchema.default(false).description('If true response will contain the user auth token'),
514+
515+
sess: sessSchema,
516+
ip: sessIPSchema
517+
},
518+
queryParams: {},
519+
pathParams: { user: userId },
520+
response: {
521+
200: {
522+
description: 'Success',
523+
model: Joi.object({
524+
success: successRes,
525+
response: Joi.object({
526+
authenticated: booleanSchema.required().description('Authentication status'),
527+
credential: Joi.string().required().description('WebAuthN credential ID')
528+
})
529+
.required()
530+
.description('Auth data'),
531+
token: Joi.string().description('User auth token')
532+
})
533+
}
534+
}
535+
}
536+
},
437537
tools.responseWrapper(async (req, res) => {
438538
res.charSet('utf-8');
439-
const schema = Joi.object().keys({
440-
user: Joi.string().hex().lowercase().length(24).required(),
441-
442-
challenge: Joi.string().empty('').hex().max(2048).required(),
443-
rawId: Joi.string().empty('').hex().max(2048).required(),
444-
clientDataJSON: Joi.string()
445-
.empty('')
446-
.hex()
447-
.max(1024 * 1024)
448-
.required(),
449-
authenticatorData: Joi.string()
450-
.empty('')
451-
.hex()
452-
.max(1024 * 1024)
453-
.required(),
454-
455-
signature: Joi.string()
456-
.empty('')
457-
.hex()
458-
.max(1024 * 1024)
459-
.required(),
460-
461-
rpId: Joi.string().hostname().empty(''),
462-
463-
token: booleanSchema.default(false),
464-
465-
sess: sessSchema,
466-
ip: sessIPSchema
539+
540+
const { pathParams, requestBody, queryParams } = req.route.spec.validationObjs;
541+
542+
const schema = Joi.object({
543+
...pathParams,
544+
...requestBody,
545+
...queryParams
467546
});
468547

469548
const result = schema.validate(req.params, {

0 commit comments

Comments
 (0)