Skip to content

Commit 18a64dc

Browse files
committed
A few more tweaks before we update.
1 parent be8fc13 commit 18a64dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1300
-4417
lines changed

.idea/dictionaries/neonexusdemortis.xml

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# Changelog
22

3-
## [v3.1.2](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v3.1.1...v3.1.2) (2022-10-24)
3+
## [v3.2.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v3.1.1...v3.2.0) (2022-11-16)
44

55
### Features
66

77
* Built out PnwedPasswords.com (HaveIBeenPwned.com) API functionality into `is-password-valid` helper.
8+
* Can be disabled in [config/security.js](config/security.js).
89
* FINALLY removed the usage of `res._headers`, so no more annoying deprecation message.
910
* Simplified stored session data.
11+
* Updated dependencies.
1012

1113
## [v3.1.1](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v3.1.0...v3.1.1) (2022-09-08)
1214

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:16.17
1+
FROM node:18.12
22
MAINTAINER NeoNexus DeMortis
33

44
RUN apt-get update && apt-get upgrade -y
@@ -22,7 +22,7 @@ COPY assets /var/www/myapp/assets
2222
COPY webpack /var/www/myapp/webpack
2323
RUN npm run build
2424

25-
# Copy the reset of the app
25+
# Copy the rest of the app
2626
COPY . /var/www/myapp/
2727

2828
# Expose the compiled public assets, so Nginx can route to them, instead of using Sails to do the file serving.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Need help? Want to hire me to build your next app or prototype? You can contact
1313
* Setup so Sails will serve Webpack-built bundles as separate apps (so, a marketing site, and an admin site can live side-by-side).
1414
* Includes [react-bootstrap](https://www.npmjs.com/package/react-bootstrap) to make using Bootstrap styles / features with React easier.
1515
* Schema validation and enforcement for `PRODUCTION`. This repo is set up for `MySQL`. If you plan to use a different datastore, you will likely want to disable the schema validation and enforcement feature inside [`config/bootstrap.js`](config/bootstrap.js). See [schema validation and enforcement](#schema-validation-and-enforcement) for more info.
16-
* Can enforce password creation isn't found in [PwnedPasswords]()
16+
* New passwords can be checked against the [PwnedPasswords API](https://haveibeenpwned.com/API/v3#PwnedPasswords). If there is a single hit for the password, an error will be given, and the user will be forced to choose another. See [PwnedPasswords integration](#pwnedpasswordscom-integration) for more info.
1717

1818
## Branch Warning
1919
The `master` branch is experimental, and the [release branch](https://github.com/neonexus/sails-react-bootstrap-webpack/tree/release) (or the [`releases section`](https://github.com/neonexus/sails-react-bootstrap-webpack/releases)) is where one should base their use of this template.
@@ -116,6 +116,11 @@ module.exports.bootstrap = function(next) {
116116
};
117117
```
118118

119+
## PwnedPasswords.com Integration
120+
When a new password is being created, it is checked with the [PwnedPasswords.com API](https://haveibeenpwned.com/API/v3#PwnedPasswords). This API uses a k-anonymity model, so the password that is searched for is never exposed to the API. Basically, the password is hashed, then the first 5 characters are sent to the API, and the API returns any hashes that start with those 5 characters, including the amount of times that hash (aka password) has been found in known security breaches.
121+
122+
This functionality is turned on by default, and can be shutoff per-use, or globally throughout the app. `sails.helpers.isPasswordValid` can be used with `skipPwned` option set to `true`, to disable the check per use. Inside of [`config/security.js`](config/security.js), the variable `checkPwned` can be set to `false` to disable it globally.
123+
119124
## What about SEO?
120125
I recommend looking at [prerender.io](https://prerender.io). They offer a service (free up to 250 pages) that caches the end result of a JavaScript-rendered view (React, Vue, Angular), allowing search engines to crawl otherwise un-crawlable web views. You can use the service in a number of ways. One way, is to use the [prerender-node](https://www.npmjs.com/package/prerender-node) package. To use it with Sails, you'll have to add it to the [HTTP Middleware](https://sailsjs.com/documentation/concepts/middleware#?http-middleware). Here's a quick example:
121126

api/controllers/admin/edit-user.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
module.exports = {
2+
friendlyName: 'Edit User',
3+
4+
description: 'Edit an active user.',
5+
6+
inputs: {
7+
id: {
8+
type: 'string',
9+
required: true,
10+
isUUID: true
11+
},
12+
13+
firstName: {
14+
type: 'string',
15+
required: true,
16+
maxLength: 70
17+
},
18+
19+
lastName: {
20+
type: 'string',
21+
required: true,
22+
maxLength: 70
23+
},
24+
25+
password: {
26+
type: 'string',
27+
maxLength: 70
28+
},
29+
30+
email: {
31+
type: 'string',
32+
isEmail: true,
33+
required: true,
34+
maxLength: 191
35+
},
36+
37+
role: {
38+
type: 'string',
39+
defaultsTo: 'user',
40+
isIn: [
41+
'user',
42+
'admin'
43+
]
44+
},
45+
46+
setPassword: {
47+
type: 'boolean',
48+
defaultsTo: true
49+
}
50+
},
51+
52+
exits: {
53+
ok: {
54+
responseType: 'created'
55+
},
56+
badRequest: {
57+
responseType: 'badRequest'
58+
},
59+
serverError: {
60+
responseType: 'serverError'
61+
}
62+
},
63+
64+
fn: async (inputs, exits) => {
65+
let isPasswordValid = true;
66+
const foundUser = await sails.models.user.findOne({id: inputs.id});
67+
68+
if (!foundUser) {
69+
return exits.badRequest('There is no user with that ID.');
70+
}
71+
72+
if (foundUser.deletedAt !== null) {
73+
return exits.badRequest('This user has been deleted, and can not be edited until reactivated.');
74+
}
75+
76+
if (inputs.setPassword) {
77+
isPasswordValid = await sails.helpers.isPasswordValid.with({
78+
password: inputs.password,
79+
user: {firstName: inputs.firstName, lastName: inputs.lastName, email: inputs.email}
80+
});
81+
}
82+
83+
if (isPasswordValid !== true) {
84+
return exits.badRequest(isPasswordValid);
85+
}
86+
87+
let updatedUser = {
88+
firstName: inputs.firstName,
89+
lastName: inputs.lastName,
90+
role: inputs.role,
91+
email: inputs.email
92+
};
93+
94+
if (inputs.setPassword) {
95+
updatedUser.password = inputs.password;
96+
}
97+
98+
const user = await sails.models.user.updateOne({id: inputs.id}).set(updatedUser);
99+
100+
return exits.ok({user});
101+
}
102+
};

api/controllers/admin/get-deleted-users.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ module.exports = {
3939
where: {
4040
deletedAt: {'!=': null} // get all soft-deleted users
4141
},
42-
sort: [{deletedAt: 'ASC'}, {createdAt: 'DESC'}]
42+
sort: 'deletedAt DESC'
4343
});
4444

4545
let out = await sails.helpers.paginateForJson.with({
@@ -50,7 +50,7 @@ module.exports = {
5050

5151
// We assign the users to the object afterward, so we can run our safety checks.
5252
// Otherwise, if we were to put the users object into "objToWrap", they would be transformed, and the "customToJSON" feature would no longer work, and hashed passwords would leak.
53-
out.users = await sails.models.user.find(_.omit(pagination, ['page'])).populate('deletedBy');
53+
out.users = await sails.models.user.find(_.omit(query, ['page'])).populate('deletedBy');
5454

5555
return exits.ok(out);
5656
}

api/controllers/common/login.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ module.exports = {
3636
}
3737

3838
const badEmailPass = 'Bad email / password combination.';
39+
40+
if (await sails.helpers.isPasswordValid.with({password: inputs.password, skipPwned: true}) !== true) {
41+
return exits.badRequest(badEmailPass);
42+
}
43+
3944
const foundUser = await sails.models.user.findOne({email: inputs.email, deletedAt: null});
4045

4146
if (!foundUser) {
4247
return exits.badRequest(badEmailPass);
4348
}
4449

45-
await sails.helpers.isPasswordValid(inputs.password);
46-
4750
if (!await sails.models.user.doPasswordsMatch(inputs.password, foundUser.password)) {
4851
return exits.badRequest(badEmailPass);
4952
}

api/helpers/is-password-valid.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,15 @@ module.exports = {
9090
}
9191

9292
if (res.text && res.text.length) {
93-
const chunks = res.text.replaceAll('\r', '').split('\n');
93+
const chunks = res.text.split('\r\n');
9494
const matches = chunks.filter(s => s.includes(passChunk2));
9595

9696
if (matches.length) {
9797
const bits = matches[0].split(':');
9898

99-
return exits.success(['Provided password has been found in ' + bits[1] + ' known breaches. Please choose a new one for safety. We HIGHLY recommend using a password manager!']);
99+
return exits.success(
100+
['Provided password has been found in ' + bits[1] + ' known security breaches. Please choose a new one for safety. We HIGHLY recommend using a password manager!']
101+
);
100102
}
101103

102104
return exits.success(true);

api/helpers/paginate-for-json.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module.exports = {
1111
// this is a custom validator, which returns true or false
1212
custom: (thisQuery) => (
1313
_.isObject(thisQuery) && _.isNumber(thisQuery.limit) &&
14-
_.isNumber(thisQuery.page) && _.isString(thisQuery.sort) &&
14+
_.isNumber(thisQuery.page) && (_.isString(thisQuery.sort) || _.isArray(thisQuery.sort)) &&
1515
_.isObject(thisQuery.where)
1616
)
1717
},

api/helpers/paginate-for-query.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ module.exports = {
1515
defaultsTo: 25
1616
},
1717
sort: {
18-
type: 'string',
19-
defaultsTo: 'createdAt DESC'
18+
type: 'ref',
19+
defaultsTo: 'createdAt DESC',
20+
custom: (val) => { // custom validator
21+
return _.isString(val) || _.isArray(val);
22+
}
2023
},
2124
where: {
2225
type: 'ref', // JavaScript reference to an object

0 commit comments

Comments
 (0)