Skip to content

Commit

Permalink
fix: bump deps, implemented cryptoRandomString.async, synced FE (per <f…
Browse files Browse the repository at this point in the history
  • Loading branch information
niftylettuce committed Sep 4, 2020
1 parent 6681166 commit 75eb806
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 345 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@
"ava": "^3.12.1",
"codecov": "^3.7.2",
"cross-env": "^7.0.2",
"eslint": "^7.7.0",
"eslint": "^7.8.1",
"eslint-config-xo-lass": "^1.0.3",
"eslint-plugin-compat": "^3.8.0",
"eslint-plugin-no-smart-quotes": "^1.1.0",
"husky": "^4.2.5",
"lint-staged": "^10.2.13",
"lint-staged": "^10.3.0",
"nyc": "^15.1.0",
"remark-cli": "^8.0.1",
"remark-preset-github": "^3.0.0",
"xo": "^0.33.0"
"xo": "^0.33.1"
},
"engines": {
"node": ">=12.11.0"
Expand Down
13 changes: 9 additions & 4 deletions template/app/controllers/web/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const sendVerificationEmail = require('../../../helpers/send-verification-email'
const config = require('../../../config');
const { Inquiries } = require('../../models');

const options = { length: 10, type: 'numeric' };

const sanitize = (string) =>
sanitizeHtml(string, {
allowedTags: [],
Expand Down Expand Up @@ -264,8 +266,9 @@ async function recoveryKey(ctx) {

// handle case if the user runs out of keys
if (emptyRecoveryKeys) {
const options = { length: 10, characters: '1234567890' };
recoveryKeys = new Array(10).fill().map(() => cryptoRandomString(options));
recoveryKeys = await Promise.all(
new Array(10).fill().map(() => cryptoRandomString.async(options))
);
}

ctx.state.user[config.userFields.otpRecoveryKeys] = recoveryKeys;
Expand Down Expand Up @@ -385,7 +388,9 @@ async function forgotPassword(ctx) {
user[config.userFields.resetTokenExpiresAt] = dayjs()
.add(30, 'minutes')
.toDate();
user[config.userFields.resetToken] = cryptoRandomString({ length: 32 });
user[config.userFields.resetToken] = await cryptoRandomString.async({
length: 32
});

user = await user.save();

Expand Down Expand Up @@ -576,7 +581,7 @@ async function verify(ctx) {
// wrap with try/catch to prevent redirect looping
// (even though the koa redirect loop package will help here)
if (!err.isBoom) return ctx.throw(err);
ctx.logger.warn(err);
ctx.logger.error(err);
if (ctx.accepts('html')) {
ctx.flash('warning', err.message);
ctx.redirect(redirectTo);
Expand Down
51 changes: 31 additions & 20 deletions template/app/controllers/web/my-account.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
const Boom = require('@hapi/boom');
const _ = require('lodash');
const cryptoRandomString = require('crypto-random-string');
const moment = require('moment');
const humanize = require('humanize-string');
const isSANB = require('is-string-and-not-blank');
const moment = require('moment');
const { isEmail } = require('validator');

const config = require('../../../config');
const emailHelper = require('../../../helpers/email');
Expand Down Expand Up @@ -44,29 +45,37 @@ async function update(ctx) {
body[config.passport.fields.familyName];
}

// if we've already sent a change email request in the past half hour
if (
ctx.state.user[config.userFields.changeEmailTokenExpiresAt] &&
ctx.state.user[config.userFields.changeEmailToken] &&
moment(ctx.state.user[config.userFields.changeEmailTokenExpiresAt]).isAfter(
moment().subtract(config.changeEmailTokenTimeoutMs, 'milliseconds')
)
)
throw Boom.badRequest(
ctx.translateError(
'EMAIL_CHANGE_LIMIT',
moment.duration(config.changeEmailLimitMs, 'milliseconds').minutes(),
moment(
ctx.state.user[config.userFields.changeEmailTokenExpiresAt]
).fromNow()
)
);

// check if we need to update the email and send an email confirmation
const hasNewEmail =
isSANB(body.email) &&
ctx.state.user[config.passportLocalMongoose.usernameField] !== body.email;

// confirm user supplied email is different than current email
if (hasNewEmail) {
// validate it (so it doesn't have to use mongoose for this)
if (!isEmail(body.email))
return ctx.throw(Boom.badRequest(ctx.translateError('INVALID_EMAIL')));

// if we've already sent a change email request in the past half hour
if (
ctx.state.user[config.userFields.changeEmailTokenExpiresAt] &&
ctx.state.user[config.userFields.changeEmailToken] &&
moment(
ctx.state.user[config.userFields.changeEmailTokenExpiresAt]
).isAfter(
moment().subtract(config.changeEmailTokenTimeoutMs, 'milliseconds')
)
)
throw Boom.badRequest(
ctx.translateError(
'EMAIL_CHANGE_LIMIT',
moment.duration(config.changeEmailLimitMs, 'milliseconds').minutes(),
moment(
ctx.state.user[config.userFields.changeEmailTokenExpiresAt]
).fromNow()
)
);

// short-circuit if email already exists
const query = { email: body.email };
const user = await Users.findOne(query);
Expand All @@ -79,7 +88,9 @@ async function update(ctx) {
ctx.state.user[config.userFields.changeEmailTokenExpiresAt] = moment()
.add(config.changeEmailTokenTimeoutMs, 'milliseconds')
.toDate();
ctx.state.user[config.userFields.changeEmailToken] = cryptoRandomString({
ctx.state.user[
config.userFields.changeEmailToken
] = await cryptoRandomString.async({
length: 32
});
ctx.state.user[config.userFields.changeEmailNewAddress] = body.email;
Expand Down
90 changes: 48 additions & 42 deletions template/app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ if (config.passportLocalMongoose.usernameField !== 'email')
'User model and @ladjs/passport requires that the usernameField is email'
);

const options = { length: 10, characters: '1234567890' };
const options = { length: 10, type: 'numeric' };
const { fields } = config.passport;
const omitExtraFields = [
..._.without(mongooseOmitCommonFields.underscored.keys, 'email'),
Expand Down Expand Up @@ -204,48 +204,54 @@ User.virtual(config.userFields.verificationPinHasExpired).get(function () {
);
});

User.pre('validate', function (next) {
// create api token if doesn't exist
if (!isSANB(this[config.userFields.apiToken]))
this[config.userFields.apiToken] = cryptoRandomString({ length: 24 });

// set the user's display name to their email address
// but if they have a name or surname set then use that
this[fields.displayName] = this.email;
if (isSANB(this[fields.givenName]) || isSANB(this[fields.familyName])) {
this[fields.displayName] = `${this[fields.givenName] || ''} ${
this[fields.familyName] || ''
}`;
}

// set the user's full email address (incl display name)
this[config.userFields.fullEmail] =
this[fields.displayName] && this[fields.displayName] !== this.email
? `${this[fields.displayName]} <${this.email}>`
: this.email;

// if otp authentication values no longer valid
// then disable it completely
if (
!Array.isArray(this[config.userFields.otpRecoveryKeys]) ||
!this[config.userFields.otpRecoveryKeys] ||
this[config.userFields.otpRecoveryKeys].length === 0 ||
!this[config.passport.fields.otpToken]
)
this[fields.otpEnabled] = false;

if (
!Array.isArray(this[config.userFields.otpRecoveryKeys]) ||
this[config.userFields.otpRecoveryKeys].length === 0
)
this[config.userFields.otpRecoveryKeys] = new Array(10)
.fill()
.map(() => cryptoRandomString(options));
User.pre('validate', async function (next) {
try {
// create api token if doesn't exist
if (!isSANB(this[config.userFields.apiToken]))
this[config.userFields.apiToken] = await cryptoRandomString.async({
length: 24
});

if (!this[config.passport.fields.otpToken])
this[config.passport.fields.otpToken] = authenticator.generateSecret();
// set the user's display name to their email address
// but if they have a name or surname set then use that
this[fields.displayName] = this.email;
if (isSANB(this[fields.givenName]) || isSANB(this[fields.familyName])) {
this[fields.displayName] = `${this[fields.givenName] || ''} ${
this[fields.familyName] || ''
}`;
}

next();
// set the user's full email address (incl display name)
this[config.userFields.fullEmail] =
this[fields.displayName] && this[fields.displayName] !== this.email
? `${this[fields.displayName]} <${this.email}>`
: this.email;

// if otp authentication values no longer valid
// then disable it completely
if (
!Array.isArray(this[config.userFields.otpRecoveryKeys]) ||
!this[config.userFields.otpRecoveryKeys] ||
this[config.userFields.otpRecoveryKeys].length === 0 ||
!this[config.passport.fields.otpToken]
)
this[fields.otpEnabled] = false;

if (
!Array.isArray(this[config.userFields.otpRecoveryKeys]) ||
this[config.userFields.otpRecoveryKeys].length === 0
)
this[config.userFields.otpRecoveryKeys] = await Promise.all(
new Array(10).fill().map(() => cryptoRandomString.async(options))
);

if (!this[config.passport.fields.otpToken])
this[config.passport.fields.otpToken] = authenticator.generateSecret();

next();
} catch (err) {
next(err);
}
});

//
Expand Down Expand Up @@ -322,7 +328,7 @@ User.methods.sendVerificationEmail = async function (ctx, reset = false) {
this[config.userFields.verificationPinExpiresAt] = new Date(
Date.now() + config.verificationPinTimeoutMs
);
this[config.userFields.verificationPin] = cryptoRandomString(
this[config.userFields.verificationPin] = await cryptoRandomString.async(
config.verificationPin
);
}
Expand Down
2 changes: 1 addition & 1 deletion template/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ const config = {
verifyRoute: '/verify',
verificationPinTimeoutMs: ms(env.VERIFICATION_PIN_TIMEOUT_MS),
verificationPinEmailIntervalMs: ms(env.VERIFICATION_PIN_EMAIL_INTERVAL_MS),
verificationPin: { length: 6, characters: '1234567890' },
verificationPin: { length: 6, type: 'numeric' },

// reset token
resetTokenTimeoutMs: ms(env.RESET_TOKEN_TIMEOUT_MS),
Expand Down
26 changes: 13 additions & 13 deletions template/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@fortawesome/fontawesome-free": "^5.14.0",
"@hapi/boom": "^9.1.0",
"@koa/router": "^9.4.0",
"@ladjs/api": "^5.0.3",
"@ladjs/api": "^5.0.4",
"@ladjs/assets": "^1.0.2",
"@ladjs/env": "^2.0.1",
"@ladjs/graceful": "^1.0.4",
Expand All @@ -44,7 +44,7 @@
"@ladjs/redis": "^1.0.7",
"@ladjs/shared-config": "^3.0.9",
"@ladjs/store-ip-address": "^0.0.7",
"@ladjs/web": "^9.1.2",
"@ladjs/web": "^9.1.3",
"@primer/css": "^15.1.0",
"@sidoshi/random-string": "^1.0.0",
"@slack/web-api": "^5.11.0",
Expand All @@ -62,10 +62,10 @@
"cheerio": "^1.0.0-rc.3",
"clipboard": "^2.0.6",
"consolidate": "^0.15.1",
"crypto-random-string": "^3.2.0",
"crypto-random-string": "^3.3.0",
"custom-fonts-in-emails": "^4.0.2",
"dashify": "^2.0.0",
"dayjs": "^1.8.34",
"dayjs": "^1.8.35",
"dayjs-with-plugins": "^0.0.4",
"del": "^5.1.0",
"email-templates": "^7.1.0",
Expand Down Expand Up @@ -94,7 +94,7 @@
"markdown-it-github-headings": "^2.0.0",
"markdown-it-highlightjs": "^3.2.0",
"markdown-it-task-checkbox": "^1.0.6",
"mongoose": "^5.10.2",
"mongoose": "^5.10.3",
"mongoose-common-plugin": "2.0.2",
"mongoose-json-select": "^0.2.1",
"mongoose-omit-common-fields": "0.0.6",
Expand Down Expand Up @@ -130,10 +130,10 @@
"zxcvbn": "^4.4.2"
},
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.4",
"@babel/polyfill": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/cli": "^7.11.6",
"@babel/core": "^7.11.6",
"@babel/polyfill": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@commitlint/cli": "^9.1.2",
"@commitlint/config-conventional": "^9.1.2",
"@ladjs/browserslist-config": "^0.0.1",
Expand All @@ -145,7 +145,7 @@
"bundle-collapser": "^1.4.0",
"codecov": "^3.7.2",
"cssnano": "^4.1.10",
"eslint": "^7.7.0",
"eslint": "^7.8.1",
"eslint-config-xo-lass": "^1.0.3",
"eslint-formatter-pretty": "^4.0.0",
"eslint-plugin-compat": "^3.8.0",
Expand Down Expand Up @@ -175,7 +175,7 @@
"gulp-xo": "^0.25.0",
"husky": "^4.2.5",
"imagemin-pngquant": "^9.0.0",
"lint-staged": "10.2.13",
"lint-staged": "10.3.0",
"make-dir": "^3.1.0",
"mongodb-memory-server": "6.3",
"ms": "^2.1.2",
Expand All @@ -197,12 +197,12 @@
"remark-cli": "^8.0.1",
"remark-preset-github": "^3.0.0",
"sinon": "^9.0.3",
"stylelint": "^13.6.1",
"stylelint": "^13.7.0",
"stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.18.0",
"supertest": "^4.0.2",
"through2": "^4.0.2",
"xo": "^0.33.0"
"xo": "^0.33.1"
},
"engines": {
"node": ">=12.11.0"
Expand Down
3 changes: 2 additions & 1 deletion template/test/web/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@ test('successfully registers with strong password', async (t) => {
test('successfully registers with stronger password', async (t) => {
const { web } = t.context;

const password = await cryptoRandomString.async({ length: 50 });
const res = await web.post('/en/register').send({
email: '[email protected]',
password: cryptoRandomString({ length: 50 })
password
});

t.is(res.body.message, undefined);
Expand Down
Loading

0 comments on commit 75eb806

Please sign in to comment.