mirror of
https://github.com/NuSkooler/enigma-bbs.git
synced 2025-06-10 14:44:40 +02:00
Merge branch '0.0.9-alpha' of github.com:NuSkooler/enigma-bbs into user-interruptions
This commit is contained in:
commit
36e9356663
84 changed files with 1759 additions and 643 deletions
|
@ -847,6 +847,8 @@ function peg$parse(input, options) {
|
|||
const client = options.client;
|
||||
const user = options.client.user;
|
||||
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
const moment = require('moment');
|
||||
|
||||
function checkAccess(acsCode, value) {
|
||||
|
@ -863,7 +865,7 @@ function peg$parse(input, options) {
|
|||
value = [ value ];
|
||||
}
|
||||
|
||||
const userAccountStatus = parseInt(user.properties.account_status, 10);
|
||||
const userAccountStatus = user.getPropertyAsNumber(UserProps.AccountStatus);
|
||||
return value.map(n => parseInt(n, 10)).includes(userAccountStatus);
|
||||
},
|
||||
EC : function isEncoding() {
|
||||
|
@ -888,15 +890,15 @@ function peg$parse(input, options) {
|
|||
return value.map(n => parseInt(n, 10)).includes(client.node);
|
||||
},
|
||||
NP : function numberOfPosts() {
|
||||
const postCount = parseInt(user.properties.post_count, 10) || 0;
|
||||
const postCount = user.getPropertyAsNumber(UserProps.PostCount) || 0;
|
||||
return !isNaN(value) && postCount >= value;
|
||||
},
|
||||
NC : function numberOfCalls() {
|
||||
const loginCount = parseInt(user.properties.login_count, 10);
|
||||
const loginCount = user.getPropertyAsNumber(UserProps.LoginCount);
|
||||
return !isNaN(value) && loginCount >= value;
|
||||
},
|
||||
AA : function accountAge() {
|
||||
const accountCreated = moment(user.properties.account_created);
|
||||
const accountCreated = moment(user.getProperty(UserProps.AccountCreated));
|
||||
const now = moment();
|
||||
const daysOld = accountCreated.diff(moment(), 'days');
|
||||
return !isNaN(value) &&
|
||||
|
@ -905,36 +907,36 @@ function peg$parse(input, options) {
|
|||
daysOld >= value;
|
||||
},
|
||||
BU : function bytesUploaded() {
|
||||
const bytesUp = parseInt(user.properties.ul_total_bytes, 10) || 0;
|
||||
const bytesUp = user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0;
|
||||
return !isNaN(value) && bytesUp >= value;
|
||||
},
|
||||
UP : function uploads() {
|
||||
const uls = parseInt(user.properties.ul_total_count, 10) || 0;
|
||||
const uls = user.getPropertyAsNumber(UserProps.FileUlTotalCount) || 0;
|
||||
return !isNaN(value) && uls >= value;
|
||||
},
|
||||
BD : function bytesDownloaded() {
|
||||
const bytesDown = parseInt(user.properties.dl_total_bytes, 10) || 0;
|
||||
const bytesDown = user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0;
|
||||
return !isNaN(value) && bytesDown >= value;
|
||||
},
|
||||
DL : function downloads() {
|
||||
const dls = parseInt(user.properties.dl_total_count, 10) || 0;
|
||||
const dls = user.getPropertyAsNumber(UserProps.FileDlTotalCount) || 0;
|
||||
return !isNaN(value) && dls >= value;
|
||||
},
|
||||
NR : function uploadDownloadRatioGreaterThan() {
|
||||
const ulCount = parseInt(user.properties.ul_total_count, 10) || 0;
|
||||
const dlCount = parseInt(user.properties.dl_total_count, 10) || 0;
|
||||
const ulCount = user.getPropertyAsNumber(UserProps.FileUlTotalCount) || 0;
|
||||
const dlCount = user.getPropertyAsNumber(UserProps.FileDlTotalCount) || 0;
|
||||
const ratio = ~~((ulCount / dlCount) * 100);
|
||||
return !isNaN(value) && ratio >= value;
|
||||
},
|
||||
KR : function uploadDownloadByteRatioGreaterThan() {
|
||||
const ulBytes = parseInt(user.properties.ul_total_bytes, 10) || 0;
|
||||
const dlBytes = parseInt(user.properties.dl_total_bytes, 10) || 0;
|
||||
const ulBytes = user.getPropertyAsNumber(UserProps.FileUlTotalBytes) || 0;
|
||||
const dlBytes = user.getPropertyAsNumber(UserProps.FileDlTotalBytes) || 0;
|
||||
const ratio = ~~((ulBytes / dlBytes) * 100);
|
||||
return !isNaN(value) && ratio >= value;
|
||||
},
|
||||
PC : function postCallRatio() {
|
||||
const postCount = parseInt(user.properties.post_count, 10) || 0;
|
||||
const loginCount = parseInt(user.properties.login_count, 10);
|
||||
const postCount = user.getPropertyAsNumber(UserProps.PostCount) || 0;
|
||||
const loginCount = user.getPropertyAsNumber(UserProps.LoginCount) || 0;
|
||||
const ratio = ~~((postCount / loginCount) * 100);
|
||||
return !isNaN(value) && ratio >= value;
|
||||
},
|
||||
|
|
12
core/bbs.js
12
core/bbs.js
|
@ -10,6 +10,7 @@ const conf = require('./config.js');
|
|||
const logger = require('./logger.js');
|
||||
const database = require('./database.js');
|
||||
const resolvePath = require('./misc_util.js').resolvePath;
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -229,18 +230,21 @@ function initialize(cb) {
|
|||
},
|
||||
function getOpProps(opUserName, next) {
|
||||
const propLoadOpts = {
|
||||
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
||||
names : [
|
||||
UserProps.RealName, UserProps.Sex, UserProps.EmailAddress,
|
||||
UserProps.Location, UserProps.Affiliations,
|
||||
],
|
||||
};
|
||||
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
|
||||
return next(err, opUserName, opProps);
|
||||
return next(err, opUserName, opProps, propLoadOpts);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, opUserName, opProps) => {
|
||||
(err, opUserName, opProps, propLoadOpts) => {
|
||||
const StatLog = require('./stat_log.js');
|
||||
|
||||
if(err) {
|
||||
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
||||
propLoadOpts.concat('username').forEach(v => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const logger = require('./logger.js');
|
||||
const Events = require('./events.js');
|
||||
const logger = require('./logger.js');
|
||||
const Events = require('./events.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
const hashids = require('hashids');
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
const hashids = require('hashids');
|
||||
|
||||
exports.getActiveConnections = getActiveConnections;
|
||||
exports.getActiveConnectionList = getActiveConnectionList;
|
||||
|
@ -47,11 +48,11 @@ function getActiveConnectionList(authUsersOnly) {
|
|||
//
|
||||
if(ac.user.isAuthenticated()) {
|
||||
entry.userName = ac.user.username;
|
||||
entry.realName = ac.user.properties.real_name;
|
||||
entry.location = ac.user.properties.location;
|
||||
entry.affils = entry.affiliation = ac.user.properties.affiliation;
|
||||
entry.realName = ac.user.properties[UserProps.RealName];
|
||||
entry.location = ac.user.properties[UserProps.Location];
|
||||
entry.affils = entry.affiliation = ac.user.properties[UserProps.Affiliations];
|
||||
|
||||
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
||||
const diff = now.diff(moment(ac.user.properties[UserProps.LastLoginTs]), 'minutes');
|
||||
entry.timeOn = moment.duration(diff, 'minutes');
|
||||
}
|
||||
return entry;
|
||||
|
@ -62,7 +63,7 @@ function addNewClient(client, clientSock) {
|
|||
const id = client.session.id = clientConnections.push(client) - 1;
|
||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||
|
||||
// create a uniqe identifier one-time ID for this session
|
||||
// create a unique identifier one-time ID for this session
|
||||
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
||||
|
||||
// Create a client specific logger
|
||||
|
|
|
@ -110,7 +110,8 @@ function renegadeToAnsi(s, client) {
|
|||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
} else if(m[4] || m[1]) {
|
||||
// |AA MCI code or |Cx## movement where ## is in m[1]
|
||||
const val = getPredefinedMCIValue(client, m[4] || m[1], m[2]) || (m[0]); // value itself or literal
|
||||
let val = getPredefinedMCIValue(client, m[4] || m[1], m[2]);
|
||||
val = _.isString(val) ? val : m[0]; // value itself or literal
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||
} else if(m[5]) {
|
||||
// || -- literal '|', that is.
|
||||
|
|
|
@ -42,17 +42,53 @@ function hasMessageConferenceAndArea(config) {
|
|||
return result;
|
||||
}
|
||||
|
||||
const ArrayReplaceKeyPaths = [
|
||||
'loginServers.ssh.algorithms.kex',
|
||||
'loginServers.ssh.algorithms.cipher',
|
||||
'loginServers.ssh.algorithms.hmac',
|
||||
'loginServers.ssh.algorithms.compress',
|
||||
];
|
||||
|
||||
const ArrayReplaceKeys = [
|
||||
'args',
|
||||
'sendArgs', 'recvArgs', 'recvArgsNonBatch',
|
||||
];
|
||||
|
||||
function mergeValidateAndFinalize(config, cb) {
|
||||
const defaultConfig = getDefaultConfig();
|
||||
|
||||
const arrayReplaceKeyPathsMutable = _.clone(ArrayReplaceKeyPaths);
|
||||
const shouldReplaceArray = (arr, key) => {
|
||||
if(ArrayReplaceKeys.includes(key)) {
|
||||
return true;
|
||||
}
|
||||
for(let i = 0; i < arrayReplaceKeyPathsMutable.length; ++i) {
|
||||
const o = _.get(defaultConfig, arrayReplaceKeyPathsMutable[i]);
|
||||
if(_.isEqual(o, arr)) {
|
||||
arrayReplaceKeyPathsMutable.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function mergeWithDefaultConfig(callback) {
|
||||
const mergedConfig = _.mergeWith(
|
||||
getDefaultConfig(),
|
||||
config, (conf1, conf2) => {
|
||||
// Arrays should always concat
|
||||
if(_.isArray(conf1)) {
|
||||
// :TODO: look for collisions & override dupes
|
||||
return conf1.concat(conf2);
|
||||
defaultConfig,
|
||||
config,
|
||||
(defConfig, userConfig, key) => {
|
||||
if(Array.isArray(defConfig) && Array.isArray(userConfig)) {
|
||||
//
|
||||
// Arrays are special: Some we merge, while others
|
||||
// we simply replace.
|
||||
//
|
||||
if(shouldReplaceArray(defConfig, key)) {
|
||||
return userConfig;
|
||||
} else {
|
||||
return _.uniq(defConfig.concat(userConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -136,7 +172,6 @@ function getDefaultConfig() {
|
|||
|
||||
// :TODO: closedSystem and loginAttemps prob belong under users{}?
|
||||
closedSystem : false, // is the system closed to new users?
|
||||
loginAttempts : 3,
|
||||
|
||||
menuFile : 'menu.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path
|
||||
promptFile : 'prompt.hjson', // 'oputil.js config new' will set this appropriately in config.hjson; may be full path
|
||||
|
@ -181,6 +216,13 @@ function getDefaultConfig() {
|
|||
|
||||
preAuthIdleLogoutSeconds : 60 * 3, // 3m
|
||||
idleLogoutSeconds : 60 * 6, // 6m
|
||||
|
||||
failedLogin : {
|
||||
disconnect : 3, // 0=disabled
|
||||
lockAccount : 9, // 0=disabled; Mark user status as "locked" if >= N
|
||||
autoUnlockMinutes : 60 * 6, // 0=disabled; Auto unlock after N minutes.
|
||||
},
|
||||
unlockAtEmailPwReset : true, // if true, password reset via email will unlock locked accounts
|
||||
},
|
||||
|
||||
theme : {
|
||||
|
|
|
@ -67,7 +67,13 @@ function loadDatabaseForMod(modInfo, cb) {
|
|||
|
||||
function getISOTimestampString(ts) {
|
||||
ts = ts || moment();
|
||||
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||
if(!moment.isMoment(ts)) {
|
||||
if(_.isString(ts)) {
|
||||
ts = ts.replace(/\//g, '-');
|
||||
}
|
||||
ts = moment(ts);
|
||||
}
|
||||
return ts.utc().format('YYYY-MM-DDTHH:mm:ss.SSS[Z]');
|
||||
}
|
||||
|
||||
function sanatizeString(s) {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
'use strict';
|
||||
|
||||
const FileEntry = require('./file_entry.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const { partition } = require('lodash');
|
||||
|
@ -11,8 +12,8 @@ module.exports = class DownloadQueue {
|
|||
this.client = client;
|
||||
|
||||
if(!Array.isArray(this.client.user.downloadQueue)) {
|
||||
if(this.client.user.properties.dl_queue) {
|
||||
this.loadFromProperty(this.client.user.properties.dl_queue);
|
||||
if(this.client.user.properties[UserProps.DownloadQueue]) {
|
||||
this.loadFromProperty(this.client.user.properties[UserProps.DownloadQueue]);
|
||||
} else {
|
||||
this.client.user.downloadQueue = [];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
// ENiGMA½
|
||||
const Config = require('./config.js').get;
|
||||
const StatLog = require('./stat_log.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const fs = require('graceful-fs');
|
||||
|
@ -84,6 +85,8 @@ module.exports = class DropFile {
|
|||
const prop = this.client.user.properties;
|
||||
const now = moment();
|
||||
const secLevel = this.client.user.getLegacySecurityLevel().toString();
|
||||
const fullName = prop[UserProps.RealName] || this.client.user.username;
|
||||
const bd = moment(prop[UserProp.Birthdate).format('MM/DD/YY');
|
||||
|
||||
// :TODO: fix time remaining
|
||||
// :TODO: fix default protocol -- user prop: transfer_protocol
|
||||
|
@ -97,13 +100,13 @@ module.exports = class DropFile {
|
|||
'Y', // "Printer Toggle - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Page Bell - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Caller Alarm - Y=On N=Off (Default to Y)"
|
||||
prop.real_name || this.client.user.username, // "User Full Name"
|
||||
prop.location || 'Anywhere', // "Calling From"
|
||||
fullName, // "User Full Name"
|
||||
prop[UserProps.Location]|| 'Anywhere', // "Calling From"
|
||||
'123-456-7890', // "Home Phone"
|
||||
'123-456-7890', // "Work/Data Phone"
|
||||
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
||||
secLevel, // "Security Level"
|
||||
prop.login_count.toString(), // "Total Times On"
|
||||
prop[UserProps.LoginCount].toString(), // "Total Times On"
|
||||
now.format('MM/DD/YY'), // "Last Date Called"
|
||||
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
||||
'256', // "Minutes Remaining THIS call"
|
||||
|
@ -120,7 +123,7 @@ module.exports = class DropFile {
|
|||
'0', // "Total Downloads"
|
||||
'0', // "Daily Download "K" Total"
|
||||
'999999', // "Daily Download Max. "K" Limit"
|
||||
moment(prop.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
|
||||
bd, // "Caller's Birthdate"
|
||||
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
||||
'X:\\GEN\\', // "Path to the GEN directory"
|
||||
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
|
||||
|
@ -141,7 +144,7 @@ module.exports = class DropFile {
|
|||
'0', // "Files d/led so far today"
|
||||
'0', // "Total "K" Bytes Uploaded"
|
||||
'0', // "Total "K" Bytes Downloaded"
|
||||
prop.user_comment || 'None', // "User Comment"
|
||||
prop[UserProps.UserComment] || 'None', // "User Comment"
|
||||
'0', // "Total Doors Opened"
|
||||
'0', // "Total Messages Left"
|
||||
|
||||
|
@ -168,7 +171,7 @@ module.exports = class DropFile {
|
|||
'115200',
|
||||
Config().general.boardName,
|
||||
this.client.user.userId.toString(),
|
||||
this.client.user.properties.real_name || this.client.user.username,
|
||||
this.client.user.properties[UserProps.RealName] || this.client.user.username,
|
||||
this.client.user.username,
|
||||
this.client.user.getLegacySecurityLevel().toString(),
|
||||
'546', // :TODO: Minutes left!
|
||||
|
@ -189,21 +192,22 @@ module.exports = class DropFile {
|
|||
const opUserName = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
|
||||
const userName = /[^\s]*/.exec(this.client.user.username)[0];
|
||||
const secLevel = this.client.user.getLegacySecurityLevel().toString();
|
||||
const location = this.client.user.properties[UserProps.Location];
|
||||
|
||||
return iconv.encode( [
|
||||
Config().general.boardName, // "The name of the system."
|
||||
opUserName, // "The sysop's name up to the first space."
|
||||
opUserName, // "The sysop's name following the first space."
|
||||
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
||||
'57600', // "The current port (DTE) rate."
|
||||
'0', // "The number "0""
|
||||
userName, // "The current user's name, up to the first space."
|
||||
userName, // "The current user's name, following the first space."
|
||||
this.client.user.properties.location || '', // "Where the user lives, or a blank line if unknown."
|
||||
'1', // "The number "0" if TTY, or "1" if ANSI."
|
||||
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops."
|
||||
'546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software."
|
||||
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
||||
Config().general.boardName, // "The name of the system."
|
||||
opUserName, // "The sysop's name up to the first space."
|
||||
opUserName, // "The sysop's name following the first space."
|
||||
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
||||
'57600', // "The current port (DTE) rate."
|
||||
'0', // "The number "0""
|
||||
userName, // "The current user's name, up to the first space."
|
||||
userName, // "The current user's name, following the first space."
|
||||
location || '', // "Where the user lives, or a blank line if unknown."
|
||||
'1', // "The number "0" if TTY, or "1" if ANSI."
|
||||
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops."
|
||||
'546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software."
|
||||
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,9 @@ exports.Errors = {
|
|||
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
||||
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
||||
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
|
||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramater(s)', -32008, reason, reasonCode),
|
||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramter(s)', -32008, reason, reasonCode),
|
||||
MissingMci : (reason, reasonCode) => new EnigError('Missing required MCI code(s)', -32009, reason, reasonCode),
|
||||
BadLogin : (reason, reasonCode) => new EnigError('Bad login attempt', -32010, reason, reasonCode),
|
||||
};
|
||||
|
||||
exports.ErrorReasons = {
|
||||
|
@ -44,4 +45,9 @@ exports.ErrorReasons = {
|
|||
NoPreviousMenu : 'NOPREV',
|
||||
NoConditionMatch : 'NOCONDMATCH',
|
||||
NotEnabled : 'NOTENABLED',
|
||||
};
|
||||
AlreadyLoggedIn : 'ALREADYLOGGEDIN',
|
||||
TooMany : 'TOOMANY',
|
||||
Disabled : 'DISABLED',
|
||||
Inactive : 'INACTIVE',
|
||||
Locked : 'LOCKED',
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
const PluginModule = require('./plugin_module.js').PluginModule;
|
||||
const Config = require('./config.js').get;
|
||||
const Log = require('./logger.js').log;
|
||||
const { Errors } = require('./enig_error.js');
|
||||
|
||||
const _ = require('lodash');
|
||||
const later = require('later');
|
||||
|
@ -116,7 +117,7 @@ class ScheduledEvent {
|
|||
methodModule[this.action.what](this.action.args, err => {
|
||||
if(err) {
|
||||
Log.debug(
|
||||
{ error : err.toString(), eventName : this.name, action : this.action },
|
||||
{ error : err.message, eventName : this.name, action : this.action },
|
||||
'Error performing scheduled event action');
|
||||
}
|
||||
|
||||
|
@ -124,7 +125,7 @@ class ScheduledEvent {
|
|||
});
|
||||
} catch(e) {
|
||||
Log.warn(
|
||||
{ error : e.toString(), eventName : this.name, action : this.action },
|
||||
{ error : e.message, eventName : this.name, action : this.action },
|
||||
'Failed to perform scheduled event action');
|
||||
|
||||
return cb(e);
|
||||
|
@ -138,7 +139,22 @@ class ScheduledEvent {
|
|||
env : process.env,
|
||||
};
|
||||
|
||||
const proc = pty.spawn(this.action.what, this.action.args, opts);
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(this.action.what, this.action.args, opts);
|
||||
} catch(e) {
|
||||
Log.warn(
|
||||
{
|
||||
error : 'Failed to spawn @execute process',
|
||||
reason : e.message,
|
||||
eventName : this.name,
|
||||
action : this.action,
|
||||
what : this.action.what,
|
||||
args : this.action.args
|
||||
}
|
||||
);
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
proc.once('exit', exitCode => {
|
||||
if(exitCode) {
|
||||
|
|
|
@ -7,6 +7,7 @@ const ViewController = require('./view_controller.js').ViewContro
|
|||
const getSortedAvailableFileAreas = require('./file_base_area.js').getSortedAvailableFileAreas;
|
||||
const FileBaseFilters = require('./file_base_filter.js');
|
||||
const stringFormat = require('./string_format.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -111,7 +112,7 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
|||
//
|
||||
// If the item was also the active filter, we need to make a new one active
|
||||
//
|
||||
if(filterUuid === this.client.user.properties.file_base_filter_active_uuid) {
|
||||
if(filterUuid === this.client.user.properties[UserProps.FileBaseFilterActiveUuid]) {
|
||||
const newActive = this.filtersArray[this.currentFilterIndex];
|
||||
if(newActive) {
|
||||
filters.setActive(newActive.uuid);
|
||||
|
|
|
@ -14,6 +14,7 @@ const resolveMimeType = require('./mime_util.js').resolveMimeType;
|
|||
const stringFormat = require('./string_format.js');
|
||||
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||
const StatLog = require('./stat_log.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
@ -136,11 +137,11 @@ function changeFileAreaWithOptions(client, areaTag, options, cb) {
|
|||
},
|
||||
function changeArea(area, callback) {
|
||||
if(true === options.persist) {
|
||||
client.user.persistProperty('file_area_tag', areaTag, err => {
|
||||
client.user.persistProperty(UserProps.FileAreaTag, areaTag, err => {
|
||||
return callback(err, area);
|
||||
});
|
||||
} else {
|
||||
client.user.properties['file_area_tag'] = areaTag;
|
||||
client.user.properties[UserProps.FileAreaTag] = areaTag;
|
||||
return callback(null, area);
|
||||
}
|
||||
}
|
||||
|
@ -705,7 +706,7 @@ function scanFile(filePath, options, iterator, cb) {
|
|||
// up to many seconds in time for larger files.
|
||||
//
|
||||
const chunkSize = 1024 * 64;
|
||||
const buffer = new Buffer(chunkSize);
|
||||
const buffer = Buffer.allocUnsafe(chunkSize);
|
||||
|
||||
fs.open(filePath, 'r', (err, fd) => {
|
||||
if(err) {
|
||||
|
|
|
@ -150,11 +150,7 @@ exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
|||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
queueView.setItems(this.dlQueue.items);
|
||||
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const uuidV4 = require('uuid/v4');
|
||||
const _ = require('lodash');
|
||||
const uuidV4 = require('uuid/v4');
|
||||
|
||||
module.exports = class FileBaseFilters {
|
||||
constructor(client) {
|
||||
|
@ -64,7 +66,7 @@ module.exports = class FileBaseFilters {
|
|||
}
|
||||
|
||||
load() {
|
||||
let filtersProperty = this.client.user.properties.file_base_filters;
|
||||
let filtersProperty = this.client.user.properties[UserProps.FileBaseFilters];
|
||||
let defaulted;
|
||||
if(!filtersProperty) {
|
||||
filtersProperty = JSON.stringify(FileBaseFilters.getBuiltInSystemFilters());
|
||||
|
@ -90,7 +92,7 @@ module.exports = class FileBaseFilters {
|
|||
}
|
||||
|
||||
persist(cb) {
|
||||
return this.client.user.persistProperty('file_base_filters', JSON.stringify(this.filters), cb);
|
||||
return this.client.user.persistProperty(UserProps.FileBaseFilters, JSON.stringify(this.filters), cb);
|
||||
}
|
||||
|
||||
cleanTags(tags) {
|
||||
|
@ -102,7 +104,7 @@ module.exports = class FileBaseFilters {
|
|||
|
||||
if(activeFilter) {
|
||||
this.activeFilter = activeFilter;
|
||||
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
||||
this.client.user.persistProperty(UserProps.FileBaseFilterActiveUuid, filterUuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -129,11 +131,11 @@ module.exports = class FileBaseFilters {
|
|||
}
|
||||
|
||||
static getActiveFilter(client) {
|
||||
return new FileBaseFilters(client).get(client.user.properties.file_base_filter_active_uuid);
|
||||
return new FileBaseFilters(client).get(client.user.properties[UserProps.FileBaseFilterActiveUuid]);
|
||||
}
|
||||
|
||||
static getFileBaseLastViewedFileIdByUser(user) {
|
||||
return parseInt((user.properties.user_file_base_last_viewed || 0));
|
||||
return parseInt((user.properties[UserProps.FileBaseLastViewedId] || 0));
|
||||
}
|
||||
|
||||
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {
|
||||
|
@ -150,6 +152,6 @@ module.exports = class FileBaseFilters {
|
|||
return;
|
||||
}
|
||||
|
||||
return user.persistProperty('user_file_base_last_viewed', fileId, cb);
|
||||
return user.persistProperty(UserProps.FileBaseLastViewedId, fileId, cb);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -121,11 +121,7 @@ exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
|||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{webDlLink}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
queueView.setItems(this.dlQueue.items);
|
||||
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
|
|
|
@ -24,6 +24,7 @@ const {
|
|||
const Config = require('./config.js').get;
|
||||
const { getAddressedToInfo } = require('./mail_util.js');
|
||||
const Events = require('./events.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -479,7 +480,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
}
|
||||
|
||||
Events.emit(Events.getSystemEvents().UserPostMessage, { user : this.client.user, areaTag : this.message.areaTag });
|
||||
return StatLog.incrementUserStat(this.client.user, 'post_count', 1, cb);
|
||||
return StatLog.incrementUserStat(this.client.user, UserProps.MessagePostCount, 1, cb);
|
||||
}
|
||||
|
||||
redrawFooter(options, cb) {
|
||||
|
@ -542,7 +543,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
theme.displayThemedAsset(
|
||||
art[n],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, acsCondMember : 'art' },
|
||||
{ font : self.menuConfig.font },
|
||||
function displayed(err) {
|
||||
next(err);
|
||||
}
|
||||
|
@ -622,7 +623,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
theme.displayThemedAsset(
|
||||
art[n],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, acsCondMember : 'art' },
|
||||
{ font : self.menuConfig.font },
|
||||
function displayed(err, artData) {
|
||||
if(artData) {
|
||||
mciData[n] = artData;
|
||||
|
@ -738,7 +739,7 @@ exports.FullScreenEditorModule = exports.getModule = class FullScreenEditorModul
|
|||
const fromView = self.viewControllers.header.getView(MciViewIds.header.from);
|
||||
const area = getMessageAreaByTag(self.messageAreaTag);
|
||||
if(area && area.realNames) {
|
||||
fromView.setText(self.client.user.properties.real_name || self.client.user.username);
|
||||
fromView.setText(self.client.user.properties[UserProps.RealName] || self.client.user.username);
|
||||
} else {
|
||||
fromView.setText(self.client.user.username);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ const StatLog = require('./stat_log.js');
|
|||
const User = require('./user.js');
|
||||
const sysDb = require('./database.js').dbs.system;
|
||||
const { Errors } = require('./enig_error.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const moment = require('moment');
|
||||
|
@ -165,7 +166,7 @@ exports.getModule = class LastCallersModule extends MenuModule {
|
|||
|
||||
loadUserForHistoryItems(loginHistory, cb) {
|
||||
const getPropOpts = {
|
||||
names : [ 'real_name', 'location', 'affiliation' ]
|
||||
names : [ UserProps.RealName, UserProps.Location, UserProps.Affiliations ]
|
||||
};
|
||||
|
||||
const actionIndicatorNames = _.map(this.actionIndicators, (v, k) => k);
|
||||
|
@ -185,9 +186,9 @@ exports.getModule = class LastCallersModule extends MenuModule {
|
|||
item.userName = item.text = userName;
|
||||
|
||||
User.loadProperties(item.userId, getPropOpts, (err, props) => {
|
||||
item.location = (props && props.location) || '';
|
||||
item.affiliation = item.affils = (props && props.affiliation) || '';
|
||||
item.realName = (props && props.real_name) || '';
|
||||
item.location = (props && props[UserProps.Location]) || '';
|
||||
item.affiliation = item.affils = (props && props[UserProps.Affiliations]) || '';
|
||||
item.realName = (props && props[UserProps.RealName]) || '';
|
||||
|
||||
if(!indicatorSumsSql) {
|
||||
return next(null, item);
|
||||
|
|
|
@ -6,6 +6,7 @@ const conf = require('./config.js');
|
|||
const logger = require('./logger.js');
|
||||
const ServerModule = require('./server_module.js').ServerModule;
|
||||
const clientConns = require('./client_connections.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
@ -25,12 +26,12 @@ module.exports = class LoginServerModule extends ServerModule {
|
|||
//
|
||||
const preLoginTheme = _.get(conf.config, 'theme.preLogin');
|
||||
if('*' === preLoginTheme) {
|
||||
client.user.properties.theme_id = theme.getRandomTheme() || '';
|
||||
client.user.properties[UserProps.ThemeId] = theme.getRandomTheme() || '';
|
||||
} else {
|
||||
client.user.properties.theme_id = preLoginTheme;
|
||||
client.user.properties[UserProps.ThemeId] = preLoginTheme;
|
||||
}
|
||||
|
||||
theme.setClientTheme(client, client.user.properties.theme_id);
|
||||
theme.setClientTheme(client, client.user.properties[UserProps.ThemeId]);
|
||||
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,11 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
const mciData = {};
|
||||
let pausePosition;
|
||||
|
||||
const hasArt = () => {
|
||||
return _.isString(self.menuConfig.art) ||
|
||||
(Array.isArray(self.menuConfig.art) && _.has(self.menuConfig.art[0], 'acs'));
|
||||
};
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeArtInterrupt(callback) {
|
||||
|
@ -56,7 +61,7 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
return self.beforeArt(callback);
|
||||
},
|
||||
function displayMenuArt(callback) {
|
||||
if(!_.isString(self.menuConfig.art)) {
|
||||
if(!hasArt()) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ const Message = require('./message.js');
|
|||
const Log = require('./logger.js').log;
|
||||
const msgNetRecord = require('./msg_network.js').recordMessage;
|
||||
const sortAreasOrConfs = require('./conf_area_util.js').sortAreasOrConfs;
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -222,8 +223,8 @@ function changeMessageConference(client, confTag, cb) {
|
|||
},
|
||||
function changeConferenceAndArea(conf, areaInfo, callback) {
|
||||
const newProps = {
|
||||
message_conf_tag : confTag,
|
||||
message_area_tag : areaInfo.areaTag,
|
||||
[ UserProps.MessageConfTag ] : confTag,
|
||||
[ UserProps.MessageAreaTag ] : areaInfo.areaTag,
|
||||
};
|
||||
client.user.persistProperties(newProps, err => {
|
||||
callback(err, conf, areaInfo);
|
||||
|
@ -262,11 +263,11 @@ function changeMessageAreaWithOptions(client, areaTag, options, cb) {
|
|||
},
|
||||
function changeArea(area, callback) {
|
||||
if(true === options.persist) {
|
||||
client.user.persistProperty('message_area_tag', areaTag, function persisted(err) {
|
||||
client.user.persistProperty(UserProps.MessageAreaTag, areaTag, function persisted(err) {
|
||||
return callback(err, area);
|
||||
});
|
||||
} else {
|
||||
client.user.properties['message_area_tag'] = areaTag;
|
||||
client.user.properties[UserProps.MessageAreaTag] = areaTag;
|
||||
return callback(null, area);
|
||||
}
|
||||
}
|
||||
|
@ -303,8 +304,8 @@ function tempChangeMessageConfAndArea(client, areaTag) {
|
|||
return false;
|
||||
}
|
||||
|
||||
client.user.properties.message_conf_tag = confTag;
|
||||
client.user.properties.message_area_tag = areaTag;
|
||||
client.user.properties[UserProps.MessageConfTag] = confTag;
|
||||
client.user.properties[UserProps.MessageAreaTag] = areaTag;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -353,13 +354,19 @@ function getNewMessagesInAreaForUser(userId, areaTag, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
function getMessageListForArea(client, areaTag, cb) {
|
||||
const filter = {
|
||||
areaTag,
|
||||
resultType : 'messageList',
|
||||
sort : 'messageId',
|
||||
order : 'ascending',
|
||||
};
|
||||
function getMessageListForArea(client, areaTag, filter, cb)
|
||||
{
|
||||
if(!cb && _.isFunction(filter)) {
|
||||
cb = filter;
|
||||
filter = {
|
||||
areaTag,
|
||||
resultType : 'messageList',
|
||||
sort : 'messageId',
|
||||
order : 'ascending'
|
||||
};
|
||||
} else {
|
||||
Object.assign(filter, { areaTag } );
|
||||
}
|
||||
|
||||
if(Message.isPrivateAreaTag(areaTag)) {
|
||||
filter.privateTagUserId = client.user.userId;
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
const paths = require('path');
|
||||
|
||||
const os = require('os');
|
||||
const moment = require('moment');
|
||||
|
||||
const packageJson = require('../package.json');
|
||||
|
||||
exports.isProduction = isProduction;
|
||||
|
@ -57,4 +59,4 @@ function valueAsArray(value) {
|
|||
return [];
|
||||
}
|
||||
return Array.isArray(value) ? value : [ value ];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,10 @@
|
|||
'use strict';
|
||||
|
||||
const messageArea = require('../core/message_area.js');
|
||||
const { get } = require('lodash');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const { get } = require('lodash');
|
||||
|
||||
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
||||
|
||||
|
@ -15,8 +17,8 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
|||
|
||||
if(recordPrevious) {
|
||||
this.prevMessageConfAndArea = {
|
||||
confTag : this.client.user.properties.message_conf_tag,
|
||||
areaTag : this.client.user.properties.message_area_tag,
|
||||
confTag : this.client.user.properties[UserProps.MessageConfTag],
|
||||
areaTag : this.client.user.properties[UserProps.MessageAreaTag],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -27,8 +29,8 @@ exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
|||
|
||||
tempMessageConfAndAreaRestore() {
|
||||
if(this.prevMessageConfAndArea) {
|
||||
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||
this.client.user.properties[UserProps.MessageConfTag] = this.prevMessageConfAndArea.confTag;
|
||||
this.client.user.properties[UserProps.MessageAreaTag] = this.prevMessageConfAndArea.areaTag;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
const { MenuModule } = require('./menu_module.js');
|
||||
const messageArea = require('./message_area.js');
|
||||
const { Errors } = require('./enig_error.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -110,7 +111,7 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
initList() {
|
||||
let index = 1;
|
||||
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
||||
this.client.user.properties.message_conf_tag,
|
||||
this.client.user.properties[UserProps.MessageConfTag],
|
||||
{ client : this.client }
|
||||
).map(area => {
|
||||
return {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
const FullScreenEditorModule = require('./fse.js').FullScreenEditorModule;
|
||||
const persistMessage = require('./message_area.js').persistMessage;
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
|
@ -58,8 +59,10 @@ exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
|||
}
|
||||
|
||||
enter() {
|
||||
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
if(_.isString(this.client.user.properties[UserProps.MessageAreaTag]) &&
|
||||
!_.isString(this.messageAreaTag))
|
||||
{
|
||||
this.messageAreaTag = this.client.user.properties[UserProps.MessageAreaTag];
|
||||
}
|
||||
|
||||
super.enter();
|
||||
|
|
|
@ -8,6 +8,7 @@ const messageArea = require('./message_area.js');
|
|||
const MessageAreaConfTempSwitcher = require('./mod_mixins.js').MessageAreaConfTempSwitcher;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const Message = require('./message.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -167,7 +168,7 @@ exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(
|
|||
if(this.config.messageAreaTag) {
|
||||
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
||||
} else {
|
||||
this.config.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
this.config.messageAreaTag = this.client.user.properties[UserProps.MessageAreaTag];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
core/nua.js
35
core/nua.js
|
@ -8,9 +8,14 @@ const theme = require('./theme.js');
|
|||
const login = require('./system_menu_method.js').login;
|
||||
const Config = require('./config.js').get;
|
||||
const messageArea = require('./message_area.js');
|
||||
const {
|
||||
getISOTimestampString
|
||||
} = require('./database.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'NUA',
|
||||
|
@ -80,20 +85,20 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
areaTag = areaTag || '';
|
||||
|
||||
newUser.properties = {
|
||||
real_name : formData.value.realName,
|
||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
sex : formData.value.sex,
|
||||
location : formData.value.location,
|
||||
affiliation : formData.value.affils,
|
||||
email_address : formData.value.email,
|
||||
web_address : formData.value.web,
|
||||
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
[ UserProps.RealName ] : formData.value.realName,
|
||||
[ UserProps.Birthdate ] : getISOTimestampString(formData.value.birthdate),
|
||||
[ UserProps.Sex ] : formData.value.sex,
|
||||
[ UserProps.Location ] : formData.value.location,
|
||||
[ UserProps.Affiliations ] : formData.value.affils,
|
||||
[ UserProps.EmailAddress ] : formData.value.email,
|
||||
[ UserProps.WebAddress ] : formData.value.web,
|
||||
[ UserProps.AccountCreated ] : getISOTimestampString(),
|
||||
|
||||
message_conf_tag : confTag,
|
||||
message_area_tag : areaTag,
|
||||
[ UserProps.MessageConfTag ] : confTag,
|
||||
[ UserProps.MessageAreaTag ] : areaTag,
|
||||
|
||||
term_height : self.client.term.termHeight,
|
||||
term_width : self.client.term.termWidth,
|
||||
[ UserProps.TermHeight ] : self.client.term.termHeight,
|
||||
[ UserProps.TermWidth ] : self.client.term.termWidth,
|
||||
|
||||
// :TODO: Other defaults
|
||||
// :TODO: should probably have a place to create defaults/etc.
|
||||
|
@ -101,9 +106,9 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
|
||||
const defaultTheme = _.get(config, 'theme.default');
|
||||
if('*' === defaultTheme) {
|
||||
newUser.properties.theme_id = theme.getRandomTheme();
|
||||
newUser.properties[UserProps.ThemeId] = theme.getRandomTheme();
|
||||
} else {
|
||||
newUser.properties.theme_id = defaultTheme;
|
||||
newUser.properties[UserProps.ThemeId] = defaultTheme;
|
||||
}
|
||||
|
||||
// :TODO: User.create() should validate email uniqueness!
|
||||
|
@ -133,7 +138,7 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
|||
};
|
||||
}
|
||||
|
||||
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
|
||||
if(User.AccountStatus.inactive === self.client.user.properties[UserProps.AccountStatus]) {
|
||||
return self.gotoMenu(extraArgs.inactive, cb);
|
||||
} else {
|
||||
//
|
||||
|
|
|
@ -38,7 +38,7 @@ function getAnswers(questions, cb) {
|
|||
const ConfigIncludeKeys = [
|
||||
'theme',
|
||||
'users.preAuthIdleLogoutSeconds', 'users.idleLogoutSeconds',
|
||||
'users.newUserNames',
|
||||
'users.newUserNames', 'users.failedLogin', 'users.unlockAtEmailPwReset',
|
||||
'paths.logs',
|
||||
'loginServers',
|
||||
'contentServers',
|
||||
|
|
|
@ -26,10 +26,11 @@ commands:
|
|||
|
||||
actions:
|
||||
pw USERNAME PASSWORD set password to PASSWORD for USERNAME
|
||||
rm USERNAME permanantely removes USERNAME user from system
|
||||
rm USERNAME permanently removes USERNAME user from system
|
||||
activate USERNAME sets USERNAME's status to active
|
||||
deactivate USERNAME sets USERNAME's status to deactive
|
||||
deactivate USERNAME sets USERNAME's status to inactive
|
||||
disable USERNAME sets USERNAME's status to disabled
|
||||
lock USERNAME sets USERNAME's status to locked
|
||||
group USERNAME [+|-]GROUP adds (+) or removes (-) user from GROUP
|
||||
`,
|
||||
|
||||
|
@ -57,7 +58,7 @@ cat args:
|
|||
actions:
|
||||
scan AREA_TAG[@STORAGE_TAG] scan specified area
|
||||
may also contain optional GLOB as last parameter,
|
||||
for examle: scan some_area *.zip
|
||||
for example: scan some_area *.zip
|
||||
|
||||
info CRITERIA display information about areas and/or files
|
||||
where CRITERIA is one of the following:
|
||||
|
|
|
@ -17,7 +17,7 @@ module.exports = function() {
|
|||
process.exitCode = ExitCodes.SUCCESS;
|
||||
|
||||
if(true === argv.version) {
|
||||
return console.info(require('../package.json').version);
|
||||
return console.info(require('../../package.json').version);
|
||||
}
|
||||
|
||||
if(0 === argv._.length ||
|
||||
|
|
|
@ -8,25 +8,13 @@ const argv = require('./oputil_common.js').argv;
|
|||
const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases;
|
||||
const getHelpFor = require('./oputil_help.js').getHelpFor;
|
||||
const Errors = require('../enig_error.js').Errors;
|
||||
const UserProps = require('../user_property.js');
|
||||
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
|
||||
exports.handleUserCommand = handleUserCommand;
|
||||
|
||||
function getUser(userName, cb) {
|
||||
const User = require('../../core/user.js');
|
||||
User.getUserIdAndName(userName, (err, userId) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return cb(err);
|
||||
}
|
||||
const u = new User();
|
||||
u.userId = userId;
|
||||
return cb(null, u);
|
||||
});
|
||||
}
|
||||
|
||||
function initAndGetUser(userName, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
|
@ -34,12 +22,12 @@ function initAndGetUser(userName, cb) {
|
|||
initConfigAndDatabases(callback);
|
||||
},
|
||||
function getUserObject(callback) {
|
||||
getUser(userName, (err, user) => {
|
||||
const User = require('../../core/user.js');
|
||||
User.getUserIdAndName(userName, (err, userId) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, user);
|
||||
return User.getUser(userId, callback);
|
||||
});
|
||||
}
|
||||
],
|
||||
|
@ -55,15 +43,38 @@ function setAccountStatus(user, status) {
|
|||
}
|
||||
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
|
||||
status = {
|
||||
activate : AccountStatus.active,
|
||||
deactivate : AccountStatus.inactive,
|
||||
disable : AccountStatus.disabled,
|
||||
lock : AccountStatus.locked,
|
||||
}[status];
|
||||
|
||||
const statusDesc = _.invert(AccountStatus)[status];
|
||||
user.persistProperty('account_status', status, err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info(`User status set to ${statusDesc}`);
|
||||
|
||||
async.series(
|
||||
[
|
||||
(callback) => {
|
||||
return user.persistProperty(UserProps.AccountStatus, status, callback);
|
||||
},
|
||||
(callback) => {
|
||||
if(AccountStatus.active !== status) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
return user.unlockAccount(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info(`User status set to ${statusDesc}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
function setUserPassword(user) {
|
||||
|
@ -147,21 +158,6 @@ function modUserGroups(user) {
|
|||
}
|
||||
}
|
||||
|
||||
function activateUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.active);
|
||||
}
|
||||
|
||||
function deactivateUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.inactive);
|
||||
}
|
||||
|
||||
function disableUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.disabled);
|
||||
}
|
||||
|
||||
function handleUserCommand() {
|
||||
function errUsage() {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
|
@ -195,11 +191,12 @@ function handleUserCommand() {
|
|||
del : removeUser,
|
||||
delete : removeUser,
|
||||
|
||||
activate : activateUser,
|
||||
deactivate : deactivateUser,
|
||||
disable : disableUser,
|
||||
activate : setAccountStatus,
|
||||
deactivate : setAccountStatus,
|
||||
disable : setAccountStatus,
|
||||
lock : setAccountStatus,
|
||||
|
||||
group : modUserGroups,
|
||||
}[action] || errUsage)(user);
|
||||
}[action] || errUsage)(user, action);
|
||||
});
|
||||
}
|
|
@ -2,17 +2,18 @@
|
|||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Config = require('./config.js').get;
|
||||
const Log = require('./logger.js').log;
|
||||
const Config = require('./config.js').get;
|
||||
const Log = require('./logger.js').log;
|
||||
const {
|
||||
getMessageAreaByTag,
|
||||
getMessageConferenceByTag
|
||||
} = require('./message_area.js');
|
||||
const clientConnections = require('./client_connections.js');
|
||||
const StatLog = require('./stat_log.js');
|
||||
const FileBaseFilters = require('./file_base_filter.js');
|
||||
const { formatByteSize } = require('./string_util.js');
|
||||
const ANSI = require('./ansi_term.js');
|
||||
} = require('./message_area.js');
|
||||
const clientConnections = require('./client_connections.js');
|
||||
const StatLog = require('./stat_log.js');
|
||||
const FileBaseFilters = require('./file_base_filter.js');
|
||||
const { formatByteSize } = require('./string_util.js');
|
||||
const ANSI = require('./ansi_term.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const packageJson = require('../package.json');
|
||||
|
@ -80,62 +81,66 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
UN : function userName(client) { return client.user.username; },
|
||||
UI : function userId(client) { return client.user.userId.toString(); },
|
||||
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
||||
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
||||
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
||||
UR : function realName(client) { return userStatAsString(client, UserProps.RealName, ''); },
|
||||
LO : function location(client) { return userStatAsString(client, UserProps.Location, ''); },
|
||||
UA : function age(client) { return client.user.getAge().toString(); },
|
||||
BD : function birthdate(client) { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
||||
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
||||
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
||||
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
||||
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
||||
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
||||
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
||||
BD : function birthdate(client) { // iNiQUiTY
|
||||
return moment(client.user.properties[UserProps.Birthdate]).format(client.currentTheme.helpers.getDateFormat());
|
||||
},
|
||||
US : function sex(client) { return userStatAsString(client, UserProps.Sex, ''); },
|
||||
UE : function emailAddres(client) { return userStatAsString(client, UserProps.EmailAddress, ''); },
|
||||
UW : function webAddress(client) { return userStatAsString(client, UserProps.WebAddress, ''); },
|
||||
UF : function affils(client) { return userStatAsString(client, UserProps.Affiliations, ''); },
|
||||
UT : function themeId(client) { return userStatAsString(client, UserProps.ThemeId, ''); },
|
||||
UC : function loginCount(client) { return userStatAsString(client, UserProps.LoginCount, 0); },
|
||||
ND : function connectedNode(client) { return client.node.toString(); },
|
||||
IP : function clientIpAddress(client) { return client.remoteAddress.replace(/^::ffff:/, ''); }, // convert any :ffff: IPv4's to 32bit version
|
||||
ST : function serverName(client) { return client.session.serverName; },
|
||||
FN : function activeFileBaseFilterName(client) {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||
return activeFilter ? activeFilter.name : '';
|
||||
return activeFilter ? activeFilter.name : '(Unknown)';
|
||||
},
|
||||
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||
DN : function userNumDownloads(client) { return userStatAsString(client, UserProps.FileDlTotalCount, 0); }, // Obv/2
|
||||
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
||||
const byteSize = StatLog.getUserStatNum(client.user, UserProps.FileDlTotalBytes);
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
UP : function userNumUploads(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
||||
UP : function userNumUploads(client) { return userStatAsString(client, UserProps.FileUlTotalCount, 0); }, // Obv/2
|
||||
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
||||
const byteSize = StatLog.getUserStatNum(client.user, UserProps.FileUlTotalBytes);
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
NR : function userUpDownRatio(client) { // Obv/2
|
||||
return getUserRatio(client, 'ul_total_count', 'dl_total_count');
|
||||
return getUserRatio(client, UserProps.FileUlTotalCount, UserProps.FileDlTotalCount);
|
||||
},
|
||||
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
||||
return getUserRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
||||
return getUserRatio(client, UserProps.FileUlTotalBytes, UserProps.FileDlTotalBytes);
|
||||
},
|
||||
|
||||
MS : function accountCreatedclient(client) { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
|
||||
PS : function userPostCount(client) { return userStatAsString(client, 'post_count', 0); },
|
||||
PC : function userPostCallRatio(client) { return getUserRatio(client, 'post_count', 'login_count'); },
|
||||
MS : function accountCreatedclient(client) {
|
||||
return moment(client.user.properties[UserProps.AccountCreated]).format(client.currentTheme.helpers.getDateFormat());
|
||||
},
|
||||
PS : function userPostCount(client) { return userStatAsString(client, UserProps.MessagePostCount, 0); },
|
||||
PC : function userPostCallRatio(client) { return getUserRatio(client, UserProps.MessagePostCount, UserProps.LoginCount); },
|
||||
|
||||
MD : function currentMenuDescription(client) {
|
||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||
},
|
||||
|
||||
MA : function messageAreaName(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
const area = getMessageAreaByTag(client.user.properties[UserProps.MessageAreaTag]);
|
||||
return area ? area.name : '';
|
||||
},
|
||||
MC : function messageConfName(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
const conf = getMessageConferenceByTag(client.user.properties[UserProps.MessageConfTag]);
|
||||
return conf ? conf.name : '';
|
||||
},
|
||||
ML : function messageAreaDescription(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
const area = getMessageAreaByTag(client.user.properties[UserProps.MessageAreaTag]);
|
||||
return area ? area.desc : '';
|
||||
},
|
||||
CM : function messageConfDescription(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
const conf = getMessageConferenceByTag(client.user.properties[UserProps.MessageConfTag]);
|
||||
return conf ? conf.desc : '';
|
||||
},
|
||||
|
||||
|
@ -169,8 +174,9 @@ const PREDEFINED_MCI_GENERATORS = {
|
|||
// Clean up CPU strings a bit for better display
|
||||
//
|
||||
return os.cpus()[0].model
|
||||
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
||||
.replace(/\s+(?= )/g, '');
|
||||
.replace(/\(R\)|\(TM\)|processor|CPU/ig, '')
|
||||
.replace(/\s+(?= )/g, '')
|
||||
.trim();
|
||||
},
|
||||
|
||||
// :TODO: MCI for core count, e.g. os.cpus().length
|
||||
|
|
|
@ -1746,7 +1746,7 @@ function FTNMessageScanTossModule() {
|
|||
}
|
||||
return callback(null, localInfo); // continue even if we couldn't find an old match
|
||||
});
|
||||
} else if(fileIds.legnth > 1) {
|
||||
} else if(fileIds.length > 1) {
|
||||
return callback(Errors.General(`More than one existing entry for TIC in ${localInfo.areaTag} ([${fileIds.join(', ')}])`));
|
||||
} else {
|
||||
return callback(null, localInfo);
|
||||
|
|
|
@ -17,6 +17,7 @@ const {
|
|||
} = require('../../message_area.js');
|
||||
const { sortAreasOrConfs } = require('../../conf_area_util.js');
|
||||
const AnsiPrep = require('../../ansi_prep.js');
|
||||
const { wordWrapText } = require('../../word_wrap.js');
|
||||
|
||||
// deps
|
||||
const net = require('net');
|
||||
|
@ -27,9 +28,10 @@ const moment = require('moment');
|
|||
|
||||
const ModuleInfo = exports.moduleInfo = {
|
||||
name : 'Gopher',
|
||||
desc : 'Gopher Server',
|
||||
desc : 'A RFC-1436-ish Gopher Server',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.gopher.server',
|
||||
notes : 'https://tools.ietf.org/html/rfc1436',
|
||||
};
|
||||
|
||||
const Message = require('../../message.js');
|
||||
|
@ -158,7 +160,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
defaultGenerator(selectorMatch, cb) {
|
||||
this.log.debug( { selector : selectorMatch[0] }, 'Serving default content');
|
||||
|
||||
let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'startup_banner.asc');
|
||||
let bannerFile = _.get(Config(), 'contentServers.gopher.bannerFile', 'gopher_banner.asc');
|
||||
bannerFile = paths.isAbsolute(bannerFile) ? bannerFile : paths.join(__dirname, '../../../misc', bannerFile);
|
||||
fs.readFile(bannerFile, 'utf8', (err, banner) => {
|
||||
if(err) {
|
||||
|
@ -182,21 +184,43 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
}
|
||||
|
||||
prepareMessageBody(body, cb) {
|
||||
//
|
||||
// From RFC-1436:
|
||||
// "User display strings are intended to be displayed on a line on a
|
||||
// typical screen for a user's viewing pleasure. While many screens can
|
||||
// accommodate 80 character lines, some space is needed to display a tag
|
||||
// of some sort to tell the user what sort of item this is. Because of
|
||||
// this, the user display string should be kept under 70 characters in
|
||||
// length. Clients may truncate to a length convenient to them."
|
||||
//
|
||||
// Messages on BBSes however, have generally been <= 79 characters. If we
|
||||
// start wrapping earlier, things will generally be OK except:
|
||||
// * When we're doing with FTN-style quoted lines
|
||||
// * When dealing with ANSI/ASCII art
|
||||
//
|
||||
// Anyway, the spec says "should" and not MUST or even SHOULD! ...so, to
|
||||
// to follow the KISS principle: Wrap at 79.
|
||||
//
|
||||
const WordWrapColumn = 79;
|
||||
if(isAnsi(body)) {
|
||||
AnsiPrep(
|
||||
body,
|
||||
{
|
||||
cols : 79, // Gopher std. wants 70, but we'll have to deal with it.
|
||||
forceLineTerm : true, // ensure each line is term'd
|
||||
asciiMode : true, // export to ASCII
|
||||
fillLines : false, // don't fill up to |cols|
|
||||
cols : WordWrapColumn, // See notes above
|
||||
forceLineTerm : true, // Ensure each line is term'd
|
||||
asciiMode : true, // Export to ASCII
|
||||
fillLines : false, // Don't fill up to |cols|
|
||||
},
|
||||
(err, prepped) => {
|
||||
return cb(prepped || body);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return cb(cleanControlCodes(body, { all : true } ));
|
||||
const prepped = splitTextAtTerms(cleanControlCodes(body, { all : true } ) )
|
||||
.map(l => (wordWrapText(l, { width : WordWrapColumn } ).wrapped || []).join('\n'))
|
||||
.join('\n');
|
||||
|
||||
return cb(prepped);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -225,7 +249,7 @@ exports.getModule = class GopherModule extends ServerModule {
|
|||
|
||||
return message.load( { uuid : msgUuid }, err => {
|
||||
if(err) {
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existant message UUID!');
|
||||
this.log.debug( { uuid : msgUuid }, 'Attempted access to non-existent message UUID!');
|
||||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
|
@ -268,10 +292,17 @@ ${msgBody}
|
|||
return this.notFoundGenerator(selectorMatch, cb);
|
||||
}
|
||||
|
||||
return getMessageListForArea(null, areaTag, (err, msgList) => {
|
||||
const filter = {
|
||||
resultType : 'messageList',
|
||||
sort : 'messageId',
|
||||
order : 'descending', // we want newest messages first for Gopher
|
||||
};
|
||||
|
||||
return getMessageListForArea(null, areaTag, filter, (err, msgList) => {
|
||||
const response = [
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
this.makeItem(ItemTypes.InfoMessage, `Messages in ${area.name}`),
|
||||
this.makeItem(ItemTypes.InfoMessage, '(newest first)'),
|
||||
this.makeItem(ItemTypes.InfoMessage, '-'.repeat(70)),
|
||||
...msgList.map(msg => this.makeItem(
|
||||
ItemTypes.TextFile,
|
||||
|
|
|
@ -10,6 +10,10 @@ const userLogin = require('../../user_login.js').userLogin;
|
|||
const enigVersion = require('../../../package.json').version;
|
||||
const theme = require('../../theme.js');
|
||||
const stringFormat = require('../../string_format.js');
|
||||
const {
|
||||
Errors,
|
||||
ErrorReasons
|
||||
} = require('../../enig_error.js');
|
||||
|
||||
// deps
|
||||
const ssh2 = require('ssh2');
|
||||
|
@ -36,8 +40,6 @@ function SSHClient(clientConn) {
|
|||
|
||||
const self = this;
|
||||
|
||||
let loginAttempts = 0;
|
||||
|
||||
clientConn.on('authentication', function authAttempt(ctx) {
|
||||
const username = ctx.username || '';
|
||||
const password = ctx.password || '';
|
||||
|
@ -52,26 +54,56 @@ function SSHClient(clientConn) {
|
|||
return clientConn.end();
|
||||
}
|
||||
|
||||
function alreadyLoggedIn(username) {
|
||||
ctx.prompt(`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`);
|
||||
function promptAndTerm(msg) {
|
||||
if('keyboard-interactive' === ctx.method) {
|
||||
ctx.prompt(msg);
|
||||
}
|
||||
return terminateConnection();
|
||||
}
|
||||
|
||||
function accountAlreadyLoggedIn(username) {
|
||||
return promptAndTerm(`${username} is already connected to the system. Terminating connection.\n(Press any key to continue)`);
|
||||
}
|
||||
|
||||
function accountDisabled(username) {
|
||||
return promptAndTerm(`${username} is disabled.\n(Press any key to continue)`);
|
||||
}
|
||||
|
||||
function accountInactive(username) {
|
||||
return promptAndTerm(`${username} is waiting for +op activation.\n(Press any key to continue)`);
|
||||
}
|
||||
|
||||
function accountLocked(username) {
|
||||
return promptAndTerm(`${username} is locked.\n(Press any key to continue)`);
|
||||
}
|
||||
|
||||
function isSpecialHandleError(err) {
|
||||
return [ ErrorReasons.AlreadyLoggedIn, ErrorReasons.Disabled, ErrorReasons.Inactive, ErrorReasons.Locked ].includes(err.reasonCode);
|
||||
}
|
||||
|
||||
function handleSpecialError(err, username) {
|
||||
switch(err.reasonCode) {
|
||||
case ErrorReasons.AlreadyLoggedIn : return accountAlreadyLoggedIn(username);
|
||||
case ErrorReasons.Inactive : return accountInactive(username);
|
||||
case ErrorReasons.Disabled : return accountDisabled(username);
|
||||
case ErrorReasons.Locked : return accountLocked(username);
|
||||
default : return terminateConnection();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// If the system is open and |isNewUser| is true, the login
|
||||
// sequence is hijacked in order to start the applicaiton process.
|
||||
// sequence is hijacked in order to start the application process.
|
||||
//
|
||||
if(false === config.general.closedSystem && self.isNewUser) {
|
||||
return ctx.accept();
|
||||
}
|
||||
|
||||
if(username.length > 0 && password.length > 0) {
|
||||
loginAttempts += 1;
|
||||
|
||||
userLogin(self, ctx.username, ctx.password, function authResult(err) {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
if(isSpecialHandleError(err)) {
|
||||
return handleSpecialError(err, username);
|
||||
}
|
||||
|
||||
return ctx.reject(SSHClient.ValidAuthMethods);
|
||||
|
@ -92,15 +124,13 @@ function SSHClient(clientConn) {
|
|||
const interactivePrompt = { prompt : `${ctx.username}'s password: `, echo : false };
|
||||
|
||||
ctx.prompt(interactivePrompt, function retryPrompt(answers) {
|
||||
loginAttempts += 1;
|
||||
|
||||
userLogin(self, username, (answers[0] || ''), err => {
|
||||
if(err) {
|
||||
if(err.existingConn) {
|
||||
return alreadyLoggedIn(username);
|
||||
if(isSpecialHandleError(err)) {
|
||||
return handleSpecialError(err, username);
|
||||
}
|
||||
|
||||
if(loginAttempts >= config.general.loginAttempts) {
|
||||
if(Errors.BadLogin().code === err.code) {
|
||||
return terminateConnection();
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ const {
|
|||
updateMessageAreaLastReadId,
|
||||
getMessageIdNewerThanTimestampByArea
|
||||
} = require('./message_area.js');
|
||||
const stringFormat = require('./string_format.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -153,11 +153,13 @@ exports.getModule = class SetNewScanDate extends MenuModule {
|
|||
selections.push({
|
||||
conf : {
|
||||
confTag : conf.confTag,
|
||||
text : conf.conf.name, // standard
|
||||
name : conf.conf.name,
|
||||
desc : conf.conf.desc,
|
||||
},
|
||||
area : {
|
||||
areaTag : area.areaTag,
|
||||
text : area.area.name, // standard
|
||||
name : area.area.name,
|
||||
desc : area.area.desc,
|
||||
}
|
||||
|
@ -168,19 +170,21 @@ exports.getModule = class SetNewScanDate extends MenuModule {
|
|||
selections.unshift({
|
||||
conf : {
|
||||
confTag : '',
|
||||
text : 'All conferences',
|
||||
name : 'All conferences',
|
||||
desc : 'All conferences',
|
||||
},
|
||||
area : {
|
||||
areaTag : '',
|
||||
text : 'All areas',
|
||||
name : 'All areas',
|
||||
desc : 'All areas',
|
||||
}
|
||||
});
|
||||
|
||||
// Find current conf/area & move it directly under "All"
|
||||
const currConfTag = this.client.user.properties.message_conf_tag;
|
||||
const currAreaTag = this.client.user.properties.message_area_tag;
|
||||
const currConfTag = this.client.user.properties[UserProps.MessageConfTag];
|
||||
const currAreaTag = this.client.user.properties[UserProps.MessageAreaTag];
|
||||
if(currConfTag && currAreaTag) {
|
||||
const confAreaIndex = selections.findIndex( confArea => {
|
||||
return confArea.conf.confTag === currConfTag && confArea.area.areaTag === currAreaTag;
|
||||
|
@ -236,14 +240,9 @@ exports.getModule = class SetNewScanDate extends MenuModule {
|
|||
scanDateView.setText(today.format(scanDateFormat));
|
||||
|
||||
if('message' === self.target) {
|
||||
const messageSelectionsFormat = self.menuConfig.config.messageSelectionsFormat || '{conf.name} - {area.name}';
|
||||
const messageSelectionFocusFormat = self.menuConfig.config.messageSelectionFocusFormat || messageSelectionsFormat;
|
||||
|
||||
const targetSelectionView = vc.getView(MciViewIds.main.targetSelection);
|
||||
|
||||
targetSelectionView.setItems(self.targetSelections.map(targetSelection => stringFormat(messageSelectionFocusFormat, targetSelection)));
|
||||
targetSelectionView.setFocusItems(self.targetSelections.map(targetSelection => stringFormat(messageSelectionFocusFormat, targetSelection)));
|
||||
|
||||
targetSelectionView.setItems(self.targetSelections);
|
||||
targetSelectionView.setFocusItemIndex(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ exports.getModule = class ShowArtModule extends MenuModule {
|
|||
}
|
||||
|
||||
showByExtraArgs(cb) {
|
||||
this.getArtKeyValue( (err, artSpec) => {
|
||||
this.getArtKeyValue(this.config.key, (err, artSpec) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ exports.getModule = class ShowArtModule extends MenuModule {
|
|||
}
|
||||
|
||||
showByFileBaseArea(cb) {
|
||||
this.getArtKeyValue( (err, key) => {
|
||||
this.getArtKeyValue('areaTag', (err, key) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ exports.getModule = class ShowArtModule extends MenuModule {
|
|||
}
|
||||
|
||||
showByMessageConf(cb) {
|
||||
this.getArtKeyValue( (err, key) => {
|
||||
this.getArtKeyValue('confTag', (err, key) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ exports.getModule = class ShowArtModule extends MenuModule {
|
|||
}
|
||||
|
||||
showByMessageArea(cb) {
|
||||
this.getArtKeyValue( (err, key) => {
|
||||
this.getArtKeyValue('areaTag', (err, key) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
@ -133,8 +133,8 @@ exports.getModule = class ShowArtModule extends MenuModule {
|
|||
return this.displaySingleArtWithOptions(artSpec, options, cb);
|
||||
}
|
||||
|
||||
getArtKeyValue(cb) {
|
||||
const key = this.config.key;
|
||||
getArtKeyValue(defaultKey, cb) {
|
||||
const key = this.config.key || defaultKey;
|
||||
if(!_.isString(key)) {
|
||||
return cb(Errors.MissingConfig('Config option "key" is required for method "extraArgs"'));
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
'use strict';
|
||||
|
||||
const sysDb = require('./database.js').dbs.system;
|
||||
const {
|
||||
getISOTimestampString
|
||||
} = require('./database.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
|
||||
/*
|
||||
System Event Log & Stats
|
||||
|
@ -68,6 +70,7 @@ class StatLog {
|
|||
};
|
||||
}
|
||||
|
||||
// :TODO: fix spelling :)
|
||||
setNonPeristentSystemStat(statName, statValue) {
|
||||
this.systemStats[statName] = statValue;
|
||||
}
|
||||
|
@ -148,7 +151,9 @@ class StatLog {
|
|||
}
|
||||
|
||||
// the time "now" in the ISO format we use and love :)
|
||||
get now() { return moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ'); }
|
||||
get now() {
|
||||
return getISOTimestampString();
|
||||
}
|
||||
|
||||
appendSystemLogEntry(logName, logValue, keep, keepType, cb) {
|
||||
sysDb.run(
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const removeClient = require('./client_connections.js').removeClient;
|
||||
const { removeClient } = require('./client_connections.js');
|
||||
const ansiNormal = require('./ansi_term.js').normal;
|
||||
const userLogin = require('./user_login.js').userLogin;
|
||||
const { userLogin } = require('./user_login.js');
|
||||
const messageArea = require('./message_area.js');
|
||||
const { ErrorReasons } = require('./enig_error.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
@ -25,13 +27,23 @@ function login(callingMenu, formData, extraArgs, cb) {
|
|||
|
||||
userLogin(callingMenu.client, formData.value.username, formData.value.password, err => {
|
||||
if(err) {
|
||||
// login failure
|
||||
if(err.existingConn && _.has(callingMenu, 'menuConfig.config.tooNodeMenu')) {
|
||||
// already logged in with this user?
|
||||
if(ErrorReasons.AlreadyLoggedIn === err.reasonCode &&
|
||||
_.has(callingMenu, 'menuConfig.config.tooNodeMenu'))
|
||||
{
|
||||
return callingMenu.gotoMenu(callingMenu.menuConfig.config.tooNodeMenu, cb);
|
||||
} else {
|
||||
// Other error
|
||||
return callingMenu.prevMenu(cb);
|
||||
}
|
||||
|
||||
const ReasonsMenus = [
|
||||
ErrorReasons.TooMany, ErrorReasons.Disabled, ErrorReasons.Inactive, ErrorReasons.Locked
|
||||
];
|
||||
if(ReasonsMenus.includes(err.reasonCode)) {
|
||||
const menu = _.get(callingMenu, [ 'menuConfig', 'config', err.reasonCode.toLowerCase() ]);
|
||||
return menu ? callingMenu.gotoMenu(menu, cb) : logoff(callingMenu, {}, {}, cb);
|
||||
}
|
||||
|
||||
// Other error
|
||||
return callingMenu.prevMenu(cb);
|
||||
}
|
||||
|
||||
// success!
|
||||
|
@ -94,7 +106,7 @@ function reloadMenu(menu, cb) {
|
|||
|
||||
function prevConf(callingMenu, formData, extraArgs, cb) {
|
||||
const confs = messageArea.getSortedAvailMessageConferences(callingMenu.client);
|
||||
const currIndex = confs.findIndex( e => e.confTag === callingMenu.client.user.properties.message_conf_tag) || confs.length;
|
||||
const currIndex = confs.findIndex( e => e.confTag === callingMenu.client.user.properties[UserProps.MessageConfTag]) || confs.length;
|
||||
|
||||
messageArea.changeMessageConference(callingMenu.client, confs[currIndex - 1].confTag, err => {
|
||||
if(err) {
|
||||
|
@ -107,7 +119,7 @@ function prevConf(callingMenu, formData, extraArgs, cb) {
|
|||
|
||||
function nextConf(callingMenu, formData, extraArgs, cb) {
|
||||
const confs = messageArea.getSortedAvailMessageConferences(callingMenu.client);
|
||||
let currIndex = confs.findIndex( e => e.confTag === callingMenu.client.user.properties.message_conf_tag);
|
||||
let currIndex = confs.findIndex( e => e.confTag === callingMenu.client.user.properties[UserProps.MessageConfTag]);
|
||||
|
||||
if(currIndex === confs.length - 1) {
|
||||
currIndex = -1;
|
||||
|
@ -123,8 +135,8 @@ function nextConf(callingMenu, formData, extraArgs, cb) {
|
|||
}
|
||||
|
||||
function prevArea(callingMenu, formData, extraArgs, cb) {
|
||||
const areas = messageArea.getSortedAvailMessageAreasByConfTag(callingMenu.client.user.properties.message_conf_tag);
|
||||
const currIndex = areas.findIndex( e => e.areaTag === callingMenu.client.user.properties.message_area_tag) || areas.length;
|
||||
const areas = messageArea.getSortedAvailMessageAreasByConfTag(callingMenu.client.user.properties[UserProps.MessageConfTag]);
|
||||
const currIndex = areas.findIndex( e => e.areaTag === callingMenu.client.user.properties[UserProps.MessageAreaTag]) || areas.length;
|
||||
|
||||
messageArea.changeMessageArea(callingMenu.client, areas[currIndex - 1].areaTag, err => {
|
||||
if(err) {
|
||||
|
@ -136,8 +148,8 @@ function prevArea(callingMenu, formData, extraArgs, cb) {
|
|||
}
|
||||
|
||||
function nextArea(callingMenu, formData, extraArgs, cb) {
|
||||
const areas = messageArea.getSortedAvailMessageAreasByConfTag(callingMenu.client.user.properties.message_conf_tag);
|
||||
let currIndex = areas.findIndex( e => e.areaTag === callingMenu.client.user.properties.message_area_tag);
|
||||
const areas = messageArea.getSortedAvailMessageAreasByConfTag(callingMenu.client.user.properties[UserProps.MessageConfTag]);
|
||||
let currIndex = areas.findIndex( e => e.areaTag === callingMenu.client.user.properties[UserProps.MessageAreaTag]);
|
||||
|
||||
if(currIndex === areas.length - 1) {
|
||||
currIndex = -1;
|
||||
|
|
|
@ -13,7 +13,9 @@ const Errors = require('./enig_error.js').Errors;
|
|||
const ErrorReasons = require('./enig_error.js').ErrorReasons;
|
||||
const Events = require('./events.js');
|
||||
const AnsiPrep = require('./ansi_prep.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const fs = require('graceful-fs');
|
||||
const paths = require('path');
|
||||
const async = require('async');
|
||||
|
@ -38,7 +40,7 @@ function refreshThemeHelpers(theme) {
|
|||
getPasswordChar : function() {
|
||||
let pwChar = _.get(
|
||||
theme,
|
||||
'customization.defaults.general.passwordChar',
|
||||
'customization.defaults.passwordChar',
|
||||
Config().theme.passwordChar
|
||||
);
|
||||
|
||||
|
@ -427,8 +429,8 @@ function getThemeArt(options, cb) {
|
|||
// random
|
||||
//
|
||||
const config = Config();
|
||||
if(!options.themeId && _.has(options, 'client.user.properties.theme_id')) {
|
||||
options.themeId = options.client.user.properties.theme_id;
|
||||
if(!options.themeId && _.has(options, [ 'client', 'user', 'properties', UserProps.ThemeId ])) {
|
||||
options.themeId = options.client.user.properties[UserProps.ThemeId];
|
||||
} else {
|
||||
options.themeId = config.theme.default;
|
||||
}
|
||||
|
@ -682,8 +684,9 @@ function displayThemedAsset(assetSpec, client, options, cb) {
|
|||
options = {};
|
||||
}
|
||||
|
||||
if(Array.isArray(assetSpec) && _.isString(options.acsCondMember)) {
|
||||
assetSpec = client.acs.getConditionalValue(assetSpec, options.acsCondMember);
|
||||
if(Array.isArray(assetSpec)) {
|
||||
const acsCondMember = options.acsCondMember || 'art';
|
||||
assetSpec = client.acs.getConditionalValue(assetSpec, acsCondMember);
|
||||
}
|
||||
|
||||
const artAsset = asset.getArtAsset(assetSpec);
|
||||
|
|
|
@ -187,10 +187,10 @@ module.exports = class TicFileInfo {
|
|||
// send the file to be distributed and the accompanying TIC file.
|
||||
// Some File processors (Allfix) only insert a line with this
|
||||
// keyword when the file and the associated TIC file are to be
|
||||
// file routed through a third sysem instead of being processed
|
||||
// file routed through a third system instead of being processed
|
||||
// by a file processor on that system. Others always insert it.
|
||||
// Note that the To keyword may cause problems when the TIC file
|
||||
// is proecessed by software that does not recognise it and
|
||||
// is processed by software that does not recognize it and
|
||||
// passes the line "as is" to other systems.
|
||||
//
|
||||
// Example: To 292/854
|
||||
|
|
269
core/user.js
269
core/user.js
|
@ -1,11 +1,18 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const userDb = require('./database.js').dbs.user;
|
||||
const Config = require('./config.js').get;
|
||||
const userGroup = require('./user_group.js');
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const {
|
||||
Errors,
|
||||
ErrorReasons
|
||||
} = require('./enig_error.js');
|
||||
const Events = require('./events.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const Log = require('./logger.js').log;
|
||||
const StatLog = require('./stat_log.js');
|
||||
|
||||
// deps
|
||||
const crypto = require('crypto');
|
||||
|
@ -39,18 +46,31 @@ module.exports = class User {
|
|||
|
||||
static get StandardPropertyGroups() {
|
||||
return {
|
||||
password : [ 'pw_pbkdf2_salt', 'pw_pbkdf2_dk' ],
|
||||
password : [ UserProps.PassPbkdf2Salt, UserProps.PassPbkdf2Dk ],
|
||||
};
|
||||
}
|
||||
|
||||
static get AccountStatus() {
|
||||
return {
|
||||
disabled : 0,
|
||||
inactive : 1,
|
||||
active : 2,
|
||||
disabled : 0, // +op disabled
|
||||
inactive : 1, // inactive, aka requires +op approval/activation
|
||||
active : 2, // standard, active
|
||||
locked : 3, // locked out (too many bad login attempts, etc.)
|
||||
};
|
||||
}
|
||||
|
||||
static isSamePasswordSlowCompare(passBuf1, passBuf2) {
|
||||
if(passBuf1.length !== passBuf2.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let c = 0;
|
||||
for(let i = 0; i < passBuf1.length; i++) {
|
||||
c |= passBuf1[i] ^ passBuf2[i];
|
||||
}
|
||||
return 0 === c;
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
return true === this.authenticated;
|
||||
}
|
||||
|
@ -60,16 +80,21 @@ module.exports = class User {
|
|||
return false;
|
||||
}
|
||||
|
||||
return this.hasValidPassword();
|
||||
return this.hasValidPasswordProperties();
|
||||
}
|
||||
|
||||
hasValidPassword() {
|
||||
if(!this.properties || !this.properties.pw_pbkdf2_salt || !this.properties.pw_pbkdf2_dk) {
|
||||
hasValidPasswordProperties() {
|
||||
const salt = this.getProperty(UserProps.PassPbkdf2Salt);
|
||||
const dk = this.getProperty(UserProps.PassPbkdf2Dk);
|
||||
|
||||
if(!salt || !dk ||
|
||||
(salt.length !== User.PBKDF2.saltLen * 2) ||
|
||||
(dk.length !== User.PBKDF2.keyLen * 2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((this.properties.pw_pbkdf2_salt.length === User.PBKDF2.saltLen * 2) &&
|
||||
(this.properties.pw_pbkdf2_dk.length === User.PBKDF2.keyLen * 2));
|
||||
return true;
|
||||
}
|
||||
|
||||
isRoot() {
|
||||
|
@ -101,31 +126,85 @@ module.exports = class User {
|
|||
return 10; // :TODO: Is this what we want?
|
||||
}
|
||||
|
||||
processFailedLogin(userId, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
(callback) => {
|
||||
return User.getUser(userId, callback);
|
||||
},
|
||||
(tempUser, callback) => {
|
||||
return StatLog.incrementUserStat(
|
||||
tempUser,
|
||||
UserProps.FailedLoginAttempts,
|
||||
1,
|
||||
(err, failedAttempts) => {
|
||||
return callback(null, tempUser, failedAttempts);
|
||||
}
|
||||
);
|
||||
},
|
||||
(tempUser, failedAttempts, callback) => {
|
||||
const lockAccount = _.get(Config(), 'users.failedLogin.lockAccount');
|
||||
if(lockAccount > 0 && failedAttempts >= lockAccount) {
|
||||
const props = {
|
||||
[ UserProps.AccountStatus ] : User.AccountStatus.locked,
|
||||
[ UserProps.AccountLockedTs ] : StatLog.now,
|
||||
};
|
||||
if(!_.has(tempUser.properties, UserProps.AccountLockedPrevStatus)) {
|
||||
props[UserProps.AccountLockedPrevStatus] = tempUser.getProperty(UserProps.AccountStatus);
|
||||
}
|
||||
Log.info( { userId, failedAttempts }, '(Re)setting account to locked due to failed logins');
|
||||
return tempUser.persistProperties(props, callback);
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
unlockAccount(cb) {
|
||||
const prevStatus = this.getProperty(UserProps.AccountLockedPrevStatus);
|
||||
if(!prevStatus) {
|
||||
return cb(null); // nothing to do
|
||||
}
|
||||
|
||||
this.persistProperty(UserProps.AccountStatus, prevStatus, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return this.removeProperties( [ UserProps.AccountLockedPrevStatus, UserProps.AccountLockedTs ], cb);
|
||||
});
|
||||
}
|
||||
|
||||
authenticate(username, password, cb) {
|
||||
const self = this;
|
||||
const cachedInfo = {};
|
||||
const tempAuthInfo = {};
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function fetchUserId(callback) {
|
||||
// get user ID
|
||||
User.getUserIdAndName(username, (err, uid, un) => {
|
||||
cachedInfo.userId = uid;
|
||||
cachedInfo.username = un;
|
||||
tempAuthInfo.userId = uid;
|
||||
tempAuthInfo.username = un;
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getRequiredAuthProperties(callback) {
|
||||
// fetch properties required for authentication
|
||||
User.loadProperties(cachedInfo.userId, { names : User.StandardPropertyGroups.password }, (err, props) => {
|
||||
User.loadProperties(tempAuthInfo.userId, { names : User.StandardPropertyGroups.password }, (err, props) => {
|
||||
return callback(err, props);
|
||||
});
|
||||
},
|
||||
function getDkWithSalt(props, callback) {
|
||||
// get DK from stored salt and password provided
|
||||
User.generatePasswordDerivedKey(password, props.pw_pbkdf2_salt, (err, dk) => {
|
||||
return callback(err, dk, props.pw_pbkdf2_dk);
|
||||
User.generatePasswordDerivedKey(password, props[UserProps.PassPbkdf2Salt], (err, dk) => {
|
||||
return callback(err, dk, props[UserProps.PassPbkdf2Dk]);
|
||||
});
|
||||
},
|
||||
function validateAuth(passDk, propsDk, callback) {
|
||||
|
@ -135,30 +214,57 @@ module.exports = class User {
|
|||
const passDkBuf = Buffer.from(passDk, 'hex');
|
||||
const propsDkBuf = Buffer.from(propsDk, 'hex');
|
||||
|
||||
if(passDkBuf.length !== propsDkBuf.length) {
|
||||
return callback(Errors.AccessDenied('Invalid password'));
|
||||
}
|
||||
|
||||
let c = 0;
|
||||
for(let i = 0; i < passDkBuf.length; i++) {
|
||||
c |= passDkBuf[i] ^ propsDkBuf[i];
|
||||
}
|
||||
|
||||
return callback(0 === c ? null : Errors.AccessDenied('Invalid password'));
|
||||
return callback(User.isSamePasswordSlowCompare(passDkBuf, propsDkBuf) ?
|
||||
null :
|
||||
Errors.AccessDenied('Invalid password')
|
||||
);
|
||||
},
|
||||
function initProps(callback) {
|
||||
User.loadProperties(cachedInfo.userId, (err, allProps) => {
|
||||
User.loadProperties(tempAuthInfo.userId, (err, allProps) => {
|
||||
if(!err) {
|
||||
cachedInfo.properties = allProps;
|
||||
tempAuthInfo.properties = allProps;
|
||||
}
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function checkAccountStatus(callback) {
|
||||
const accountStatus = parseInt(tempAuthInfo.properties[UserProps.AccountStatus], 10);
|
||||
if(User.AccountStatus.disabled === accountStatus) {
|
||||
return callback(Errors.AccessDenied('Account disabled', ErrorReasons.Disabled));
|
||||
}
|
||||
if(User.AccountStatus.inactive === accountStatus) {
|
||||
return callback(Errors.AccessDenied('Account inactive', ErrorReasons.Inactive));
|
||||
}
|
||||
|
||||
if(User.AccountStatus.locked === accountStatus) {
|
||||
const autoUnlockMinutes = _.get(Config(), 'users.failedLogin.autoUnlockMinutes');
|
||||
const lockedTs = moment(tempAuthInfo.properties[UserProps.AccountLockedTs]);
|
||||
if(autoUnlockMinutes && lockedTs.isValid()) {
|
||||
const minutesSinceLocked = moment().diff(lockedTs, 'minutes');
|
||||
if(minutesSinceLocked >= autoUnlockMinutes) {
|
||||
// allow the login - we will clear any lock there
|
||||
Log.info(
|
||||
{ username, userId : tempAuthInfo.userId, lockedAt : lockedTs.format() },
|
||||
'Locked account will now be unlocked due to auto-unlock minutes policy'
|
||||
);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
return callback(Errors.AccessDenied('Account is locked', ErrorReasons.Locked));
|
||||
}
|
||||
|
||||
// anything else besides active is still not allowed
|
||||
if(User.AccountStatus.active !== accountStatus) {
|
||||
return callback(Errors.AccessDenied('Account is not active'));
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function initGroups(callback) {
|
||||
userGroup.getGroupsForUser(cachedInfo.userId, (err, groups) => {
|
||||
userGroup.getGroupsForUser(tempAuthInfo.userId, (err, groups) => {
|
||||
if(!err) {
|
||||
cachedInfo.groups = groups;
|
||||
tempAuthInfo.groups = groups;
|
||||
}
|
||||
|
||||
return callback(err);
|
||||
|
@ -166,15 +272,44 @@ module.exports = class User {
|
|||
}
|
||||
],
|
||||
err => {
|
||||
if(!err) {
|
||||
self.userId = cachedInfo.userId;
|
||||
self.username = cachedInfo.username;
|
||||
self.properties = cachedInfo.properties;
|
||||
self.groups = cachedInfo.groups;
|
||||
if(err) {
|
||||
//
|
||||
// If we failed login due to something besides an inactive or disabled account,
|
||||
// we need to update failure status and possibly lock the account.
|
||||
//
|
||||
// If locked already, update the lock timestamp -- ie, extend the lockout period.
|
||||
//
|
||||
if(![ErrorReasons.Disabled, ErrorReasons.Inactive].includes(err.reasonCode) && tempAuthInfo.userId) {
|
||||
self.processFailedLogin(tempAuthInfo.userId, persistErr => {
|
||||
if(persistErr) {
|
||||
Log.warn( { error : persistErr.message }, 'Failed to persist failed login information');
|
||||
}
|
||||
return cb(err); // pass along original error
|
||||
});
|
||||
} else {
|
||||
return cb(err);
|
||||
}
|
||||
} else {
|
||||
// everything checks out - load up info
|
||||
self.userId = tempAuthInfo.userId;
|
||||
self.username = tempAuthInfo.username;
|
||||
self.properties = tempAuthInfo.properties;
|
||||
self.groups = tempAuthInfo.groups;
|
||||
self.authenticated = true;
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
self.removeProperty(UserProps.FailedLoginAttempts);
|
||||
|
||||
//
|
||||
// We need to *revert* any locked status back to
|
||||
// the user's previous status & clean up props.
|
||||
//
|
||||
self.unlockAccount(unlockErr => {
|
||||
if(unlockErr) {
|
||||
Log.warn( { error : unlockErr.message }, 'Failed to unlock account');
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -190,7 +325,7 @@ module.exports = class User {
|
|||
const self = this;
|
||||
|
||||
// :TODO: set various defaults, e.g. default activation status, etc.
|
||||
self.properties.account_status = config.users.requireActivation ? User.AccountStatus.inactive : User.AccountStatus.active;
|
||||
self.properties[UserProps.AccountStatus] = config.users.requireActivation ? User.AccountStatus.inactive : User.AccountStatus.active;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
|
@ -211,7 +346,7 @@ module.exports = class User {
|
|||
|
||||
// Do not require activation for userId 1 (root/admin)
|
||||
if(User.RootUserID === self.userId) {
|
||||
self.properties.account_status = User.AccountStatus.active;
|
||||
self.properties[UserProps.AccountStatus] = User.AccountStatus.active;
|
||||
}
|
||||
|
||||
return callback(null, trans);
|
||||
|
@ -224,8 +359,8 @@ module.exports = class User {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
self.properties.pw_pbkdf2_salt = info.salt;
|
||||
self.properties.pw_pbkdf2_dk = info.dk;
|
||||
self.properties[UserProps.PassPbkdf2Salt] = info.salt;
|
||||
self.properties[UserProps.PassPbkdf2Dk] = info.dk;
|
||||
return callback(null, trans);
|
||||
});
|
||||
},
|
||||
|
@ -289,20 +424,32 @@ module.exports = class User {
|
|||
);
|
||||
}
|
||||
|
||||
static persistPropertyByUserId(userId, propName, propValue, cb) {
|
||||
userDb.run(
|
||||
`REPLACE INTO user_property (user_id, prop_name, prop_value)
|
||||
VALUES (?, ?, ?);`,
|
||||
[ userId, propName, propValue ],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err, propValue);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getProperty(propName) {
|
||||
return this.properties[propName];
|
||||
}
|
||||
|
||||
getPropertyAsNumber(propName) {
|
||||
return parseInt(this.getProperty(propName), 10);
|
||||
}
|
||||
|
||||
persistProperty(propName, propValue, cb) {
|
||||
// update live props
|
||||
this.properties[propName] = propValue;
|
||||
|
||||
userDb.run(
|
||||
`REPLACE INTO user_property (user_id, prop_name, prop_value)
|
||||
VALUES (?, ?, ?);`,
|
||||
[ this.userId, propName, propValue ],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
return User.persistPropertyByUserId(this.userId, propName, propValue, cb);
|
||||
}
|
||||
|
||||
removeProperty(propName, cb) {
|
||||
|
@ -321,6 +468,15 @@ module.exports = class User {
|
|||
);
|
||||
}
|
||||
|
||||
removeProperties(propNames, cb) {
|
||||
async.each(propNames, (name, next) => {
|
||||
return this.removeProperty(name, next);
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
persistProperties(properties, transOrDb, cb) {
|
||||
if(!_.isFunction(cb) && _.isFunction(transOrDb)) {
|
||||
cb = transOrDb;
|
||||
|
@ -360,8 +516,8 @@ module.exports = class User {
|
|||
}
|
||||
|
||||
const newProperties = {
|
||||
pw_pbkdf2_salt : info.salt,
|
||||
pw_pbkdf2_dk : info.dk,
|
||||
[ UserProps.PassPbkdf2Salt ] : info.salt,
|
||||
[ UserProps.PassPbkdf2Dk ] : info.dk,
|
||||
};
|
||||
|
||||
this.persistProperties(newProperties, err => {
|
||||
|
@ -371,8 +527,9 @@ module.exports = class User {
|
|||
}
|
||||
|
||||
getAge() {
|
||||
if(_.has(this.properties, 'birthdate')) {
|
||||
return moment().diff(this.properties.birthdate, 'years');
|
||||
const birthdate = this.getProperty(UserProps.Birthdate);
|
||||
if(birthdate) {
|
||||
return moment().diff(birthdate, 'years');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,7 +596,7 @@ module.exports = class User {
|
|||
WHERE id = (
|
||||
SELECT user_id
|
||||
FROM user_property
|
||||
WHERE prop_name='real_name' AND prop_value LIKE ?
|
||||
WHERE prop_name='${UserProps.RealName}' AND prop_value LIKE ?
|
||||
);`,
|
||||
[ realName ],
|
||||
(err, row) => {
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
const theme = require('./theme.js');
|
||||
const sysValidate = require('./system_view_validate.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
const {
|
||||
getISOTimestampString
|
||||
} = require('./database.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const assert = require('assert');
|
||||
const _ = require('lodash');
|
||||
|
@ -49,7 +55,7 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
|||
//
|
||||
// If nothing changed, we know it's OK
|
||||
//
|
||||
if(self.client.user.properties.email_address.toLowerCase() === data.toLowerCase()) {
|
||||
if(self.client.user.properties[UserProps.EmailAddress].toLowerCase() === data.toLowerCase()) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
|
@ -101,15 +107,15 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
|||
assert(formData.value.password === formData.value.passwordConfirm);
|
||||
|
||||
const newProperties = {
|
||||
real_name : formData.value.realName,
|
||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(),
|
||||
sex : formData.value.sex,
|
||||
location : formData.value.location,
|
||||
affiliation : formData.value.affils,
|
||||
email_address : formData.value.email,
|
||||
web_address : formData.value.web,
|
||||
term_height : formData.value.termHeight.toString(),
|
||||
theme_id : self.availThemeInfo[formData.value.theme].themeId,
|
||||
[ UserProps.RealName ] : formData.value.realName,
|
||||
[ UserProps.Birthdate ] : getISOTimestampString(formData.value.birthdate),
|
||||
[ UserProps.Sex ] : formData.value.sex,
|
||||
[ UserProps.Location ] : formData.value.location,
|
||||
[ UserProps.Affiliations ] : formData.value.affils,
|
||||
[ UserProps.EmailAddress ] : formData.value.email,
|
||||
[ UserProps.WebAddress ] : formData.value.web,
|
||||
[ UserProps.TermHeight ] : formData.value.termHeight.toString(),
|
||||
[ UserProps.ThemeId ] : self.availThemeInfo[formData.value.theme].themeId,
|
||||
};
|
||||
|
||||
// runtime set theme
|
||||
|
@ -176,22 +182,22 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
|||
}), 'name');
|
||||
|
||||
currentThemeIdIndex = Math.max(0, _.findIndex(self.availThemeInfo, function cmp(ti) {
|
||||
return ti.themeId === self.client.user.properties.theme_id;
|
||||
return ti.themeId === self.client.user.properties[UserProps.ThemeId];
|
||||
}));
|
||||
|
||||
callback(null);
|
||||
},
|
||||
function populateViews(callback) {
|
||||
var user = self.client.user;
|
||||
const user = self.client.user;
|
||||
|
||||
self.setViewText('menu', MciCodeIds.RealName, user.properties.real_name);
|
||||
self.setViewText('menu', MciCodeIds.BirthDate, moment(user.properties.birthdate).format('YYYYMMDD'));
|
||||
self.setViewText('menu', MciCodeIds.Sex, user.properties.sex);
|
||||
self.setViewText('menu', MciCodeIds.Loc, user.properties.location);
|
||||
self.setViewText('menu', MciCodeIds.Affils, user.properties.affiliation);
|
||||
self.setViewText('menu', MciCodeIds.Email, user.properties.email_address);
|
||||
self.setViewText('menu', MciCodeIds.Web, user.properties.web_address);
|
||||
self.setViewText('menu', MciCodeIds.TermHeight, user.properties.term_height.toString());
|
||||
self.setViewText('menu', MciCodeIds.RealName, user.properties[UserProps.RealName]);
|
||||
self.setViewText('menu', MciCodeIds.BirthDate, moment(user.properties[UserProps.Birthdate]).format('YYYYMMDD'));
|
||||
self.setViewText('menu', MciCodeIds.Sex, user.properties[UserProps.Sex]);
|
||||
self.setViewText('menu', MciCodeIds.Loc, user.properties[UserProps.Location]);
|
||||
self.setViewText('menu', MciCodeIds.Affils, user.properties[UserProps.Affiliations]);
|
||||
self.setViewText('menu', MciCodeIds.Email, user.properties[UserProps.EmailAddress]);
|
||||
self.setViewText('menu', MciCodeIds.Web, user.properties[UserProps.WebAddress]);
|
||||
self.setViewText('menu', MciCodeIds.TermHeight, user.properties[UserProps.TermHeight].toString());
|
||||
|
||||
|
||||
var themeView = self.getView(MciCodeIds.Theme);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
const { MenuModule } = require('./menu_module.js');
|
||||
const { getUserList } = require('./user.js');
|
||||
const { Errors } = require('./enig_error.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const moment = require('moment');
|
||||
|
@ -44,7 +45,7 @@ exports.getModule = class UserListModule extends MenuModule {
|
|||
}
|
||||
|
||||
const fetchOpts = {
|
||||
properties : [ 'real_name', 'location', 'affiliation', 'last_login_timestamp' ],
|
||||
properties : [ UserProps.RealName, UserProps.Location, UserProps.Affiliations, UserProps.LastLoginTs ],
|
||||
propsCamelCase : true, // e.g. real_name -> realName
|
||||
};
|
||||
getUserList(fetchOpts, (err, userList) => {
|
||||
|
|
|
@ -8,34 +8,44 @@ const StatLog = require('./stat_log.js');
|
|||
const logger = require('./logger.js');
|
||||
const Events = require('./events.js');
|
||||
const Config = require('./config.js').get;
|
||||
const {
|
||||
Errors,
|
||||
ErrorReasons
|
||||
} = require('./enig_error.js');
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
|
||||
exports.userLogin = userLogin;
|
||||
|
||||
function userLogin(client, username, password, cb) {
|
||||
client.user.authenticate(username, password, function authenticated(err) {
|
||||
client.user.authenticate(username, password, err => {
|
||||
const config = Config();
|
||||
|
||||
if(err) {
|
||||
client.user.sessionFailedLoginAttempts = _.get(client.user, 'sessionFailedLoginAttempts', 0) + 1;
|
||||
const disconnect = config.users.failedLogin.disconnect;
|
||||
if(disconnect > 0 && client.user.sessionFailedLoginAttempts >= disconnect) {
|
||||
err = Errors.BadLogin('To many failed login attempts', ErrorReasons.TooMany);
|
||||
}
|
||||
|
||||
client.log.info( { username : username, error : err.message }, 'Failed login attempt');
|
||||
|
||||
// :TODO: if username exists, record failed login attempt to properties
|
||||
// :TODO: check Config max failed logon attempts/etc. - set err.maxAttempts = true
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
const user = client.user;
|
||||
|
||||
const user = client.user;
|
||||
|
||||
// Good login; reset any failed attempts
|
||||
delete user.sessionFailedLoginAttempts;
|
||||
|
||||
//
|
||||
// Ensure this user is not already logged in.
|
||||
// Loop through active connections -- which includes the current --
|
||||
// and check for matching user ID. If the count is > 1, disallow.
|
||||
//
|
||||
let existingClientConnection;
|
||||
clientConnections.forEach(function connEntry(cc) {
|
||||
if(cc.user !== user && cc.user.userId === user.userId) {
|
||||
existingClientConnection = cc;
|
||||
}
|
||||
const existingClientConnection = clientConnections.find(cc => {
|
||||
return user !== cc.user && // not current connection
|
||||
user.userId === cc.user.userId; // ...but same user
|
||||
});
|
||||
|
||||
if(existingClientConnection) {
|
||||
|
@ -48,12 +58,10 @@ function userLogin(client, username, password, cb) {
|
|||
'Already logged in'
|
||||
);
|
||||
|
||||
const existingConnError = new Error('Already logged in as supplied user');
|
||||
existingConnError.existingConn = true;
|
||||
|
||||
// :TODO: We should use EnigError & pass existing connection as second param
|
||||
|
||||
return cb(existingConnError);
|
||||
return cb(Errors.BadLogin(
|
||||
`User ${user.username} already logged in.`,
|
||||
ErrorReasons.AlreadyLoggedIn
|
||||
));
|
||||
}
|
||||
|
||||
// update client logger with addition of username
|
||||
|
@ -67,24 +75,24 @@ function userLogin(client, username, password, cb) {
|
|||
client.log.info('Successful login');
|
||||
|
||||
// User's unique session identifier is the same as the connection itself
|
||||
user.sessionId = client.session.uniqueId; // convienence
|
||||
user.sessionId = client.session.uniqueId; // convenience
|
||||
|
||||
Events.emit(Events.getSystemEvents().UserLogin, { user } );
|
||||
|
||||
async.parallel(
|
||||
[
|
||||
function setTheme(callback) {
|
||||
setClientTheme(client, user.properties.theme_id);
|
||||
setClientTheme(client, user.properties[UserProps.ThemeId]);
|
||||
return callback(null);
|
||||
},
|
||||
function updateSystemLoginCount(callback) {
|
||||
return StatLog.incrementSystemStat('login_count', 1, callback);
|
||||
return StatLog.incrementSystemStat('login_count', 1, callback); // :TODO: create system_property.js
|
||||
},
|
||||
function recordLastLogin(callback) {
|
||||
return StatLog.setUserStat(user, 'last_login_timestamp', StatLog.now, callback);
|
||||
return StatLog.setUserStat(user, UserProps.LastLoginTs, StatLog.now, callback);
|
||||
},
|
||||
function updateUserLoginCount(callback) {
|
||||
return StatLog.incrementUserStat(user, 'login_count', 1, callback);
|
||||
return StatLog.incrementUserStat(user, UserProps.LoginCount, 1, callback);
|
||||
},
|
||||
function recordLoginHistory(callback) {
|
||||
const loginHistoryMax = Config().statLog.systemEvents.loginHistoryMax;
|
||||
|
|
53
core/user_property.js
Normal file
53
core/user_property.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
//
|
||||
// Common user properties used throughout the system.
|
||||
//
|
||||
// This IS NOT a full list. For example, custom modules
|
||||
// can utilize their own properties as well!
|
||||
//
|
||||
module.exports = {
|
||||
PassPbkdf2Salt : 'pw_pbkdf2_salt',
|
||||
PassPbkdf2Dk : 'pw_pbkdf2_dk',
|
||||
|
||||
AccountStatus : 'account_status', // See User.AccountStatus enum
|
||||
|
||||
RealName : 'real_name',
|
||||
Sex : 'sex',
|
||||
Birthdate : 'birthdate',
|
||||
Location : 'location',
|
||||
Affiliations : 'affiliation',
|
||||
EmailAddress : 'email_address',
|
||||
WebAddress : 'web_address',
|
||||
TermHeight : 'term_height',
|
||||
TermWidth : 'term_width',
|
||||
ThemeId : 'theme_id',
|
||||
AccountCreated : 'account_created',
|
||||
LastLoginTs : 'last_login_timestamp',
|
||||
LoginCount : 'login_count',
|
||||
UserComment : 'user_comment', // NYI
|
||||
|
||||
DownloadQueue : 'dl_queue', // download_queue.js
|
||||
|
||||
FailedLoginAttempts : 'failed_login_attempts',
|
||||
AccountLockedTs : 'account_locked_timestamp',
|
||||
AccountLockedPrevStatus : 'account_locked_prev_status', // previous account status before lock out
|
||||
|
||||
EmailPwResetToken : 'email_password_reset_token',
|
||||
EmailPwResetTokenTs : 'email_password_reset_token_ts',
|
||||
|
||||
FileAreaTag : 'file_area_tag',
|
||||
FileBaseFilters : 'file_base_filters',
|
||||
FileBaseFilterActiveUuid : 'file_base_filter_active_uuid',
|
||||
FileBaseLastViewedId : 'user_file_base_last_viewed',
|
||||
FileDlTotalCount : 'dl_total_count',
|
||||
FileUlTotalCount : 'ul_total_count',
|
||||
FileDlTotalBytes : 'dl_total_bytes',
|
||||
FileUlTotalBytes : 'ul_total_bytes',
|
||||
|
||||
MessageConfTag : 'message_conf_tag',
|
||||
MessageAreaTag : 'message_area_tag',
|
||||
MessagePostCount : 'post_count',
|
||||
};
|
||||
|
|
@ -10,6 +10,7 @@ const User = require('./user.js');
|
|||
const userDb = require('./database.js').dbs.user;
|
||||
const getISOTimestampString = require('./database.js').getISOTimestampString;
|
||||
const Log = require('./logger.js').log;
|
||||
const UserProps = require('./user_property.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
|
@ -17,6 +18,7 @@ const crypto = require('crypto');
|
|||
const fs = require('graceful-fs');
|
||||
const url = require('url');
|
||||
const querystring = require('querystring');
|
||||
const _ = require('lodash');
|
||||
|
||||
const PW_RESET_EMAIL_TEXT_TEMPLATE_DEFAULT =
|
||||
`%USERNAME%:
|
||||
|
@ -57,7 +59,7 @@ class WebPasswordReset {
|
|||
}
|
||||
|
||||
User.getUser(userId, (err, user) => {
|
||||
if(err || !user.properties.email_address) {
|
||||
if(err || !user.properties[UserProps.EmailAddress]) {
|
||||
return callback(Errors.DoesNotExist('No email address associated with this user'));
|
||||
}
|
||||
|
||||
|
@ -77,8 +79,8 @@ class WebPasswordReset {
|
|||
token = token.toString('hex');
|
||||
|
||||
const newProperties = {
|
||||
email_password_reset_token : token,
|
||||
email_password_reset_token_ts : getISOTimestampString(),
|
||||
[ UserProps.EmailPwResetToken ] : token,
|
||||
[ UserProps.EmailPwResetTokenTs ] : getISOTimestampString(),
|
||||
};
|
||||
|
||||
// we simply place the reset token in the user's properties
|
||||
|
@ -103,13 +105,13 @@ class WebPasswordReset {
|
|||
function buildAndSendEmail(user, textTemplate, htmlTemplate, callback) {
|
||||
const sendMail = require('./email.js').sendMail;
|
||||
|
||||
const resetUrl = webServer.instance.buildUrl(`/reset_password?token=${user.properties.email_password_reset_token}`);
|
||||
const resetUrl = webServer.instance.buildUrl(`/reset_password?token=${user.properties[UserProps.EmailPwResetToken]}`);
|
||||
|
||||
function replaceTokens(s) {
|
||||
return s
|
||||
.replace(/%BOARDNAME%/g, Config().general.boardName)
|
||||
.replace(/%USERNAME%/g, user.username)
|
||||
.replace(/%TOKEN%/g, user.properties.email_password_reset_token)
|
||||
.replace(/%TOKEN%/g, user.properties[UserProps.EmailPwResetToken])
|
||||
.replace(/%RESET_URL%/g, resetUrl)
|
||||
;
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ class WebPasswordReset {
|
|||
}
|
||||
|
||||
const message = {
|
||||
to : `${user.properties.display_name||user.username} <${user.properties.email_address}>`,
|
||||
to : `${user.properties[UserProps.RealName]||user.username} <${user.properties[UserProps.EmailAddress]}>`,
|
||||
// from will be filled in
|
||||
subject : 'Forgot Password',
|
||||
text : textTemplate,
|
||||
|
@ -283,8 +285,15 @@ class WebPasswordReset {
|
|||
}
|
||||
|
||||
// delete assoc properties - no need to wait for completion
|
||||
user.removeProperty('email_password_reset_token');
|
||||
user.removeProperty('email_password_reset_token_ts');
|
||||
user.removeProperties([ UserProps.EmailPwResetToken, UserProps.EmailPwResetTokenTs ]);
|
||||
|
||||
if(true === _.get(config, 'users.unlockAtEmailPwReset')) {
|
||||
Log.info(
|
||||
{ username : user.username, userId : user.userId },
|
||||
'Remove any lock on account due to password reset policy'
|
||||
);
|
||||
user.unlockAccount( () => { /* dummy */ } );
|
||||
}
|
||||
|
||||
resp.writeHead(200);
|
||||
return resp.end('Password changed successfully');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue