summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgumi <git@gumi.ca>2018-03-29 14:27:57 -0400
committergumi <git@gumi.ca>2018-03-29 14:27:57 -0400
commit3ae54ef5e1443ded92f0a5b93bd542873ec8656f (patch)
tree14c4caf3ed90d6b3932d1236d7087c8f0dc57228
downloadapi-0.1.0.tar.gz
api-0.1.0.tar.bz2
api-0.1.0.tar.xz
api-0.1.0.zip
version v0.1.0v0.1.0
-rw-r--r--.gitignore2
-rw-r--r--LICENSE15
-rw-r--r--README.md1
-rw-r--r--config.json.template15
-rw-r--r--package.json16
-rw-r--r--server.js117
6 files changed, 166 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f8a403c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/config.json
+/node_modules
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..fccea72
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2018, The Mana World
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..05c11e5
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+a RESTful API for The Mana World
diff --git a/config.json.template b/config.json.template
new file mode 100644
index 0000000..31968fb
--- /dev/null
+++ b/config.json.template
@@ -0,0 +1,15 @@
+{
+ "port": 8080,
+
+ "sql": {
+ "host": "localhost",
+ "user": "db user",
+ "password": "db password",
+ "database": "db",
+ "table": "table"
+ },
+
+ "recaptcha": {
+ "secret": "recaptcha secret key"
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..006dca5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "tmw-api",
+ "version": "0.1.0",
+ "description": "TMW RESTful API",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "The Mana World",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.18.2",
+ "express": "^4.16.3",
+ "mysql": "^2.15.0"
+ }
+}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..ac45cc5
--- /dev/null
+++ b/server.js
@@ -0,0 +1,117 @@
+const express = require("express");
+const mysql = require("mysql");
+const bodyParser = require("body-parser");
+const https = require("https");
+const config = require("./config.json");
+const api = express();
+
+const db = mysql.createConnection({
+ host : config.sql.host,
+ user : config.sql.user,
+ password : config.sql.password,
+ database : config.sql.database
+});
+
+const checkCaptcha = (req, res, next) => {
+ const token = String(req.get("X-CAPTCHA-TOKEN"));
+
+ if (!token.match(/^[a-zA-Z0-9-_]{8,}$/)) {
+ res.status(403).json({
+ status: "error",
+ error: "no token sent"
+ });
+ return;
+ }
+
+ https.get(`https://www.google.com/recaptcha/api/siteverify?secret=${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: ${JSON.stringify(data)}`);
+ res.status(403).json({
+ status: "error",
+ error: "captcha validation failed"
+ });
+ return;
+ }
+
+ 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"
+ });
+ return;
+ })
+};
+
+
+
+api.use(checkCaptcha);
+api.use(bodyParser.json());
+api.post("/api/account", (req, res) => {
+ if (!req.body || !Reflect.has(req.body, "username") ||
+ !Reflect.has(req.body, "password") || !Reflect.has(req.body, "email") ||
+ !req.body.username.match(/^[a-zA-Z0-9]{4,23}$/) ||
+ !req.body.password.match(/^[a-zA-Z0-9]{4,23}$/) ||
+ !req.body.email.match(/^|(?:[a-zA-Z0-9.$&+=_~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/) ||
+ req.body.email.length >= 40)
+ {
+ res.status(400).json({
+ status: "error",
+ error: "malformed request"
+ });
+ return;
+ }
+
+ let account = {
+ username: req.body.username,
+ password: req.body.password,
+ email: req.body.email || "a@a.com"
+ };
+
+ db.connect();
+ db.query(`SELECT COUNT(*) FROM ${config.sql.table} WHERE USERNAME="${account.username}"`, (err, rows, fields) => {
+ if (err) {
+ res.status(500).json({
+ status: "error",
+ error: "couldn't reach the database"
+ });
+ } else if (rows[0].count > 0) {
+ res.status(409).json({
+ status: "error",
+ error: "already exists"
+ });
+ } else {
+ db.query(`INSERT INTO ${config.sql.table} (USERNAME, PASSWORD, EMAIL, GENDER) VALUES ("${account.username}", "${account.password}", "${account.email}", "N")`, (err, rows, fields) => {
+ if (err) {
+ res.status(500).json({
+ status: "error",
+ error: "couldn't add the user"
+ });
+ } else {
+ res.status(201).json({
+ status: "success"
+ });
+ }
+ });
+ }
+ });
+
+ db.close();
+});
+
+
+
+api.use((req, res, next) => {
+ res.status(404).json({
+ status: "error",
+ error: "unknown endpoint"
+ });
+});
+
+api.set("trust proxy", "loopback"); // only allow localhost to communicate with the API
+api.listen(config.port, () => console.info(`Listening on port ${config.port}`));