Skip to content

Commit

Permalink
user authentication & registration implemented inside Dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
semeniuk committed Apr 7, 2020
1 parent eb0e64d commit 29657ac
Show file tree
Hide file tree
Showing 28 changed files with 666 additions and 49 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog

## **2.0.2** - *2020-xx-xx*
## **2.1.0** - *2020-04-07*
* API - `REGISTRATION_MODE` option added (possible values are `open` and `invite`);
* Facade - show Slick header slideshow only on homepage;
* Dashboard - check user authentication before rendering page;
* Dashboard:
* check user authentication before rendering page;
* user authentication & registration implemented inside Dashboard;
* Gallery:
* `GALLERY_NEW_PHOTOS_FIRST` option added;
* resize photoset cover;
Expand Down
22 changes: 15 additions & 7 deletions api/lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ var security = require("./security"),
strategies = {
local: new LocalStrategy({
// by default, local strategy uses username and password, we will override with email
usernameField: "email",
usernameField: "username",
passwordField: "password",
passReqToCallback: true // allows us to pass back the entire request to the callback
},
Expand All @@ -31,23 +31,23 @@ var security = require("./security"),
// find a user
let user;
try {
user = await store.users.getByEmail(username);
user = await store.users[username.includes("@") ? "getByEmail": "getByUsername"](username);
logger.dir(user);
} catch (err) {
logger.error(err);
return done(null, false, "Unknown error");
return done(null, false, { message: "Unknown error" });
}

// if no user is found or password is wrong return error
if (!user) {
logger.info("User not found");
return done(null, false, "User was not found or password is incorrect");
return done(null, false, { message: "User was not found or password is incorrect" });
}

switch (config.passwordHashAlgorithm) {
case "md5":
if (md5(password) !== user.password) {
return done(null, false, "User was not found or password is incorrect");
return done(null, false, { message: "User was not found or password is incorrect" });
}
// all is well, return successful user
logger.info("Signin successful");
Expand All @@ -56,11 +56,11 @@ var security = require("./security"),
security.bcryptCheck(password, user.password, function (err, result) {
if (err) {
logger.error("password check failed", err);
return done(null, false, "Unknown error");
return done(null, false, { message: "Unknown error" });
}
if (!result) {
logger.info(!user ? "User not found" : "Password is incorrect");
return done(null, false, "User was not found or password is incorrect");
return done(null, false, { message: "User was not found or password is incorrect" });
}

// all is well, return successful user
Expand Down Expand Up @@ -231,6 +231,14 @@ var security = require("./security"),
module.exports = function (express) {
logger.info("Init Authentication");

if (config.registrationMode === "open") {
express.set("registration-enabled", true);
} else {
store.users.count().then(count => {
express.set("registration-enabled", count === 0);
});
}

// used to serialize the user for the session
passport.serializeUser(function (user, done) {
logger.debug("serializeUser " + user.email);
Expand Down
2 changes: 1 addition & 1 deletion api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "express-template-api",
"version": "2.0.1",
"version": "2.1.0",
"description": "Website template (skeleton) based on Express.js 4, Vue.js and Vuetify 2",
"author": "NordicSoft",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions api/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module.exports = function (express) {
express.use("/profile", signinRequired, require("./routes/profile"));
express.use("/gallery", signinRequired, require("./routes/gallery"));
express.use("/facade", facadeOnly, require("./routes/facade"));
express.use("/auth", require("./routes/auth"));
express.use("/", signinRequired, require("./routes"));

// handle 404
Expand Down
114 changes: 114 additions & 0 deletions api/routes/auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
const express = require("express"),
router = express.Router(),
logger = require("@logger"),
config = require("@config"),
store = require("@store");

function signin(req, res) {
req.signin(function (err, user, info) {
if (user) {
return res.json({});
}
return res.status(400).json(info);
});
}

async function refreshRegistrationEnabled(app) {
let registrationEnabled = config.registrationMode === "open" || await store.users.count() === 0;
app.set("registration-enabled", registrationEnabled);
}

router.get("/", function (req, res) {
return res.json({
isAuthenticated: req.isAuthenticated(),
registrationEnabled: req.app.get("registration-enabled")
});
});

router.get("/check-email/:email", async function (req, res) {
var email = req.params.email,
user = await store.users.getByEmail(email);
return res.json(!user);
});

router.get("/check-username/:username", async function (req, res) {
var username = req.params.username,
user = await store.users.getByUsername(username);
return res.json(!user);
});

router.post("/signin", signin);

router.post("/register", async function (req, res) {
if (req.isAuthenticated()) {
return res.status(400).json({ message: "User is authenticated"});
}

if (!req.app.get("registration-enabled")) {
return res.sendStatus(404);
}

let name = req.body.name,
email = req.body.email,
username = req.body.username,
password = req.body.password,
passwordConfirm = req.body.passwordConfirm;

logger.info(`Register new user ${name} (${email})`);

if (password !== passwordConfirm) {
return res.status(400).json({
message: "Password and confirm password does not match",
});
}

// check username
if (await store.users.getByUsername(username)) {
return res.status(400).json({ message: "This username is already taken" });
}

// check email
if (await store.users.getByEmail(email)) {
return res.status(400).json({ message: "This email is already taken" });
}

let security = require("@lib/security");
let user = {
name,
email,
username
};

let usersCount = await store.users.count();
if (usersCount === 0) {
user.roles = ["owner"];
}

switch (config.passwordHashAlgorithm) {
case "md5":
user.password = security.md5(password);
await store.users.insert(user);
refreshRegistrationEnabled(req.app);
signin(req, res);
break;
case "bcrypt":
security.bcryptHash(password, async function (err, passwordHash) {
user.password = passwordHash;
await store.users.insert(user);
refreshRegistrationEnabled(req.app);
signin(req, res);
});
break;
default:
logger.error("Incorrect passwordHashAlgorithm specified in config.json");
break;
}
});

router.get("/signout", function (req, res) {
req.signout();
req.session.destroy();
return res.json(true);
});

module.exports = router;
5 changes: 1 addition & 4 deletions api/routes/facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ const express = require("express"),
store = require("@store");

router.get("/users/can-register", async function (req, res) {
if (config.registrationMode === "open" || await store.users.count() === 0) {
return res.json(true);
}
return res.json(false);
return res.json(req.app.get("registration-enabled"));
});

router.get("/user/:usernameOrEmail", async function (req, res) {
Expand Down
4 changes: 0 additions & 4 deletions api/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ const express = require("express"),
config = require("@config"),
vuetifyFileBrowserSDK = require("vuetify-file-browser-server/sdk");

router.get("/auth", (req, res) => {
res.json(true);
});

router.post("/send-email", async (req, res) => {
let subject = req.body.subject,
message = req.body.message;
Expand Down
3 changes: 2 additions & 1 deletion dashboard/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ PORT=8083
BASE_URL=/dashboard/
COMMON_STATIC_PATH=../public
VUE_APP_API_BASE_URL=http://localhost:8080/api
VUE_APP_SIGNIN_URL=/signin?return=
VUE_APP_FACADE_URL=/
VUE_APP_SIGNIN_URL=/dashboard/auth?return=
VUE_APP_GALLERY_DASHBOARD_THUMBNAIL_SUFFIX=tm
VUE_APP_GALLERY_NEW_PHOTOS_FIRST=true
9 changes: 8 additions & 1 deletion dashboard/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ module.exports = {
},
rules: {
//"no-console": process.env.NODE_ENV === "production" ? "error" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off"
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
indent: ["error", 4, { SwitchCase: 1 }],
"linebreak-style": [
process.env.NODE_ENV === "production" ? "error" : "off",
"windows"
],
quotes: ["error", "double"],
semi: ["error", "always"]
},
overrides: [
{
Expand Down
2 changes: 1 addition & 1 deletion dashboard/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "express-template-dashboard",
"version": "2.0.1",
"version": "2.1.0",
"private": true,
"main": "server.js",
"scripts": {
Expand Down
41 changes: 41 additions & 0 deletions dashboard/public/error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="favicon.ico">
<title>Unknown Error</title>
</head>
<style>
* {
margin: 0;
padding: 0;
}
html, body {
height: 100%;
}
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
img {
max-width: 80%;
max-height: 80%;
margin: 0 auto 20px;
}
h1 {
font-family: 'Roboto',-apple-system,BlinkMacSystemFont,'Segoe UI','Oxygen','Ubuntu','Fira Sans','Droid Sans','Helvetica Neue',sans-serif;
font-size: 22px;
text-align: center;
}
</style>
<body>
<img src="img/error.svg" />
<h1>Unknown error occurred</h1>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
1 change: 1 addition & 0 deletions dashboard/public/img/error.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 15 additions & 3 deletions dashboard/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require("dotenv-defaults").config();
const express = require("express"),
path = require("path"),
history = require("connect-history-api-fallback"),
publicPath = process.env.BASE_URL,
port = parseInt(process.env.DASHBOARD_PORT || process.env.PORT || 8083);

const app = express();
Expand All @@ -18,18 +19,29 @@ const publicMiddleware = express.static(

// https://github.com/bripkens/connect-history-api-fallback/blob/master/examples/static-files-and-index-rewrite/README.md

app.use(process.env.BASE_URL, distMiddleware);
app.use(publicPath, distMiddleware);
app.use(publicMiddleware);

app.use(
history({
disableDotRule: true,
verbose: true,
index: path.join(process.env.BASE_URL, "index.html")
index: path.join(publicPath, "index.html"),
rewrites: [
{
from: new RegExp(`^${publicPath}auth.*$`),
to: `${publicPath}auth.html`
},
{
from: new RegExp(`^${publicPath}error/?$`),
to: `${publicPath}error.html`
},
{ from: /./, to: publicPath + "index.html" }
]
})
);

app.use(process.env.BASE_URL, distMiddleware);
app.use(publicPath, distMiddleware);
app.use(publicMiddleware);

app.get("/", function(req, res) {
Expand Down
Loading

0 comments on commit 29657ac

Please sign in to comment.