Skip to content

Commit

Permalink
Merge pull request #3 from semeniuk/master
Browse files Browse the repository at this point in the history
v2.1.0
  • Loading branch information
NordicSoft authored Apr 7, 2020
2 parents 6f9553c + 29657ac commit f063281
Show file tree
Hide file tree
Showing 59 changed files with 1,241 additions and 330 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode
node_modules
*.log
/facade/public/service-worker.js
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## **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;
* user authentication & registration implemented inside Dashboard;
* Gallery:
* `GALLERY_NEW_PHOTOS_FIRST` option added;
* resize photoset cover;
* `jimp` support dropped;
* refresh photoset's cover after changing it;
* view photo set - load the smallest thumbnails by default;
* Facade - consider `GALLERY_IMAGE_SIZES` and `GALLERY_PHOTOSET_COVER_SIZES` options in `justifiedGallery`;
* image loading adjusted (show blurred thumbnail while loading larger image);
* show last `GALLERY_LAST_PHOTOS_COUNT` photos below photo sets;
* navigation in `All Photos` photo set;
* photo sets order - init;
* dev proxy - copy dev URL to clipboard;
* minor changes;

## **2.0.1** - *2020-04-01*
* ability to use MongoDB as session storage added;
* consider `FACADE_PORT`, `DASHBOARD_PORT`, `API_PORT` env vars;
Expand Down
14 changes: 8 additions & 6 deletions api/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ LOG_CONSOLE_PREFIX=api
LOG_CONSOLE_FG_COLOR=azure
LOG_CONSOLE_BG_COLOR=turquoise

// `open` or `invite`
REGISTRATION_MODE=open

# password hashing algorithm (md5 or bcrypt; for bcrypt install https://www.npmjs.com/package/bcrypt)
PASSWORD_HASH_ALGORITHM=md5

Expand All @@ -36,10 +39,6 @@ AWS_S3_BUCKET=CHANGE_ME
AWS_SES_FROM=CHANGE_ME
AWS_SES_SEND_RATE=1

# Telegram
TELEGRAM_BOT_TOKEN=CHANGE_ME
TELEGRAM_CHAT_ID=CHANGE_ME

# FileBrowser
FILEBROWSER_UPLOAD_PATH=../public/uploads
FILEBROWSER_ROOT_PATH=../public
Expand All @@ -56,5 +55,8 @@ GALLERY_TRASH_PATH=gallery/trash
# image processing module (sharp or jimp, must be installed)
GALLERY_IMAGE_PROCESSING_MODULE=sharp
GALLERY_JPG_QUALITY=90
# comma-separated image sizes: "<suffix>:<width>x<height>"
GALLERY_IMAGE_SIZES=ts:192x192,tm:256x256,tl:512x512,s:900x600,m:1200x800,l:1860x1020
# comma-separated image sizes. Format of each size: "<suffix>:<fit>:<width>x<height>"
# default fit is `cover`. See https://sharp.pixelplumbing.com/api-resize
GALLERY_IMAGE_SIZES=ts:inside:192x192,tm:inside:256x256,tl:inside:512x512,s:inside:900x600,m:inside:1200x800,l:inside:1860x1020
GALLERY_PHOTOSET_COVER_SIZES=::375x250,2x::750x500

26 changes: 20 additions & 6 deletions api/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
const path = require("path"),
packageJson = require(process.cwd() + "/package.json");

function parseImageSizes(value) {
return value.split(",").map(x => {
let splitted = x.split(":"),
suffix = splitted[0],
fit = splitted[1] || "cover",
size = splitted[2];
return {
suffix,
fit,
width: Number(size.split("x")[0]),
height: Number(size.split("x")[1])
};
});
}

const config = {
// environment
env: process.env.NODE_ENV || "development",
Expand All @@ -20,6 +35,8 @@ const config = {

facadeToken: process.env.FACADE_TOKEN,

registrationMode: process.env.REGISTRATION_MODE,

//password hashing algorithm (md5 or bcrypt; for bcrypt install https://www.npmjs.com/package/bcrypt)
passwordHashAlgorithm: process.env.PASSWORD_HASH_ALGORITHM,

Expand Down Expand Up @@ -57,11 +74,6 @@ const config = {
sesSendRate: parseInt(process.env.AWS_SES_SEND_RATE)
},

telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN,
chatId: process.env.TELEGRAM_CHAT_ID
},

fileBrowser: {
uploadPath: path.resolve(process.env.FILEBROWSER_UPLOAD_PATH),
rootPath: path.resolve(process.env.FILEBROWSER_ROOT_PATH)
Expand All @@ -79,7 +91,9 @@ const config = {
imageProcessingModule: process.env.GALLERY_IMAGE_PROCESSING_MODULE,
jpgQuality: parseInt(process.env.GALLERY_JPG_QUALITY),
// comma-separated image sizes: "<suffix>:<width>x<height>"
imageSizes: process.env.GALLERY_IMAGE_SIZES
imageSizes: parseImageSizes(process.env.GALLERY_IMAGE_SIZES),
// comma-separated photoset cover sizes: "<suffix>:<width>x<height>"
photoSetCoverSizes: parseImageSizes(process.env.GALLERY_PHOTOSET_COVER_SIZES)
}
};
module.exports = config;
12 changes: 11 additions & 1 deletion api/facade.http
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
@auth = Authorization: Bearer {{$dotenv FACADE_TOKEN}}
@ajax = X-Requested-With: XMLHttpRequest

### Can register
GET {{baseUrl}}/users/can-register
{{auth}}
{{ajax}}

### Get user
GET {{baseUrl}}/users/mail@ysemeniuk.com
GET {{baseUrl}}/user/mail@ysemeniuk.com
{{auth}}
{{ajax}}

Expand All @@ -12,6 +17,11 @@ GET {{baseUrl}}/gallery/photosets/not-empty
{{auth}}
{{ajax}}

### Get all photos count
GET {{baseUrl}}/gallery/photos/count
{{auth}}
{{ajax}}

### Get all photos
GET {{baseUrl}}/gallery/photos
{{auth}}
Expand Down
18 changes: 0 additions & 18 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,24 +94,6 @@ require("@store/client").connect((err, client) => {
// init authentication module
require("@lib/auth")(express);

// define global response locals
var devCacheHash = require("crypto").randomBytes(20).toString("hex").slice(0, 7);
express.use(function (req, res, next) {
if (req.user && req.user.config) {
//req.user.config.foo = "bar";
}

res.locals = {
config: config,
lang: require("./langs").en,
user: req.user,
isAuthenticated: req.isAuthenticated(),
production: "production" === config.env,
cacheHash: "production" === config.env ? config.commit : devCacheHash
};
next();
});

// init Express' routes
require("./router")(express);

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;
Loading

0 comments on commit f063281

Please sign in to comment.