From 33c87b434c56ecbc9696cf698bebc107788b75ac Mon Sep 17 00:00:00 2001 From: dbroqua Date: Sun, 13 Feb 2022 17:59:42 +0100 Subject: [PATCH] Updated env + Added SignIn/SignUp --- .babelrc | 12 ++++ .editorconfig | 7 ++ .eslintrc.js | 48 +++++++------ package.json | 33 ++++++--- public/css/main.css | 0 public/favicon.png | Bin 0 -> 15271 bytes public/img/404.svg | 1 + public/img/logo.png | Bin 0 -> 12970 bytes public/js/main.js | 0 src/app.js | 117 ++++++++++++++++++++++++++++++-- src/config/index.js | 6 +- src/helpers/index.js | 3 + src/libs/format.js | 3 + src/libs/passport.js | 39 +++++++++++ src/middleware/Auth.js | 44 ++++++++++++ src/middleware/Pages.js | 62 +++++++++++++++++ src/models/users.js | 58 ++++++++++++++++ src/routes/index.js | 79 +++++++++++++++++++-- src/views/error.ejs | 23 +++++++ src/views/index.ejs | 26 +++++++ src/views/pages/connexion.ejs | 26 +++++++ src/views/pages/inscription.ejs | 31 +++++++++ src/views/partials/footer.ejs | 5 ++ src/views/partials/head.ejs | 18 +++++ src/views/partials/header.ejs | 25 +++++++ 25 files changed, 625 insertions(+), 41 deletions(-) create mode 100644 .babelrc create mode 100644 .editorconfig create mode 100644 public/css/main.css create mode 100644 public/favicon.png create mode 100644 public/img/404.svg create mode 100644 public/img/logo.png create mode 100644 public/js/main.js create mode 100644 src/helpers/index.js create mode 100644 src/libs/format.js create mode 100644 src/libs/passport.js create mode 100644 src/middleware/Auth.js create mode 100644 src/middleware/Pages.js create mode 100644 src/models/users.js create mode 100644 src/views/error.ejs create mode 100644 src/views/index.ejs create mode 100644 src/views/pages/connexion.ejs create mode 100644 src/views/pages/inscription.ejs create mode 100644 src/views/partials/footer.ejs create mode 100644 src/views/partials/head.ejs create mode 100644 src/views/partials/header.ejs diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..aea7cd7 --- /dev/null +++ b/.babelrc @@ -0,0 +1,12 @@ +{ + "presets": [ + [ + "@babel/preset-env", + { + "targets": { + "esmodules": true + } + } + ] + ] +} \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a882442 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 diff --git a/.eslintrc.js b/.eslintrc.js index b864f33..5a6a105 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,22 +1,30 @@ module.exports = { - 'env': { - 'es6': true, - 'node': true, - }, - 'extends': [ - 'google', - ], - 'globals': { - 'Atomics': 'readonly', - 'SharedArrayBuffer': 'readonly', - }, - 'parserOptions': { - 'ecmaVersion': 2018, - 'sourceType': 'module', - }, - 'rules': { - 'max-len': ['error', {'code': 180}], - 'new-cap': ['error', {'capIsNewExceptions': ['ENUM', 'ARRAY', 'TEXT', 'STRING', 'ObjectId']}], - }, + env: { + browser: true, + es2020: true, + node: true, + jquery: true, + }, + extends: ['airbnb-base', 'prettier'], + plugins: ['prettier'], + parserOptions: { + ecmaVersion: 11, + sourceType: 'module', + }, + rules: { + 'prettier/prettier': ['error'], + 'no-underscore-dangle': [ + 'error', + { + allow: ['_id'], + }, + ], + }, + ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'], + overrides: [ + { + files: ['**/*.js'], + excludedFiles: '*.ejs', + }, + ], }; - diff --git a/package.json b/package.json index 06908e5..d049d8e 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,16 @@ "scripts": { "start": "node ./dist/bin/www", "dev": "npm-run-all build start", - "watch": "nodemon", + "watch": "nodemon -e js,ejs", "prebuild": "rimraf dist", - "build": "babel ./src --out-dir dist", + "build": "babel ./src --out-dir dist --copy-files", "test": "jest", "prepare": "husky install" }, + "engines": { + "node": "16.x", + "yarn": "1.x" + }, "repository": { "type": "git", "url": "git@git.darkou.fr:dbroqua/nodecdtheque.git" @@ -26,15 +30,31 @@ "@babel/core": "^7.17.2", "@babel/preset-env": "^7.16.11", "eslint": "^8.9.0", - "eslint-config-google": "^0.14.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-prettier": "^4.0.0", "husky": "^7.0.4", "lint-staged": "^12.3.3", "nodemon": "^2.0.15", "npm-run-all": "^4.1.5", + "prettier": "^2.5.1", "rimraf": "^3.0.2" }, "dependencies": { - "express": "^4.17.2" + "connect-ensure-login": "^0.1.1", + "connect-flash": "^0.1.1", + "connect-mongo": "^4.6.0", + "cookie-parser": "^1.4.6", + "ejs": "^3.1.6", + "express": "^4.17.2", + "express-session": "^1.17.2", + "jquery": "^3.6.0", + "mdbootstrap": "^4.20.0", + "mongoose": "^6.2.1", + "mongoose-unique-validator": "^3.0.0", + "passport": "^0.5.2", + "passport-local": "^1.0.0" }, "nodemonConfig": { "exec": "npm run dev", @@ -47,11 +67,6 @@ "*.spec.js" ] }, - "babel": { - "presets": [ - "@babel/preset-env" - ] - }, "jest": { "testEnvironment": "node" }, diff --git a/public/css/main.css b/public/css/main.css new file mode 100644 index 0000000..e69de29 diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..a06747bcba3402404df3b749a01b153c4a174d56 GIT binary patch literal 15271 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 literal 0 HcmV?d00001 diff --git a/public/img/404.svg b/public/img/404.svg new file mode 100644 index 0000000..c963bb6 --- /dev/null +++ b/public/img/404.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/logo.png b/public/img/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..bbb404cfdb92acf4605a071767de610317c71082 GIT binary patch literal 12970 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 literal 0 HcmV?d00001 diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..e69de29 diff --git a/src/app.js b/src/app.js index 9546de8..0ca0bf5 100644 --- a/src/app.js +++ b/src/app.js @@ -1,14 +1,119 @@ -import express from 'express'; -import path from 'path'; +import path from "path"; +import express from "express"; +import cookieParser from "cookie-parser"; +import passport from "passport"; +import mongoose from "mongoose"; +import flash from "connect-flash"; +import session from "express-session"; +import MongoStore from "connect-mongo"; -import indexRouter from './routes/index'; +import config, { env, mongoDbUri, secret } from "./config"; + +import indexRouter from "./routes/index"; + +// Mongoose schema init +require("./models/users"); + +require("./libs/passport")(passport); + +mongoose + .connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true }) + .catch(() => { + process.exit(); + }); + +const sess = { + cookie: { + maxAge: 86400000, + }, + secret, + saveUninitialized: false, + resave: false, + store: MongoStore.create({ + mongoUrl: mongoDbUri, + mongoOptions: { useNewUrlParser: true, useUnifiedTopology: true }, + }), +}; const app = express(); app.use(express.json()); -app.use(express.urlencoded({extended: false})); -app.use(express.static(path.join(__dirname, '../public'))); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(flash()); -app.use('/', indexRouter); +app.use(session(sess)); + +if (["production"].indexOf(env) !== -1) { + app.enable("trust proxy", 1); + sess.cookie.secure = true; + /* eslint-disable func-names */ + app.use((req, res, next) => { + if (req.secure) { + // request was via https, so do no special handling + next(); + } else { + // request was via http, so redirect to https + res.redirect(`https://${req.headers.host}${req.url}`); + } + }); +} + +app.use(passport.initialize()); +app.use(passport.session()); + +app.set("views", path.join(__dirname, "views")); +app.set("view engine", "ejs"); + +app.use(express.static(path.join(__dirname, "../public"))); +app.use( + "/libs/jquery", + express.static(path.join(__dirname, "../node_modules/jquery/dist/")) +); +app.use( + "/libs/mdbootstrap", + express.static(path.join(__dirname, "../node_modules/mdbootstrap")) +); + +app.use("/", indexRouter); + +// Handle 404 +app.use((req, res) => { + if (req.xhr) { + res.status(404).send({ message: "404: Not found" }); + } else { + res.status(404).render("error", { + page: { title: `404: Cette page n'existe pas.` }, + errorCode: 404, + user: req.user || null, + config, + session: req.session || null, + flashInfo: null, + query: null, + params: null, + }); + } +}); + +// Handle 500 +app.use((error, req, res, next) => { + if (req.xhr) { + res.status(error.errorCode || 500).send({ message: error.message }); + } else { + res.status(500); + res.render("error", { + page: { title: "500: Oups… le serveur a crashé !", error }, + errorCode: error.errorCode || 500, + user: req.user || null, + config, + session: req.session || null, + flashInfo: null, + query: null, + params: null, + }); + + next(); + } +}); export default app; diff --git a/src/config/index.js b/src/config/index.js index 92d8438..4577bd4 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,4 +1,6 @@ module.exports = { - nodeEnv: process.env.NODE_ENV || 'development', - port: parseInt(process.env.PORT || '3001', 10), + nodeEnv: process.env.NODE_ENV || "development", + port: parseInt(process.env.PORT || "3001", 10), + mongoDbUri: process.env.MONGODB_URI || "mongodb://nodecdtheque-db/cdtheque", + secret: process.env.SECRET || "waemaeMe5ahc6ce1chaeKohKa6Io8Eik", }; diff --git a/src/helpers/index.js b/src/helpers/index.js new file mode 100644 index 0000000..c03e285 --- /dev/null +++ b/src/helpers/index.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`; diff --git a/src/libs/format.js b/src/libs/format.js new file mode 100644 index 0000000..abe1b7b --- /dev/null +++ b/src/libs/format.js @@ -0,0 +1,3 @@ +export default (res, page) => { + res.status(200).render("index", page.render()); +}; diff --git a/src/libs/passport.js b/src/libs/passport.js new file mode 100644 index 0000000..1b46ab0 --- /dev/null +++ b/src/libs/passport.js @@ -0,0 +1,39 @@ +/* eslint-disable func-names */ +const mongoose = require("mongoose"); +const LocalStrategy = require("passport-local").Strategy; + +const Users = mongoose.model("Users"); + +module.exports = function (passport) { + passport.serializeUser((user, done) => { + done(null, user); + }); + + passport.deserializeUser((user, done) => { + done(null, user); + }); + passport.use( + "user", + new LocalStrategy( + { + usernameField: "email", + passwordField: "password", + }, + (email, password, done) => { + Users.findOne({ email }) + .then((user) => { + if (!user || !user.validPassword(password)) { + return done( + null, + false, + "Oops! Identifiants incorrects" + ); + } + + return done(null, user); + }) + .catch(done); + } + ) + ); +}; diff --git a/src/middleware/Auth.js b/src/middleware/Auth.js new file mode 100644 index 0000000..c20df57 --- /dev/null +++ b/src/middleware/Auth.js @@ -0,0 +1,44 @@ +import Pages from "./Pages"; + +import Users from "../models/users"; + +/** + * Classe permettant la gestion des utilisateurs + */ +class Auth extends Pages { + /** + * Méthode permettant de créer un nouvel utilisateur + * @param {Req} req + * + * @return {Object} + */ + static async register(req) { + try { + const { username, email, password } = req.body; + const user = new Users({ + username, + email, + salt: password, + }); + + const resUser = await user.save(); + + await new Promise((resolve, reject) => { + req.login(resUser, (errLogin) => { + if (errLogin) { + return reject(errLogin); + } + + return resolve(null); + }); + }); + + return resUser; + } catch (err) { + req.flash("error", err.toString()); + throw err; + } + } +} + +export default Auth; diff --git a/src/middleware/Pages.js b/src/middleware/Pages.js new file mode 100644 index 0000000..198c063 --- /dev/null +++ b/src/middleware/Pages.js @@ -0,0 +1,62 @@ +import config from "../config"; +import { getBaseUrl } from "../helpers"; + +/** + * Classe permettant de gérer les page du back office + */ +class Pages { + /** + * @param {Object} req + * @param {String} viewname + */ + constructor(req, viewname) { + this.req = req; + this.pageContent = { + page: { + title: null, + user: null, + }, + viewname: `pages/${viewname}`, + }; + + this.user = null; + this.pagename = viewname; + + if (req.session && req.session.passport && req.session.passport.user) { + this.user = req.session.passport.user; + } + + if (!req.query.page) { + req.query.page = 1; + } + if (!req.query.limit) { + req.query.limit = config.pagination; + } + } + + /** + * Rendu de la page + * @return {Object} + */ + render() { + this.pageContent.session = this.req.session; + this.pageContent.flashInfo = this.req.flash("info"); + this.pageContent.error = this.req.flash("error") || null; + this.pageContent.query = this.req.query; + this.pageContent.params = this.req.params; + this.pageContent.user = this.user; + this.pageContent.config = config; + this.pageContent.getBaseUrl = getBaseUrl(); + + if (this.req.session.flash && this.req.session.flash.error) { + // eslint-disable-next-line prefer-destructuring + this.pageContent.page.failureFlash = + this.req.session.flash.error[0]; + this.req.session.flash = null; + } + + return this.pageContent; + } +} + +export default Pages; diff --git a/src/models/users.js b/src/models/users.js new file mode 100644 index 0000000..0807ccb --- /dev/null +++ b/src/models/users.js @@ -0,0 +1,58 @@ +/* eslint-disable func-names */ +/* eslint-disable no-invalid-this */ +import mongoose from "mongoose"; +import uniqueValidator from "mongoose-unique-validator"; +import crypto from "crypto"; + +const UserSchema = new mongoose.Schema( + { + username: { + type: String, + unique: true, + required: [true, "est requis"], + match: [/^[a-zA-Z0-9]+$/, "est invalide"], + index: true, + }, + email: { + type: String, + lowercase: true, + unique: true, + required: [true, "est requis"], + match: [/\S+@\S+\.\S+/, "est invalide"], + index: true, + }, + hash: String, + salt: String, + }, + { timestamps: true } +); + +UserSchema.plugin(uniqueValidator, { message: "est déjà utilisé" }); + +UserSchema.methods.validPassword = function (password) { + const [salt, key] = this.hash.split(":"); + + return key === crypto.scryptSync(password, salt, 64).toString("hex"); +}; + +UserSchema.pre("save", function (next) { + const user = this; + + if (!user.isModified("salt")) { + return next(); + } + + const salt = crypto.randomBytes(16).toString("hex"); + + return crypto.scrypt(user.salt, salt, 64, (err, derivedKey) => { + if (err) { + next(err); + } + this.salt = salt; + this.hash = `${salt}:${derivedKey.toString("hex")}`; + + next(); + }); +}); + +export default mongoose.model("Users", UserSchema); diff --git a/src/routes/index.js b/src/routes/index.js index 472ba28..e2cb331 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,11 +1,82 @@ -import express from 'express'; +import express from "express"; +import passport from "passport"; +import { ensureLoggedIn } from "connect-ensure-login"; + +import Pages from "../middleware/Pages"; +import Auth from "../middleware/Auth"; + +import render from "../libs/format"; // eslint-disable-next-line new-cap const router = express.Router(); -/* GET home page. */ -router.get('/', function(req, res, next) { - res.render('index', {title: 'World'}); +router + .route("/") + .get(ensureLoggedIn("/connexion"), (req, res) => + res.redirect("/ma-collection") + ); + +router + .route("/connexion") + .get((req, res, next) => { + try { + const page = new Pages(req, "connexion"); + + render(res, page); + } catch (err) { + next(err); + } + }) + .post( + passport.authenticate("user", { + failureRedirect: "/connexion", + failureFlash: true, + }), + (req, res) => { + const { next, query } = req.body; + let url = `/${next}`; + + if (next) { + if (query) { + const params = JSON.parse(query); + Object.keys(params).forEach((key) => { + url += `${url.indexOf("?") === -1 ? "?" : "&"}${key}=${ + params[key] + }`; + }); + } + return res.redirect(url); + } + return res.redirect("/"); + } + ); + +router + .route("/inscription") + .get((req, res, next) => { + try { + const page = new Pages(req, "inscription"); + + render(res, page); + } catch (err) { + next(err); + } + }) + .post(async (req, res) => { + try { + await Auth.register(req); + + res.redirect("/"); + } catch (err) { + res.redirect("/inscription"); + } + }); + +router.route("/se-deconnecter").get((req, res) => { + req.logout(); + req.session.destroy(() => { + res.redirect("/"); + }); }); export default router; diff --git a/src/views/error.ejs b/src/views/error.ejs new file mode 100644 index 0000000..d2057dd --- /dev/null +++ b/src/views/error.ejs @@ -0,0 +1,23 @@ + + +<%- include('partials/head', {page: page, user: user}); %> + + + <%- include('partials/header'); %> +
+
+
+

<%= page.title %>

+ <% if ( errorCode && errorCode === 404 ) { %> + Erreur 404 + <% } %> +

+ <%= page.error %> +

+
+
+
+ <%- include('partials/footer', {page: page, user: user, blog: null}); %> + + + \ No newline at end of file diff --git a/src/views/index.ejs b/src/views/index.ejs new file mode 100644 index 0000000..4c21a1f --- /dev/null +++ b/src/views/index.ejs @@ -0,0 +1,26 @@ + + + <%- include('partials/head'); %> + + <%- include('partials/header'); %> + + <% if ( page.failureFlash ) {%> + + <% } %> + <% + if (error && error.length > 0) { + for( let i = 0 ; i < error.length ; i += 1 ) { + %> + + <% + } + } + %> + <%- include(viewname) %> + <%- include('partials/footer'); %> + + diff --git a/src/views/pages/connexion.ejs b/src/views/pages/connexion.ejs new file mode 100644 index 0000000..5c3c444 --- /dev/null +++ b/src/views/pages/connexion.ejs @@ -0,0 +1,26 @@ +
+
+
+
+ DarKou +

Connexion

+ +
+ + +
+ +
+ + +
+ + + +

Pas encore inscrit ? + Inscrivez-vous +

+
+
+
+
\ No newline at end of file diff --git a/src/views/pages/inscription.ejs b/src/views/pages/inscription.ejs new file mode 100644 index 0000000..9ca3a04 --- /dev/null +++ b/src/views/pages/inscription.ejs @@ -0,0 +1,31 @@ +
+
+
+
+ DarKou +

Inscription

+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +

Déjà inscrit ? + Connectez-vous +

+
+
+
+
\ No newline at end of file diff --git a/src/views/partials/footer.ejs b/src/views/partials/footer.ejs new file mode 100644 index 0000000..b929419 --- /dev/null +++ b/src/views/partials/footer.ejs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/views/partials/head.ejs b/src/views/partials/head.ejs new file mode 100644 index 0000000..7b0f74f --- /dev/null +++ b/src/views/partials/head.ejs @@ -0,0 +1,18 @@ + + + + + + <% if (page.title) { %><%= page.title %> <% } else { %> DarKou - Ma CDThèque <% } %> + + + + + + + + + + + + \ No newline at end of file diff --git a/src/views/partials/header.ejs b/src/views/partials/header.ejs new file mode 100644 index 0000000..74d6c0e --- /dev/null +++ b/src/views/partials/header.ejs @@ -0,0 +1,25 @@ + \ No newline at end of file