Good progress on 2FA/OTP config: Most of email register lifecycle complete

This commit is contained in:
Bryan Ashby
2019-06-13 19:47:04 -06:00
parent 3efea3de9a
commit 94747cfe7e
4 changed files with 107 additions and 60 deletions

View File

@@ -370,6 +370,7 @@ function twoFactorAuthOTP(user) {
const {
OTPTypes,
prepareOTP,
createBackupCodes,
} = require('../../core/user_2fa_otp.js');
let otpType = argv._[argv._.length - 1];
@@ -414,7 +415,7 @@ function twoFactorAuthOTP(user) {
qrType : argv['qr-type'] || 'ascii',
};
prepareOTP(otpType, otpOpts, (err, otpInfo) => {
return callback(err, Object.assign(otpInfo, { otpType }));
return callback(err, Object.assign(otpInfo, { otpType, backupCodes : createBackupCodes() }));
});
},
function storeOrDisplayQR(otpInfo, callback) {

View File

@@ -20,6 +20,7 @@ const crypto = require('crypto');
const qrGen = require('qrcode-generator');
exports.prepareOTP = prepareOTP;
exports.createBackupCodes = createBackupCodes;
exports.createQRCode = createQRCode;
exports.otpFromType = otpFromType;
exports.loginFactor2_OTP = loginFactor2_OTP;
@@ -82,7 +83,7 @@ function generateOTPBackupCode() {
return bits.join('-');
}
function generateNewBackupCodes() {
function createBackupCodes() {
const codes = [...Array(6)].map(() => generateOTPBackupCode());
return codes;
}
@@ -120,7 +121,7 @@ function createQRCode(otp, options, secret) {
data : qrCode.createDataURL,
img : qrCode.createImgTag,
svg : qrCode.createSvgTag,
}[options.qrType]();
}[options.qrType](options.cellSize);
} catch(e) {
return;
}
@@ -141,10 +142,9 @@ function prepareOTP(otpType, options, cb) {
otp.generateSecret() :
crypto.randomBytes(64).toString('base64').substr(0, 32);
const backupCodes = generateNewBackupCodes();
const qr = createQRCode(otp, options, secret);
const qr = createQRCode(otp, options, secret);
return cb(null, { secret, backupCodes, qr } );
return cb(null, { secret, qr } );
}
function loginFactor2_OTP(client, token, cb) {

View File

@@ -11,6 +11,11 @@ const {
getTokenInfo,
WellKnownTokenTypes,
} = require('./user_temp_token.js');
const {
prepareOTP,
createBackupCodes,
otpFromType,
} = require('./user_2fa_otp.js');
const { sendMail } = require('./email.js');
const UserProps = require('./user_property.js');
const Log = require('./logger.js').log;
@@ -70,7 +75,7 @@ module.exports = class User2FA_OTPWebRegister
(token, textTemplate, htmlTemplate, callback) => {
const webServer = getWebServer();
const registerUrl = webServer.instance.buildUrl(
`/enable_2fa_otp?token=&otpType=${otpType}&token=${token}`
`/enable_2fa_otp?token=${token}&otpType=${otpType}`
);
const replaceTokens = (s) => {
@@ -133,36 +138,48 @@ module.exports = class User2FA_OTPWebRegister
getTokenInfo(token, (err, tokenInfo) => {
if(err) {
// assume expired
return webServer.instance.respondWithError(resp, 410, 'Invalid or expired registration link.', 'Expired Link');
return webServer.instance.respondWithError(
resp,
410,
'Invalid or expired registration link.', 'Expired Link'
);
}
if(tokenInfo.tokenType !== 'auth_factor2_otp_register') {
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
}
const qrImg = ''; // :TODO: fix me
const secret = '';
const backupCodes = '';
const prepareOptions = {
qrType : 'data',
cellSize : 8,
username : tokenInfo.user.username,
};
const postUrl = webServer.instance.buildUrl('/enable_2fa_otp');
const config = Config();
return webServer.instance.routeTemplateFilePage(
_.get(config, 'users.twoFactorAuth.otp.registerPageTemplate'),
(templateData, next) => {
const finalPage = templateData
.replace(/%BOARDNAME%/g, config.general.boardName)
.replace(/%USERNAME%/g, tokenInfo.user.username)
.replace(/%TOKEN%/g, token)
.replace(/%OTP_TYPE%/g, otpType)
.replace(/%POST_URL%/g, postUrl)
.replace(/%QR_IMG%/g, qrImg)
.replace(/%SECRET%/g, secret)
.replace(/%BACKUP_CODES%/g, backupCodes)
;
return next(null, finalPage);
},
resp
);
prepareOTP(otpType, prepareOptions, (err, otpInfo) => {
if(err) {
// :TODO: Log error
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
}
const postUrl = webServer.instance.buildUrl('/enable_2fa_otp');
const config = Config();
return webServer.instance.routeTemplateFilePage(
_.get(config, 'users.twoFactorAuth.otp.registerPageTemplate'),
(templateData, next) => {
const finalPage = templateData
.replace(/%BOARDNAME%/g, config.general.boardName)
.replace(/%USERNAME%/g, tokenInfo.user.username)
.replace(/%TOKEN%/g, token)
.replace(/%OTP_TYPE%/g, otpType)
.replace(/%POST_URL%/g, postUrl)
.replace(/%QR_IMG_DATA%/g, otpInfo.qr)
.replace(/%SECRET%/g, otpInfo.secret)
;
return next(null, finalPage);
},
resp
);
});
});
}
@@ -181,13 +198,52 @@ module.exports = class User2FA_OTPWebRegister
req.on('end', () => {
const formData = querystring.parse(bodyData);
const config = Config();
if(!formData.token || !formData.otpType || !formData.otp) {
if(!formData.token || !formData.otpType || !formData.otp ||
!formData.secret)
{
return badRequest();
}
});
return webServer.instance.respondWithError(resp, 410, 'Invalid or expired registration link.', 'Expired Link');
const otp = otpFromType(formData.otpType);
if(!otp) {
return badRequest();
}
const valid = otp.verify( { token : formData.otp, secret : formData.secret } );
if(!valid) {
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
}
getTokenInfo(formData.token, (err, tokenInfo) => {
if(err) {
return User2FA_OTPWebRegister.accessDenied(webServer, resp);
}
const backupCodes = createBackupCodes();
const props = {
[ UserProps.AuthFactor2OTP ] : formData.otpType,
[ UserProps.AuthFactor2OTPSecret ] : formData.secret,
[ UserProps.AuthFactor2OTPBackupCodes ] : JSON.stringify(backupCodes),
};
tokenInfo.user.persistProperties(props, err => {
if(err) {
return webServer.instance.respondWithError(resp, 500, 'Internal Server Error', 'Internal Server Error');
}
// :TODO: remove token
// :TODO: use a html template here too, if provided
resp.writeHead(200);
return resp.end(
`2-Factor Authentication via One-Time-Password has been enabled for this account. Please write down your backup codes and store them in safe place:
${backupCodes}
`
);
});
});
});
}
static registerRoutes(cb) {