Skip to content

Commit

Permalink
Merge pull request #118 from neonexus/master
Browse files Browse the repository at this point in the history
v5.1.0
  • Loading branch information
neonexus authored Jan 7, 2024
2 parents 71e46f9 + bf2c560 commit dee0b67
Show file tree
Hide file tree
Showing 36 changed files with 1,006 additions and 886 deletions.
14 changes: 6 additions & 8 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
# ║╣ ║║║ ║ ║ ║╠╦╝│ │ ││││├┤ ││ ┬
# o╚═╝═╩╝╩ ╩ ╚═╝╩╚═└─┘└─┘┘└┘└ ┴└─┘
#
# > Formatting conventions for your Sails app.
# > Formatting conventions for your app.
#
# This file (`.editorconfig`) exists to help
# maintain consistent formatting throughout the
# files in your Sails app.
# files in your app.
#
# For the sake of convention, the Sails team's
# preferred settings are included here out of the
# box. You can also change this file to fit your
# team's preferences (for example, if all of the
# developers on your team have a strong preference
# for tabs over spaces),
# These defaults have proven to be effective
# though many teams / years / projects. 4 spaces
# is much easier to see block separations than
# with just 2 (as is the Sails default).
#
# To review what each of these options mean, see:
# http://editorconfig.org/
Expand Down
2 changes: 1 addition & 1 deletion .idea/runConfigurations/Run_Tests.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions .mocharc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"bail": false,
"async-only": true,
"require": [
"test/startTests.js"
],
"spec": [
"test/unit/index.js",
"test/integration/index.js"
],
"timeout": 60000,
"checkLeaks": true,
"global": [
"_",
"sails",
"__core-js_shared__",
"Archive",
"User",
"Session",
"RequestLog",
"Log",
"__coverage__",
"match"
]
}
20 changes: 0 additions & 20 deletions .mocharc.yml

This file was deleted.

16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## [v5.1.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v5.0.0...v5.1.0) (2024-01-06)
### Features

* Created the datastore wipe script, to clear LOCAL / DEVELOPMENT datastore(s). It's just like `DROP`ing the database. Will **not** run on PRODUCTION (or when `migrate = 'safe'`).
* Converted `.mocharc.yml` -> `.mocharc` (JSON) to be more consistent.
* Made the Ngrok script capable of installing [`@ngrok/ngrok`](https://npmjs.com/package/@ngrok/ngrok) when needed.
* Minor visual fix in security settings page.
* Built the "reactivate user" endpoint.
* Corrected "edit" and "delete" user routes to use ID in the route.
* Fixed issue in 2FA backup token generation, where it was possible to generate a pure number backup token. Now will ALWAYS have at least 1 letter.
* Updated dependencies.

## [v5.0.0](https://github.com/neonexus/sails-react-bootstrap-webpack/compare/v4.3.1...v5.0.0) (2023-12-05)
### Features

Expand Down Expand Up @@ -277,4 +289,6 @@
* Fixed webpack config.
* Made `isLoggedIn` policy clear cookie if not logged in.

This changelog is incomplete, as it was not started until **v2** (and rehashing that far back in the past is a lot of work for very little gain, the commits are there). I've gone as far back as this template became "popular" (people other than me began to clone it).
<br/><br/>

#### This changelog is incomplete, as it was not started until **v2** (and rehashing that far back in the past is a lot of work for very little gain, the commits are there). I've gone as far back as this template became "popular" (people other than me began to clone it).
27 changes: 17 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@

[![Travis CI status](https://img.shields.io/travis/com/neonexus/sails-react-bootstrap-webpack.svg?branch=release&logo=travis)](https://app.travis-ci.com/github/neonexus/sails-react-bootstrap-webpack)
[![Codecov](https://img.shields.io/codecov/c/github/neonexus/sails-react-bootstrap-webpack?logo=codecov)](https://codecov.io/gh/neonexus/sails-react-bootstrap-webpack)
[![Sails version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.0.0%2Fpackage.json&query=%24.dependencies.sails&label=Sails&logo=sailsdotjs)](https://sailsjs.com)
[![React version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.0.0%2Fpackage.json&query=%24.devDependencies.react&label=React&logo=react)](https://react.dev)
[![Bootstrap version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.0.0%2Fpackage.json&query=%24.devDependencies.bootstrap&label=Bootstrap&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Webpack version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.0.0%2Fpackage.json&query=%24.devDependencies.webpack&label=Webpack&logo=webpack)](https://webpack.js.org)
[![Sails version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.1.0%2Fpackage.json&query=%24.dependencies.sails&label=Sails&logo=sailsdotjs)](https://sailsjs.com)
[![React version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.1.0%2Fpackage.json&query=%24.devDependencies.react&label=React&logo=react)](https://react.dev)
[![Bootstrap version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.1.0%2Fpackage.json&query=%24.devDependencies.bootstrap&label=Bootstrap&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Webpack version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fraw.githubusercontent.com%2Fneonexus%2Fsails-react-bootstrap-webpack%2Fv5.1.0%2Fpackage.json&query=%24.devDependencies.webpack&label=Webpack&logo=webpack)](https://webpack.js.org)

[![Discord Server](https://img.shields.io/badge/Discord_server-silver?logo=discord)](http://discord.gg/Y5K73E84Tc)

This is an easily-modifiable, opinionated, [Sails v1](https://sailsjs.com) base application, using [Webpack](https://webpack.js.org) to handle [Bootstrap](https://getbootstrap.com)
(using [SASS](https://sass-lang.com)) and [React](https://react.dev) 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](http://npmjs.com) libraries, like [Moment.js](https://momentjs.com)
This is a starter application, built on [Sails v1](https://sailsjs.com), [React](https://react.dev), [Bootstrap](https://getbootstrap.com), and [Webpack](https://webpack.js.org). It is designed
so that multiple front-ends (a customer front-end, and an admin panel perhaps; more if need be) can live side-by-side, and use the same API. It even has built-in [Ngrok support](#working-with-ngrok).
A virtual start-up in a box!

## Quick Install

```shell
npx drfg neonexus/sails-react-bootstrap-webpack my-new-site
npm run setup
npm run start OR npm run ngrok
```

## Table of Contents

Expand Down Expand Up @@ -56,7 +63,7 @@ compiled before it is handed to the end user), they can share [NPM](http://npmjs
* Automatic (incoming) request logging (manual outgoing), via Sails models / hooks.
* Setup for Webpack auto-reload dev server. Build; save; auto-reload.
* Setup so Sails will serve Webpack-built bundles as separate apps (so, a marketing site, and an admin site can live side-by-side).
* More than a few custom [helper functions](api/helpers) to make life a little easier.
* More than a few custom [API helper functions](api/helpers) to make life a little easier.
* Includes [react-bootstrap](https://www.npmjs.com/package/react-bootstrap) to make using Bootstrap styles / features with React easier.
* Schema validation and enforcement for `PRODUCTION`. See [schema validation and enforcement](#schema-validation-and-enforcement).
* New passwords will 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
Expand Down Expand Up @@ -449,7 +456,7 @@ It may also be a good idea to consider using something like [Nginx](https://ngin

## Schema Validation and Enforcement

This repo is set up for `MySQL` (can LIKELY be used with most if not all other SQL-based datastores [I have not tried]). If you plan to use a different datastore, you will likely want to disable this
This feature is designed for `MySQL` (can LIKELY be used with most if not all other SQL-based datastores [I have not tried]). If you plan to use a different datastore, you will likely want to disable this
feature.

Inside [`config/bootstrap.js`](config/bootstrap.js) is a bit of logic (**HEAVILY ROOTED IN NATIVE `MySQL` QUERIES**), which validates column types in the `PRODUCTION` database (
Expand Down
6 changes: 3 additions & 3 deletions api/controllers/admin/create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ module.exports = {
type: 'string',
isEmail: true,
required: true,
maxLength: 191
maxLength: 191 // Max size of an utf8mb4 varchar in MySQL.
},

role: {
Expand Down Expand Up @@ -69,7 +69,7 @@ module.exports = {
isPasswordValid = true;
password = sails.helpers.generateToken().substring(0, 42);

// should probably send password somehow; it will be scrubbed in the custom response (would be hashed anyway...)
// should probably send password somehow; it will be scrubbed in the response (would be hashed anyway...)
}

if (isPasswordValid !== true) {
Expand Down Expand Up @@ -99,7 +99,7 @@ module.exports = {
}

/**
* TODO: We should probably email the new user their new account info here if the password was generated (!inputs.setPassword)...
* TODO: We should probably email the new user their new account info here if the password was generated (inputs.generatePassword)...
*/

return exits.created({user});
Expand Down
4 changes: 2 additions & 2 deletions api/controllers/admin/edit-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ module.exports = {

setPassword: {
type: 'boolean',
defaultsTo: true
defaultsTo: false
}
},

exits: {
ok: {
responseType: 'created'
responseType: 'ok'
},
badRequest: {
responseType: 'badRequest'
Expand Down
47 changes: 47 additions & 0 deletions api/controllers/admin/reactivate-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
module.exports = {
friendlyName: 'Reactivate User',

description: 'Reactivate a soft-deleted user.',

inputs: {
id: {
type: 'string',
required: true,
isUUID: true
}
},

exits: {
ok: {
responseType: 'ok'
},
badRequest: {
responseType: 'badRequest'
},
serverError: {
responseType: 'serverError'
}
},

fn: async (inputs, exits) => {
const foundUser = await sails.models.user.findOne({id: inputs.id});

if (!foundUser) {
return exits.badRequest('There is no user with that ID.');
}

if (!foundUser.deletedAt) {
return exits.badRequest('User is already active.');
}

const activeUser = await sails.models.user.findOne({email: foundUser.email, deletedAt: null});

if (activeUser) {
return exits.badRequest('There is already an active user with the email address ' + activeUser.email);
}

await sails.models.user.update({id: inputs.id}).set({deletedAt: null});

return exits.ok();
}
};
14 changes: 12 additions & 2 deletions api/helpers/generate-backup-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,21 @@ module.exports = {
exits: {},

fn: (inputs, exits) => {
const token = sails.helpers.generateToken();
let token = sails.helpers.generateToken();
let backupTokens = [];

let last = null;
for (let i = 0; i < 10; ++i) {
backupTokens[i] = token.substring(i * 8, (i * 8) + 8);
do {
// Regenerate the token if this is our second time around in the do...while loop.
if (last === i) {
token = sails.helpers.generateToken();
} else {
last = i;
}

backupTokens[i] = token.substring(i * 8, (i * 8) + 8);
} while (!isNaN(backupTokens[i])); // Don't let pure number tokens through. They MUST have at least 1 letter.
}

return exits.success(backupTokens);
Expand Down
2 changes: 1 addition & 1 deletion api/models/Log.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {

description: {
type: 'string',
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 charset
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

data: {
Expand Down
2 changes: 1 addition & 1 deletion api/models/OTP.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module.exports = {

secret: {
type: 'string',
columnType: 'varchar(191)',
columnType: 'varchar(191)', // 191 is the max length to safely use the utf8mb4 varchar.
encrypt: true,
required: true
},
Expand Down
4 changes: 2 additions & 2 deletions api/models/RequestLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ module.exports = {
host: {
type: 'string',
required: true,
columnType: 'varchar(191)'
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

path: {
type: 'string',
required: true,
columnType: 'varchar(191)'
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

headers: {
Expand Down
22 changes: 11 additions & 11 deletions api/models/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ module.exports = {
isEmail: true,
required: true,
// unique: true, // can NOT be unique, if we are using soft-deleted users; controller must deal with uniqueness
columnType: 'varchar(191)'
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

firstName: {
Expand All @@ -71,7 +71,7 @@ module.exports = {
password: {
type: 'string',
allowNull: true,
columnType: 'varchar(191)',
columnType: 'varchar(191)', // 191 is the max length to safely use the utf8mb4 varchar.
// see: https://sailsjs.com/documentation/reference/waterline-orm/queries/decrypt
// You will need to "decrypt" the user object before you can check if the password is valid.
// encrypt: true // currently, does not work as intended, as password is encrypted before we can hash it
Expand All @@ -80,13 +80,13 @@ module.exports = {
verificationKey: { // placeholder for something like email verification
type: 'string',
allowNull: true,
columnType: 'varchar(191)'
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

avatar: {
type: 'string',
isURL: true,
columnType: 'varchar(191)'
columnType: 'varchar(191)' // 191 is the max length to safely use the utf8mb4 varchar.
},

isGravatar: {
Expand Down Expand Up @@ -166,23 +166,23 @@ module.exports = {
},

beforeUpdate: async function(user, next) {
if (user.email && user.email !== '') {
if (user.email && user.email.trim().length) {
const email = user.email.toLowerCase().trim();

user.email = email;
user.avatar = getGravatarUrl(email);
}

if (user.firstName && user.firstName !== '') {
user.firstName = forceUppercaseOnFirst(user.firstName);
if (user.firstName && user.firstName.trim().length) {
user.firstName = forceUppercaseOnFirst(user.firstName.trim());
}

if (user.lastName && user.lastName !== '') {
user.lastName = forceUppercaseOnFirst(user.lastName);
if (user.lastName && user.lastName.trim().length) {
user.lastName = forceUppercaseOnFirst(user.lastName.trim());
}

if (user.password && user.password !== '') {
user.password = await updatePassword(user.password);
if (user.password && user.password !== '' && user.password.length > 7) {
user.password = await updatePassword(user.password.trim());
}

return next();
Expand Down
7 changes: 5 additions & 2 deletions assets/src/Admin/AdminRouter.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, StrictMode, Suspense, lazy } from 'react';
import {Component, StrictMode, Suspense, lazy} from 'react';
import PropTypes from 'prop-types';
import '../../styles/admin/admin.scss';
import {
Expand Down Expand Up @@ -27,7 +27,10 @@ function RenderOrLogin(props) {

if (props.api) {
return (
<Login api={props.api} />
<>
<NavBar handleLogout={() => alert('logout')} />
<Login api={props.api} />
</>
);
}

Expand Down
2 changes: 1 addition & 1 deletion assets/src/Admin/Login.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class Login extends Component {
return done(body.user);
}

// This should not happen, as the error handler below should display the error from the server.
// This should not happen, as the `defaultAPIErrorHandler` should display the error from the server.
alert('Unknown error. Please try again. If this error persists, please contact support.');
console.error('Something is wrong in the handleLogin API post...');
}, defaultAPIErrorHandler);
Expand Down
Loading

0 comments on commit dee0b67

Please sign in to comment.