Add passkey support
This commit is contained in:
@@ -27,6 +27,7 @@ const assert = require('node:assert'),
|
||||
marked = require('marked'),
|
||||
middleware = require('./middleware'),
|
||||
oidcClients = require('./oidcclients.js'),
|
||||
passkeys = require('./passkeys.js'),
|
||||
path = require('node:path'),
|
||||
paths = require('./paths.js'),
|
||||
http = require('node:http'),
|
||||
@@ -383,13 +384,50 @@ async function interactionLogin(req, res, next) {
|
||||
|
||||
if (!req.body.username || typeof req.body.username !== 'string') return next(new HttpError(400, 'A username must be non-empty string'));
|
||||
if (!req.body.password || typeof req.body.password !== 'string') return next(new HttpError(400, 'A password must be non-empty string'));
|
||||
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a string' ));
|
||||
if ('totpToken' in req.body && typeof req.body.totpToken !== 'string') return next(new HttpError(400, 'totpToken must be a string'));
|
||||
if ('passkeyResponse' in req.body && typeof req.body.passkeyResponse !== 'object') return next(new HttpError(400, 'passkeyResponse must be an object'));
|
||||
|
||||
const { username, password, totpToken } = req.body;
|
||||
const { username, password, totpToken, passkeyResponse } = req.body;
|
||||
|
||||
const verifyFunc = username.indexOf('@') === -1 ? users.verifyWithUsername : users.verifyWithEmail;
|
||||
|
||||
const [verifyError, user] = await safe(verifyFunc(username, password, users.AP_WEBADMIN, { totpToken, skipTotpCheck: false }));
|
||||
// First verify password, skip 2FA check initially to determine what 2FA methods are available
|
||||
const [verifyError, user] = await safe(verifyFunc(username, password, users.AP_WEBADMIN, { totpToken, passkeyResponse, skipTotpCheck: !totpToken && !passkeyResponse }));
|
||||
|
||||
// Handle passkey verification if provided
|
||||
if (!verifyError && user && !user.ghost && passkeyResponse && !totpToken) {
|
||||
const userPasskeys = await passkeys.list(user.id);
|
||||
if (userPasskeys.length > 0) {
|
||||
const [passkeyError] = await safe(passkeys.verifyAuthentication(user, passkeyResponse));
|
||||
if (passkeyError) {
|
||||
debug(`interactionLogin: passkey verification failed for ${username}: ${passkeyError.message}`);
|
||||
return next(new HttpError(401, 'Invalid passkey'));
|
||||
}
|
||||
debug(`interactionLogin: passkey verified for ${username}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If password verified but 2FA is required and not provided, return challenge
|
||||
if (!verifyError && user && !user.ghost && !totpToken && !passkeyResponse) {
|
||||
const userPasskeys = await passkeys.list(user.id);
|
||||
const has2FA = user.twoFactorAuthenticationEnabled || userPasskeys.length > 0;
|
||||
|
||||
if (has2FA) {
|
||||
// Generate passkey options if user has passkeys
|
||||
let passkeyOptions = null;
|
||||
if (userPasskeys.length > 0) {
|
||||
const [optionsError, options] = await safe(passkeys.generateAuthenticationOptions(user));
|
||||
if (!optionsError) passkeyOptions = options;
|
||||
}
|
||||
|
||||
return res.status(200).send({
|
||||
twoFactorRequired: true,
|
||||
totpRequired: user.twoFactorAuthenticationEnabled,
|
||||
passkeyOptions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, verifyError.message));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new HttpError(401, 'Username and password does not match'));
|
||||
if (verifyError) return next(new HttpError(500, verifyError));
|
||||
|
||||
Reference in New Issue
Block a user