Started chapter 5
This commit is contained in:
@@ -0,0 +1,14 @@
|
|||||||
|
[ignore]
|
||||||
|
|
||||||
|
[include]
|
||||||
|
|
||||||
|
[libs]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
all=warn
|
||||||
|
unsafe-getters-setters=off
|
||||||
|
|
||||||
|
[options]
|
||||||
|
include_warnings=true
|
||||||
|
|
||||||
|
[strict]
|
||||||
Generated
+6678
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,97 @@
|
|||||||
|
{
|
||||||
|
"name": "simpleproject",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "A simple project to show package.json creation",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "flow-remove-types src/ -d out/",
|
||||||
|
"buildWithMaps": "flow-remove-types src/ -d out/ --pretty --sourcemaps",
|
||||||
|
"start": "npm run build && node out/doroundmath.js",
|
||||||
|
"start-db": "npm run build && node out/dbaccess.js",
|
||||||
|
"nodemon": "nodemon --watch src --delay 1 --exec npm start",
|
||||||
|
"addTypes": "flow-typed install",
|
||||||
|
"update": "npm install && flow-typed install",
|
||||||
|
"flow": "flow",
|
||||||
|
"flow-coverage": "flow-coverage-report",
|
||||||
|
"eslint": "eslint src",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Federico Kereki",
|
||||||
|
"license": "ISC",
|
||||||
|
"babel": {
|
||||||
|
"presets": [
|
||||||
|
"env",
|
||||||
|
"node",
|
||||||
|
"flow"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2017,
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:flowtype/recommended"
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"babel",
|
||||||
|
"flowtype"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off",
|
||||||
|
"no-var": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"flowtype/no-types-missing-file-annotation": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"eslintIgnore": [
|
||||||
|
"**/out/*.js"
|
||||||
|
],
|
||||||
|
"flow-coverage-report": {
|
||||||
|
"concurrentFiles": 1,
|
||||||
|
"excludeGlob": [
|
||||||
|
"node_modules/**"
|
||||||
|
],
|
||||||
|
"includeGlob": [
|
||||||
|
"src/**/*.js"
|
||||||
|
],
|
||||||
|
"threshold": 90,
|
||||||
|
"type": [
|
||||||
|
"text",
|
||||||
|
"html",
|
||||||
|
"json"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"tabWidth": 4,
|
||||||
|
"printWidth": 75
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.18.0",
|
||||||
|
"cors": "^2.8.4",
|
||||||
|
"express": "^4.16.3",
|
||||||
|
"helmet": "^3.12.1",
|
||||||
|
"jsonwebtoken": "^8.2.1",
|
||||||
|
"mariasql": "^0.2.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-eslint": "^8.2.2",
|
||||||
|
"babel-preset-env": "^1.7.0",
|
||||||
|
"babel-preset-flow": "^6.23.0",
|
||||||
|
"eslint": "^4.19.0",
|
||||||
|
"eslint-config-recommended": "^2.0.0",
|
||||||
|
"eslint-plugin-flowtype": "^2.47.1",
|
||||||
|
"flow-bin": "^0.68.0",
|
||||||
|
"flow-coverage-report": "^0.5.0",
|
||||||
|
"flow-remove-types": "^1.2.3",
|
||||||
|
"flow-typed": "^2.4.0",
|
||||||
|
"nodemon": "^1.17.5",
|
||||||
|
"prettier": "^1.11.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const cors = require("cors");
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
app.get("/", (req, res) => res.send("Server alive, with CORS!"));
|
||||||
|
|
||||||
|
app.listen(8080, () =>
|
||||||
|
console.log("CORS server ready at http://localhost:8080/!")
|
||||||
|
);
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
const http = require("http");
|
||||||
|
|
||||||
|
http.createServer(app).listen(8080);
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (req.secure) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
res.redirect(
|
||||||
|
`https://${req.headers.host.replace(/8080/, "8443")}${req.url}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
const https = require("https");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const keysPath = path.join(__dirname, "../../certificates");
|
||||||
|
const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
|
||||||
|
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
|
||||||
|
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
|
||||||
|
|
||||||
|
https.createServer({ ca, cert, key }, app).listen(8443);
|
||||||
|
|
||||||
|
app.get("/", (req, res) => res.send("Secure server!"));
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
const jwt = require("jsonwebtoken");
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
|
||||||
|
const validateUser = require("./validate_user.js");
|
||||||
|
|
||||||
|
const SECRET_JWT_KEY = "modernJSbook";
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
|
||||||
|
app.get("/public", (req, res) => {
|
||||||
|
res.send("the /public endpoint needs no token!");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/gettoken", (req, res) => {
|
||||||
|
validateUser(req.body.user, req.body.password, (idErr, userid) => {
|
||||||
|
if (idErr !== null) {
|
||||||
|
res.status(401).send(idErr);
|
||||||
|
} else {
|
||||||
|
jwt.sign(
|
||||||
|
{ userid },
|
||||||
|
SECRET_JWT_KEY,
|
||||||
|
{ algorithm: "HS256", expiresIn: "1h" },
|
||||||
|
(err, token) => res.status(200).send(token)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
// First check for the Authorization header
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
||||||
|
return res.status(401).send("No token specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now validate the token itself
|
||||||
|
const token = authHeader.split(" ")[1];
|
||||||
|
|
||||||
|
jwt.verify(token, SECRET_JWT_KEY, (err, decoded) => {
|
||||||
|
if (err) {
|
||||||
|
// Token bad formed, or expired, or other problem
|
||||||
|
return res.status(403).send("Token expired or not valid");
|
||||||
|
} else {
|
||||||
|
// Token OK; get the user id from it
|
||||||
|
req.userid = decoded.userid;
|
||||||
|
// Keep processing the request
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/private", (req, res) => {
|
||||||
|
res.send("the /private endpoint needs JWT, but it was provided: OK!");
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.error("Error....", err.message);
|
||||||
|
res.status(500).send("INTERNAL SERVER ERROR");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080, () =>
|
||||||
|
console.log("Mini JWT server ready, at http://localhost:8080/!")
|
||||||
|
);
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
console.log("Logger... ", new Date(), req.method, req.path);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (req.method === "DELETE") {
|
||||||
|
next(new Error("DELETEs are not accepted!"));
|
||||||
|
} else {
|
||||||
|
res.send("Server alive, with Express!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.error("Error....", err.message);
|
||||||
|
res.status(500).send("INTERNAL SERVER ERROR");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080, () =>
|
||||||
|
console.log(
|
||||||
|
"Mini server (with Express) ready at http://localhost:8080/!"
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const mariaSQL = require("mariasql");
|
||||||
|
const { promisify } = require("util");
|
||||||
|
|
||||||
|
const DB_HOST = "127.0.0.1";
|
||||||
|
const DB_USER = "fkereki";
|
||||||
|
const DB_PASS = "modernJS!!";
|
||||||
|
const DB_SCHEMA = "world";
|
||||||
|
|
||||||
|
const getDbConnection = (host, user, password, db) => {
|
||||||
|
const dbConn = new mariaSQL({ host, user, password, db });
|
||||||
|
dbConn.query = promisify(dbConn.query);
|
||||||
|
return dbConn;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dbConn = getDbConnection(DB_HOST, DB_USER, DB_PASS, DB_SCHEMA);
|
||||||
|
|
||||||
|
module.exports = dbConn;
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const getRegion = async (
|
||||||
|
res: any,
|
||||||
|
dbConn: any,
|
||||||
|
country: ?string,
|
||||||
|
region: ?string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
let sqlQuery = "";
|
||||||
|
if (country == null) {
|
||||||
|
sqlQuery = `
|
||||||
|
SELECT rr.*
|
||||||
|
FROM regions rr
|
||||||
|
JOIN countries cc
|
||||||
|
ON cc.countryCode=rr.countryCode
|
||||||
|
ORDER BY cc.countryCode, rr.regionCode
|
||||||
|
`;
|
||||||
|
} else if (region == null) {
|
||||||
|
sqlQuery = `
|
||||||
|
SELECT 1
|
||||||
|
FROM countries
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const countries = await dbConn.query(sqlQuery);
|
||||||
|
if (countries.length === 0) {
|
||||||
|
return res.status(404).send("Country not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlQuery = `
|
||||||
|
SELECT rr.*
|
||||||
|
FROM regions rr
|
||||||
|
JOIN countries cc
|
||||||
|
ON cc.countryCode=rr.countryCode
|
||||||
|
WHERE rr.countryCode="${country}"
|
||||||
|
ORDER BY rr.regionCode
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
sqlQuery = `
|
||||||
|
SELECT rr.*
|
||||||
|
FROM regions rr
|
||||||
|
JOIN countries cc
|
||||||
|
ON cc.countryCode=rr.countryCode
|
||||||
|
WHERE rr.countryCode="${country}"
|
||||||
|
AND rr.regionCode="${region}"
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regions = await dbConn.query(sqlQuery);
|
||||||
|
if (regions.length > 0 || region === null) {
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.set("Content-Type", "application/json")
|
||||||
|
.send(JSON.stringify(regions));
|
||||||
|
} else {
|
||||||
|
res.status(404).send("Not found");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send("Server error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRegion = async (
|
||||||
|
res: any,
|
||||||
|
dbConn: any,
|
||||||
|
country: string,
|
||||||
|
region: string
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const sqlCities = `
|
||||||
|
SELECT 1 FROM cities
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
AND regionCode="${region}"
|
||||||
|
LIMIT 1
|
||||||
|
`;
|
||||||
|
const cities = await dbConn.query(sqlCities);
|
||||||
|
if (cities.length > 0) {
|
||||||
|
res.status(405).send("Cannot delete a region with cities");
|
||||||
|
} else {
|
||||||
|
const deleteRegion = `
|
||||||
|
DELETE FROM regions
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
AND regionCode="${region}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await dbConn.query(deleteRegion);
|
||||||
|
if (result.info.affectedRows > 0) {
|
||||||
|
res.status(204).send();
|
||||||
|
} else {
|
||||||
|
res.status(404).send("Region not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send("Server error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const postRegion = async (
|
||||||
|
res: any,
|
||||||
|
dbConn: any,
|
||||||
|
country: string,
|
||||||
|
name: string
|
||||||
|
) => {
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).send("Missing name");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sqlCountry = `
|
||||||
|
SELECT 1
|
||||||
|
FROM countries
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
`;
|
||||||
|
const countries = await dbConn.query(sqlCountry);
|
||||||
|
if (countries.length === 0) {
|
||||||
|
res.status(403).send("Country must exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sqlGetId = `
|
||||||
|
SELECT MAX(CAST(regionCode AS INTEGER)) AS maxr
|
||||||
|
FROM regions
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
`;
|
||||||
|
const regions = await dbConn.query(sqlGetId);
|
||||||
|
const newId =
|
||||||
|
regions.length === 0 ? 1 : 1 + Number(regions[0].maxr);
|
||||||
|
|
||||||
|
const sqlAddRegion = `
|
||||||
|
INSERT INTO regions SET
|
||||||
|
countryCode="${country}",
|
||||||
|
regionCode="${newId}",
|
||||||
|
regionName="${name}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await dbConn.query(sqlAddRegion);
|
||||||
|
if (result.info.affectedRows > 0) {
|
||||||
|
res
|
||||||
|
.status(201)
|
||||||
|
.header("Location", `/regions/${country}/${newId}`)
|
||||||
|
.send("Region created");
|
||||||
|
} else {
|
||||||
|
res.status(409).send("Region not created");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send("Server error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const putRegion = async (
|
||||||
|
res: any,
|
||||||
|
dbConn: any,
|
||||||
|
country: string,
|
||||||
|
region: string,
|
||||||
|
name: string
|
||||||
|
) => {
|
||||||
|
if (!name) {
|
||||||
|
return res.status(400).send("Missing name");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sqlUpdateRegion = `
|
||||||
|
UPDATE regions
|
||||||
|
SET regionName="${name}"
|
||||||
|
WHERE countryCode="${country}"
|
||||||
|
AND regionCode="${region}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await dbConn.query(sqlUpdateRegion);
|
||||||
|
|
||||||
|
if (result.info.affectedRows > 0) {
|
||||||
|
res.status(204).send();
|
||||||
|
} else {
|
||||||
|
res.status(409).send("Region not updated");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send("Server error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { getRegion, deleteRegion, postRegion, putRegion };
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const express = require("express");
|
||||||
|
const app = express();
|
||||||
|
const bodyParser = require("body-parser");
|
||||||
|
|
||||||
|
const dbConn = require("./restful_db.js");
|
||||||
|
|
||||||
|
app.get("/", (req, res) => res.send("Secure server!"));
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add here the logic for CORS
|
||||||
|
|
||||||
|
const cors = require("cors");
|
||||||
|
app.use(cors());
|
||||||
|
*/
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({ extended: false }));
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add here the logic for providing a JWT at /gettoken
|
||||||
|
and the logic for validating a JWT, as shown earlier
|
||||||
|
*/
|
||||||
|
|
||||||
|
const {
|
||||||
|
getRegion,
|
||||||
|
deleteRegion,
|
||||||
|
postRegion,
|
||||||
|
putRegion
|
||||||
|
} = require("./restful_regions.js");
|
||||||
|
|
||||||
|
app.get("/regions/", (req, res) => getRegion(res, dbConn));
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
"/regions/:country/",
|
||||||
|
(req, res) => (
|
||||||
|
console.log(req), getRegion(res, dbConn, req.params.country)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.get("/regions/:country/:region/", (req, res) =>
|
||||||
|
getRegion(res, dbConn, req.params.country, req.params.region)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.delete("/regions/:country/:region", (req, res) =>
|
||||||
|
deleteRegion(res, dbConn, req.params.country, req.params.region)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.post("/regions/:country", (req, res) =>
|
||||||
|
postRegion(res, dbConn, req.params.country, req.body.name)
|
||||||
|
);
|
||||||
|
|
||||||
|
app.put("/regions/:country/:region", (req, res) =>
|
||||||
|
putRegion(
|
||||||
|
res,
|
||||||
|
dbConn,
|
||||||
|
req.params.country,
|
||||||
|
req.params.region,
|
||||||
|
req.body.name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
app.use((err, req, res, next) => {
|
||||||
|
console.error("Error....", err.message);
|
||||||
|
res.status(500).send("INTERNAL SERVER ERROR");
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add here the logic for HTTPS
|
||||||
|
|
||||||
|
const https = require("https");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
const keysPath = path.join(__dirname, "../../certificates");
|
||||||
|
const ca = fs.readFileSync(`${keysPath}/modernjsbook.csr`);
|
||||||
|
const cert = fs.readFileSync(`${keysPath}/modernjsbook.crt`);
|
||||||
|
const key = fs.readFileSync(`${keysPath}/modernjsbook.key`);
|
||||||
|
|
||||||
|
https.createServer({ ca, cert, key }, app).listen(8443);
|
||||||
|
|
||||||
|
and remove the following line if HTTPS is used
|
||||||
|
*/
|
||||||
|
app.listen(8080, () =>
|
||||||
|
console.log("Routing ready at http://localhost:8080")
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/* @flow */
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/*
|
||||||
|
In real life, validateUser could check a database,
|
||||||
|
look into an Active Directory, call another service,
|
||||||
|
etc. -- but for this demo, let's keep it quite
|
||||||
|
simple and only accept a single hardcoded user.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const validateUser = (
|
||||||
|
userName: string,
|
||||||
|
password: string,
|
||||||
|
callback: (?string, ?string) => void
|
||||||
|
) => {
|
||||||
|
if (userName === "fkereki" && password === "modernjsbook") {
|
||||||
|
callback(null, "fkereki"); // OK, send userName back
|
||||||
|
} else {
|
||||||
|
callback("Not valid user", null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateUser;
|
||||||
Reference in New Issue
Block a user