From 0256599b94bcf5ccee03ba674dc3700ef7c5edaf Mon Sep 17 00:00:00 2001 From: mmdzov Date: Sat, 19 Aug 2023 08:02:37 +0330 Subject: [PATCH] Completed /api/add --- .env.example | 3 ++ .gitignore | 1 + api/controller.js | 12 +++++- api/middlewares.js | 104 +++++++++++++++++++++++++++++++++++++++++++++ api/model.js | 43 ++++++++++++++++++- api/utils.js | 14 ++++++ api/validator.js | 28 ------------ db/DBSqlite3.js | 2 +- types.js | 17 ++++++++ utils.js | 10 ++++- 10 files changed, 200 insertions(+), 34 deletions(-) create mode 100644 api/middlewares.js create mode 100644 api/utils.js delete mode 100644 api/validator.js diff --git a/.env.example b/.env.example index f6a29ff..3eb2303 100644 --- a/.env.example +++ b/.env.example @@ -43,6 +43,9 @@ SSH_PORT=22 # true | false API_ENABLE=true +# api documentation +API_DOCS=false + # Secret key for your tokens API_SECRET=S@CrET diff --git a/.gitignore b/.gitignore index ad61f9a..2e37049 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ users.json blocked_ips.csv +users.csv # Logs logs diff --git a/api/controller.js b/api/controller.js index ba7ca76..c375163 100644 --- a/api/controller.js +++ b/api/controller.js @@ -1,14 +1,22 @@ +const { AuthApiKey, Validator } = require("./middlewares"); const Model = require("./model"); -const Validator = require("./validator"); const router = require("express").Router(); const validator = new Validator(); const model = new Model(); +// Send your api username and password in json format to generate api_key router.post("/token", validator.token, (req, res) => { const result = model.token(); - return res.json({ api_key: result }); + return res.json(result); +}); + +// Send your proxy email and limit data in json format to be applied +router.post("/add", AuthApiKey, validator.add, (req, res) => { + const result = model.add(req.body); + + return res.json(result); }); module.exports = router; diff --git a/api/middlewares.js b/api/middlewares.js new file mode 100644 index 0000000..79ed096 --- /dev/null +++ b/api/middlewares.js @@ -0,0 +1,104 @@ +const { response } = require("./utils"); +const crypto = require("crypto-js"); +const Joi = require("joi"); + +const AuthApiKey = (req, res, next) => { + let apiKey = (req.headers["api_key"] || "").trim(); + + if (!apiKey) + return res.status(403).json( + response({ + error: { + type: "AUTH", + reason: "api_key Not found!", + }, + }), + ); + + let decryptedKey = crypto.AES.decrypt( + apiKey, + process.env.API_SECRET, + ).toString(crypto.enc.Utf8); + + const parseKey = JSON.parse(decryptedKey); + + if (Date.now() > +parseKey.expireAt) + return res.status(403).json( + response({ + error: { + type: "AUTH", + reason: "api_key expired Please get a new api_key", + }, + }), + ); + + return next(); +}; + +class Validator { + token(req, res, next) { + /** + * @type {ApiSetTokenType} + */ + const data = req.body; + + const schema = Joi.object({ + username: Joi.string().required(), + password: Joi.string().required(), + }); + + const { error } = schema.validate(data); + + if (error) { + return res.status(403).json( + response({ + error: { + type: "INVALID", + reason: error.message, + }, + }), + ); + } + + if (process.env.API_LOGIN !== `${data.username}:${data.password}`) + return res.status(403).json( + response({ + error: { + type: "NOT_MATCH", + reason: "Username or password doesn't match", + }, + }), + ); + + return next(); + } + + add(req, res, next) { + const schema = Joi.object({ + email: Joi.string().required(), + limit: Joi.number().required(), + }); + + const data = req.body; + + const { error } = schema.validate(data); + + if (error) { + return res.status(403).json( + response({ + error: { + type: "INVALID", + reason: error.message, + }, + }), + ); + } + + return next(); + } +} + +module.exports = { + AuthApiKey, + Validator, +}; diff --git a/api/model.js b/api/model.js index 3f52df6..14689c4 100644 --- a/api/model.js +++ b/api/model.js @@ -1,6 +1,14 @@ const crypto = require("crypto-js"); +const { File } = require("../utils"); +const { join } = require("path"); +const fs = require("fs"); +const { response } = require("./utils"); class Model { + constructor() { + this.usersCsvPath = join(__dirname, "../", "users.csv"); + } + token() { const expireAt = Date.now() + 1000 * 60 * 60 * 24 * +process.env.API_EXPIRE_TOKEN_AT; @@ -12,7 +20,40 @@ class Model { process.env.API_SECRET, ).toString(); - return token; + return response({ + data: { api_key: token }, + status: 1, + }); + } + + /** + * @typedef {Object} ApiAddDataType + * @property {string} email + * @property {string} limit + * + * @param {ApiAddDataType} data + */ + + add(data) { + let file = new File().GetCsvFile(this.usersCsvPath).toString(); + + if (file.includes(data.email)) + return response({ + error: { + type: "DUPLICATE", + reason: "This email already exists", + }, + }); + + const dataToCsv = `${data.email},${data.limit}\r\n`; + + file += dataToCsv; + + fs.writeFileSync(this.usersCsvPath, file); + + return response({ + status: 1, + }); } } diff --git a/api/utils.js b/api/utils.js new file mode 100644 index 0000000..695f26e --- /dev/null +++ b/api/utils.js @@ -0,0 +1,14 @@ +/** + * + * @param {ApiResponseType} params + */ + +const response = (params) => { + if (!params?.data) params.data = null; + if (!params?.error) params.error = null; + if (!params?.status) params.status = 0; + + return params; +}; + +module.exports = { response }; diff --git a/api/validator.js b/api/validator.js deleted file mode 100644 index 19523a3..0000000 --- a/api/validator.js +++ /dev/null @@ -1,28 +0,0 @@ -const Joi = require("joi"); - -class Validator { - token(req, res, next) { - /** - * @type {ApiSetTokenType} - */ - const data = req.body; - - console.log(data); - - const schema = Joi.object({ - username: Joi.string().required(), - password: Joi.string().required(), - }); - - const { error } = schema.validate(data); - - if (error) return next(error.message); - - if (process.env.API_LOGIN !== `${data.username}:${data.password}`) - return next("Username or password doesn't match"); - - return next(); - } -} - -module.exports = Validator; diff --git a/db/DBSqlite3.js b/db/DBSqlite3.js index 9d0e643..d36f0f4 100644 --- a/db/DBSqlite3.js +++ b/db/DBSqlite3.js @@ -87,7 +87,7 @@ class DBSqlite3 extends DBInterface { const indexOfIp = ips.findIndex((item) => item.ip === ipData.ip); // Get the users.json file - const usersJson = new File().GetFilesJson(join("users.json")); + const usersJson = new File().GetJsonFile(join("users.json")); const indexOfUser = usersJson.findIndex((item) => item[0] === email); const userJson = usersJson[indexOfUser] || [ diff --git a/types.js b/types.js index 1b1264e..d9d26b4 100644 --- a/types.js +++ b/types.js @@ -45,3 +45,20 @@ * @property {string} username * @property {string} password */ + +/** + * @typedef {"INVALID" | "NOT_MATCH" | "AUTH" | "DUPLICATE"} ApiErrorTypes + */ + +/** + * @typedef {Object} ApiResponseErrorType + * @property {ApiErrorTypes} type + * @property {string} reason + */ + +/** + * @typedef {Object} ApiResponseType + * @property {Object | null} data + * @property {ApiResponseErrorType} error + * @property {0|1} status + */ diff --git a/utils.js b/utils.js index 9d754c4..8fe6577 100644 --- a/utils.js +++ b/utils.js @@ -145,11 +145,17 @@ class File { return; } - GetFilesJson(path) { + GetJsonFile(path) { this.ForceExistsFile(path); return JSON.parse(fs.readFileSync(path)); } + + GetCsvFile(path) { + this.ForceExistsFile(path, ""); + + return fs.readFileSync(path); + } } /** @@ -174,7 +180,7 @@ class IPGuard { const indexOfIp = data.ips.findIndex((item) => item.ip === `${ip}`); - const users = new File().GetFilesJson("users.json"); + const users = new File().GetJsonFile("users.json"); const user = users.filter((item) => item[0] === data.email)[0] || null; const maxAllowConnection = user ? +user[1] : +process.env.MAX_ALLOW_USERS;