summaryrefslogtreecommitdiff
path: root/src/api.js
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2018-04-10 18:55:43 -0400
committergumi <git@gumi.ca>2018-04-10 19:05:44 -0400
commita944a52e264febae95626ff20c8a3ed89e3d6e9c (patch)
tree9691aed9f41363f8aaf50294970c1bea40e177f6 /src/api.js
parentb0b04027206e928e5a511c8f8f060b27006aa382 (diff)
downloadapi-a944a52e264febae95626ff20c8a3ed89e3d6e9c.tar.gz
api-a944a52e264febae95626ff20c8a3ed89e3d6e9c.tar.bz2
api-a944a52e264febae95626ff20c8a3ed89e3d6e9c.tar.xz
api-a944a52e264febae95626ff20c8a3ed89e3d6e9c.zip
new major version: v1.0.0v1.0.0
this will make it easier to switch to typescript
Diffstat (limited to 'src/api.js')
-rw-r--r--src/api.js127
1 files changed, 127 insertions, 0 deletions
diff --git a/src/api.js b/src/api.js
new file mode 100644
index 0000000..eb6d726
--- /dev/null
+++ b/src/api.js
@@ -0,0 +1,127 @@
+const express = require("express"); // from npm registry
+const mysql = require("mysql"); // from npm registry
+const https = require("https"); // built-in
+const api = express();
+
+if (process.env.npm_package_config_port === undefined) {
+ console.error("Please run this package with `npm start`");
+ process.exit(1);
+}
+
+
+
+// config common to all routers:
+api.locals = Object.assign({
+ rate_limiting: new Set(), // XXX: or do we want routers to each have their own rate limiter?
+}, api.locals);
+
+
+
+/*******************************
+ BEGIN MIDDLEWARES
+********************************/
+
+const checkRateLimiting = (req, res, next) => {
+ if (req.app.locals.rate_limiting.has(req.ip)) {
+ res.status(429).json({
+ status: "error",
+ error: "too many requests"
+ });
+ } else {
+ next();
+ }
+ return;
+};
+
+const checkCaptcha = (req, res, next) => {
+ const token = String(req.get("X-CAPTCHA-TOKEN") || "");
+
+ if (!token.match(/^[a-zA-Z0-9-_]{30,60}$/)) {
+ res.status(403).json({
+ status: "error",
+ error: "no token sent"
+ });
+ console.info("a request with an empty token was received", req.ip);
+ req.app.locals.rate_limiting.add(req.ip);
+ setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 300000);
+ return false;
+ }
+
+ https.get(`https://www.google.com/recaptcha/api/siteverify?secret=${process.env.npm_package_config_recaptcha_secret}&response=${token}`, re => {
+ re.setEncoding("utf8");
+ re.on("data", response => {
+ const data = JSON.parse(response);
+ if (!data.success) {
+ console.error(`recaptcha returned an error: ${response}`);
+ res.status(403).json({
+ status: "error",
+ error: "captcha validation failed"
+ });
+ console.info("a request failed to validate", req.ip);
+ req.app.locals.rate_limiting.add(req.ip);
+ setTimeout(() => req.app.locals.rate_limiting.delete(req.ip), 300000);
+ return false;
+ }
+
+ next(); // challenge passed, so process the request
+ });
+ }).on("error", error => {
+ console.error(error);
+ res.status(403).json({
+ status: "error",
+ error: "recaptcha couldn't be reached"
+ });
+ console.warn("reCaptcha couldn't be reached");
+ return false;
+ })
+};
+
+/*******************************
+ END MIDDLEWARES
+********************************/
+
+
+
+/*******************************
+ BEGIN ROUTERS
+********************************/
+
+const global_router = express.Router(["caseSensitive", "strict"]);
+
+const tmwa_router = new (require("./routers/tmwa"))({
+ timezone: process.env.npm_package_config_timezone,
+ name: process.env.npm_package_config_tmwa_name,
+ url: process.env.npm_package_config_tmwa_url,
+ db_pool: mysql.createPool({
+ connectionLimit: 10,
+ host : process.env.npm_package_config_sql_host,
+ user : process.env.npm_package_config_sql_user,
+ password : process.env.npm_package_config_sql_password,
+ database : process.env.npm_package_config_sql_database
+ }),
+ db_tables: {
+ register: process.env.npm_package_config_sql_table,
+ },
+}, api, checkCaptcha, checkRateLimiting);
+
+global_router.use("/tmwa", tmwa_router);
+api.use("/api", global_router);
+
+/*******************************
+ END ROUTERS
+********************************/
+
+
+
+// default endpoint:
+api.use((req, res, next) => {
+ res.status(404).json({
+ status: "error",
+ error: "unknown endpoint"
+ });
+ console.info("a request for an unknown endpoint was received", req.ip, req.originalUrl);
+});
+
+api.set("trust proxy", "loopback"); // only allow localhost to communicate with the API
+api.disable("x-powered-by"); // we don't need this header
+api.listen(process.env.npm_package_config_port, () => console.info(`Listening on port ${process.env.npm_package_config_port}`));