diff --git a/back/config.template.yml b/back/config.template.yml index 0d70e81d91d820482d1ae0fcdbdd6eecf345a906..41ffb4ca18177edc9b6a2dce715cdec7f593b7c8 100644 --- a/back/config.template.yml +++ b/back/config.template.yml @@ -1,6 +1,7 @@ port: 3000 secret: GENERATE cryptRounds: 10 +jwtExpiresInMinutes: 1440 maxActionPoints: 480 initAttributesPoints: 15 attributes: diff --git a/back/data/lastActionPointsUpdate b/back/data/lastActionPointsUpdate index 43a8da0c143d1e7048c43fa109cd24e757def613..96188cedf835a65f1ddafeb6a61a76f35b3be50b 100644 --- a/back/data/lastActionPointsUpdate +++ b/back/data/lastActionPointsUpdate @@ -1 +1 @@ -Sun Feb 17 2019 01:14:58 GMT+0100 (Central European Standard Time) \ No newline at end of file +Mon Feb 18 2019 21:20:58 GMT+0100 (Central European Standard Time) \ No newline at end of file diff --git a/back/index.js b/back/index.js index 30bafd702e094663c6ee3b43593dd2c366b79538..aca5f5cf058ebe6cdecfd49061fd6ae416dd1f32 100644 --- a/back/index.js +++ b/back/index.js @@ -4,47 +4,34 @@ const session = require('express-session'); const bodyParser = require('body-parser'); const mongoose = require('mongoose'); const morgan = require('morgan'); -const mongoDBStore = require('connect-mongodb-session')(session); +const cors = require('cors'); // Config const config = require('js-yaml').safeLoad(fs.readFileSync('./config.yml', 'utf8')); // Middlewares -const userLoader = require('./middlewares/userLoader'); +const loader = require('./middlewares/loader'); const loginChecker = require('./middlewares/loginChecker'); // Utils -const render = require('./utils/render'); const { warn, error } = require('./utils/notifications'); // Routes -const authRouter = require('./routes/auth'); +const usersRouter = require('./routes/users'); const charactersRouter = require('./routes/characters'); // Crons require('./utils/crons'); // Configuration const app = express(); -const store = new mongoDBStore({ - uri: 'mongodb://localhost/rolegame', - collection: 'sessions' -}); -store.on('error', function(error) { - console.error(error); -}); -app.set('view engine', 'pug'); // Middlewares app.use(morgan('tiny')); -app.use(session({ secret: config.secret, resave: false, saveUninitialized: false, store })); -app.use(bodyParser.urlencoded({ extended: false })); -app.use('/media', express.static('media')); -app.use(userLoader); +app.use(cors()) +app.use(bodyParser.json()); +app.use(loader); app.use(loginChecker); // Routes -app.get('/', async (req, res) => { - return render(req, res, 'home'); -}); -app.use('/auth', authRouter); -app.use('/characters', charactersRouter); +app.use('/users', usersRouter); +// app.use('/characters', charactersRouter); mongoose.connect('mongodb://localhost/rolegame', err => { if (err) { diff --git a/back/middlewares/loader.js b/back/middlewares/loader.js new file mode 100644 index 0000000000000000000000000000000000000000..d82e0d9828fa66411e5c268775223396f910951f --- /dev/null +++ b/back/middlewares/loader.js @@ -0,0 +1,9 @@ +const User = require('../models/user'); + +module.exports = async (req, res, next) => { + if (req.get('Authorization')) { + req.user = await User.findOne({ token: req.get('Authorization')}); + console.log(req.user); + } + next(); +}; diff --git a/back/middlewares/loginChecker.js b/back/middlewares/loginChecker.js index 7c59919cfc7e90b1a02dbc1f7c0dabb311d65deb..c340d61cc2173c381671a07410b7e7748e2157fc 100644 --- a/back/middlewares/loginChecker.js +++ b/back/middlewares/loginChecker.js @@ -1,7 +1,5 @@ module.exports = (req, res, next) => { - if (req.session.user || ['/', '/auth/signup', '/auth/login'].includes(req.url)) { - next(); - } else { - return res.redirect('/auth/signup'); - } + console.log(req.url); + if (!req.user && !['/users', '/users/authenticate'].includes(req.url)) return res.status(401).json({ error: 'Authentication needed'}); + return next(); }; \ No newline at end of file diff --git a/back/middlewares/userLoader.js b/back/middlewares/userLoader.js deleted file mode 100644 index d72167ff221694b08233a25ecc32368452945103..0000000000000000000000000000000000000000 --- a/back/middlewares/userLoader.js +++ /dev/null @@ -1,13 +0,0 @@ -const User = require('../models/user'); - -module.exports = (req, res, next) => { - if (req.session.user) { - User.findById(req.session.user._id, (err, user) => { - err ? error(req, res, 'Error fetching user', err) : null; - req.session.user = user; - next(); - }); - } else { - next(); - } -}; diff --git a/back/models/user.js b/back/models/user.js index 5a7dfc7ff73e21fffbb02d6930774eebd9f6713f..cd466e86d558052b324d81bfced42f06c3dc8832 100644 --- a/back/models/user.js +++ b/back/models/user.js @@ -2,11 +2,10 @@ const mongoose = require('mongoose'); const Notification = require('./notification'); const User = new mongoose.Schema({ - firstName: { type: String, required: true }, - lastName: { type: String, required: true }, username: { unique: true, type: String, lowercase: true }, email: { type: String, required: true }, passwordHash: { type: String }, + token: { type: String }, notifications: [Notification], }); diff --git a/back/package.json b/back/package.json index 13cee154e1e8b94f4e6ef1531713a6c12883bfde..09e1a1d699af98b6fe3bbe256f3db999d298f41c 100644 --- a/back/package.json +++ b/back/package.json @@ -5,16 +5,15 @@ "dependencies": { "bcrypt": "^3.0.4", "body-parser": "^1.18.3", - "connect-mongodb-session": "^2.1.1", + "cors": "^2.8.5", "eslint": "^5.13.0", "express": "^4.16.4", - "express-session": "^1.15.6", "js-yaml": "^3.12.1", "mongoose": "^5.4.11", "morgan": "^1.9.1", "node-cron": "^2.0.3", "nodemon": "^1.18.10", - "pug": "^2.0.3", + "rand-token": "^0.4.0", "random": "^2.0.13" } } diff --git a/back/routes/auth.js b/back/routes/auth.js deleted file mode 100644 index 16c14a8298da5119081f7f092ffc669b864a5ef2..0000000000000000000000000000000000000000 --- a/back/routes/auth.js +++ /dev/null @@ -1,48 +0,0 @@ -const express = require('express'); -const bcrypt = require('bcrypt'); -const User = require('../models/user'); -const render = require('../utils/render'); -const { warn, error } = require('../utils/notifications'); - -const router = express.Router(); - -router.get('/signup', (req, res) => { - return render(req, res, 'signup'); -}); - -router.post('/signup', (req, res) => { - const passwordHash = bcrypt.hashSync(req.body.password, config.cryptRounds); - User.create(req.body, (err, user) => { - err ? error(req, res, 'Error creating user', err) : null; - user.passwordHash = passwordHash; - user.save(); - req.session.user = user; - return res.redirect('/'); - }); -}); - -router.post('/login', (req, res) => { - if (!req.body.username || !req.body.password) { - return res.redirect('/auth/signup'); - } else if (req.session.user) { - error(req, res, 'User already logged in', 'You must logout before log in.') - } else { - User.findOne({ username: req.body.username }, (err, user) => { - err ? error(req, res, 'Error fetching user', err) : null; - if (bcrypt.compareSync(req.body.password, user.passwordHash)) { - req.session.user = user; - return res.redirect(req.query.nextUrl || '/'); - } else { - error(req, res, 'Bad credentials') - return res.redirect('/auth/signup'); - } - }); - } -}); - -router.post('/logout', (req, res) => { - req.session.destroy(); - return res.redirect('/'); -}); - -module.exports = router; \ No newline at end of file diff --git a/back/routes/characters.js b/back/routes/characters.js index 91a3e76cfc7b8449318b2d0739b9a7c077e34e50..bf48dc3bbeba15c83d978089388ed3329b1ed005 100644 --- a/back/routes/characters.js +++ b/back/routes/characters.js @@ -2,7 +2,6 @@ const express = require('express'); const fs = require('fs'); const { initAttributesPoints, costTable, attributes, maxActionPoints, maxCharactersNumber } = require('js-yaml').safeLoad(fs.readFileSync('./config.yml', 'utf8')); const Character = require('../models/character'); -const render = require('../utils/render'); const { warn, error } = require('../utils/notifications'); const router = express.Router(); @@ -13,7 +12,8 @@ router.get('/', async (req, res) => { // TODO: Login required const avatars = fs.readdirSync('media/avatars'); Character.find({ user: req.session.user._id }, (err, characters) => { - return render(req, res, 'characters', { characters, avatars, initAttributesPoints, maxActionPoints }); + if (err) return res.status(400).json({ error: err }); + return res.json(characters); }); }); diff --git a/back/routes/users.js b/back/routes/users.js new file mode 100644 index 0000000000000000000000000000000000000000..9b057072ba9efa18d6ec5dbd290ebb781bfc76a7 --- /dev/null +++ b/back/routes/users.js @@ -0,0 +1,40 @@ +const express = require('express'); +const bcrypt = require('bcrypt'); +const randToken = require('rand-token'); +const User = require('../models/user'); +const { warn, error } = require('../utils/notifications'); +const config = require('js-yaml').safeLoad(require('fs').readFileSync('./config.yml', 'utf8')); + +const router = express.Router(); + +router.post('/', (req, res) => { + const passwordHash = bcrypt.hashSync(req.body.password, config.cryptRounds); + const token = randToken.generate(32); + User.create({ + username: req.body.username, + email: req.body.email, + passwordHash, + token + }).then(user => { + delete user.passwordHash; + delete user.token; + return res.status(201).json(user); + }).catch(err => res.status(400).json({ error: err })); +}); + +router.post('/authenticate', (req, res) => { + if (!req.body.username || !req.body.password) return res.status(400).json({ error: 'Credentials required' }); + User.findOne({ username: req.body.username }, (err, user) => { + if (err) return res.status(500).json({ error: err }); + if (!user) return res.status(404).json({ error: "User not found"}); + if (bcrypt.compareSync(req.body.password, user.passwordHash)) return res.json({ token: user.token }); + return res.status(400).json({ error: "Bad password" }); + }); +}); + +router.get('/me', (req, res) => { + // User required + return res.json(req.user); +}) + +module.exports = router; \ No newline at end of file diff --git a/back/utils/render.js b/back/utils/render.js deleted file mode 100644 index 3ebc53514e9e8890d2cdfcf9e1407b5d0ef87976..0000000000000000000000000000000000000000 --- a/back/utils/render.js +++ /dev/null @@ -1,24 +0,0 @@ -const render = (req, res, view, options) => { - // Load notifications - if (req.session.user) { - res.render(view, { - ...options, - user: req.session.user, - nextUrl: req.url, - notifications: req.session.user.notifications - }); - req.session.user.notifications - .filter(notification => !notification.persistant) - .forEach(notification => notification.remove()); - req.session.user.save(); - } else { - return res.render(view, { - ...options, - user: req.session.user, - nextUrl: req.url, - notifications: [] - }); - } -}; - -module.exports = render; \ No newline at end of file diff --git a/back/views/base.pug b/back/views/base.pug deleted file mode 100644 index 99efae0a216207cfed851ee8d9310534f58c61ea..0000000000000000000000000000000000000000 --- a/back/views/base.pug +++ /dev/null @@ -1,42 +0,0 @@ -<!DOCTYPE html> -html(lang="en") - head - meta(charset="UTF-8") - meta(name="viewport", content="width=device-width, initial-scale=1.0") - meta(http-equiv="X-UA-Compatible", content="ie=edge") - link(rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" integrity="sha256-9mbkOfVho3ZPXfM7W8sV2SndrGDuh7wuyLjtsWeTI1Q=" crossorigin="anonymous") - title RoleGame - body - script(src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous") - script(src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.js" integrity="sha256-t8GepnyPmw9t+foMh3mKNvcorqNHamSKtKRxxpUEgFI=" crossorigin="anonymous") - script(src="/media/script.js") - block navbar - if !user - form(action="/auth/login", method="post") - .ui.pointing.menu - a.item(href="/") Home - .right.menu - .item - .ui.input.transparent - input#username(type="text" name="username" placeholder="username") - .ui.transparent.input - input#password(type="password" name="password" placeholder="password") - .ui.transparent.input - input.ui.button(type="submit" value="Login") - a.item(href="/auth/signup") Sign up - else - form(action="/auth/logout", method="post") - .ui.pointing.menu - a.item(href="/") Home - a.item(href="/characters") My characters - .right.menu - .item #{user.firstName} #{user.lastName} - .item - .ui.transparent.input - input.ui.button(type="submit" value="Logout") - .ui.grid - .three.wide.column - .ten.wide.column - block main - .three.wide.column - include notifications.pug