Add possibility to remove mfa

This commit is contained in:
Julian Gassner
2025-02-06 16:47:56 +00:00
parent 6228a54ecf
commit 0bfd2f901d
9 changed files with 168 additions and 57 deletions

View File

@@ -1,5 +1,5 @@
const authModel = require('../models/auth');
const error = require('../lib/error');
const error = require('../lib/error');
const speakeasy = require('speakeasy');
module.exports = {
@@ -13,10 +13,10 @@ module.exports = {
throw new error.AuthError('MFA is not enabled for this user.');
}
const verified = speakeasy.totp.verify({
secret: auth.mfa_secret,
secret: auth.mfa_secret,
encoding: 'base32',
token: token,
window: 2
token: token,
window: 2
});
if (!verified) {
throw new error.AuthError('Invalid MFA token.');
@@ -58,10 +58,10 @@ module.exports = {
throw new error.AuthError('MFA is not set up for this user.');
}
const verified = speakeasy.totp.verify({
secret: auth.mfa_secret,
secret: auth.mfa_secret,
encoding: 'base32',
token: token,
window: 2
token: token,
window: 2
});
if (!verified) {
throw new error.AuthError('Invalid MFA token.');
@@ -73,4 +73,25 @@ module.exports = {
.then(() => true);
});
},
disableMfaForUser: (data, userId) => {
return authModel
.query()
.where('user_id', userId)
.first()
.then((auth) => {
if (!auth) {
throw new error.AuthError('User not found.');
}
return auth.verifyPassword(data.secret)
.then((valid) => {
if (!valid) {
throw new error.AuthError('Invalid password.');
}
return authModel
.query()
.where('user_id', userId)
.update({ mfa_enabled: false, mfa_secret: null });
});
});
},
};

View File

@@ -1,37 +1,18 @@
const express = require('express');
const jwtdecode = require('../lib/express/jwt-decode');
const apiValidator = require('../lib/validator/api');
const internalToken = require('../internal/token');
const schema = require('../schema');
const internalMfa = require('../internal/mfa');
const qrcode = require('qrcode');
const speakeasy = require('speakeasy');
const userModel = require('../models/user');
const express = require('express');
const jwtdecode = require('../lib/express/jwt-decode');
const apiValidator = require('../lib/validator/api');
const schema = require('../schema');
const internalMfa = require('../internal/mfa');
const qrcode = require('qrcode');
const speakeasy = require('speakeasy');
const userModel = require('../models/user');
let router = express.Router({
caseSensitive: true,
strict: true,
mergeParams: true
strict: true,
mergeParams: true
});
router
.route('/')
.options((_, res) => {
res.sendStatus(204);
})
.get(async (req, res, next) => {
internalToken.getFreshToken(res.locals.access, {
expiry: (typeof req.query.expiry !== 'undefined' ? req.query.expiry : null),
scope: (typeof req.query.scope !== 'undefined' ? req.query.scope : null)
})
.then((data) => {
res.status(200)
.send(data);
})
.catch(next);
});
router
.route('/create')
.post(jwtdecode(), (req, res, next) => {
@@ -54,7 +35,7 @@ router
.then(({ secret, user }) => {
const otpAuthUrl = speakeasy.otpauthURL({
secret: secret.ascii,
label: user.email,
label: user.email,
issuer: 'Nginx Proxy Manager'
});
qrcode.toDataURL(otpAuthUrl, (err, dataUrl) => {
@@ -71,12 +52,13 @@ router
router
.route('/enable')
.post(jwtdecode(), (req, res, next) => {
apiValidator(schema.getValidationSchema('/mfa', 'post'), req.body).then((params) => {
apiValidator(schema.getValidationSchema('/mfa/enable', 'post'), req.body).then((params) => {
internalMfa.enableMfaForUser(res.locals.access.token.getUserId(), params.token)
.then(() => res.status(200).send({ success: true }))
.catch(next);
}
);});
).catch(next);
});
router
.route('/check')
@@ -86,4 +68,14 @@ router
.catch(next);
});
router
.route('/delete')
.delete(jwtdecode(), (req, res, next) => {
apiValidator(schema.getValidationSchema('/mfa/delete', 'delete'), req.body).then((params) => {
internalMfa.disableMfaForUser(params, res.locals.access.token.getUserId())
.then(() => res.status(200).send({ success: true }))
.catch(next);
}).catch(next);
});
module.exports = router;

View File

@@ -0,0 +1,44 @@
{
"operationId": "disableMfa",
"summary": "Disable multi-factor authentication for a user",
"tags": [
"MFA"
],
"requestBody": {
"description": "Payload to disable MFA",
"required": true,
"content": {
"application/json": {
"schema": {
"additionalProperties": false,
"properties": {
"secret": {
"type": "string",
"minLength": 1
}
},
"required": [
"secret"
]
}
}
}
},
"responses": {
"200": {
"description": "MFA disabled successfully",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean"
}
}
}
}
}
}
}
}

View File

@@ -1,14 +1,15 @@
{
"operationId": "enableMfa",
"summary": "Enable multi-factor authentication for a user",
"tags": ["MFA"],
"tags": [
"MFA"
],
"requestBody": {
"description": "MFA Token Payload",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"additionalProperties": false,
"properties": {
"token": {
@@ -16,7 +17,9 @@
"minLength": 1
}
},
"required": ["token"]
"required": [
"token"
]
}
}
}
@@ -38,4 +41,4 @@
}
}
}
}
}

View File

@@ -15,9 +15,14 @@
"$ref": "./paths/get.json"
}
},
"/mfa": {
"/mfa/enable": {
"post": {
"$ref": "./paths/mfa/post.json"
"$ref": "./paths/mfa/enable/post.json"
}
},
"/mfa/delete": {
"delete": {
"$ref": "./paths/mfa/delete/delete.json"
}
},
"/audit-log": {