Compare commits
4 Commits
f51079eeb4
...
feature/in
| Author | SHA1 | Date | |
|---|---|---|---|
| 6d8f19c68c | |||
| 29f93bb0ad | |||
| 391be9b833 | |||
|
|
94226c140e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
**/node_modules
|
||||
docker/mongodb/data
|
||||
**/public/bundle.js
|
||||
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@@ -5,13 +5,15 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Program with debugger",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
"cwd": "${workspaceFolder}",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"start"
|
||||
],
|
||||
"program": "${workspaceFolder}/src/js/app.js"
|
||||
"port": 5858
|
||||
}
|
||||
]
|
||||
}
|
||||
10
backend/public/index.html
Normal file
10
backend/public/index.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!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>
|
||||
@@ -3,13 +3,14 @@ const bodyParser = require('body-parser');
|
||||
const applicationController = require('./controller/applicationCtrl');
|
||||
const userController = require('./controller/userCtrl');
|
||||
|
||||
const port = 3000;
|
||||
const port = 8080;
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.static('backend/public'));
|
||||
|
||||
app.use('/apps', applicationController);
|
||||
app.use('/users', userController);
|
||||
app.use('/api/apps', applicationController);
|
||||
app.use('/api/users', userController);
|
||||
|
||||
app.listen(port, () => console.log('Mock is listening at port ', port, '\n'));
|
||||
6
backend/src/js/configuration.js
Normal file
6
backend/src/js/configuration.js
Normal file
@@ -0,0 +1,6 @@
|
||||
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;
|
||||
@@ -1,6 +1,8 @@
|
||||
const Repository = require('../repository/repository');
|
||||
const router = require('express').Router();
|
||||
const authenticationFilter = require('../filter/authenticationFilter');
|
||||
const express = require('express');
|
||||
|
||||
const router = express.Router();
|
||||
const applicationRepository = new Repository('applications');
|
||||
|
||||
router.get('/', (request, response) => {
|
||||
@@ -36,7 +38,7 @@ router.put('/:applicationId', (request, response) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:applicationId', (request, response) => {
|
||||
router.delete('/:applicationId', authenticationFilter, (request, response) => {
|
||||
const applicationId = request.params.applicationId;
|
||||
applicationRepository.find({_id: applicationId}, entity => {
|
||||
if (entity.length === 0) {
|
||||
@@ -1,4 +1,3 @@
|
||||
const Repository = require('../repository/repository');
|
||||
const router = require('express').Router();
|
||||
|
||||
// Develop routes here
|
||||
41
backend/src/js/controller/userCtrl.js
Normal file
41
backend/src/js/controller/userCtrl.js
Normal file
@@ -0,0 +1,41 @@
|
||||
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;
|
||||
17
backend/src/js/filter/authenticationFilter.js
Normal file
17
backend/src/js/filter/authenticationFilter.js
Normal file
@@ -0,0 +1,17 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
@@ -2,9 +2,20 @@ const mongodb = require('mongodb');
|
||||
const configuration = require('../configuration');
|
||||
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 {
|
||||
constructor() {
|
||||
mongodb.MongoClient.connect(mongoConfig.url, (error, client) => {
|
||||
mongodb.MongoClient.connect(buildDatabaseUrl(), (error, client) => {
|
||||
if (error !== null) {
|
||||
throw new Error(`Unable de connect to Mongo database: ${error}`);
|
||||
}
|
||||
@@ -14,14 +25,15 @@ class MongoClient {
|
||||
});
|
||||
}
|
||||
|
||||
find(collectionName, query, callback) {
|
||||
find(collectionName, query, onSuccess, onError) {
|
||||
this.db.collection(collectionName).find(query).toArray()
|
||||
.then(results => {
|
||||
console.log(`Entities ${collectionName} founded.`);
|
||||
callback(results);
|
||||
onSuccess(results);
|
||||
})
|
||||
.catch(error => {
|
||||
throw new Error(`Unable to find entities in collection ${collectionName}: ${error}`);
|
||||
console.error(`Unable to find entities in collection ${collectionName}: ${error}`);
|
||||
onError(error);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,6 +72,5 @@ class MongoClient {
|
||||
}
|
||||
}
|
||||
|
||||
// Define a singleton of class "MongoClient".
|
||||
const mongoClient = new MongoClient();
|
||||
module.exports = mongoClient;
|
||||
const singleton = new MongoClient();
|
||||
module.exports = singleton;
|
||||
@@ -13,7 +13,7 @@ function convertIdToMongodbFormat(entity) {
|
||||
module.exports = class Repository {
|
||||
/**
|
||||
* Creates a new repository which read and write into the {@code collectionName} collection in database.
|
||||
* @param {*} collectionName
|
||||
* @param {String} collectionName
|
||||
*/
|
||||
constructor(collectionName) {
|
||||
this.collectionName = collectionName;
|
||||
@@ -22,12 +22,13 @@ module.exports = class Repository {
|
||||
|
||||
/**
|
||||
* Returns the entities that matches criteria in {@code query}.
|
||||
* @param {*} query The query which contains criteria to find some entities.
|
||||
* @param {*} callback The function to execute after getting entities.
|
||||
* @param {Object} query The query which contains criteria to find some entities.
|
||||
* @param {Function} onSuccess The function to execute after getting entities.
|
||||
* @param {Function} onError The function to execute if entity finding failed.
|
||||
*/
|
||||
find(query, callback) {
|
||||
find(query, onSuccess, onError) {
|
||||
convertIdToMongodbFormat(query);
|
||||
this.mongoClient.find(this.collectionName, query, callback);
|
||||
this.mongoClient.find(this.collectionName, query, onSuccess, onError);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2,11 +2,22 @@ const bcrypt = require('bcrypt');
|
||||
const saltRounds = 10;
|
||||
|
||||
class PasswordService {
|
||||
/**
|
||||
* Hashes the password in parameters.
|
||||
* @param {String} password The plain text password.
|
||||
* @returns The hashed password.
|
||||
*/
|
||||
hashPassword(password) {
|
||||
const salt = bcrypt.genSaltSync(saltRounds);
|
||||
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) {
|
||||
return bcrypt.compareSync(plainTextPassword, hashedPassword);
|
||||
}
|
||||
@@ -3,10 +3,20 @@ const configuration = require('../configuration');
|
||||
const securityConfig = configuration.security;
|
||||
|
||||
class TokenService {
|
||||
/**
|
||||
* Builds a JWT token.
|
||||
* @param {Object} tokenPayload The JWT payload.
|
||||
* @returns {String} The JWT token.
|
||||
*/
|
||||
build(tokenPayload) {
|
||||
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) {
|
||||
try {
|
||||
jwt.verify(token, securityConfig.jwt.secret);
|
||||
@@ -17,5 +27,5 @@ class TokenService {
|
||||
}
|
||||
}
|
||||
|
||||
const jwtInstance = new TokenService();
|
||||
module.exports = jwtInstance;
|
||||
const singleton = new TokenService();
|
||||
module.exports = singleton;
|
||||
42
backend/src/js/service/userService.js
Normal file
42
backend/src/js/service/userService.js
Normal file
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
@@ -1,8 +1,9 @@
|
||||
mongodb:
|
||||
url: 'mongodb://localhost:27017'
|
||||
username: 'express-user'
|
||||
url: 'localhost'
|
||||
port: 27017
|
||||
username: 'application_user'
|
||||
password: 'P@ssword1'
|
||||
database: 'express-test'
|
||||
database: 'db_application'
|
||||
security:
|
||||
jwt:
|
||||
secret: 5ubtcCCo7hWBqjNGtzzVKnLT1KxN9uS4D6kRZowCunZAYPmxtKy6mvgoxANe4WqLVfiVI7AZSVqZCtvlSWFwIsnXGH6lxeKG0U8Wu7Kw0jwfFOGLvlO8bXaB
|
||||
@@ -1,7 +1,14 @@
|
||||
version: "3.6"
|
||||
version: "3.0"
|
||||
services:
|
||||
mongo:
|
||||
container_name: "mongo"
|
||||
image: "mongo:latest"
|
||||
ports:
|
||||
- "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"
|
||||
10
docker/mongodb/docker-entrypoint-initdb.d/initDatabase.js
Normal file
10
docker/mongodb/docker-entrypoint-initdb.d/initDatabase.js
Normal file
@@ -0,0 +1,10 @@
|
||||
db.createUser(
|
||||
{
|
||||
user: "application_user",
|
||||
pwd: "P@ssword1",
|
||||
roles:
|
||||
[
|
||||
{ role: "readWrite", db: "db_application" }
|
||||
]
|
||||
}
|
||||
)
|
||||
7
frontend/src/index.js
Normal file
7
frontend/src/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
console.log('Hello world! ++++++');
|
||||
|
||||
document.getElementById('content').innerHTML = `
|
||||
<div>
|
||||
<h1>Hello world!</h1>
|
||||
</div>
|
||||
`;
|
||||
24
frontend/webpack.config.js
Normal file
24
frontend/webpack.config.js
Normal file
@@ -0,0 +1,24 @@
|
||||
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
5309
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -4,8 +4,9 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "nodemon ./src/js/app.js localhost 3000",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "npm-run-all --parallel start-frontend start-backend",
|
||||
"start-backend": "nodemon ./backend/src/js/app.js localhost 3000",
|
||||
"start-frontend": "webpack --watch --config frontend/webpack.config.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -20,6 +21,11 @@
|
||||
"devDependencies": {
|
||||
"eslint": "^7.4.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
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'
|
||||
// }
|
||||
// };
|
||||
@@ -1,17 +0,0 @@
|
||||
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;
|
||||
@@ -1,26 +0,0 @@
|
||||
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;
|
||||
@@ -1,14 +0,0 @@
|
||||
use express-test;
|
||||
|
||||
db.collection.test.insert({id: 'test'});
|
||||
|
||||
db.createUser(
|
||||
{
|
||||
user: "express-user",
|
||||
pwd: "P@ssword1",
|
||||
roles:
|
||||
[
|
||||
{ role: "readWrite", db: "express-test" }
|
||||
]
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user