Better naming, start on temp tokens table, etc.

This commit is contained in:
Bryan Ashby 2019-06-11 18:25:24 -06:00
parent 29e0c8f790
commit 4158b07ad0
No known key found for this signature in database
GPG Key ID: B49EB437951D2542
4 changed files with 119 additions and 19 deletions

View File

@ -79,7 +79,7 @@ exports.getModule = class AbracadabraModule extends MenuModule {
/* /*
:TODO: :TODO:
* disconnecting wile door is open leaves dosemu * disconnecting while door is open leaves dosemu
* http://bbslink.net/sysop.php support * http://bbslink.net/sysop.php support
* Font support ala all other menus... or does this just work? * Font support ala all other menus... or does this just work?
*/ */

View File

@ -203,6 +203,22 @@ const DB_INIT_TABLE = {
);` );`
); );
//
// Table for temporary tokens, generally used for e.g. 'outside'
// access such as email links.
// Examples: PW reset, enabling of 2FA/OTP, etc.
//
dbs.user.run(
`CREATE TABLE IF NOT EXISTS user_temporary_token (
user_id INTEGER NOT NULL,
token VARCHAR NOT NULL,
timestamp DATETIME NOT NULL,
purpose VARCHAR NOT NULL,
UNIQUE(user_id, token),
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
);`
);
return cb(null); return cb(null);
}, },

View File

@ -9,11 +9,16 @@ const {
otpFromType, otpFromType,
createQRCode, createQRCode,
} = require('./user_2fa_otp.js'); } = require('./user_2fa_otp.js');
const { Errors } = require('./enig_error.js');
const { sendMail } = require('./email.js');
const { getServer } = require('./listening_server.js');
const WebServerPackageName = require('./servers/content/web.js').moduleInfo.packageName;
// deps // deps
const async = require('async'); const async = require('async');
const _ = require('lodash'); const _ = require('lodash');
const iconv = require('iconv-lite'); const iconv = require('iconv-lite');
const crypto = require('crypto');
exports.moduleInfo = { exports.moduleInfo = {
name : 'User 2FA/OTP Configuration', name : 'User 2FA/OTP Configuration',
@ -26,10 +31,10 @@ const FormIds = {
}; };
const MciViewIds = { const MciViewIds = {
enableToggle : 1, enableToggle : 1,
typeSelection : 2, otpType : 2,
submission : 3, submit : 3,
infoText : 4, infoText : 4,
customRangeStart : 10, // 10+ = customs customRangeStart : 10, // 10+ = customs
}; };
@ -53,10 +58,22 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
}, },
showBackupCodes : (formData, extraArgs, cb) => { showBackupCodes : (formData, extraArgs, cb) => {
return this.showBackupCodes(cb); return this.showBackupCodes(cb);
},
saveChanges : (formData, extraArgs, cb) => {
return this.saveChanges(formData, cb);
} }
}; };
} }
initSequence() {
this.webServer = getServer(WebServerPackageName);
if(!this.webServer || !this.webServer.instance.isEnabled()) {
this.client.log.warn('User 2FA/OTP configuration requires the web server to be enabled!');
return this.prevMenu( () => { /* dummy */ } );
}
return super.initSequence();
}
mciReady(mciData, cb) { mciReady(mciData, cb) {
super.mciReady(mciData, err => { super.mciReady(mciData, err => {
if(err) { if(err) {
@ -71,8 +88,8 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
(callback) => { (callback) => {
const requiredCodes = [ const requiredCodes = [
MciViewIds.enableToggle, MciViewIds.enableToggle,
MciViewIds.typeSelection, MciViewIds.otpType,
MciViewIds.submission, MciViewIds.submit,
]; ];
return this.validateMCIByViewIds('menu', requiredCodes, callback); return this.validateMCIByViewIds('menu', requiredCodes, callback);
}, },
@ -86,19 +103,19 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
return this.enableToggleUpdate(idx); return this.enableToggleUpdate(idx);
}); });
const typeSelectionView = this.getView('menu', MciViewIds.typeSelection); const otpTypeView = this.getView('menu', MciViewIds.otpType);
initialIndex = this.typeSelectionIndexFromUserOTPType(); initialIndex = this.otpTypeIndexFromUserOTPType();
typeSelectionView.setFocusItemIndex(initialIndex); otpTypeView.setFocusItemIndex(initialIndex);
typeSelectionView.on('index update', idx => { otpTypeView.on('index update', idx => {
return this.typeSelectionUpdate(idx); return this.otpTypeUpdate(idx);
}); });
this.viewControllers.menu.on('return', view => { this.viewControllers.menu.on('return', view => {
if(view === enableToggleView) { if(view === enableToggleView) {
return this.enableToggleUpdate(enableToggleView.focusedItemIndex); return this.enableToggleUpdate(enableToggleView.focusedItemIndex);
} else if (view === typeSelectionView) { } else if (view === otpTypeView) {
return this.typeSelectionUpdate(typeSelectionView.focusedItemIndex); return this.otpTypeUpdate(otpTypeView.focusedItemIndex);
} }
}); });
@ -169,8 +186,74 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
return this.displayDetails(info, cb); return this.displayDetails(info, cb);
} }
saveChanges(formData, cb) {
const enabled = 1 === _.get(formData, 'value.enableToggle', 0);
return enabled ? this.saveChangesEnable(formData, cb) : this.saveChangesDisable(cb);
}
saveChangesEnable(formData, cb) {
const otpTypeProp = this.otpTypeFromOTPTypeIndex(_.get(formData, 'value.otpType'));
// sanity check
if(!otpFromType(otpTypeProp)) {
return cb(Errors.Invalid('Cannot convert selected index to valid OTP type'));
}
async.waterfall(
[
(callback) => {
return this.removeUserOTPProperties(callback);
},
(callback) => {
return crypto.randomBytes(256, callback);
},
(token, callback) => {
// :TODO: consider temporary tokens table - this has become semi-common
// token | timestamp | token_type |
// abc | ISO | '2fa_otp_register'
token = token.toString('hex');
this.client.user.persistProperty(UserProps.AuthFactor2OTPEnableToken, token, err => {
return callback(err, token);
});
},
(token, callback) => {
const resetUrl = this.webServer.instance.buildUrl(
`/enable_2fa_otp?token=&otpType=${otpTypeProp}&token=${token}`
);
// clear any existing (e.g. same as disable) -> send activation email
return callback(null);
}
],
err => {
return cb(err);
}
);
}
removeUserOTPProperties(cb) {
const props = [
UserProps.AuthFactor2OTP,
UserProps.AuthFactor2OTPSecret,
UserProps.AuthFactor2OTPBackupCodes,
];
return this.client.user.removeProperties(props, cb);
}
saveChangesDisable(cb) {
this.removeUserOTPProperties( err => {
if(err) {
return cb(err);
}
// :TODO: show "saved+disabled" art/message -> prevMenu
return cb(null);
});
}
isOTPEnabledForUser() { isOTPEnabledForUser() {
return this.typeSelectionIndexFromUserOTPType(-1) != -1; return this.otpTypeIndexFromUserOTPType(-1) != -1;
} }
getInfoText(key) { getInfoText(key) {
@ -185,7 +268,7 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } ); this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } );
} }
typeSelectionIndexFromUserOTPType(defaultIndex = 0) { otpTypeIndexFromUserOTPType(defaultIndex = 0) {
const type = this.client.user.getProperty(UserProps.AuthFactor2OTP); const type = this.client.user.getProperty(UserProps.AuthFactor2OTP);
return { return {
[ OTPTypes.RFC6238_TOTP ] : 0, [ OTPTypes.RFC6238_TOTP ] : 0,
@ -194,7 +277,7 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
}[type] || defaultIndex; }[type] || defaultIndex;
} }
otpTypeFromTypeSelectionIndex(idx) { otpTypeFromOTPTypeIndex(idx) {
return { return {
0 : OTPTypes.RFC6238_TOTP, 0 : OTPTypes.RFC6238_TOTP,
1 : OTPTypes.RFC4266_HOTP, 1 : OTPTypes.RFC4266_HOTP,
@ -202,8 +285,8 @@ exports.getModule = class User2FA_OTPConfigModule extends MenuModule {
}[idx]; }[idx];
} }
typeSelectionUpdate(idx) { otpTypeUpdate(idx) {
const key = this.otpTypeFromTypeSelectionIndex(idx); const key = this.otpTypeFromOTPTypeIndex(idx);
this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } ); this.updateCustomViewTextsWithFilter('menu', MciViewIds.customRangeStart, { infoText : this.getInfoText(key) } );
} }
}; };

View File

@ -63,5 +63,6 @@ module.exports = {
AuthFactor2OTP : 'auth_factor2_otp', // If present, OTP type for 2FA. See OTPTypes AuthFactor2OTP : 'auth_factor2_otp', // If present, OTP type for 2FA. See OTPTypes
AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA AuthFactor2OTPSecret : 'auth_factor2_otp_secret', // Secret used in conjunction with OTP 2FA
AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes AuthFactor2OTPBackupCodes : 'auth_factor2_otp_backup', // JSON array of backup codes
AuthFactor2OTPEnableToken : 'auth_factor2_otp_enable_token',
}; };