Add passkey support

This commit is contained in:
Johannes Zellner
2026-02-12 21:10:51 +01:00
parent 3e09bef613
commit 5724ca73b4
16 changed files with 992 additions and 69 deletions
+56 -1
View File
@@ -18,7 +18,11 @@ exports = module.exports = {
enableTwoFactorAuthentication,
disableTwoFactorAuthentication,
setNotificationConfig,
destroyUserSession
destroyUserSession,
getPasskey,
getPasskeyRegistrationOptions,
registerPasskey,
deletePasskey
};
const assert = require('node:assert'),
@@ -27,6 +31,7 @@ const assert = require('node:assert'),
HttpError = require('@cloudron/connect-lastmile').HttpError,
HttpSuccess = require('@cloudron/connect-lastmile').HttpSuccess,
oidcServer = require('../oidcserver.js'),
passkeys = require('../passkeys.js'),
safe = require('safetydance'),
tokens = require('../tokens.js'),
userDirectory = require('../user-directory.js'),
@@ -252,3 +257,53 @@ async function destroyUserSession(req, res, next) {
next(new HttpSuccess(204));
}
async function getPasskey(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
const [error, result] = await safe(passkeys.list(req.user.id));
if (error) return next(BoxError.toHttpError(error));
// Only one passkey per user - return first one or null
const passkey = result.length > 0 ? passkeys.removePrivateFields(result[0]) : null;
next(new HttpSuccess(200, { passkey }));
}
async function getPasskeyRegistrationOptions(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
const [error, options] = await safe(passkeys.generateRegistrationOptions(req.user));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, options));
}
async function registerPasskey(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
assert.strictEqual(typeof req.body, 'object');
if (!req.body.credential || typeof req.body.credential !== 'object') return next(new HttpError(400, 'credential must be an object'));
if (req.body.name && typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string'));
const name = req.body.name || 'Passkey';
const [error, result] = await safe(passkeys.verifyRegistration(req.user, req.body.credential, name));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, { id: result.id }));
}
async function deletePasskey(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
// Get the user's passkey (only one allowed)
const [listError, result] = await safe(passkeys.list(req.user.id));
if (listError) return next(BoxError.toHttpError(listError));
if (result.length === 0) return next(new HttpError(404, 'No passkey registered'));
const [error] = await safe(passkeys.del(result[0].id, req.user.id));
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(204));
}