Updated env + Added SignIn/SignUp

This commit is contained in:
Damien Broqua 2022-02-13 17:59:42 +01:00
parent 2218d2663b
commit 33c87b434c
25 changed files with 625 additions and 41 deletions

View file

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

View file

@ -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",
};

3
src/helpers/index.js Normal file
View file

@ -0,0 +1,3 @@
/* eslint-disable import/prefer-default-export */
export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`;

3
src/libs/format.js Normal file
View file

@ -0,0 +1,3 @@
export default (res, page) => {
res.status(200).render("index", page.render());
};

39
src/libs/passport.js Normal file
View file

@ -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);
}
)
);
};

44
src/middleware/Auth.js Normal file
View file

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

62
src/middleware/Pages.js Normal file
View file

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

58
src/models/users.js Normal file
View file

@ -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);

View file

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

23
src/views/error.ejs Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="fr">
<%- include('partials/head', {page: page, user: user}); %>
<body class="error">
<%- include('partials/header'); %>
<main class="mt-4">
<div class="container">
<section class="px-md-5 mx-md-5 dark-grey-text mb-4">
<h1><%= page.title %></h1>
<% if ( errorCode && errorCode === 404 ) { %>
<img src="/img/404.svg" alt="Erreur 404" style="max-height: 400px;" />
<% } %>
<p class="lead">
<%= page.error %>
</p>
</section>
</div>
</main>
<%- include('partials/footer', {page: page, user: user, blog: null}); %>
</body>
</html>

26
src/views/index.ejs Normal file
View file

@ -0,0 +1,26 @@
<!doctype html>
<html lang="fr">
<%- include('partials/head'); %>
<body>
<%- include('partials/header'); %>
<% if ( page.failureFlash ) {%>
<div class="alert alert-danger" role="alert">
<%= page.failureFlash %>
</div>
<% } %>
<%
if (error && error.length > 0) {
for( let i = 0 ; i < error.length ; i += 1 ) {
%>
<div class="alert alert-danger" role="alert">
<%= error %>
</div>
<%
}
}
%>
<%- include(viewname) %>
<%- include('partials/footer'); %>
</body>
</html>

View file

@ -0,0 +1,26 @@
<div class="container">
<div class="d-flex justify-content-center">
<div class="p-2">
<form class="text-center border border-light p-5" method="POST">
<img class="mb-4" src="/img/logo.png" alt="DarKou">
<p class="h4 mb-4">Connexion</p>
<div class="md-form">
<input type="email" id="email" name="email" class="form-control" required>
<label for="email">Adresse e-mail</label>
</div>
<div class="md-form">
<input type="password" id="password" name="password" class="form-control" required>
<label for="password">Mot de passe</label>
</div>
<button class="btn btn-primary btn-block my-4" type="submit">Connexion</button>
<p>Pas encore inscrit ?
<a href="/inscription">Inscrivez-vous</a>
</p>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,31 @@
<div class="container">
<div class="d-flex justify-content-center">
<div class="p-2">
<form class="text-center border border-light p-5" method="POST">
<img class="mb-4" src="/img/logo.png" alt="DarKou">
<p class="h4 mb-4">Inscription</p>
<div class="md-form">
<input type="text" id="username" name="username" class="form-control" required>
<label for="username">Nom d'utilisateur</label>
</div>
<div class="md-form">
<input type="email" id="email" name="email" class="form-control" required>
<label for="email">Adresse e-mail</label>
</div>
<div class="md-form">
<input type="password" id="password" name="password" class="form-control" required>
<label for="password">Mot de passe</label>
</div>
<button class="btn btn-primary btn-block my-4" type="submit">Inscription</button>
<p>Déjà inscrit ?
<a href="/connexion">Connectez-vous</a>
</p>
</form>
</div>
</div>
</div>

View file

@ -0,0 +1,5 @@
<script type="text/javascript" src="/libs/mdbootstrap/js/jquery.min.js"></script>
<script type="text/javascript" src="/libs/mdbootstrap/js/popper.min.js"></script>
<script type="text/javascript" src="/libs/mdbootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="/libs/mdbootstrap/js/mdb.min.js"></script>
<script type="text/javascript" src="/js/main.js"></script>

View file

@ -0,0 +1,18 @@
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><% if (page.title) { %><%= page.title %> <% } else { %> DarKou - Ma CDThèque <% } %></title>
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.11.2/css/all.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap">
<link rel="stylesheet" href="/libs/mdbootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/libs/mdbootstrap/css/mdb.min.css">
<link rel="stylesheet" href="/libs/mdbootstrap/css/style.css">
<link rel="stylesheet" href="/css/main.css" />
</head>

View file

@ -0,0 +1,25 @@
<nav class="navbar navbar-expand-md navbar-dark primary-color sticky-top">
<a class="navbar-brand" href="/">CDThèque</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<% if ( user ) { %>
<div class="navbar-collapse collapse w-100 order-1 dual-collapse2">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="/upload">Ajouter une image</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true"
aria-expanded="false">Mon compte</a>
<div class="dropdown-menu">
<a class="dropdown-item" href="/gallery">Mes images</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="/se-deconnecter">Déconnexion</a>
</div>
</li>
</ul>
</div>
<% } %>
</nav>