Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,34 @@
"Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com)"
],
"dependencies": {
"cac": "^6.5.3",
"cac": "^6.5.8",
"camelcase": "^5.3.1",
"github-username-regex": "^1.0.0",
"is-email": "^1.0.0",
"is-url": "^1.2.4",
"is-valid-npm-name": "^0.0.4",
"is-valid-npm-name": "^0.0.5",
"npm-conf": "^1.1.3",
"sao": "0.x",
"semver": "^7.0.0",
"semver": "^7.1.3",
"speakingurl": "^14.0.1",
"superb": "^4.0.0",
"update-notifier": "^4.0.0",
"update-notifier": "^4.1.0",
"uppercamelcase": "^3.0.0"
},
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"ava": "^2.4.0",
"codecov": "^3.6.1",
"codecov": "^3.6.5",
"cross-env": "^6.0.3",
"eslint": "^6.7.2",
"eslint": "^6.8.0",
"eslint-config-xo-lass": "^1.0.3",
"eslint-plugin-compat": "^3.3.0",
"eslint-plugin-compat": "^3.5.1",
"husky": "^3.1.0",
"lint-staged": "^9.5.0",
"nyc": "^14.1.1",
"remark-cli": "^7.0.1",
"remark-preset-github": "^0.0.16",
"lint-staged": "^10.1.1",
"nyc": "^15.0.0",
"remark-cli": "^8.0.0",
"remark-preset-github": "^1.0.0",
"supertest": "^4.0.2",
"xo": "^0.25.3"
},
Expand Down
30 changes: 15 additions & 15 deletions sao.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ module.exports = {
name: {
message: 'What is the name of the new project',
default: ':folderName:',
validate: val => {
if (process.env.NODE_ENV === 'test' && val === 'lad') return true;
return isValidNpmName(val);
validate: value => {
if (process.env.NODE_ENV === 'test' && value === 'lad') return true;
return isValidNpmName(value);
}
},
description: {
Expand All @@ -40,40 +40,40 @@ module.exports = {
store: true
},
author: {
message: "What is your name (the author's)",
message: 'What is your name (the author’s)',
default: conf.get('init-author-name') || ':gitUser:',
store: true
},
email: {
message: "What is your email (the author's)",
message: 'What is your email (the author’s)',
default: conf.get('init-author-email') || ':gitEmail:',
store: true,
validate: val => (isEmail(val) ? true : 'Invalid email')
validate: value => (isEmail(value) ? true : 'Invalid email')
},
website: {
message: "What is your personal website (the author's)",
message: 'What is your personal website (the author’s)',
default: conf.get('init-author-url') || '',
store: true,
validate: val => (val === '' || isURL(val) ? true : 'Invalid URL')
validate: value => (value === '' || isURL(value) ? true : 'Invalid URL')
},
username: {
message: 'What is your GitHub username or organization',
default: ':gitUser:',
store: true,
validate: val =>
githubUsernameRegex.test(val) ? true : 'Invalid GitHub username'
validate: value =>
githubUsernameRegex.test(value) ? true : 'Invalid GitHub username'
},
repo: {
message: "What is your GitHub repository's URL",
message: 'What is your GitHub repositorys URL',
default(answers) {
return `https://github.com/${slug(answers.username)}/${slug(
slug(answers.name)
)}`;
},
validate: val =>
isURL(val) &&
val.indexOf('https://github.com/') === 0 &&
val.lastIndexOf('/') !== val.length - 1
validate: value =>
isURL(value) &&
value.indexOf('https://github.com/') === 0 &&
value.lastIndexOf('/') !== value.length - 1
? true
: 'Please include a valid GitHub.com URL without a trailing slash'
},
Expand Down
3 changes: 2 additions & 1 deletion template/.build.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"commonjs": true,
"jquery": true
},
"plugins": ["compat", "@lwc/eslint-plugin-lwc"],
"plugins": ["compat", "@lwc/eslint-plugin-lwc", "no-smart-quotes"],
"rules": {
"compat/compat": "error",
"@lwc/lwc/no-async-await": "error",
"promise/prefer-await-to-then": "off",
"no-smart-quotes/no-smart-quotes": "error",
"no-func-assign": "off",
"no-redeclare": "off",
"no-unused-vars": "off",
Expand Down
1 change: 1 addition & 0 deletions template/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ AUTH_GOOGLE_ENABLED=false
AUTH_GITHUB_ENABLED=false
AUTH_LINKEDIN_ENABLED=false
AUTH_INSTAGRAM_ENABLED=false
AUTH_OTP_ENABLED=false
AUTH_STRIPE_ENABLED=false
# your google client ID and secret from:
# https://console.developers.google.com
Expand Down
1 change: 1 addition & 0 deletions template/.env.schema
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ AUTH_GOOGLE_ENABLED=
AUTH_GITHUB_ENABLED=
AUTH_LINKEDIN_ENABLED=
AUTH_INSTAGRAM_ENABLED=
AUTH_OTP_ENABLED=
AUTH_STRIPE_ENABLED=
# your google client ID and secret from:
# https://console.developers.google.com
Expand Down
8 changes: 8 additions & 0 deletions template/.lintstagedrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
"*.md,!test/snapshots/**/*.md,!test/**/snapshots/**/*.md,!locales/README.md": [
filenames => filenames.map(filename => `remark ${filename} -qfo`),
'git add'
],
'package.json': ['fixpack', 'git add'],
'*.js': ['xo --fix', 'git add ']
};
1 change: 1 addition & 0 deletions template/.remarkignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
test/snapshots/**/*.md
test/**/snapshots/**/*.md
locales/README.md
*-*.md
5 changes: 3 additions & 2 deletions template/.travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
language: node_js
node_js:
- '8'
- '10'
- '12'
- 'lts/*'
- 'node'
services:
- mongodb
- redis-server
Expand Down
7 changes: 4 additions & 3 deletions template/app/controllers/api/v1/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ async function create(ctx) {
const query = { email: body.email };
query[config.userFields.hasVerifiedEmail] = false;
query[config.userFields.hasSetPassword] = true;
query[config.userFields.pendingRecovery] = false;
const user = await Users.register(query, body.password);

// send a verification email
await user.sendVerificationEmail();

// send the response
const obj = select(user.toObject(), Users.schema.options.toJSON.select);
obj[config.userFields.apiToken] = user[config.userFields.apiToken];
ctx.body = obj;
const object = select(user.toObject(), Users.schema.options.toJSON.select);
object[config.userFields.apiToken] = user[config.userFields.apiToken];
ctx.body = object;
}

async function retrieve(ctx) {
Expand Down
3 changes: 3 additions & 0 deletions template/app/controllers/web/2fa/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const recovery = require('./recovery');

module.exports = { recovery };
163 changes: 163 additions & 0 deletions template/app/controllers/web/2fa/recovery.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
const Boom = require('@hapi/boom');
const { boolean } = require('boolean');
const bull = require('../../../../bull');
const isSANB = require('is-string-and-not-blank');
const config = require('../../../../config');
const { Inquiries } = require('../../../models');

async function recover(ctx) {
let redirectTo = `/${ctx.locale}/2fa/recovery/verify`;

if (ctx.session && ctx.session.returnTo) {
redirectTo = ctx.session.returnTo;
delete ctx.session.returnTo;
}

ctx.state.redirectTo = redirectTo;

ctx.state.user[config.userFields.pendingRecovery] = true;
await ctx.state.user.save();

try {
ctx.state.user = await ctx.state.user.sendVerificationEmail(ctx);
} catch (err) {
// 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);
if (ctx.accepts('html')) {
ctx.flash('warning', err.message);
ctx.redirect('/login');
} else {
ctx.body = { message: err.message };
}

return;
}

if (ctx.accepts('html')) {
ctx.redirect(redirectTo);
} else {
ctx.body = { redirectTo };
}
}

// eslint-disable-next-line complexity
async function verify(ctx) {
let redirectTo = `/${ctx.locale}/login`;

if (ctx.session && ctx.session.returnTo) {
redirectTo = ctx.session.returnTo;
delete ctx.session.returnTo;
}

ctx.state.redirectTo = redirectTo;

// allow user to click a button to request a new email after 60 seconds
// after their last attempt to get a verification email
const resend = ctx.method === 'GET' && boolean(ctx.query.resend);

if (
!ctx.state.user[config.userFields.verificationPin] ||
!ctx.state.user[config.userFields.verificationPinExpiresAt] ||
ctx.state.user[config.userFields.verificationPinHasExpired] ||
resend
) {
try {
ctx.state.user = await ctx.state.user.sendVerificationEmail(ctx);
} catch (err) {
// 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);
if (ctx.accepts('html')) {
ctx.flash('warning', err.message);
ctx.redirect(redirectTo);
} else {
ctx.body = { message: err.message };
}

return;
}

const message = ctx.translate(
ctx.state.user[config.userFields.verificationPinHasExpired]
? 'EMAIL_VERIFICATION_EXPIRED'
: 'EMAIL_VERIFICATION_SENT'
);

if (!ctx.accepts('html')) {
ctx.body = { message };
return;
}

ctx.flash('success', message);
}

// if it's a GET request then render the page
if (ctx.method === 'GET' && !isSANB(ctx.query.pin))
return ctx.render('verify');

// if it's a POST request then ensure the user entered the 6 digit pin
// otherwise if it's a GET request then use the ctx.query.pin
let pin = '';
if (ctx.method === 'GET') pin = ctx.query.pin;
else pin = isSANB(ctx.request.body.pin) ? ctx.request.body.pin : '';

// convert to digits only
pin = pin.replace(/\D/g, '');

// ensure pin matches up
if (
!ctx.state.user[config.userFields.verificationPin] ||
pin !== ctx.state.user[config.userFields.verificationPin]
)
return ctx.throw(
Boom.badRequest(ctx.translate('INVALID_VERIFICATION_PIN'))
);

try {
const body = {};
body.email = ctx.state.user.email;
body.message = ctx.translate('SUPPORT_REQUEST_MESSAGE');
body.is_email_only = true;
const inquiry = await Inquiries.create({
...body,
ip: ctx.ip
});

ctx.logger.debug('created inquiry', inquiry);

const job = await bull.add('email', {
template: 'inquiry',
message: {
to: ctx.state.user.email,
cc: config.email.message.from
},
locals: {
locale: ctx.locale,
inquiry
}
});

ctx.logger.info('added job', bull.getMeta({ job }));

const message = ctx.translate('PENDING_RECOVERY_VERIFICATION_SUCCESS');
if (ctx.accepts('html')) {
ctx.flash('success', message);
ctx.redirect(redirectTo);
} else {
ctx.body = { message, redirectTo };
}
} catch (err) {
ctx.logger.error(err);
throw Boom.badRequest(ctx.translate('SUPPORT_REQUEST_ERROR'));
}

ctx.logout();
}

module.exports = {
recover,
verify
};
6 changes: 6 additions & 0 deletions template/app/controllers/web/admin/users.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const paginate = require('koa-ctx-paginate');
const { boolean } = require('boolean');

const { Users } = require('../../../models');
const config = require('../../../../config');
Expand Down Expand Up @@ -39,9 +40,14 @@ async function update(ctx) {
body[config.passport.fields.givenName];
user[config.passport.fields.familyName] =
body[config.passport.fields.familyName];
user[config.passport.fields.twoFactorEnabled] =
body[config.passport.fields.twoFactorEnabled];
user.email = body.email;
user.group = body.group;

if (boolean(!body[config.passport.fields.twoFactorEnabled]))
user[config.userFields.pendingRecovery] = false;

await user.save();

if (user.id === ctx.state.user.id) await ctx.login(user);
Expand Down
Loading