Merge branch '459-activitypub-integration' of github.com:NuSkooler/enigma-bbs into 459-activitypub-integration
This commit is contained in:
commit
77b0e6dd23
|
@ -13,6 +13,7 @@ This document attempts to track **major** changes and additions in ENiGMA½. For
|
||||||
* New users now have randomly generated avatars assigned to them that can be served up via the new System General [Web Handler](/docs/_docs/servers/contentservers/web-handlers.md).
|
* New users now have randomly generated avatars assigned to them that can be served up via the new System General [Web Handler](/docs/_docs/servers/contentservers/web-handlers.md).
|
||||||
* CombatNet has shut down, so the module (`combatnet.js`) has been removed.
|
* CombatNet has shut down, so the module (`combatnet.js`) has been removed.
|
||||||
* New `NewUserPrePersist` system event available to developers to 'hook' account creation and add their own properties/etc.
|
* New `NewUserPrePersist` system event available to developers to 'hook' account creation and add their own properties/etc.
|
||||||
|
* The signature for `viewValidationListener`'s callback has changed: It is now `(err, newFocusId)`. To ignore a validation error, implementors can simply call the callback with a `null` error, else they should forward it on.
|
||||||
|
|
||||||
## 0.0.13-beta
|
## 0.0.13-beta
|
||||||
* **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information.
|
* **Note for contributors**: ENiGMA has switched to [Prettier](https://prettier.io) for formatting/style. Please see [CONTRIBUTING](CONTRIBUTING.md) and the Prettier website for more information.
|
||||||
|
|
Binary file not shown.
|
@ -400,6 +400,7 @@
|
||||||
TL1: { width: 19, textOverflow: "..." }
|
TL1: { width: 19, textOverflow: "..." }
|
||||||
ET2: { width: 19, textOverflow: "..." }
|
ET2: { width: 19, textOverflow: "..." }
|
||||||
ET3: { width: 19, textOverflow: "..." }
|
ET3: { width: 19, textOverflow: "..." }
|
||||||
|
ET4: { width: 21, textOverflow: "..." }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1: {
|
1: {
|
||||||
|
@ -773,6 +774,7 @@
|
||||||
TL1: { width: 19, textOverflow: "..." }
|
TL1: { width: 19, textOverflow: "..." }
|
||||||
ET2: { width: 19, textOverflow: "..." }
|
ET2: { width: 19, textOverflow: "..." }
|
||||||
ET3: { width: 19, textOverflow: "..." }
|
ET3: { width: 19, textOverflow: "..." }
|
||||||
|
ET4: { width: 21, textOverflow: "..." }
|
||||||
//TL4: { width: 25 }
|
//TL4: { width: 25 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,7 @@ module.exports = class Activity extends ActivityPubObject {
|
||||||
return postJson(actorUrl, activityJson, reqOpts, cb);
|
return postJson(actorUrl, activityJson, reqOpts, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: we need dp/support a bit more here...
|
||||||
recipientIds() {
|
recipientIds() {
|
||||||
const ids = [];
|
const ids = [];
|
||||||
|
|
||||||
|
|
|
@ -80,13 +80,13 @@ exports.getModule = class BBSListModule extends MenuModule {
|
||||||
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
||||||
if (errMsgView) {
|
if (errMsgView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.friendlyText);
|
||||||
} else {
|
} else {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(err, null);
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -413,6 +413,18 @@ module.exports = () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// General ActivityPub integration configuration
|
||||||
|
activityPub: {
|
||||||
|
// Mimics Mastodon max 500 characters for *outgoing* Notes
|
||||||
|
// (messages destined for ActivityPub); This is a soft limit;
|
||||||
|
// Implementations including Mastodon should still display
|
||||||
|
// longer messages, but this keeps us as a "good citizen"
|
||||||
|
autoSignatures: false,
|
||||||
|
|
||||||
|
// by default, don't include auto-signatures in AP outgoing
|
||||||
|
maxMessageLength: 500,
|
||||||
|
},
|
||||||
|
|
||||||
infoExtractUtils: {
|
infoExtractUtils: {
|
||||||
Exiftool2Desc: {
|
Exiftool2Desc: {
|
||||||
cmd: `${__dirname}/../util/exiftool2desc.js`, // ensure chmod +x
|
cmd: `${__dirname}/../util/exiftool2desc.js`, // ensure chmod +x
|
||||||
|
|
|
@ -61,6 +61,8 @@ exports.Errors = {
|
||||||
new EnigError('Bad or missing form data', -32016, reason, reasonCode),
|
new EnigError('Bad or missing form data', -32016, reason, reasonCode),
|
||||||
Duplicate: (reason, reasonCode) =>
|
Duplicate: (reason, reasonCode) =>
|
||||||
new EnigError('Duplicate', -32017, reason, reasonCode),
|
new EnigError('Duplicate', -32017, reason, reasonCode),
|
||||||
|
ValidationFailed: (reason, reasonCode) =>
|
||||||
|
new EnigError('Validation failed', -32018, reason, reasonCode),
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ErrorReasons = {
|
exports.ErrorReasons = {
|
||||||
|
@ -76,4 +78,11 @@ exports.ErrorReasons = {
|
||||||
Locked: 'LOCKED',
|
Locked: 'LOCKED',
|
||||||
NotAllowed: 'NOTALLOWED',
|
NotAllowed: 'NOTALLOWED',
|
||||||
Invalid2FA: 'INVALID2FA',
|
Invalid2FA: 'INVALID2FA',
|
||||||
|
|
||||||
|
ValueTooShort: 'VALUE_TOO_SHORT',
|
||||||
|
ValueTooLong: 'VALUE_TOO_LONG',
|
||||||
|
ValueInvalid: 'VALUE_INVALID',
|
||||||
|
|
||||||
|
NotAvailable: 'NOT_AVAILABLE',
|
||||||
|
DoesNotExist: 'EEXIST',
|
||||||
};
|
};
|
||||||
|
|
|
@ -146,18 +146,17 @@ exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
const errorView = this.viewControllers.editor.getView(
|
const errorView = this.viewControllers.editor.getView(
|
||||||
MciViewIds.editor.error
|
MciViewIds.editor.error
|
||||||
);
|
);
|
||||||
let newFocusId;
|
|
||||||
|
|
||||||
if (errorView) {
|
if (errorView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errorView.setText(err.message);
|
errorView.setText(err.friendlyText);
|
||||||
err.view.clearText(); // clear out the invalid data
|
err.view.clearText(); // clear out the invalid data
|
||||||
} else {
|
} else {
|
||||||
errorView.clearText();
|
errorView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(newFocusId);
|
return cb(err, null);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
52
core/fse.js
52
core/fse.js
|
@ -18,6 +18,7 @@ const { stripMciColorCodes, controlCodesToAnsi } = require('./color_codes.js');
|
||||||
const Config = require('./config.js').get;
|
const Config = require('./config.js').get;
|
||||||
const {
|
const {
|
||||||
getAddressedToInfo,
|
getAddressedToInfo,
|
||||||
|
messageInfoFromAddressedToInfo,
|
||||||
setExternalAddressedToInfo,
|
setExternalAddressedToInfo,
|
||||||
copyExternalAddressedToInfo,
|
copyExternalAddressedToInfo,
|
||||||
} = require('./mail_util.js');
|
} = require('./mail_util.js');
|
||||||
|
@ -37,6 +38,7 @@ const fse = require('fs-extra');
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const sanatizeFilename = require('sanitize-filename');
|
const sanatizeFilename = require('sanitize-filename');
|
||||||
|
const { ErrorReasons } = require('./enig_error.js');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name: 'Full Screen Editor (FSE)',
|
name: 'Full Screen Editor (FSE)',
|
||||||
|
@ -164,23 +166,35 @@ exports.FullScreenEditorModule =
|
||||||
//
|
//
|
||||||
// Validation stuff
|
// Validation stuff
|
||||||
//
|
//
|
||||||
viewValidationListener: function (err, cb) {
|
viewValidationListener: (err, cb) => {
|
||||||
var errMsgView = self.viewControllers.header.getView(
|
if (
|
||||||
|
err &&
|
||||||
|
err.view.getId() === MciViewIds.header.subject &&
|
||||||
|
err.reasonCode === ErrorReasons.ValueTooShort
|
||||||
|
) {
|
||||||
|
// Ignore validation errors if this is the subject field
|
||||||
|
// and it's optional
|
||||||
|
const toView = this.getView('header', MciViewIds.header.to);
|
||||||
|
const msgInfo = messageInfoFromAddressedToInfo(
|
||||||
|
getAddressedToInfo(toView.getData())
|
||||||
|
);
|
||||||
|
if (true === msgInfo.subjectOptional) {
|
||||||
|
return cb(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const errMsgView = this.viewControllers.header.getView(
|
||||||
MciViewIds.header.errorMsg
|
MciViewIds.header.errorMsg
|
||||||
);
|
);
|
||||||
var newFocusViewId;
|
|
||||||
if (errMsgView) {
|
if (errMsgView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.friendlyText);
|
||||||
|
|
||||||
if (MciViewIds.header.subject === err.view.getId()) {
|
|
||||||
// :TODO: for "area" mode, should probably just bail if this is emtpy (e.g. cancel)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(newFocusViewId);
|
|
||||||
|
return cb(err, null);
|
||||||
},
|
},
|
||||||
headerSubmit: function (formData, extraArgs, cb) {
|
headerSubmit: function (formData, extraArgs, cb) {
|
||||||
self.switchToBody();
|
self.switchToBody();
|
||||||
|
@ -424,10 +438,15 @@ exports.FullScreenEditorModule =
|
||||||
//
|
//
|
||||||
// Append auto-signature, if enabled for the area & the user has one
|
// Append auto-signature, if enabled for the area & the user has one
|
||||||
//
|
//
|
||||||
if (false != area.autoSignatures) {
|
const msgInfo = messageInfoFromAddressedToInfo(
|
||||||
const sig = this.client.user.getProperty(UserProps.AutoSignature);
|
getAddressedToInfo(headerValues.to)
|
||||||
if (sig) {
|
);
|
||||||
messageBody += `\r\n-- \r\n${sig}`;
|
if (false !== msgInfo.autoSignatures) {
|
||||||
|
if (false !== area.autoSignatures) {
|
||||||
|
const sig = this.client.user.getProperty(UserProps.AutoSignature);
|
||||||
|
if (sig) {
|
||||||
|
messageBody += `\r\n-- \r\n${sig}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1391,6 +1410,13 @@ exports.FullScreenEditorModule =
|
||||||
}
|
}
|
||||||
|
|
||||||
switchToBody() {
|
switchToBody() {
|
||||||
|
const to = this.getView('header', MciViewIds.header.to).getData();
|
||||||
|
const msgInfo = messageInfoFromAddressedToInfo(getAddressedToInfo(to));
|
||||||
|
if (msgInfo.maxMessageLength > 0) {
|
||||||
|
const bodyView = this.getView('body', MciViewIds.body.message);
|
||||||
|
bodyView.maxLength = msgInfo.maxMessageLength;
|
||||||
|
}
|
||||||
|
|
||||||
this.viewControllers.header.setFocus(false);
|
this.viewControllers.header.setFocus(false);
|
||||||
this.viewControllers.body.switchFocus(1);
|
this.viewControllers.body.switchFocus(1);
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,15 @@ const EnigmaAssert = require('./enigma_assert.js');
|
||||||
const Address = require('./ftn_address.js');
|
const Address = require('./ftn_address.js');
|
||||||
const MessageConst = require('./message_const');
|
const MessageConst = require('./message_const');
|
||||||
const { getQuotePrefix } = require('./ftn_util');
|
const { getQuotePrefix } = require('./ftn_util');
|
||||||
|
const Config = require('./config').get;
|
||||||
|
|
||||||
|
// deps
|
||||||
|
const { get } = require('lodash');
|
||||||
|
|
||||||
exports.getAddressedToInfo = getAddressedToInfo;
|
exports.getAddressedToInfo = getAddressedToInfo;
|
||||||
exports.setExternalAddressedToInfo = setExternalAddressedToInfo;
|
exports.setExternalAddressedToInfo = setExternalAddressedToInfo;
|
||||||
exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo;
|
exports.copyExternalAddressedToInfo = copyExternalAddressedToInfo;
|
||||||
|
exports.messageInfoFromAddressedToInfo = messageInfoFromAddressedToInfo;
|
||||||
exports.getQuotePrefixFromName = getQuotePrefixFromName;
|
exports.getQuotePrefixFromName = getQuotePrefixFromName;
|
||||||
|
|
||||||
const EMAIL_REGEX =
|
const EMAIL_REGEX =
|
||||||
|
@ -148,6 +153,24 @@ function copyExternalAddressedToInfo(fromMessage, toMessage) {
|
||||||
toMessage.setExternalFlavor(fromMessage.meta.System[sm.ExternalFlavor]);
|
toMessage.setExternalFlavor(fromMessage.meta.System[sm.ExternalFlavor]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function messageInfoFromAddressedToInfo(addressInfo) {
|
||||||
|
switch (addressInfo.flavor) {
|
||||||
|
case MessageConst.AddressFlavor.ActivityPub: {
|
||||||
|
const config = Config();
|
||||||
|
const maxMessageLength = get(config, 'activityPub.maxMessageLength', 500);
|
||||||
|
const autoSignatures = get(config, 'activityPub.autoSignatures', false);
|
||||||
|
|
||||||
|
// Additionally, it's ot necessary to supply a subject
|
||||||
|
// (aka summary) with a 'Note' Activity
|
||||||
|
return { subjectOptional: true, maxMessageLength, autoSignatures };
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// autoSignatures: null = varies by additional config
|
||||||
|
return { subjectOptional: false, maxMessageLength: 0, autoSignatures: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getQuotePrefixFromName(name) {
|
function getQuotePrefixFromName(name) {
|
||||||
const addrInfo = getAddressedToInfo(name);
|
const addrInfo = getAddressedToInfo(name);
|
||||||
return getQuotePrefix(addrInfo.name || name);
|
return getQuotePrefix(addrInfo.name || name);
|
||||||
|
|
|
@ -113,6 +113,7 @@ function MultiLineEditTextView(options) {
|
||||||
this.textLines = [];
|
this.textLines = [];
|
||||||
this.topVisibleIndex = 0;
|
this.topVisibleIndex = 0;
|
||||||
this.mode = options.mode || 'edit'; // edit | preview | read-only
|
this.mode = options.mode || 'edit'; // edit | preview | read-only
|
||||||
|
this.maxLength = 0; // no max by default
|
||||||
|
|
||||||
if ('preview' === this.mode) {
|
if ('preview' === this.mode) {
|
||||||
this.autoScroll = options.autoScroll || true;
|
this.autoScroll = options.autoScroll || true;
|
||||||
|
@ -317,6 +318,15 @@ function MultiLineEditTextView(options) {
|
||||||
return text;
|
return text;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getCharacterLength = function () {
|
||||||
|
// :TODO: FSE needs re-write anyway, but this should just be known all the time vs calc. Too much of a mess right now...
|
||||||
|
let len = 0;
|
||||||
|
this.textLines.forEach(tl => {
|
||||||
|
len += tl.text.length;
|
||||||
|
});
|
||||||
|
return len;
|
||||||
|
};
|
||||||
|
|
||||||
this.replaceCharacterInText = function (c, index, col) {
|
this.replaceCharacterInText = function (c, index, col) {
|
||||||
self.textLines[index].text = strUtil.replaceAt(
|
self.textLines[index].text = strUtil.replaceAt(
|
||||||
self.textLines[index].text,
|
self.textLines[index].text,
|
||||||
|
@ -664,6 +674,10 @@ function MultiLineEditTextView(options) {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.keyPressCharacter = function (c) {
|
this.keyPressCharacter = function (c) {
|
||||||
|
if (this.maxLength > 0 && this.getCharacterLength() + 1 >= this.maxLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var index = self.getTextLinesIndex();
|
var index = self.getTextLinesIndex();
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -1170,6 +1184,12 @@ MultiLineEditTextView.prototype.setPropertyValue = function (propName, value) {
|
||||||
this.specialKeyMap.next = this.specialKeyMap.next || [];
|
this.specialKeyMap.next = this.specialKeyMap.next || [];
|
||||||
this.specialKeyMap.next.push('tab');
|
this.specialKeyMap.next.push('tab');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'maxLength':
|
||||||
|
if (_.isNumber(value)) {
|
||||||
|
this.maxLength = value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiLineEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);
|
MultiLineEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||||
|
|
|
@ -49,10 +49,10 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
||||||
|
|
||||||
viewValidationListener: function (err, cb) {
|
viewValidationListener: function (err, cb) {
|
||||||
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
||||||
let newFocusId;
|
|
||||||
|
|
||||||
|
let newFocusId;
|
||||||
if (err) {
|
if (err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.friendlyText);
|
||||||
err.view.clearText();
|
err.view.clearText();
|
||||||
|
|
||||||
if (err.view.getId() === MciViewIds.confirm) {
|
if (err.view.getId() === MciViewIds.confirm) {
|
||||||
|
@ -65,7 +65,7 @@ exports.getModule = class NewUserAppModule extends MenuModule {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(newFocusId);
|
return cb(err, newFocusId);
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const User = require('./user.js');
|
const User = require('./user');
|
||||||
const Config = require('./config.js').get;
|
const Config = require('./config').get;
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger').log;
|
||||||
const { getAddressedToInfo } = require('./mail_util.js');
|
const { getAddressedToInfo } = require('./mail_util');
|
||||||
const Message = require('./message.js');
|
const Message = require('./message');
|
||||||
|
const { Errors, ErrorReasons } = require('./enig_error'); // note: Only use ValidationFailed in this module!
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const fs = require('graceful-fs');
|
const fs = require('graceful-fs');
|
||||||
|
@ -22,36 +23,66 @@ exports.validateBirthdate = validateBirthdate;
|
||||||
exports.validatePasswordSpec = validatePasswordSpec;
|
exports.validatePasswordSpec = validatePasswordSpec;
|
||||||
|
|
||||||
function validateNonEmpty(data, cb) {
|
function validateNonEmpty(data, cb) {
|
||||||
return cb(data && data.length > 0 ? null : new Error('Field cannot be empty'));
|
return cb(
|
||||||
|
data && data.length > 0
|
||||||
|
? null
|
||||||
|
: Errors.ValidationFailed('Field cannot be empty', ErrorReasons.ValueTooShort)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateMessageSubject(data, cb) {
|
function validateMessageSubject(data, cb) {
|
||||||
return cb(data && data.length > 1 ? null : new Error('Subject too short'));
|
return cb(
|
||||||
|
data && data.length > 1
|
||||||
|
? null
|
||||||
|
: Errors.ValidationFailed('Subject too short', ErrorReasons.ValueTooShort)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateUserNameAvail(data, cb) {
|
function validateUserNameAvail(data, cb) {
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if (!data || data.length < config.users.usernameMin) {
|
if (!data || data.length < config.users.usernameMin) {
|
||||||
cb(new Error('Username too short'));
|
cb(Errors.ValidationFailed('Username too short', ErrorReasons.ValueTooShort));
|
||||||
} else if (data.length > config.users.usernameMax) {
|
} else if (data.length > config.users.usernameMax) {
|
||||||
// generally should be unreached due to view restraints
|
// generally should be unreached due to view restraints
|
||||||
return cb(new Error('Username too long'));
|
return cb(
|
||||||
|
Errors.ValidationFailed('Username too long', ErrorReasons.ValueTooLong)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const usernameRegExp = new RegExp(config.users.usernamePattern);
|
const usernameRegExp = new RegExp(config.users.usernamePattern);
|
||||||
const invalidNames = config.users.newUserNames + config.users.badUserNames;
|
const invalidNames = config.users.newUserNames + config.users.badUserNames;
|
||||||
|
|
||||||
if (!usernameRegExp.test(data)) {
|
if (!usernameRegExp.test(data)) {
|
||||||
return cb(new Error('Username contains invalid characters'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
'Username contains invalid characters',
|
||||||
|
ErrorReasons.ValueInvalid
|
||||||
|
)
|
||||||
|
);
|
||||||
} else if (invalidNames.indexOf(data.toLowerCase()) > -1) {
|
} else if (invalidNames.indexOf(data.toLowerCase()) > -1) {
|
||||||
return cb(new Error('Username is blacklisted'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
'Username is blacklisted',
|
||||||
|
ErrorReasons.NotAllowed
|
||||||
|
)
|
||||||
|
);
|
||||||
} else if (/^[0-9]+$/.test(data)) {
|
} else if (/^[0-9]+$/.test(data)) {
|
||||||
return cb(new Error('Username cannot be a number'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
'Username cannot be a number',
|
||||||
|
ErrorReasons.ValueInvalid
|
||||||
|
)
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// a new user name cannot be an existing user name or an existing real name
|
// a new user name cannot be an existing user name or an existing real name
|
||||||
User.getUserIdAndNameByLookup(data, function userIdAndName(err) {
|
User.getUserIdAndNameByLookup(data, function userIdAndName(err) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
// err is null if we succeeded -- meaning this user exists already
|
// err is null if we succeeded -- meaning this user exists already
|
||||||
return cb(new Error('Username unavailable'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
'Username unavailable',
|
||||||
|
ErrorReasons.NotAvailable
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
|
@ -60,25 +91,41 @@ function validateUserNameAvail(data, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const invalidUserNameError = () => new Error('Invalid username');
|
|
||||||
|
|
||||||
function validateUserNameExists(data, cb) {
|
function validateUserNameExists(data, cb) {
|
||||||
if (0 === data.length) {
|
if (0 === data.length) {
|
||||||
return cb(invalidUserNameError());
|
return cb(
|
||||||
|
Errors.ValidationFailed('Invalid username', ErrorReasons.ValueTooShort)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.getUserIdAndName(data, err => {
|
User.getUserIdAndName(data, err => {
|
||||||
return cb(err ? invalidUserNameError() : null);
|
return cb(
|
||||||
|
err
|
||||||
|
? Errors.ValidationFailed(
|
||||||
|
'Failed to find username',
|
||||||
|
err.reasonCode || ErrorReasons.DoesNotExist
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateUserNameOrRealNameExists(data, cb) {
|
function validateUserNameOrRealNameExists(data, cb) {
|
||||||
if (0 === data.length) {
|
if (0 === data.length) {
|
||||||
return cb(invalidUserNameError());
|
return cb(
|
||||||
|
Errors.ValidationFailed('Invalid username', ErrorReasons.ValueTooShort)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.getUserIdAndNameByLookup(data, err => {
|
User.getUserIdAndNameByLookup(data, err => {
|
||||||
return cb(err ? invalidUserNameError() : null);
|
return cb(
|
||||||
|
err
|
||||||
|
? Errors.ValidationFailed(
|
||||||
|
'Failed to find user',
|
||||||
|
err.reasonCode || ErrorReasons.DoesNotExist
|
||||||
|
)
|
||||||
|
: null
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +159,9 @@ function validateEmailAvail(data, cb) {
|
||||||
//
|
//
|
||||||
const emailRegExp = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
|
const emailRegExp = /[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(.[a-z0-9-]+)*/;
|
||||||
if (!emailRegExp.test(data)) {
|
if (!emailRegExp.test(data)) {
|
||||||
return cb(new Error('Invalid email address'));
|
return cb(
|
||||||
|
Errors.ValidationFailed('Invalid email address', ErrorReasons.ValueInvalid)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
User.getUserIdsWithProperty(
|
User.getUserIdsWithProperty(
|
||||||
|
@ -120,9 +169,19 @@ function validateEmailAvail(data, cb) {
|
||||||
data,
|
data,
|
||||||
function userIdsWithEmail(err, uids) {
|
function userIdsWithEmail(err, uids) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return cb(new Error('Internal system error'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
err.message,
|
||||||
|
err.reasonCode || ErrorReasons.DoesNotExist
|
||||||
|
)
|
||||||
|
);
|
||||||
} else if (uids.length > 0) {
|
} else if (uids.length > 0) {
|
||||||
return cb(new Error('Email address not unique'));
|
return cb(
|
||||||
|
Errors.ValidationFailed(
|
||||||
|
'Email address not unique',
|
||||||
|
ErrorReasons.NotAvailable
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
|
@ -132,25 +191,36 @@ function validateEmailAvail(data, cb) {
|
||||||
|
|
||||||
function validateBirthdate(data, cb) {
|
function validateBirthdate(data, cb) {
|
||||||
// :TODO: check for dates in the future, or > reasonable values
|
// :TODO: check for dates in the future, or > reasonable values
|
||||||
return cb(isNaN(Date.parse(data)) ? new Error('Invalid birthdate') : null);
|
return cb(
|
||||||
|
isNaN(Date.parse(data))
|
||||||
|
? Errors.ValidationFailed('Invalid birthdate', ErrorReasons.ValueInvalid)
|
||||||
|
: null
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validatePasswordSpec(data, cb) {
|
function validatePasswordSpec(data, cb) {
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if (!data || data.length < config.users.passwordMin) {
|
if (!data || data.length < config.users.passwordMin) {
|
||||||
return cb(new Error('Password too short'));
|
return cb(
|
||||||
|
Errors.ValidationFailed('Password too short', ErrorReasons.ValueTooShort)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check badpass, if avail
|
// check badpass, if avail
|
||||||
fs.readFile(config.users.badPassFile, 'utf8', (err, passwords) => {
|
fs.readFile(config.users.badPassFile, 'utf8', (err, passwords) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
Log.warn({ error: err.message }, 'Cannot read bad pass file');
|
Log.warn(
|
||||||
|
{ error: err.message, path: config.users.badPassFile },
|
||||||
|
'Cannot read bad pass file'
|
||||||
|
);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
passwords = passwords.toString().split(/\r\n|\n/g);
|
passwords = passwords.toString().split(/\r\n|\n/g);
|
||||||
if (passwords.includes(data)) {
|
if (passwords.includes(data)) {
|
||||||
return cb(new Error('Password is too common'));
|
return cb(
|
||||||
|
Errors.ValidationFailed('Password is too common', ErrorReasons.NotAllowed)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
|
|
|
@ -120,13 +120,13 @@ exports.getModule = class UploadModule extends MenuModule {
|
||||||
);
|
);
|
||||||
if (errView) {
|
if (errView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errView.setText(err.message);
|
errView.setText(err.friendlyText);
|
||||||
} else {
|
} else {
|
||||||
errView.clearText();
|
errView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(err, null);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
||||||
var newFocusId;
|
var newFocusId;
|
||||||
if (errMsgView) {
|
if (errMsgView) {
|
||||||
if (err) {
|
if (err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.friendlyText);
|
||||||
|
|
||||||
if (err.view.getId() === MciCodeIds.PassConfirm) {
|
if (err.view.getId() === MciCodeIds.PassConfirm) {
|
||||||
newFocusId = MciCodeIds.Password;
|
newFocusId = MciCodeIds.Password;
|
||||||
|
@ -102,7 +102,8 @@ exports.getModule = class UserConfigModule extends MenuModule {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(newFocusId);
|
|
||||||
|
return cb(err, newFocusId);
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
|
@ -150,7 +150,7 @@ View.prototype.setPosition = function (pos) {
|
||||||
this.position.col = parseInt(arguments[1], 10);
|
this.position.col = parseInt(arguments[1], 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// sanatize
|
// sanitize
|
||||||
this.position.row = Math.max(this.position.row, 1);
|
this.position.row = Math.max(this.position.row, 1);
|
||||||
this.position.col = Math.max(this.position.col, 1);
|
this.position.col = Math.max(this.position.col, 1);
|
||||||
this.position.row = Math.min(this.position.row, this.client.term.termHeight);
|
this.position.row = Math.min(this.position.row, this.client.term.termHeight);
|
||||||
|
|
|
@ -385,19 +385,18 @@ function ViewController(options) {
|
||||||
this.validateView = function (view, cb) {
|
this.validateView = function (view, cb) {
|
||||||
if (view && _.isFunction(view.validate)) {
|
if (view && _.isFunction(view.validate)) {
|
||||||
view.validate(view.getData(), function validateResult(err) {
|
view.validate(view.getData(), function validateResult(err) {
|
||||||
var viewValidationListener =
|
const viewValidationListener =
|
||||||
self.client.currentMenuModule.menuMethods.viewValidationListener;
|
self.client.currentMenuModule.menuMethods.viewValidationListener;
|
||||||
if (_.isFunction(viewValidationListener)) {
|
if (_.isFunction(viewValidationListener)) {
|
||||||
if (err) {
|
if (err) {
|
||||||
err.view = view; // pass along the view that failed
|
err.view = view; // pass along the view that failed
|
||||||
|
err.friendlyText = err.reason || err.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewValidationListener(
|
viewValidationListener(err, (err, newFocusedViewId) => {
|
||||||
err,
|
// validator may have updated |err|
|
||||||
function validationComplete(newViewFocusId) {
|
return cb(err, newFocusedViewId);
|
||||||
cb(err, newViewFocusId);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
cb(err);
|
cb(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -226,6 +226,7 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
|
||||||
enter() {
|
enter() {
|
||||||
this.client.stopIdleMonitor();
|
this.client.stopIdleMonitor();
|
||||||
this._applyOpVisibility();
|
this._applyOpVisibility();
|
||||||
|
|
||||||
Events.on(
|
Events.on(
|
||||||
Events.getSystemEvents().ClientDisconnected,
|
Events.getSystemEvents().ClientDisconnected,
|
||||||
this._clientDisconnected.bind(this)
|
this._clientDisconnected.bind(this)
|
||||||
|
@ -240,7 +241,7 @@ exports.getModule = class WaitingForCallerModule extends MenuModule {
|
||||||
|
|
||||||
Events.removeListener(
|
Events.removeListener(
|
||||||
Events.getSystemEvents().ClientDisconnected,
|
Events.getSystemEvents().ClientDisconnected,
|
||||||
this._clientDisconnected
|
this._clientDisconnected.bind(this)
|
||||||
);
|
);
|
||||||
|
|
||||||
this._restoreOpVisibility();
|
this._restoreOpVisibility();
|
||||||
|
|
Loading…
Reference in New Issue