Skip to content

Commit 37a2a30

Browse files
committed
Added a few more comments. Updated NPM packages. Built better testing for project hook. Added Dockerfile.
1 parent 3cebe1e commit 37a2a30

28 files changed

+980
-967
lines changed

.dockerignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules
2+
.git
3+
.gitignore
4+
config/local.js
5+
.tmp
6+
.idea
7+
8+
README.md
9+
LICENSE

.idea/dictionaries/neonexusdemortis.xml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.mocharc.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ async-only: true # require use of "done" cb or an async function (a Promise)
33
require:
44
- 'test/hooks.js'
55
spec: 'test/**/*.test.js'
6-
timeout: 5000 # Give Sails some breathing room... We are building schemas / data fixtures.
6+
timeout: 10000 # Give Sails some breathing room... We are building schemas / data fixtures.
77
checkLeaks: true
88
global:
99
- '_'

.sailsrc

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"hooks": {
1010
"grunt": false,
1111
"session": false,
12-
"csrf": false
12+
"csrf": false,
13+
"blueprints": false
1314
}
1415
}

Dockerfile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM node:12.18
2+
MAINTAINER NeoNexus DeMortis
3+
4+
RUN apt-get update && apt-get upgrade -y
5+
RUN apt-get install -y curl ntp nano
6+
RUN mkdir /var/www && mkdir /var/www/myapp
7+
WORKDIR /var/www/myapp
8+
9+
EXPOSE 1337
10+
# REMEMBER! NEVER STORE SECRETS, DEK's, PASSWORDS, OR ANYTHING OF A SENSITIVE NATURE IN SOURCE CONTROL! USE ENVIRONMENT VARIABLES!
11+
ENV PORT=1337 DB_HOSTNAME=localhost DB_USERNAME=user DB_PASSWORD=pass DB_NAME=myappdb DB_PORT=3306 DB_SSL=true DATA_ENCRYPTION_KEY=1234abcd4321asdf0987lkjh SESSION_SECRET=0987poiuqwer1234zxcvmnbv
12+
13+
# This keeps builds more efficient, because we can use cache more effectively.
14+
COPY package.json /var/www/myapp/package.json
15+
RUN npm install
16+
17+
COPY . /var/www/myapp/
18+
RUN npm run build
19+
20+
# Expose the compiled public assets, so Nginx can route to them, instead of using Sails to do the file serving.
21+
VOLUME /var/www/myapp/.tmp/public
22+
23+
CMD NODE_ENV=production node app.js --max-stack-size 32000

README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# sails-react-bootstrap-webpack
22

3-
[![Join the chat at https://gitter.im/sails-react-bootstrap-webpack/community](https://badges.gitter.im/sails-react-bootstrap-webpack/community.svg)](https://gitter.im/sails-react-bootstrap-webpack/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3+
This is an opinionated base [Sails v1](https://sailsjs.com) application, using Webpack to handle Bootstrap (SASS) and React builds. It is designed such that, one can build multiple React frontends (an admin panel, and a customer site maybe), that use the same API backend. This allows developers to easily share React components across different frontends / applications. Also, because the backend and frontend are in the same repo (and the frontend is compiled before it is handed to the end user), they can share NPM libraries, like [Moment.js](https://momentjs.com)
44

5-
This is an opinionated base [Sails v1](https://sailsjs.com) application, using Webpack to handle Bootstrap (SASS) and React.
5+
Need help? Want to hire me to build your next app or prototype? You can contact me any time via Gitter: [![Join the chat at https://gitter.im/sails-react-bootstrap-webpack/community](https://badges.gitter.im/sails-react-bootstrap-webpack/community.svg)](https://gitter.im/sails-react-bootstrap-webpack/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
66

77
# Branch Warning
88
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.
@@ -22,6 +22,9 @@ The `master` branch is experimental, and the [release branch](https://github.com
2222
## How to Use
2323
This repo is not installable via `npm`. Instead, Github provides a handy "Use this template" (green) button at the top of this page. That will create a special fork of this repo (so there is a single, init commit, instead of the commit history from this repo).
2424

25+
### Configuration
26+
In the `config` folder, there is `local.js.sample` file, which is meant to be copied to `local.js`. This file is ignored by Git, and intended for use in local development, not remote servers.
27+
2528
### Scripts built into [`package.json`](package.json):
2629

2730
| Command | Description

api/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,34 @@ This is where all API controllers, custom hooks, helpers, models, policies and r
66

77
Controllers handle API requests, and decide how to respond, using the custom responses, which track API requests in our datastore.
88

9+
See: [Actions and Controllers](https://sailsjs.com/documentation/concepts/actions-and-controllers)
10+
911
## Helpers
1012

1113
Helpers are generic, reusable functions used by multiple controllers (or hooks, policies, etc).
1214

15+
See: [Helpers](https://sailsjs.com/documentation/concepts/helpers)
16+
1317
## Hooks
1418

1519
Custom hooks (currently only the 1), are used to tap into different parts of a request. The custom hook setup in this repo, is designed to start recording an API request, while the custom responses finalize the record.
1620

21+
See: [Project Hooks](https://sailsjs.com/documentation/concepts/extending-sails/hooks/project-hooks)
22+
1723
## Models
1824

1925
The data models inform Sails (really Waterline) how to create / modify tables (if `sails.config.models.migrate === 'alter'`), or our custom [schema validation and enforcement](../README.md#schema-validation-and-enforcement) on how it should behave.
2026

27+
See: [Models](https://sailsjs.com/documentation/concepts/models-and-orm/models)
28+
2129
## Policies
2230

2331
Policies are simple functions, that determine if a request should continue, or fail. These are configured in [`config/policies.js`](../config/policies.js)
2432

33+
See: [Policies](https://sailsjs.com/documentation/concepts/policies)
34+
2535
## Responses
2636

2737
These custom responses handle the final leg of request logging. They are responsible for ensuring data isn't leaked, by utilizing the `customToJSON` functionality of models.
38+
39+
See: [Custom Responses](https://sailsjs.com/documentation/concepts/extending-sails/custom-responses)

api/controllers/admin/login.js

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ module.exports = {
6363
}).fetch();
6464

6565
return exits.ok({
66+
// Cookies are automatically handled in our custom response `ok`.
6667
cookies: [
6768
{
6869
name: sails.config.session.name,

api/helpers/generate-token.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
inputs: {
1212
extra: {
1313
type: 'string',
14+
description: 'A bit of random, extra bits to change up the hash.',
1415
defaultsTo: 'Evil will always triumph, because good is dumb. -Lord Helmet'
1516
}
1617
},
@@ -23,7 +24,7 @@ module.exports = {
2324
.update(
2425
crypto.randomBytes(21)
2526
+ moment(new Date()).format()
26-
+ 'I am a little tea pot'
27+
+ 'I am a tea pot' // The best HTTP status code
2728
+ inputs.extra
2829
+ crypto.randomBytes(21)
2930
)

api/helpers/keep-models-safe.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module.exports = {
2-
friendlyName: 'Keep Model Safe',
2+
friendlyName: 'Keep Models Safe',
33

4-
description: 'Enforce custom .toJSON() is called recursively.',
4+
description: 'Enforce custom .toJSON(), called recursively on the input data object to prevent data leaking to outside world.',
55

66
sync: true, // function is not async
77

@@ -17,17 +17,18 @@ module.exports = {
1717
},
1818

1919
fn: (inputs, exits) => {
20-
const dataCopy = _.merge({}, inputs.data);
20+
const dataCopy = _.merge({}, inputs.data); // don't modify the object given
2121

22-
// force all objects to their JSON formats, if it has said function
23-
// this prevents accidental leaking of sensitive data, by utilizing customToJSON on models
22+
// Force all objects to their JSON formats, if it has .toJSON() function.
23+
// This prevents accidental leaking of sensitive data, by utilizing .customToJSON() in model definitions.
2424
(function findTheJson(data) {
2525
_.forEach(data, (val, key) => {
2626
if (_.isObject(val)) {
2727
return findTheJson(val);
2828
}
2929

3030
if (val && val.toJSON && typeof val.toJSON === 'function') {
31+
// If this is a moment.js object, force it to .format() instead of .toJSON().
3132
if (val.toDate && typeof val.toDate === 'function' && typeof val.format === 'function') {
3233
data[key] = val.format();
3334
} else {

api/helpers/set-cookies.js renamed to api/helpers/set-or-remove-cookies.js

+22-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ module.exports = {
1818
},
1919

2020
exits: {
21-
success: {}
21+
success: {
22+
description: 'Returns `data` input, minus the cookies object.'
23+
},
24+
25+
missingName: {
26+
description: 'Cookie name is missing.'
27+
},
28+
29+
missingValue: {
30+
description: 'Cookie value is not defined.'
31+
}
2232
},
2333

2434
fn: function(inputs, exits) {
@@ -31,10 +41,18 @@ module.exports = {
3141
: [inputs.data.cookies];
3242

3343
cookies.map((cookie) => {
44+
if (!_.isDefined(cookie.name) || !_.isString(cookie.name) || _.isEmpty(cookie.name)) {
45+
throw 'missingName';
46+
}
47+
48+
if (!_.isDefined(cookie.value)) {
49+
throw 'missingValue';
50+
}
51+
3452
const defaultCookie = {
35-
signed: true,
36-
httpOnly: true,
37-
secure: sails.config.session.cookie.secure
53+
signed: true, // Sign the cookie to prevent tampering.
54+
httpOnly: true, // Prevent JS from being able to read / write to this cookie in most browsers.
55+
secure: sails.config.session.cookie.secure // Only allow over HTTPS.
3856
};
3957

4058
if (cookie.value === null) {

api/helpers/update-csrf.js

+13-2
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,27 @@ module.exports = {
1515
}
1616
},
1717

18-
exits: {},
18+
exits: {
19+
success: {},
20+
21+
serverError: {}
22+
},
1923

2024
fn: async (inputs, exits) => {
25+
// Do nothing if we don't have a session, or this is a GET request.
2126
if (inputs.req.method === 'GET' || !inputs.req.session || !inputs.req.session.user || !inputs.req.session.id) {
2227
return exits.success(inputs.data);
2328
}
2429

2530
const foundSession = await Session.findOne({id: inputs.req.session.id});
2631

27-
const csrf = sails.helpers.generateCsrfToken();
32+
if (!foundSession) {
33+
throw new exits.serverError('Session could not be found in Update CSRF helper.');
34+
}
35+
36+
const csrf = sails.helpers.generateCsrfTokenAndSecret();
37+
38+
// Update stored session data, so we can compare our token to the secret on the next request (handled in the isLoggedIn policy).
2839
const newData = _.merge({}, foundSession.data, {_csrfSecret: csrf.secret});
2940

3041
Session.update({id: inputs.req.session.id}).set({

api/models/Session.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = {
99
},
1010

1111
user: {
12-
model: 'user',
12+
model: 'User',
1313
required: true
1414
},
1515

api/policies/isLoggedIn.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module.exports = async function(req, res, next) {
22
const sessionId = req.signedCookies[sails.config.session.name] || null;
33

44
if (sessionId) {
5-
const foundSession = await Session.findOne({id: sessionId});
5+
const foundSession = await sails.models.session.findOne({id: sessionId});
66

77
if (foundSession && foundSession.data.user) {
88
req.session = {id: sessionId, user: foundSession.data.user};
@@ -18,6 +18,7 @@ module.exports = async function(req, res, next) {
1818
}
1919
}
2020

21+
// Doesn't look like this session is valid, remove the cookie.
2122
res.clearCookie(sails.config.session.name, {signed: true, httpOnly: true, secure: sails.config.session.cookie.secure});
2223
}
2324

api/responses/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Custom Responses
2+
3+
Here are the custom responses, used to capture the final leg of a request for logging, including run time.
4+
5+
These responses are also responsible for running the [`keepModelsSafe`](../helpers/keep-models-safe.js) helper, to prevent leaking of sensitive info, by utilizing the `customToJSON` functionality of models.

api/responses/ok.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ module.exports = async function sendOK(data) {
1010
data = {message: data};
1111
}
1212

13+
// ensure our models stay safe out there
1314
data = sails.helpers.keepModelsSafe(data);
14-
data = sails.helpers.setCookies(data, res);
15+
16+
// set or remove cookies
17+
data = sails.helpers.setOrRemoveCookies(data, res);
18+
19+
// handle CSRF tokens
1520
data = await sails.helpers.updateCsrf(data, req);
1621

1722
const out = _.merge({success: true}, data);

assets/src/Admin/AdminRouter.jsx

+19-25
Original file line numberDiff line numberDiff line change
@@ -73,31 +73,25 @@ class AdminRouter extends React.Component {
7373
}
7474

7575
return (
76-
<Router>
77-
<Route
78-
render={({location}) => (
79-
<APIProvider>
80-
<UserProvider>
81-
<RenderOrLogin location={location}>
82-
<SidebarNav>
83-
<Switch>
84-
<Route path="/admin/dashboard">
85-
<Dashboard />
86-
</Route>
87-
<Route path="/admin/upgrade">
88-
<Upgrade />
89-
</Route>
90-
<Route>
91-
<Redirect to="/admin/dashboard" />
92-
</Route>
93-
</Switch>
94-
</SidebarNav>
95-
</RenderOrLogin>
96-
</UserProvider>
97-
</APIProvider>
98-
)}
99-
/>
100-
</Router>
76+
<APIProvider>
77+
<UserProvider>
78+
<RenderOrLogin>
79+
<SidebarNav>
80+
<Switch>
81+
<Route path="/admin/dashboard">
82+
<Dashboard />
83+
</Route>
84+
<Route path="/admin/upgrade">
85+
<Upgrade />
86+
</Route>
87+
<Route>
88+
<Redirect to="/admin/dashboard" />
89+
</Route>
90+
</Switch>
91+
</SidebarNav>
92+
</RenderOrLogin>
93+
</UserProvider>
94+
</APIProvider>
10195
);
10296
}
10397
}

assets/src/index.jsx

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import AdminRouter from './Admin/AdminRouter';
1010
import MainRouter from './Main/MainRouter';
1111

1212
function IndexApp() {
13+
// This file is here mainly for Webpack's dev server; not used in remote settings.
14+
// Webpack is configured to build the individual apps in their own folders in `.tmp/public` via `npm run build`.
15+
// Sails will handle the webapp redirects in remote configurations.
1316
return (
1417
<Router>
1518
<Switch>

config/README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ The majority of configuration files live in this folder. `local.js.sample` is in
44

55
## Things to keep in-mind
66

7-
NEVER store credentials, security keys, session secrets, etc in Git-tracked files!
7+
* NEVER store credentials, security keys, session secrets, etc in Git-tracked files!
8+
* `bootstrap.js` Has nothing to do with the CSS framework Bootstrap. It is actually the last file Sails runs before being fully "lifted". Currently, this repo uses this file for schema validation and enforcement on production servers. Read more about this in the main [README](../README.md#schema-validation-and-enforcement).
89

9-
`bootstrap.js` Has nothing to do with the CSS framework Bootstrap. It is actually the last file Sails runs before being fully "lifted". Currently, this repo uses this file for schema validation and enforcement on production servers. Read more about this in the main [README](../README.md#schema-validation-and-enforcement).
10+
### Useful Links
11+
12+
* [Sails documentation for configuration](https://sailsjs.com/documentation/concepts/configuration)
13+
* [Sails bootstrap documentation](https://sailsjs.com/documentation/reference/configuration/sails-config-bootstrap)

config/env/production.js

+5-8
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ module.exports = {
170170
* > (For a full list, see https://sailsjs.com/plugins/sessions) *
171171
* *
172172
***************************************************************************/
173-
adapter: 'express-mysql-session',
173+
174+
// We are handling sessions without Sails' middleware, and using Models directly.
175+
176+
//adapter: 'express-mysql-session',
174177
// adapter: '@sailshq/connect-redis',
175178
// url: 'redis://user:password@localhost:6379/databasenumber',
176179
//--------------------------------------------------------------------------
@@ -208,18 +211,12 @@ module.exports = {
208211
***************************************************************************/
209212
cookie: {
210213
secure: true,
214+
// this age is when we choose not to use "session" cookies, and want max-age instead.
211215
maxAge: 24 * 60 * 60 * 1000 // 24 hours
212216
},
213217

214218
// https://jsfiddle.net/fsbd3ey5/1/
215219
secret: process.env.SESSION_SECRET, // DO NOT STORE THIS IN SOURCE CONTROL!!!
216-
217-
host: process.env.DB_HOSTNAME || 'localhost',
218-
user: process.env.DB_USERNAME || 'produser',
219-
password: process.env.DB_PASSWORD || 'myprodpassword',
220-
database: process.env.DB_NAME || 'proddatabase',
221-
port: process.env.DB_PORT || 3306,
222-
ssl: (process.env.DB_SSL === 'true')
223220
},
224221

225222

0 commit comments

Comments
 (0)