Skip to content

Commit 5c566e7

Browse files
committed
Built basic login / logout. Updated policies.
1 parent 8d2d8cb commit 5c566e7

20 files changed

+374
-64
lines changed

.sailsrc

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
2-
"generators": {
3-
"modules": {}
4-
},
5-
"_generatedWith": {
6-
"sails": "1.2.3",
7-
"sails-generate": "1.16.13"
8-
},
9-
"hooks": {
10-
"grunt": false
11-
}
2+
"generators": {
3+
"modules": {}
4+
},
5+
"_generatedWith": {
6+
"sails": "1.2.3",
7+
"sails-generate": "1.16.13"
8+
},
9+
"hooks": {
10+
"grunt": false,
11+
"session": false,
12+
"csrf": false
13+
}
1214
}

README.md

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
This is an opinionated base [Sails v1](https://sailsjs.com) application, using Webpack to handle Bootstrap (SASS) and React.
66

7-
# Preface
8-
The `master` branch is experimental, and the [`releases section`](https://github.com/neonexus/sails-react-bootstrap-webpack/releases) is where one should base their use of this template (or simply change the tag to the most recent, stable release in the top left). `master` is **volatile**, likely to change at any time, for any reason; this includes `git push --force`.
7+
# Branch Warning
8+
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. `master` is **volatile**, likely to change at any time, for any reason; this includes `git push --force` updates.
99

1010
**FINAL WARNING: DO NOT RELY ON THE MASTER BRANCH!**
1111

@@ -36,16 +36,18 @@ This repo is not installable via `npm`. Instead, Github provides a handy "Use th
3636
|npm run coverage | Runs [NYC](https://www.npmjs.com/package/nyc) coverage reporting of the Mocha tests, which generates HTML in `test/coverage`.
3737

3838
### Environment Variables used for remote servers:
39-
| Variable | DEV default | PROD default | Description
40-
|------------|----------------------|-------------------------|----------------------
41-
| ASSETS_URL | "" (empty string) | "" (empty string) | Webpack is configured to modify static asset URLs to point to a CDN, like CloudFront. MUST end with a slash " / ", or be empty.
42-
| BASE_URL | https://myapi.app | https://myapi.app | The address of the Sails instance.
43-
| DB_HOST | localhost | localhost | The hostname of the datastore.
44-
| DB_USER | root | produser | Username for the datastore.
45-
| DB_PASS | mypass | myprodpassword | Password for the datastore.
46-
| DB_NAME | myapp | proddatabase | The name of the database inside the datastore.
47-
| DB_PORT | 3306 | 3306 | The port number for datastore.
48-
| DB_SSL | false | false | If the datastore requires SSL, set this to "true".
39+
| Variable | DEV default | PROD default | Description
40+
|-----------------------|-------------------|-------------------|----------------------
41+
| ASSETS_URL | "" (empty string) | "" (empty string) | Webpack is configured to modify static asset URLs to point to a CDN, like CloudFront. MUST end with a slash " / ", or be empty.
42+
| BASE_URL | https://myapi.app | https://myapi.app | The address of the Sails instance.
43+
| DB_HOST | localhost | localhost | The hostname of the datastore.
44+
| DB_USER | root | produser | Username for the datastore.
45+
| DB_PASS | mypass | myprodpassword | Password for the datastore.
46+
| DB_NAME | myapp | proddatabase | The name of the database inside the datastore.
47+
| DB_PORT | 3306 | 3306 | The port number for datastore.
48+
| DB_SSL | false | false | If the datastore requires SSL, set this to "true".
49+
| SESSION_SECRET | "" (empty string) | "" (empty string) | This is used to sign cookies, and SHOULD be set, especially on PRODUCTION environments.
50+
| DATA_ENCRYPTION_KEY | "" (empty string) | "" (empty string) | **Currently unused; intended for future use.**
4951

5052
## Request Logging
5153
Automatic incoming request logging, is a 2 part process. First, the [`request-logger` hook](api/hooks/request-logger.js) gathers info from the request, and creates a new [`RequestLog` record](api/models/RequestLog.js), making sure to mask anything that may be sensitive, such as passwords. Then, a custom response gathers information from the response, again, scrubbing sensitive data (using the [customToJSON](https://sailsjs.com/documentation/concepts/models-and-orm/model-settings?identity=#customtojson) feature of Sails models) to prevent leaking of password hashes, or anything else that should never be publicly accessible. The [`keepModelsSafe` helper](api/helpers/keep-models-safe.js) and the custom responses (such as [ok](api/responses/ok.js) or [serverError](api/responses/serverError.js)) are responsible for the final leg of request logs.

api/controllers/admin/create-user.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ module.exports = {
1818

1919
password: {
2020
type: 'string',
21-
required: true
21+
required: true,
22+
maxLength: 70
2223
},
2324

2425
email: {
@@ -31,7 +32,7 @@ module.exports = {
3132

3233
exits: {
3334
ok: {
34-
responseType: 'ok'
35+
responseType: 'created'
3536
},
3637
badRequest: {
3738
responseType: 'badRequest'
@@ -51,7 +52,13 @@ module.exports = {
5152
return exits.badRequest(isPasswordValid);
5253
}
5354

54-
sails.models.user.create({
55+
const foundUser = await User.findOne({email: inputs.email});
56+
57+
if (foundUser) {
58+
return exits.badRequest('Email is already in-use.');
59+
}
60+
61+
User.create({
5562
id: 'c', // required, but auto-generated
5663
firstName: inputs.firstName,
5764
lastName: inputs.lastName,

api/controllers/admin/get-me.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module.exports = {
2+
friendlyName: 'Get me',
3+
4+
description: 'Get the currently logged in user.',
5+
6+
inputs: {},
7+
8+
exits: {
9+
ok: {
10+
responseType: 'ok'
11+
},
12+
badRequest: {
13+
responseType: 'badRequest'
14+
},
15+
serverError: {
16+
responseType: 'serverError'
17+
}
18+
},
19+
20+
fn: async (inputs, exits, env) => {
21+
const foundUser = await User.findOne({id: env.req.session.user.id}); // req.session.user is filled in by the isLoggedIn policy
22+
23+
if (!foundUser) {
24+
// this should not happen
25+
return exits.serverError();
26+
}
27+
28+
return exits.ok({user: foundUser});
29+
}
30+
};

api/controllers/admin/login.js

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ module.exports = {
88
type: 'string',
99
required: true,
1010
isEmail: true,
11-
maxLength: 191 // max length of UTF8MB4 varchar
11+
maxLength: 191 // max length of UTF8-MB4 varchar
1212
},
1313

1414
password: {
1515
type: 'string',
1616
required: true,
17-
maxLength: 191
17+
maxLength: 70
1818
}
1919
},
2020

@@ -30,9 +30,47 @@ module.exports = {
3030
}
3131
},
3232

33-
fn: (inputs, exits) => {
33+
fn: async (inputs, exits, env) => {
34+
if (env.req.signedCookies[sails.config.session.name]) {
35+
return exits.badRequest('Already logged in.');
36+
}
37+
38+
const badEmailPass = 'Bad email / password combination.';
39+
const foundUser = await User.findOne({email: inputs.email});
40+
41+
if (!foundUser) {
42+
return exits.badRequest(badEmailPass);
43+
}
44+
45+
if (!await User.doPasswordsMatch(inputs.password, foundUser.password)) {
46+
return exits.badRequest(badEmailPass);
47+
}
3448

49+
const csrf = sails.helpers.generateCsrfToken();
50+
const newSession = await Session.create({
51+
id: 'c', // required, auto-generated
52+
user: foundUser.id,
53+
data: {
54+
user: {
55+
id: foundUser.id,
56+
firstName: foundUser.firstName,
57+
lastName: foundUser.lastName,
58+
email: foundUser.email,
59+
role: foundUser.role
60+
},
61+
_csrfSecret: csrf.secret
62+
}
63+
}).fetch();
3564

36-
return exits.ok();
65+
return exits.ok({
66+
cookies: [
67+
{
68+
name: sails.config.session.name,
69+
value: newSession.id,
70+
isSession: true
71+
}
72+
],
73+
_csrf: csrf.token
74+
});
3775
}
3876
};

api/controllers/admin/logout.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module.exports = {
2+
friendlyName: 'Logout',
3+
4+
description: 'Destroy current user session.',
5+
6+
inputs: {},
7+
8+
exits: {
9+
ok: {
10+
responseType: 'ok'
11+
},
12+
badRequest: {
13+
responseType: 'badRequest'
14+
},
15+
serverError: {
16+
responseType: 'serverError'
17+
}
18+
},
19+
20+
fn: async (inputs, exits, env) => {
21+
const foundSession = await Session.findOne({id: env.req.session.id});
22+
23+
if (!foundSession) {
24+
return exits.ok();
25+
}
26+
27+
await Session.destroy({id: foundSession.id});
28+
29+
return exits.ok({
30+
cookies: [
31+
{
32+
name: sails.config.session.name,
33+
value: null, // setting null will tell sails.helpers.setCookies to remove it
34+
isSession: true
35+
}
36+
]
37+
});
38+
}
39+
};

api/helpers/generate-csrf-token.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const Tokens = require('csrf');
2+
3+
module.exports = {
4+
sync: true,
5+
6+
friendlyName: 'Generate CSRF token',
7+
8+
description: 'Generate a CSRF token, and a secret.',
9+
10+
inputs: {
11+
saltLength: {
12+
type: 'number',
13+
defaultsTo: 8
14+
},
15+
16+
secretLength: {
17+
type: 'number',
18+
defaultsTo: 18
19+
}
20+
},
21+
22+
exits: {},
23+
24+
fn: (inputs, exits) => {
25+
const tokens = new Tokens({
26+
saltLength: inputs.saltLength,
27+
secretLength: inputs.secretLength
28+
});
29+
30+
const secret = tokens.secretSync();
31+
32+
return exits.success({
33+
token: tokens.create(secret),
34+
secret
35+
});
36+
}
37+
};

api/helpers/set-cookies.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
module.exports = {
2+
sync: true, // this is a synchronous helper
3+
4+
friendlyName: 'Set Cookies',
5+
6+
description: 'Removes `cookies` from `data`, and attaches them to `res`.',
7+
8+
inputs: {
9+
data: {
10+
type: 'ref',
11+
required: true
12+
},
13+
14+
res: {
15+
type: 'ref',
16+
required: true
17+
}
18+
},
19+
20+
exits: {
21+
success: {}
22+
},
23+
24+
fn: function(inputs, exits) {
25+
if (!inputs.data.cookies) {
26+
return exits.success(inputs.data);
27+
}
28+
29+
const cookies = (_.isArray(inputs.data.cookies))
30+
? inputs.data.cookies
31+
: [inputs.data.cookies];
32+
33+
cookies.map((cookie) => {
34+
const defaultCookie = {
35+
signed: true,
36+
httpOnly: true,
37+
secure: sails.config.session.cookie.secure
38+
};
39+
40+
if (cookie.value === null) {
41+
return inputs.res.clearCookie(cookie.name, defaultCookie);
42+
}
43+
44+
const newCookie = (cookie.isSession)
45+
? _.merge({}, defaultCookie, {maxAge: sails.config.session.cookie.maxAge})
46+
: defaultCookie;
47+
48+
return inputs.res.cookie(cookie.name, cookie.value, newCookie);
49+
});
50+
51+
return exits.success(_.omit(inputs.data, ['cookies']));
52+
}
53+
};
54+

api/helpers/verify-csrf-token.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const Tokens = require('csrf');
2+
3+
module.exports = {
4+
sync: true,
5+
6+
friendlyName: 'Verify CSRF token',
7+
8+
description: 'Verify a CSRF token, given a secret.',
9+
10+
inputs: {
11+
token: {
12+
type: 'string',
13+
required: true
14+
},
15+
16+
secret: {
17+
type: 'string',
18+
required: true
19+
},
20+
21+
saltLength: {
22+
type: 'number',
23+
defaultsTo: 8
24+
},
25+
26+
secretLength: {
27+
type: 'number',
28+
defaultsTo: 18
29+
}
30+
},
31+
32+
exits: {},
33+
34+
fn: (inputs, exits) => {
35+
const tokens = new Tokens({
36+
saltLength: inputs.saltLength,
37+
secretLength: inputs.secretLength
38+
});
39+
40+
return exits.success(tokens.verify(inputs.secret, inputs.token));
41+
}
42+
};

api/models/User.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@ module.exports = {
5757
firstName: {
5858
type: 'string',
5959
allowNull: true,
60-
columnType: 'varchar(191)'
60+
columnType: 'varchar(70)'
6161
},
6262

6363
lastName: {
6464
type: 'string',
6565
allowNull: true,
66-
columnType: 'varchar(191)'
66+
columnType: 'varchar(70)'
6767
},
6868

6969
password: {

0 commit comments

Comments
 (0)