12 Commits

Author SHA1 Message Date
f51079eeb4 Refactor code and include yaml file reading. 2020-09-26 12:54:31 +02:00
fae622aafc Add security layer. 2020-09-26 12:41:12 +02:00
5e6da61ccb Update repository functions. 2020-09-26 12:09:46 +02:00
59c4b0de38 Code cleaning. 2020-09-06 19:37:38 +02:00
29411d3c87 Clean code. 2020-09-06 19:36:58 +02:00
dc79b7bc0e Correct crud functions. 2020-09-06 19:23:11 +02:00
Pierre THIERRY
d4cc887bf5 WIP 2020-09-06 12:20:22 +02:00
Pierre THIERRY
14da8bf0f1 Correct the insert function in base repository. 2020-09-06 11:54:57 +02:00
Pierre THIERRY
c618675a9a Add src folder and correct base repository functions. 2020-09-05 19:18:51 +02:00
Pierre THIERRY
80253cabf8 Add mongo init database script. 2020-09-05 16:30:41 +02:00
Pierre THIERRY
2c424ac5d6 Add other function to base repository. 2020-09-05 16:30:25 +02:00
Pierre THIERRY
a558f09fb4 Add generics functions to mongo class. 2020-09-05 13:01:05 +02:00
25 changed files with 134 additions and 5522 deletions

2
.gitignore vendored
View File

@@ -1,3 +1 @@
**/node_modules **/node_modules
docker/mongodb/data
**/public/bundle.js

10
.vscode/launch.json vendored
View File

@@ -5,15 +5,13 @@
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": "Launch Program with debugger",
"type": "node", "type": "node",
"request": "launch", "request": "launch",
"cwd": "${workspaceFolder}", "name": "Launch Program",
"runtimeExecutable": "npm", "skipFiles": [
"runtimeArgs": [ "<node_internals>/**"
"start"
], ],
"port": 5858 "program": "${workspaceFolder}/src/js/app.js"
} }
] ]
} }

View File

@@ -1,10 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Vanilla JS frontend app.</title>
</head>
<body>
<div id="content"></div>
<script type="application/javascript" src="./bundle.js"></script>
</body>
</html>

View File

@@ -1,6 +0,0 @@
const fs = require('fs');
const yaml = require('yaml');
const configurationFile = fs.readFileSync('backend/src/resources/application.yml', 'utf8');
const configuration = yaml.parse(configurationFile);
module.exports = configuration;

View File

@@ -1,41 +0,0 @@
const router = require('express').Router();
const tokenService = require('../service/tokenService');
const userService = require('../service/userService');
const passwordService = require('../service/passwordService');
const Repository = require('../repository/repository');
const userRepository = new Repository('users');
router.post('/login', (request, response) => {
const loginRequest = request.body;
if (!loginRequest) {
response.status(403).send();
} else {
userService.checkCredentials(loginRequest.login, loginRequest.password,
() => {
const tokenPayload = { login: loginRequest.login };
response.json(tokenService.build(tokenPayload));
},
() => response.status(403).send());
}
});
router.post('/init', (request, response) => {
userRepository.insert({_id: 'takiguchi', login:'takiguchi', password:passwordService.hashPassword('azer')}, () => {
console.log('ok');
response.status(200).send();
}, () => {
console.error('KO');
response.status(400).send();
});
})
router.get('/test', (request, response) => {
response.json({
content: 'Hello world from users controller!'
});
});
module.exports = router;

View File

@@ -1,17 +0,0 @@
const tokenService = require('../service/tokenService');
module.exports = (request, response, next) => {
const authenticationHeader = request.headers.authorization;
if (authenticationHeader) {
const token = authenticationHeader.split(' ')[1];
if (tokenService.isValid(token)) {
next();
} else {
response.status(403).send();
}
} else {
response.status(401).send();
}
};

View File

@@ -1,42 +0,0 @@
const Repository = require('../repository/repository');
const passwordService = require('./passwordService');
const userRepository = new Repository('users');
class UserService {
/**
* Get a user from database by its login.
* @param {String} login User login.
* @param {Function} onSuccess Callback function to execute if a user exists with this login.
* @param {Function} onError Callback function to execute if not any user exists with this login.
*/
getUser(login, onSuccess, onError) {
userRepository.find({login: login}, results => onSuccess(results[0]), onError);
}
/**
* Checks if credentials matches to an existing user, that have this password.
* @param {String} login User login.
* @param {String} password User password, in plain text.
* @param {Function} onSuccess Callback function to execute if a user exists with this login.
* @param {Function} onError Callback function to execute if not any user exists with this login.
*/
checkCredentials(login, password, onSuccess, onError) {
this.getUser(
login,
dbUser => {
if (!!dbUser && passwordService.areSamePasswords(password, dbUser.password)) {
onSuccess();
} else {
onError();
}
},
// If login is incorrect, the "getUser" function will return "undefined".
// So if "user" is "undefined", this proofs that login is incorrect
onError
);
}
}
const singleton = new UserService();
module.exports = singleton;

View File

@@ -1,14 +1,7 @@
version: "3.0" version: "3.6"
services: services:
mongo: mongo:
container_name: "mongo" container_name: "mongo"
image: "mongo:latest" image: "mongo:latest"
ports: ports:
- "27017:27017" - "27017:27017"
environment:
- "MONGO_INITDB_DATABASE=db_application"
- "MONGO_INITDB_ROOT_USERNAME=god"
- "MONGO_INITDB_ROOT_PASSWORD=P@ssword"
volumes:
- "./mongodb/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d/:ro"
- "./mongodb/data:/data/db"

View File

@@ -1,10 +0,0 @@
db.createUser(
{
user: "application_user",
pwd: "P@ssword1",
roles:
[
{ role: "readWrite", db: "db_application" }
]
}
)

View File

@@ -1,7 +0,0 @@
console.log('Hello world! ++++++');
document.getElementById('content').innerHTML = `
<div>
<h1>Hello world!</h1>
</div>
`;

View File

@@ -1,24 +0,0 @@
const webpack = require("webpack");
const path = require("path");
const UglifyJSPlugin = require("uglifyjs-webpack-plugin");
let config = {
entry: "./frontend/src/index.js",
output: {
path: path.resolve(__dirname, "../backend/public"),
filename: "bundle.js"
},
devServer: {
contentBase: path.resolve(__dirname, "../backend/public"),
historyApiFallback: true,
inline: true,
open: true,
hot: true
},
plugins: [
new UglifyJSPlugin(),
new webpack.SourceMapDevToolPlugin({})
],
devtool: "eval-source-map"
}
module.exports = config;

5309
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,8 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"start": "npm-run-all --parallel start-frontend start-backend", "start": "nodemon ./src/js/app.js localhost 3000",
"start-backend": "nodemon ./backend/src/js/app.js localhost 3000", "test": "echo \"Error: no test specified\" && exit 1"
"start-frontend": "webpack --watch --config frontend/webpack.config.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -21,11 +20,6 @@
"devDependencies": { "devDependencies": {
"eslint": "^7.4.0", "eslint": "^7.4.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"nodemon": "^2.0.4", "nodemon": "^2.0.4"
"uglifyjs-webpack-plugin": "^2.2.0",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"npm-run-all": "^4.1.5"
} }
} }

View File

@@ -3,14 +3,13 @@ const bodyParser = require('body-parser');
const applicationController = require('./controller/applicationCtrl'); const applicationController = require('./controller/applicationCtrl');
const userController = require('./controller/userCtrl'); const userController = require('./controller/userCtrl');
const port = 8080; const port = 3000;
const app = express(); const app = express();
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(express.static('backend/public'));
app.use('/api/apps', applicationController); app.use('/apps', applicationController);
app.use('/api/users', userController); app.use('/users', userController);
app.listen(port, () => console.log('Mock is listening at port ', port, '\n')); app.listen(port, () => console.log('Mock is listening at port ', port, '\n'));

16
src/js/configuration.js Normal file
View File

@@ -0,0 +1,16 @@
const fs = require('fs');
const yaml = require('yaml');
const configurationFile = fs.readFileSync('src/resources/application.yml', 'utf8');
const configuration = yaml.parse(configurationFile);
console.log(configuration);
module.exports = configuration;
// module.exports = {
// database: {
// url: 'mongodb://localhost:27017',
// username: 'express-user',
// password: 'P@ssword1',
// database: 'express-test'
// }
// };

View File

@@ -1,8 +1,6 @@
const Repository = require('../repository/repository'); const Repository = require('../repository/repository');
const authenticationFilter = require('../filter/authenticationFilter'); const router = require('express').Router();
const express = require('express');
const router = express.Router();
const applicationRepository = new Repository('applications'); const applicationRepository = new Repository('applications');
router.get('/', (request, response) => { router.get('/', (request, response) => {
@@ -38,7 +36,7 @@ router.put('/:applicationId', (request, response) => {
}); });
}); });
router.delete('/:applicationId', authenticationFilter, (request, response) => { router.delete('/:applicationId', (request, response) => {
const applicationId = request.params.applicationId; const applicationId = request.params.applicationId;
applicationRepository.find({_id: applicationId}, entity => { applicationRepository.find({_id: applicationId}, entity => {
if (entity.length === 0) { if (entity.length === 0) {

View File

@@ -1,3 +1,4 @@
const Repository = require('../repository/repository');
const router = require('express').Router(); const router = require('express').Router();
// Develop routes here // Develop routes here

View File

@@ -0,0 +1,17 @@
const router = require('express').Router();
const tokenService = require('../service/tokenService');
const userService = require('../service/userService');
// Develop routes here
router.post('/login', (request, response) => {
const loginRequest = request.body;
if (!!loginRequest && userService.areCredentialsValid(loginRequest.login, loginRequest.password)) {
const tokenPayload = { login: loginRequest.login };
response.json(tokenService.build(tokenPayload));
} else {
response.status(403).send();
}
});
module.exports = router;

View File

@@ -2,20 +2,9 @@ const mongodb = require('mongodb');
const configuration = require('../configuration'); const configuration = require('../configuration');
const mongoConfig = configuration.mongodb; const mongoConfig = configuration.mongodb;
function buildDatabaseUrl() {
const username = encodeURIComponent(mongoConfig.username);
const password = encodeURIComponent(mongoConfig.password);
const url = mongoConfig.url;
const port = mongoConfig.port;
const database = mongoConfig.database;
const test = `mongodb://${username}:${password}@${url}:${port}/${database}`;
console.log(test);
return test;
}
class MongoClient { class MongoClient {
constructor() { constructor() {
mongodb.MongoClient.connect(buildDatabaseUrl(), (error, client) => { mongodb.MongoClient.connect(mongoConfig.url, (error, client) => {
if (error !== null) { if (error !== null) {
throw new Error(`Unable de connect to Mongo database: ${error}`); throw new Error(`Unable de connect to Mongo database: ${error}`);
} }
@@ -25,15 +14,14 @@ class MongoClient {
}); });
} }
find(collectionName, query, onSuccess, onError) { find(collectionName, query, callback) {
this.db.collection(collectionName).find(query).toArray() this.db.collection(collectionName).find(query).toArray()
.then(results => { .then(results => {
console.log(`Entities ${collectionName} founded.`); console.log(`Entities ${collectionName} founded.`);
onSuccess(results); callback(results);
}) })
.catch(error => { .catch(error => {
console.error(`Unable to find entities in collection ${collectionName}: ${error}`); throw new Error(`Unable to find entities in collection ${collectionName}: ${error}`);
onError(error);
}); });
} }
@@ -72,5 +60,6 @@ class MongoClient {
} }
} }
const singleton = new MongoClient(); // Define a singleton of class "MongoClient".
module.exports = singleton; const mongoClient = new MongoClient();
module.exports = mongoClient;

View File

@@ -13,7 +13,7 @@ function convertIdToMongodbFormat(entity) {
module.exports = class Repository { module.exports = class Repository {
/** /**
* Creates a new repository which read and write into the {@code collectionName} collection in database. * Creates a new repository which read and write into the {@code collectionName} collection in database.
* @param {String} collectionName * @param {*} collectionName
*/ */
constructor(collectionName) { constructor(collectionName) {
this.collectionName = collectionName; this.collectionName = collectionName;
@@ -22,13 +22,12 @@ module.exports = class Repository {
/** /**
* Returns the entities that matches criteria in {@code query}. * Returns the entities that matches criteria in {@code query}.
* @param {Object} query The query which contains criteria to find some entities. * @param {*} query The query which contains criteria to find some entities.
* @param {Function} onSuccess The function to execute after getting entities. * @param {*} callback The function to execute after getting entities.
* @param {Function} onError The function to execute if entity finding failed.
*/ */
find(query, onSuccess, onError) { find(query, callback) {
convertIdToMongodbFormat(query); convertIdToMongodbFormat(query);
this.mongoClient.find(this.collectionName, query, onSuccess, onError); this.mongoClient.find(this.collectionName, query, callback);
} }
/** /**

View File

@@ -2,22 +2,11 @@ const bcrypt = require('bcrypt');
const saltRounds = 10; const saltRounds = 10;
class PasswordService { class PasswordService {
/**
* Hashes the password in parameters.
* @param {String} password The plain text password.
* @returns The hashed password.
*/
hashPassword(password) { hashPassword(password) {
const salt = bcrypt.genSaltSync(saltRounds); const salt = bcrypt.genSaltSync(saltRounds);
return bcrypt.hashSync(password, salt); return bcrypt.hashSync(password, salt);
} }
/**
* Checks if the {@code plainTextPassword} matches the hashed password.
* @param {String} plainTextPassword The plain text password.
* @param {String} hashedPassword The hashed password.
* @returns A boolean.
*/
areSamePasswords(plainTextPassword, hashedPassword) { areSamePasswords(plainTextPassword, hashedPassword) {
return bcrypt.compareSync(plainTextPassword, hashedPassword); return bcrypt.compareSync(plainTextPassword, hashedPassword);
} }

View File

@@ -3,20 +3,10 @@ const configuration = require('../configuration');
const securityConfig = configuration.security; const securityConfig = configuration.security;
class TokenService { class TokenService {
/**
* Builds a JWT token.
* @param {Object} tokenPayload The JWT payload.
* @returns {String} The JWT token.
*/
build(tokenPayload) { build(tokenPayload) {
return jwt.sign(tokenPayload, securityConfig.jwt.secret, {expiresIn: securityConfig.jwt.validity}); return jwt.sign(tokenPayload, securityConfig.jwt.secret, {expiresIn: securityConfig.jwt.validity});
} }
/**
* Checks if the given token is valid, and provides from this application or not.
* @param {String} token The JWT token to check.
* @returns A boolean.
*/
isValid(token) { isValid(token) {
try { try {
jwt.verify(token, securityConfig.jwt.secret); jwt.verify(token, securityConfig.jwt.secret);
@@ -27,5 +17,5 @@ class TokenService {
} }
} }
const singleton = new TokenService(); const jwtInstance = new TokenService();
module.exports = singleton; module.exports = jwtInstance;

View File

@@ -0,0 +1,26 @@
const Repository = require('../repository/repository');
const passwordService = require('./passwordService');
const userRepository = new Repository('users');
class UserService {
getUser(login) {
return login === 'toto'
? {login: 'toto', password: passwordService.hashPassword('pwd')}
: undefined;
}
isAuthenticated() {
return false;
}
areCredentialsValid(login, password) {
const user = this.getUser(login);
// If login is incorrect, the "getUser" function will return "undefined".
// So if "user" is not "undefined", this proofs that login is correct.
return !!user && passwordService.areSamePasswords(password, user.password);
}
}
const singleton = new UserService();
module.exports = singleton;

View File

@@ -0,0 +1,14 @@
use express-test;
db.collection.test.insert({id: 'test'});
db.createUser(
{
user: "express-user",
pwd: "P@ssword1",
roles:
[
{ role: "readWrite", db: "express-test" }
]
}
)

View File

@@ -1,9 +1,8 @@
mongodb: mongodb:
url: 'localhost' url: 'mongodb://localhost:27017'
port: 27017 username: 'express-user'
username: 'application_user'
password: 'P@ssword1' password: 'P@ssword1'
database: 'db_application' database: 'express-test'
security: security:
jwt: jwt:
secret: 5ubtcCCo7hWBqjNGtzzVKnLT1KxN9uS4D6kRZowCunZAYPmxtKy6mvgoxANe4WqLVfiVI7AZSVqZCtvlSWFwIsnXGH6lxeKG0U8Wu7Kw0jwfFOGLvlO8bXaB secret: 5ubtcCCo7hWBqjNGtzzVKnLT1KxN9uS4D6kRZowCunZAYPmxtKy6mvgoxANe4WqLVfiVI7AZSVqZCtvlSWFwIsnXGH6lxeKG0U8Wu7Kw0jwfFOGLvlO8bXaB