diff --git a/modules/iapi.mjs b/modules/iapi.mjs index 77b99e44..3eed43f5 100644 --- a/modules/iapi.mjs +++ b/modules/iapi.mjs @@ -1,4 +1,9 @@ -import { generateNewToken, blake2bHash } from "./utils.mjs"; +import { + generateNewToken, blake2bHash, + strASCIIOnly, strStrictLegal, basicPasswordRequirement, isValidEmail +} from "./utils.mjs"; + +Object.prototype.p = Object.prototype.hasOwnProperty; class IAPI { constructor(mysql, redis, siteConfig, log, salt) { @@ -8,8 +13,31 @@ class IAPI { this.log = log; this.salt = salt; } - userLogin(username, password) { + IP(req) { + if(this.siteConfig.ip_detect_method == "connection"){ + return req.connection.remoteAddress; + }else if(this.siteConfig.ip_detect_method == "header"){ + //Default header X-Forwarded-From. + if(req.headers.p(this.siteConfig.ip_detect_header)){ + return req.headers[this.siteConfig.ip_detect_header]; + }else{ + this.log("error", "IAPI", "Dictated IP detection method is header, but header is not found"); + if(req.headers.p("x-forwarded-for")){ + return req.headers["x-forwarded-for"]; + }else{ + return req.connection.remoteAddress; + } + } + }else{ + this.log("error", "IAPI", "Dictated IP detection method is not found"); + return req.connection.remoteAddress; + } + } + userLogin(req, username, password) { + let connIP = this.IP(req); + let userAgent = req.headers['user-agent']; return new Promise((resolve, reject) => { + password = blake2bHash(this.salt + password); this.mysql.query( "SELECT * FROM users WHERE username = ?", [username], @@ -24,9 +52,11 @@ class IAPI { } else { let user = results[0]; if (user.password === password) { + let newToken = generateNewToken(this.salt, username); + this.log("debug", "IAPI", "User logged in: " + username); resolve({ - "uid":user + "uid": user }); } else { this.log("debug", "IAPI", "Wrong password"); @@ -38,42 +68,52 @@ class IAPI { ); }); } - userRegister(username, nickname, email, password) { - password = blake2bHash(password, this.salt); + userRegister(req, username, password, email, nickname) { + let connIP = this.IP(req); + let userAgent = req.headers['user-agent']; return new Promise((resolve, reject) => { - this.mysql.query( - "SELECT * FROM users WHERE username = ?", - [username], - (err, results) => { - if (err) { - this.log("debug", "IAPI", "Failed to query database"); - reject(err); - } else { - if (results.length === 0) { - this.mysql.query( - "INSERT INTO users (username, nickname, email, password, avatar, about, statistics, permissions, preferences) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", - [username, nickname, email, password, "", "", "{}", "{}", "{}"], - (err, results) => { - if (err) { - this.log("debug", "IAPI", "Failed to insert user"); - reject(err); - } else { - this.log("debug", "IAPI", "User registered: " + username); - resolve({ - "uid":results.insertId - }); - } - } - ); + if (!basicPasswordRequirement(password)) { + reject("Password does not meet basic requirements(> 8 characters, contains at least one number, one letter)"); + }else if (!strStrictLegal(username)) { + reject("Username does not meet strict legal requirements(only letters, numbers, and underscores)"); + }else if (!isValidEmail(email)) { + reject("Email is not valid"); + }else{ + password = blake2bHash(this.salt + password); + this.mysql.query( + "SELECT * FROM users WHERE username = ?", + [username], + (err, results) => { + if (err) { + this.log("debug", "IAPI", "Failed to query database"); + reject(err); } else { - this.log("debug", "IAPI", "User already exists: " + username); - reject("User already exists"); + if (results.length === 0) { + this.mysql.query( + "INSERT INTO users (username, nickname, email, password, avatar, about, statistics, permissions, preferences) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", + [username, nickname, email, password, "", "", "{}", "{}", "{}"], + (err, results) => { + if (err) { + this.log("debug", "IAPI", "Failed to insert user"); + reject(err); + } else { + this.log("debug", "IAPI", "User registered: " + username); + resolve({ + "uid": results.insertId + }); + } + } + ); + } else { + this.log("debug", "IAPI", "User already exists: " + username); + reject("User already exists"); + } } } - } - ); + ); + } }); } } -export {IAPI}; \ No newline at end of file +export { IAPI }; \ No newline at end of file diff --git a/modules/router.mjs b/modules/router.mjs index 69eba67f..de57e5b7 100644 --- a/modules/router.mjs +++ b/modules/router.mjs @@ -1,11 +1,13 @@ import {default as express} from "express"; -import { version } from "./utils.mjs"; +import { version, isAllString } from "./utils.mjs"; import { IAPI } from "./iapi.mjs"; import { default as fs } from "fs"; import { fileURLToPath } from "url"; import { join } from "path"; import { default as bodyParser } from "body-parser"; +Object.prototype.p = Object.prototype.hasOwnProperty; + function initializeRouter(mysqlConnection, redisConnection, siteConfig, log, salt){ const iapi = new IAPI(mysqlConnection, redisConnection, siteConfig, log, salt); let blorumRouter = express(); @@ -79,7 +81,19 @@ function initializeRouter(mysqlConnection, redisConnection, siteConfig, log, sal ); blorumRouter.post('/user/login', function (req, res) { - console.log(req,res); + let b = req.body + if(b.p("username") && b.p("password")){ + if(isAllString(b.username, b.password)){ + iapi.userLogin(b.username, b.password, req).then(function(result){ + res.set(commonHeader); + res.status(200).send(result); + }).catch(function(err){ + res.set(commonHeader); + res.status(500).send(err); + } + ); + } + } res.set("Content-Type","application/json"); res.set(commonHeader); res.status(200).send(); @@ -87,17 +101,17 @@ function initializeRouter(mysqlConnection, redisConnection, siteConfig, log, sal blorumRouter.post('/user/register', function (req, res) { let b = req.body; - if(b.hasOwnProperty("username") && b.hasOwnProperty("password") && b.hasOwnProperty("email") && b.hasOwnProperty("nickname")){ - if(typeof b.username == "string" && typeof b.password == "string" && typeof b.email == "string" && typeof b.nickname == "string" ){ + if(b.p("username") && b.p("password") && b.p("email") && b.p("nickname")){ + if(isAllString(b.username, b.password, b.email, b.nickname)){ try { res.set("Content-Type","application/json"); res.set(commonHeader); - iapi.userRegister(b.username, b.nickname, b.password, b.email).then(function (result) { + iapi.userRegister(req, b.username, b.password, b.email, b.nickname).then(function (result) { res.status(200).send(result); } ).catch(function (error) { log("debug", "Router", "Failed to register user: " + error); - res.sendStatus(500); + res.status(500).send(error); }); } catch (error) { log("debug", "Router", "Failed to register user: " + error); diff --git a/modules/utils.mjs b/modules/utils.mjs index 0139a3f1..e96c6b28 100644 --- a/modules/utils.mjs +++ b/modules/utils.mjs @@ -1,7 +1,7 @@ import { default as crypto } from "crypto"; import Redis from "ioredis"; -const version = "1.0.0 in_dev (unf, debug) dv 10001"; +const version = "1.0.0 in_dev (unf, debug) dv 10002"; const c = { "reset": "\x1b[0m", @@ -101,6 +101,15 @@ function isModuleAvailable(name) { } } +function isAllString(...args){ + for(let i = 0; i < args.length; i++){ + if(typeof args[i] !== "string"){ + return false; + } + } + return true; +} + function strASCIIOnly(str){ return /^[\x00-\x7F]*$/.test(str); } @@ -109,6 +118,17 @@ function strStrictLegal(str){ return /^[a-zA-Z0-9_]+$/.test(str); } +function basicPasswordRequirement(str){ + //at least 8 characters, includes at least one number, one letter + return /^(?:(?=.*[a-z])|(?=.*[A-Z]))(?=.*\d)[^]{8,}$/.test(str); +} + +function isValidEmail(str){ + return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str); +} + export { - version, outputLogs, outputLogsColored, blake2bHash, generateNewToken, isModuleAvailable, promisifiedMysqlConnect, promisifiedRedisConnect + version, outputLogs, outputLogsColored, blake2bHash, generateNewToken, + isModuleAvailable, promisifiedMysqlConnect, promisifiedRedisConnect, + strASCIIOnly, strStrictLegal, basicPasswordRequirement, isValidEmail, isAllString }; \ No newline at end of file diff --git a/site_config.sql b/site_config.sql index eeceb034..cfb2faff 100644 --- a/site_config.sql +++ b/site_config.sql @@ -9,7 +9,7 @@ INSERT INTO `config`(`flag`, `value`) VALUES ("site_url","127.0.0.1"); INSERT INTO `config`(`flag`, `value`) VALUES ("site_title","Yet another Blorum site."); INSERT INTO `config`(`flag`, `value`) VALUES ("site_excerpt","This is a Blorum site, where you could publish blogs and chat."); INSERT INTO `config`(`flag`, `value`) VALUES ("site_logo","/favicon.ico"); -INSERT INTO `config`(`flag`, `value`) VALUES ("ip_detect_method","raw_ip"); +INSERT INTO `config`(`flag`, `value`) VALUES ("ip_detect_method","connection"); INSERT INTO `config`(`flag`, `value`) VALUES ("ip_detect_header","X-Forwarded-From"); INSERT INTO `config`(`flag`, `value`) VALUES ("user_cookie_expire_after","2630000"); INSERT INTO `config`(`flag`, `value`) VALUES ("admin_cookie_expire_after","1315000"); diff --git a/unitTest.js b/test.mjs similarity index 100% rename from unitTest.js rename to test.mjs