enigma-bbs/core/user_2fa_otp_config.js
2019-05-24 22:27:50 -06:00

171 lines
5.5 KiB
JavaScript

/* jslint node: true */
'use strict';
// ENiGMA½
const { MenuModule } = require('./menu_module.js');
const UserProps = require('./user_property.js');
const {
OTPTypes,
otpFromType,
createQRCode,
} = require('./user_2fa_otp.js');
// deps
const async = require('async');
const _ = require('lodash');
const iconv = require('iconv-lite');
exports.moduleInfo = {
name : 'User 2FA/OTP Configuration',
desc : 'Module for user 2FA/OTP configuration',
author : 'NuSkooler',
};
const FormIds = {
menu : 0,
};
const MciViewIds = {
enableToggle : 1,
typeSelection : 2,
submission : 3,
infoText : 4,
customRangeStart : 10, // 10+ = customs
};
exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
constructor(options) {
super(options);
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), { extraArgs : options.extraArgs });
this.menuMethods = {
showQRCode : (formData, extraArgs, cb) => {
return this.showQRCode(cb);
}
};
}
mciReady(mciData, cb) {
super.mciReady(mciData, err => {
if(err) {
return cb(err);
}
async.series(
[
(callback) => {
return this.prepViewController('menu', FormIds.menu, mciData.menu, callback);
},
(callback) => {
const requiredCodes = [
MciViewIds.enableToggle,
MciViewIds.typeSelection,
MciViewIds.submission,
];
return this.validateMCIByViewIds('menu', requiredCodes, callback);
},
(callback) => {
const enableToggleView = this.getView('menu', MciViewIds.enableToggle);
let initialIndex = this.isOTPEnabledForUser() ? 1 : 0;
enableToggleView.setFocusItemIndex(initialIndex);
this.enableToggleUpdate(initialIndex);
enableToggleView.on('index update', idx => {
return this.enableToggleUpdate(idx);
});
const typeSelectionView = this.getView('menu', MciViewIds.typeSelection);
initialIndex = this.typeSelectionIndexFromUserOTPType();
typeSelectionView.setFocusItemIndex(initialIndex);
typeSelectionView.on('index update', idx => {
return this.typeSelectionUpdate(idx);
});
this.viewControllers.menu.on('return', view => {
if(view === enableToggleView) {
return this.enableToggleUpdate(enableToggleView.focusedItemIndex);
} else if (view === typeSelectionView) {
return this.typeSelectionUpdate(typeSelectionView.focusedItemIndex);
}
});
return callback(null);
}
],
err => {
return cb(err);
}
);
});
}
showQRCode(cb) {
const otp = otpFromType(this.client.user.getProperty(UserProps.AuthFactor2OTP));
let qrCodeAscii = '';
if(!otp) {
qrCodeAscii = '2FA/OTP is not currently enabled for this account';
}
const qrOptions = {
username : this.client.user.username,
qrType : 'ascii',
};
qrCodeAscii = createQRCode(
otp,
qrOptions,
this.client.user.getProperty(UserProps.AuthFactor2OTPSecret)
).replace(/\n/g, '\r\n');
const modOpts = {
extraArgs : {
artData : iconv.encode(`${qrCodeAscii}\r\n`, 'cp437'),
}
};
this.gotoMenu(
this.menuConfig.config.mainMenuUser2FAOTP_ShowQR || 'mainMenuUser2FAOTP_ShowQR',
modOpts,
cb
);
}
isOTPEnabledForUser() {
return this.typeSelectionIndexFromUserOTPType(-1) != -1;
}
getInfoText(key) {
return _.get(this.config, [ 'infoText', key ], '');
}
enableToggleUpdate(idx) {
const key = {
0 : '2faDisabled',
1 : '2faEnabled',
}[idx];
this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } );
}
typeSelectionIndexFromUserOTPType(defaultIndex = 0) {
const type = this.client.user.getProperty(UserProps.AuthFactor2OTP);
return {
[ OTPTypes.RFC6238_TOTP ] : 0,
[ OTPTypes.RFC4266_HOTP ] : 1,
[ OTPTypes.GoogleAuthenticator ] : 2,
}[type] || defaultIndex;
}
otpTypeFromTypeSelectionIndex(idx) {
return {
0 : OTPTypes.RFC6238_TOTP,
1 : OTPTypes.RFC4266_HOTP,
2 : OTPTypes.GoogleAuthenticator,
}[idx];
}
typeSelectionUpdate(idx) {
const key = '2faType_' + this.otpTypeFromTypeSelectionIndex(idx);
this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } );
}
};