From e8fb2fdbe0b40fe892c41bd96b10d4d39e829daa Mon Sep 17 00:00:00 2001 From: davidcrammer Date: Mon, 1 Apr 2024 17:15:07 -0700 Subject: [PATCH] Delete unused files and update dependencies --- .gitignore | 3 +- api/classes/authFactorClass.js | 126 ++++---- api/classes/capabilitiesClass.js | 86 ++---- api/classes/cookiesClass.js | 57 ++-- api/classes/jwtClass.js | 85 +++--- api/classes/responseClass.js | 61 ++-- api/classes/rolesClass.js | 219 ++++++-------- api/classes/totpClass.js | 165 +++++----- api/classes/usersClass.js | 416 ++++++++++++------------- api/config/CodedError.js | 30 +- api/config/config.js | 19 +- api/config/database.js | 32 +- api/index.js | 80 ++--- api/middleware/errorMiddleware.js | 17 +- api/middleware/rateLimiters.js | 27 +- api/middleware/requireSSL.js | 19 +- api/middleware/verifyUser.js | 72 ++--- api/models/AuthFactor.js | 39 ++- api/models/Capability.js | 33 +- api/models/Role.js | 57 ++-- api/models/Role_Capability.js | 23 +- api/models/Token.js | 35 +-- api/models/User.js | 77 ++--- api/models/associations.js | 113 ++++--- api/models/index.js | 37 ++- api/routes/baseRoutes.js | 33 +- api/routes/loginRoutes.js | 100 +++--- api/routes/passwordRoutes.js | 162 +++++----- api/routes/refreshRoutes.js | 62 ++-- api/routes/registerRoutes.js | 101 +++---- api/routes/testRoutes.js | 15 +- api/routes/totpRoutes.js | 170 +++++------ api/routes/userRoutes.js | 195 ++++++------ api/utils/sendEmail.js | 29 +- package.json | 3 +- public/test.txt | 0 src/classes/authFactorClass.js | 137 --------- src/classes/capabilitiesClass.js | 88 ------ src/classes/cookiesClass.js | 80 ----- src/classes/jwtClass.js | 94 ------ src/classes/responseClass.js | 77 ----- src/classes/rolesClass.js | 189 ------------ src/classes/totpClass.js | 182 ----------- src/classes/usersClass.js | 486 ------------------------------ src/config/CodedError.js | 16 - src/config/config.js | 29 -- src/config/database.js | 15 - src/index.js | 50 --- src/middleware/errorMiddleware.js | 8 - src/middleware/rateLimiters.js | 15 - src/middleware/requireSSL.js | 12 - src/middleware/verifyUser.js | 52 ---- src/models/AuthFactor.js | 30 -- src/models/Capability.js | 21 -- src/models/Role.js | 32 -- src/models/Role_Capability.js | 12 - src/models/Token.js | 25 -- src/models/User.js | 42 --- src/models/associations.js | 57 ---- src/models/index.js | 22 -- src/routes/baseRoutes.js | 18 -- src/routes/loginRoutes.js | 53 ---- src/routes/passwordRoutes.js | 91 ------ src/routes/refreshRoutes.js | 32 -- src/routes/registerRoutes.js | 49 --- src/routes/testRoutes.js | 6 - src/routes/totpRoutes.js | 113 ------- src/routes/userRoutes.js | 113 ------- src/utils/sendEmail.js | 11 - tsconfig.json | 23 -- 70 files changed, 1283 insertions(+), 3795 deletions(-) delete mode 100644 public/test.txt delete mode 100644 src/classes/authFactorClass.js delete mode 100644 src/classes/capabilitiesClass.js delete mode 100644 src/classes/cookiesClass.js delete mode 100644 src/classes/jwtClass.js delete mode 100644 src/classes/responseClass.js delete mode 100644 src/classes/rolesClass.js delete mode 100644 src/classes/totpClass.js delete mode 100644 src/classes/usersClass.js delete mode 100644 src/config/CodedError.js delete mode 100644 src/config/config.js delete mode 100644 src/config/database.js delete mode 100644 src/index.js delete mode 100644 src/middleware/errorMiddleware.js delete mode 100644 src/middleware/rateLimiters.js delete mode 100644 src/middleware/requireSSL.js delete mode 100644 src/middleware/verifyUser.js delete mode 100644 src/models/AuthFactor.js delete mode 100644 src/models/Capability.js delete mode 100644 src/models/Role.js delete mode 100644 src/models/Role_Capability.js delete mode 100644 src/models/Token.js delete mode 100644 src/models/User.js delete mode 100644 src/models/associations.js delete mode 100644 src/models/index.js delete mode 100644 src/routes/baseRoutes.js delete mode 100644 src/routes/loginRoutes.js delete mode 100644 src/routes/passwordRoutes.js delete mode 100644 src/routes/refreshRoutes.js delete mode 100644 src/routes/registerRoutes.js delete mode 100644 src/routes/testRoutes.js delete mode 100644 src/routes/totpRoutes.js delete mode 100644 src/routes/userRoutes.js delete mode 100644 src/utils/sendEmail.js delete mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 5b31d10..25a8032 100644 --- a/.gitignore +++ b/.gitignore @@ -131,4 +131,5 @@ dist .pnp.* # Keys -*.pem \ No newline at end of file +*.pem +.vercel diff --git a/api/classes/authFactorClass.js b/api/classes/authFactorClass.js index 2d0bf75..9bdda12 100644 --- a/api/classes/authFactorClass.js +++ b/api/classes/authFactorClass.js @@ -1,13 +1,7 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _AuthFactor = _interopRequireDefault(require("../models/AuthFactor")); -var _User = _interopRequireDefault(require("../models/User")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +import AuthFactorModel from "../models/AuthFactor" +import User from "../models/User" +import CodedError from "../config/CodedError" + /** * @typedef {Object} AuthFactorType * Options @@ -23,6 +17,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de * @property {AuthFactorType} factor - The type of auth factor (TOTP, SMS, etc.) * @property {string} secret - The secret that is used to generate the TOTP */ + /** * Handles the methods for interacitng with the AuthFactor data */ @@ -39,15 +34,16 @@ class AuthFactor { */ async createRecord(userId, factor, secret) { try { - const authFactor = await _AuthFactor.default.create({ + const authFactor = await AuthFactorModel.create({ userId, factor, secret, - verified: false - }); - return authFactor.dataValues; + verified: false, + }) + + return authFactor.dataValues } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|01"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|01") } } @@ -62,30 +58,23 @@ class AuthFactor { */ async activateRecord(id) { try { - const authFactor = await _AuthFactor.default.findOne({ - where: { - id - } - }); - if (!authFactor) throw new _CodedError.default("Auth factor not found", 404, "AUTHFACTOR|02"); + const authFactor = await AuthFactorModel.findOne({ where: { id } }) + if (!authFactor) throw new CodedError("Auth factor not found", 404, "AUTHFACTOR|02") + await authFactor.update({ verified: true, - verifiedAt: new Date() - }); + verifiedAt: new Date(), + }) // update user mfa flag - const user = await _User.default.findOne({ - where: { - id: authFactor.userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "AUTHFACTOR|03"); - await user.update({ - mfa: true - }); - return true; + const user = await User.findOne({ where: { id: authFactor.userId } }) + if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|03") + + await user.update({ mfa: true }) + + return true } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|03"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|03") } } @@ -98,28 +87,22 @@ class AuthFactor { */ async deleteRecord(id) { try { - const authFactor = await _AuthFactor.default.findOne({ - where: { - id - } - }); - if (!authFactor) throw new _CodedError.default("Auth factor not found", 404, "AUTHFACTOR|04"); - await authFactor.destroy(); - const user = await _User.default.findOne({ - where: { - id: authFactor.userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "AUTHFACTOR|05"); - const userAuthFactors = await user.getAuthFactors(); + const authFactor = await AuthFactorModel.findOne({ where: { id } }) + if (!authFactor) throw new CodedError("Auth factor not found", 404, "AUTHFACTOR|04") + + await authFactor.destroy() + + const user = await User.findOne({ where: { id: authFactor.userId } }) + if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|05") + + const userAuthFactors = await user.getAuthFactors() if (userAuthFactors.length === 0) { - await user.update({ - mfa: false - }); + await user.update({ mfa: false }) } - return true; + + return true } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|05"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|05") } } @@ -132,28 +115,23 @@ class AuthFactor { */ async disableMFA(userId) { try { - const user = await _User.default.findOne({ - where: { - id: userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "AUTHFACTOR|06"); - await user.update({ - mfa: false - }); - const authFactors = await _AuthFactor.default.getAll({ - where: { - userId - } - }); - if (!authFactors) throw new _CodedError.default("Auth factors not found", 404, "AUTHFACTOR|07"); - authFactors.forEach(async authFactor => { - await authFactor.destroy(); - }); - return true; + const user = await User.findOne({ where: { id: userId } }) + if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|06") + + await user.update({ mfa: false }) + + const authFactors = await AuthFactorModel.getAll({ where: { userId } }) + if (!authFactors) throw new CodedError("Auth factors not found", 404, "AUTHFACTOR|07") + + authFactors.forEach(async (authFactor) => { + await authFactor.destroy() + }) + + return true } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|08"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|08") } } } -var _default = exports.default = AuthFactor; \ No newline at end of file + +export default AuthFactor diff --git a/api/classes/capabilitiesClass.js b/api/classes/capabilitiesClass.js index 621083c..65ca799 100644 --- a/api/classes/capabilitiesClass.js +++ b/api/classes/capabilitiesClass.js @@ -1,13 +1,7 @@ -"use strict"; +import CapabilityModel from "../models/Capability" +import RoleModel from "../models/Role" +import CodedError from "../config/CodedError" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _Capability = _interopRequireDefault(require("../models/Capability")); -var _Role = _interopRequireDefault(require("../models/Role")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class Capability { /** * Get all capabilities that match the conditions @@ -17,12 +11,10 @@ class Capability { */ async getCapabilities(conditions = {}) { try { - const capabilities = await _Capability.default.findAll({ - where: conditions - }); - return capabilities; + const capabilities = await CapabilityModel.findAll({ where: conditions }) + return capabilities } catch (error) { - throw new _CodedError.default(error.message, 400, "CAPABILITY|00"); + throw new CodedError(error.message, 400, "CAPABILITY|00") } } @@ -31,12 +23,10 @@ class Capability { */ async getCapability(conditions = {}) { try { - const capability = await _Capability.default.findOne({ - where: conditions - }); - return capability; + const capability = await CapabilityModel.findOne({ where: conditions }) + return capability } catch (error) { - throw new _CodedError.default(error.message, 400, "CAPABILITY|01"); + throw new CodedError(error.message, 400, "CAPABILITY|01") } } @@ -50,24 +40,19 @@ class Capability { * @returns {Promise} */ async createCapability(data = {}, options = {}) { - const { - name, - description - } = data; - const { - roles - } = options; + const { name, description } = data + const { roles } = options + try { - const capability = await _Capability.default.create({ - name, - description - }); + const capability = await CapabilityModel.create({ name, description }) + if (roles) { - await capability.setRoles(roles); + await capability.setRoles(roles) } - return capability; + + return capability } catch (error) { - throw new _CodedError.default(error.message, 400, "CAPABILITY|02"); + throw new CodedError(error.message, 400, "CAPABILITY|02") } } @@ -81,30 +66,23 @@ class Capability { */ async deleteCapabilities(capabilities) { try { - if (!capabilities) throw new _CodedError.default("No capabilities provided", 400, "CAPABILITY|02"); - if (!Array.isArray(capabilities)) throw new _CodedError.default("Capabilities must be an array", 400, "CAPABILITY|03"); - await _Capability.default.destroy({ - where: { - name: capabilities - } - }); + if (!capabilities) throw new CodedError("No capabilities provided", 400, "CAPABILITY|02") + if (!Array.isArray(capabilities)) throw new CodedError("Capabilities must be an array", 400, "CAPABILITY|03") + await CapabilityModel.destroy({ where: { name: capabilities } }) // Remove the capabilities from all roles - const roles = await _Role.default.findAll({ - include: [{ - model: _Capability.default, - as: "capabilities" - }] - }); - roles.forEach(async role => { - const roleCapabilities = role.capabilities.map(capability => capability.name); - const updatedCapabilities = roleCapabilities.filter(capability => !capabilities.includes(capability)); - await role.setCapabilities(updatedCapabilities); - }); - return true; + const roles = await RoleModel.findAll({ include: [{ model: CapabilityModel, as: "capabilities" }] }) + roles.forEach(async (role) => { + const roleCapabilities = role.capabilities.map((capability) => capability.name) + const updatedCapabilities = roleCapabilities.filter((capability) => !capabilities.includes(capability)) + await role.setCapabilities(updatedCapabilities) + }) + + return true } catch (error) { - throw new _CodedError.default(error.message, 400, "CAPABILITY|03"); + throw new CodedError(error.message, 400, "CAPABILITY|03") } } } -var _default = exports.default = Capability; \ No newline at end of file + +export default Capability diff --git a/api/classes/cookiesClass.js b/api/classes/cookiesClass.js index 583aaaa..e454f07 100644 --- a/api/classes/cookiesClass.js +++ b/api/classes/cookiesClass.js @@ -1,20 +1,9 @@ -"use strict"; +import CodedError from "../config/CodedError" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } -function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } -function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } -function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } class Cookies { constructor(req, res) { - this.req = req; - this.res = res; + this.req = req + this.res = res } /** @@ -22,18 +11,19 @@ class Cookies { */ set(name, value, options = {}) { try { - this.res.cookie(name, value, options); - return true; + this.res.cookie(name, value, options) + return true } catch (error) { - throw new _CodedError.default(`Error setting cookie: ${error.message}`, 500, "LOG|05"); + throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") } } + setSessionCookie(value) { this.set("session", value, { httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict" - }); + sameSite: "strict", + }) } /** @@ -42,10 +32,10 @@ class Cookies { */ getRefreshToken() { try { - const token = this.req?.cookies?.refreshToken; - return token; + const token = this.req?.cookies?.refreshToken + return token } catch (error) { - return false; + return false } } @@ -58,13 +48,14 @@ class Cookies { */ setHttpOnly(name, value, options = {}) { try { - this.res.cookie(name, value, _objectSpread({ + this.res.cookie(name, value, { httpOnly: true, - secure: process.env.NODE_ENV === "production" - }, options)); - return true; + secure: process.env.NODE_ENV === "production", + ...options, + }) + return true } catch (error) { - throw new _CodedError.default(`Error setting cookie: ${error.message}`, 500, "LOG|05"); + throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") } } @@ -77,13 +68,13 @@ class Cookies { setRefreshToken(token) { try { this.setHttpOnly("refreshToken", token, { - maxAge: 1000 * 60 * 60 * 24 * 7 // 7 days - }); - - return true; + maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days + }) + return true } catch (error) { - throw new _CodedError.default(`Error setting cookie: ${error.message}`, 500, "LOG|05"); + throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") } } } -var _default = exports.default = Cookies; \ No newline at end of file + +export default Cookies diff --git a/api/classes/jwtClass.js b/api/classes/jwtClass.js index 0841bdf..1d2d30b 100644 --- a/api/classes/jwtClass.js +++ b/api/classes/jwtClass.js @@ -1,13 +1,7 @@ -"use strict"; +import jwt from "jsonwebtoken" +import CodedError from "../config/CodedError" +import Token from "../models/Token" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _jsonwebtoken = _interopRequireDefault(require("jsonwebtoken")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _Token = _interopRequireDefault(require("../models/Token")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * The Standard payload for a JWT token * @typedef {Object} JWTToken @@ -24,9 +18,10 @@ class JWT { */ constructor(publicKey, privateKey) { - this.publicKey = publicKey || process.env.JWT_PUBLIC; - this.privateKey = privateKey || process.env.JWT_PRIVATE; - if (!this.publicKey || !this.privateKey) throw new _CodedError.default("Invalid key configuration", 500, "JWT|00"); + this.publicKey = publicKey || process.env.JWT_PUBLIC + this.privateKey = privateKey || process.env.JWT_PRIVATE + + if (!this.publicKey || !this.privateKey) throw new CodedError("Invalid key configuration", 500, "JWT|00") } /** @@ -37,18 +32,17 @@ class JWT { * @returns {Promise} - The signed JWT token */ async sign(payload, expiresIn = "1m") { - const token = _jsonwebtoken.default.sign(payload, this.privateKey, { - expiresIn, - algorithm: "RS256" - }); - const expiresUnix = _jsonwebtoken.default.decode(token).exp; - const expires = new Date(expiresUnix * 1000); - const logToken = await _Token.default.create({ + const token = jwt.sign(payload, this.privateKey, { expiresIn, algorithm: "RS256" }) + const expiresUnix = jwt.decode(token).exp + const expires = new Date(expiresUnix * 1000) + + const logToken = await Token.create({ token, - expires - }); - if (!logToken) throw new _CodedError.default("Error logging token", 500, "JWT|05"); - return token; + expires, + }) + if (!logToken) throw new CodedError("Error logging token", 500, "JWT|05") + + return token } /** @@ -59,18 +53,14 @@ class JWT { */ async verify(token) { try { - const tokenExists = await _Token.default.findOne({ - where: { - token - } - }); - if (!tokenExists) throw new _CodedError.default("Token not found", 400, "JWT|02"); - if (tokenExists.blacklisted) throw new _CodedError.default("Token is blacklisted", 400, "JWT|03"); - return _jsonwebtoken.default.verify(token, this.publicKey, { - algorithms: ["RS256"] - }); + const tokenExists = await Token.findOne({ where: { token } }) + if (!tokenExists) throw new CodedError("Token not found", 400, "JWT|02") + + if (tokenExists.blacklisted) throw new CodedError("Token is blacklisted", 400, "JWT|03") + + return jwt.verify(token, this.publicKey, { algorithms: ["RS256"] }) } catch (error) { - return new _CodedError.default(error.message, 400, "JWT|01"); + return new CodedError(error.message, 400, "JWT|01") } } @@ -82,24 +72,23 @@ class JWT { */ async blacklist(token) { try { - let tokenExists = await _Token.default.findOne({ - where: { - token - } - }); + let tokenExists = await Token.findOne({ where: { token } }) if (!tokenExists) { - const decodedToken = _jsonwebtoken.default.decode(token); - tokenExists = await _Token.default.create({ + const decodedToken = jwt.decode(token) + tokenExists = await Token.create({ token, - expires: decodedToken.exp - }); + expires: decodedToken.exp, + }) } - tokenExists.blacklisted = true; - await tokenExists.save(); - return true; + + tokenExists.blacklisted = true + await tokenExists.save() + + return true } catch (error) { - throw new _CodedError.default(error.message, 400, "JWT|04"); + throw new CodedError(error.message, 400, "JWT|04") } } } -var _default = exports.default = JWT; \ No newline at end of file + +export default JWT diff --git a/api/classes/responseClass.js b/api/classes/responseClass.js index 777d439..55875b6 100644 --- a/api/classes/responseClass.js +++ b/api/classes/responseClass.js @@ -1,11 +1,5 @@ -"use strict"; +import CodedError from "../config/CodedError" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } class Response { /** * Used to normalize the response @@ -14,8 +8,8 @@ class Response { * @param {*} res */ constructor(req, res) { - this.req = req; - this.res = res; + this.req = req + this.res = res } /** @@ -24,13 +18,14 @@ class Response { * @returns {boolean} true */ success(data) { - const response = { - success: true - }; - if (this.req.token) response.token = this.req.token; - if (data) response.data = data; - this.res.status(this.req.status ?? 200).json(response); - return true; + const response = { success: true } + + if (this.req.token) response.token = this.req.token + if (data) response.data = data + + this.res.status(this.req.status ?? 200).json(response) + + return true } /** @@ -39,26 +34,23 @@ class Response { * @returns {boolean} true */ error(error) { - const isDev = process.env.NODE_ENV === "development"; - if (isDev) console.error(error); - if (error instanceof _CodedError.default) { + const isDev = process.env.NODE_ENV === "development" + if (isDev) console.error(error) + if (error instanceof CodedError) { const responseBody = { error: true, success: false, message: error.message, location: isDev ? error.location : undefined, req: isDev ? this.req.body : undefined, - data: error.data - }; - this.res.status(error.status ?? 500).json(responseBody); - return true; + data: error.data, + } + this.res.status(error.status ?? 500).json(responseBody) + return true } - this.res.status(500).json({ - success: false, - error: true, - message: error.message - }); - return true; + + this.res.status(500).json({ success: false, error: true, message: error.message }) + return true } /** @@ -67,8 +59,8 @@ class Response { * @returns {boolean} - true */ setStatus(status) { - this.req.status = status; - return true; + this.req.status = status + return true } /** @@ -77,8 +69,9 @@ class Response { * @returns {boolean} - true */ setToken(token) { - this.req.token = token; - return true; + this.req.token = token + return true } } -var _default = exports.default = Response; \ No newline at end of file + +export default Response diff --git a/api/classes/rolesClass.js b/api/classes/rolesClass.js index 37f0a9e..1b9dae5 100644 --- a/api/classes/rolesClass.js +++ b/api/classes/rolesClass.js @@ -1,14 +1,8 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _Role = _interopRequireDefault(require("../models/Role")); -var _Capability = _interopRequireDefault(require("../models/Capability")); -var _capabilitiesClass = _interopRequireDefault(require("./capabilitiesClass")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +import RoleModel from "../models/Role" +import CapabilityModel from "../models/Capability" +import Capability from "./capabilitiesClass" +import CodedError from "../config/CodedError" + class Role { /** * Get all roles that match the conditions @@ -19,12 +13,10 @@ class Role { */ async getRoles(conditions = {}) { try { - const roles = await _Role.default.findAll({ - where: conditions - }); - return roles; + const roles = await RoleModel.findAll({ where: conditions }) + return roles } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|00"); + throw new CodedError(error.message, 400, "ROLE|00") } } @@ -36,12 +28,10 @@ class Role { */ async getRole(conditions = {}) { try { - const role = await _Role.default.findOne({ - where: conditions - }); - return role; + const role = await RoleModel.findOne({ where: conditions }) + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|01"); + throw new CodedError(error.message, 400, "ROLE|01") } } @@ -54,44 +44,33 @@ class Role { * @returns {Promise} */ async createRole(data = {}, options = {}) { - const { - name, - description, - capabilities: cababilityNames - } = data; - const { - copyFrom - } = options; + const { name, description, capabilities: cababilityNames } = data + const { copyFrom } = options + try { - const role = await _Role.default.create({ - name, - description - }); - const capability = new _capabilitiesClass.default(); - const SETTING_NEW_CAPABILITIES = cababilityNames && cababilityNames.length; - const capabilityObjects = SETTING_NEW_CAPABILITIES ? await capability.getCapabilities({ - name: cababilityNames - }) : []; + const role = await RoleModel.create({ name, description }) + const capability = new Capability() + + const SETTING_NEW_CAPABILITIES = cababilityNames && cababilityNames.length + const capabilityObjects = SETTING_NEW_CAPABILITIES ? await capability.getCapabilities({ name: cababilityNames }) : [] + if (copyFrom) { - const existingRole = await _Role.default.findOne({ - where: { - name: copyFrom - }, - include: [{ - model: _Capability.default, - as: "capabilities" - }] - }); - if (!existingRole?.capabilities) return role; - const capabilitiesList = [...existingRole.capabilities, ...capabilityObjects]; - await role.setCapabilities(capabilitiesList); + const existingRole = await RoleModel.findOne({ + where: { name: copyFrom }, + include: [{ model: CapabilityModel, as: "capabilities" }], + }) + + if (!existingRole?.capabilities) return role + const capabilitiesList = [...existingRole.capabilities, ...capabilityObjects] + await role.setCapabilities(capabilitiesList) } else { - if (!capabilityObjects.length) return role; - await role.setCapabilities(capabilityObjects); + if (!capabilityObjects.length) return role + await role.setCapabilities(capabilityObjects) } - return role; + + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|02"); + throw new CodedError(error.message, 400, "ROLE|02") } } @@ -104,41 +83,30 @@ class Role { * @returns {Promise} */ async updateRole(data = {}) { - const { - id, - name, - description, - capabilities: capabilityNames - } = data; - const capability = new _capabilitiesClass.default(); + const { id, name, description, capabilities: capabilityNames } = data + const capability = new Capability() + try { - const condition = id ? { - id - } : { - name - }; - const role = await _Role.default.findOne({ - where: condition - }); - if (!role) throw new _CodedError.default("Role not found", 400, "ROLE|03"); - await role.update({ - name, - description - }); + const condition = id ? { id } : { name } + const role = await RoleModel.findOne({ where: condition }) + if (!role) throw new CodedError("Role not found", 400, "ROLE|03") + + await role.update({ name, description }) + if (Array.isArray(capabilityNames) && !capabilityNames.length) { - await role.setCapabilities([]); - return role; + await role.setCapabilities([]) + return role } + if (capabilityNames) { - const capabilities = await capability.getCapabilities({ - name: capabilityNames - }); - if (!capabilities.length) return role; - await role.setCapabilities(capabilities); + const capabilities = await capability.getCapabilities({ name: capabilityNames }) + if (!capabilities.length) return role + await role.setCapabilities(capabilities) } - return role; + + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|04"); + throw new CodedError(error.message, 400, "ROLE|04") } } @@ -150,23 +118,20 @@ class Role { * @returns {Promise} */ async addCapabilities(roleName, capabilities = []) { - const capability = new _capabilitiesClass.default(); + const capability = new Capability() + try { - if (typeof roleName !== "string") throw new _CodedError.default("Role name is required", 400, "ROLE|05"); - const role = await _Role.default.findOne({ - where: { - name: roleName - } - }); - if (!role) throw new _CodedError.default("Role not found", 400, "ROLE|05"); - const capabilityObjects = await capability.getCapabilities({ - name: capabilities - }); - if (!capabilityObjects.length) return role; - await role.addCapabilities(capabilityObjects); - return role; + if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|05") + const role = await RoleModel.findOne({ where: { name: roleName } }) + if (!role) throw new CodedError("Role not found", 400, "ROLE|05") + + const capabilityObjects = await capability.getCapabilities({ name: capabilities }) + if (!capabilityObjects.length) return role + + await role.addCapabilities(capabilityObjects) + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|06"); + throw new CodedError(error.message, 400, "ROLE|06") } } @@ -178,23 +143,20 @@ class Role { * @returns {Promise} */ async removeCapabilities(roleName, capabilities = []) { - const capability = new _capabilitiesClass.default(); + const capability = new Capability() + try { - if (typeof roleName !== "string") throw new _CodedError.default("Role name is required", 400, "ROLE|07"); - const role = await _Role.default.findOne({ - where: { - name: roleName - } - }); - if (!role) throw new _CodedError.default("Role not found", 400, "ROLE|07"); - const capabilityObjects = await capability.getCapabilities({ - name: capabilities - }); - if (!capabilityObjects.length) return role; - await role.removeCapabilities(capabilityObjects); - return role; + if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|07") + const role = await RoleModel.findOne({ where: { name: roleName } }) + if (!role) throw new CodedError("Role not found", 400, "ROLE|07") + + const capabilityObjects = await capability.getCapabilities({ name: capabilities }) + if (!capabilityObjects.length) return role + + await role.removeCapabilities(capabilityObjects) + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|08"); + throw new CodedError(error.message, 400, "ROLE|08") } } @@ -208,25 +170,20 @@ class Role { */ async deleteRole(roleName, migrateToRole) { try { - if (typeof roleName !== "string") throw new _CodedError.default("Role name is required", 400, "ROLE|09"); - const role = await _Role.default.findOne({ - where: { - name: roleName - } - }); - if (!role) throw new _CodedError.default("Role not found", 400, "ROLE|09"); - const migrateToRoleObject = await _Role.default.findOne({ - where: { - name: migrateToRole - } - }); - if (!migrateToRoleObject) throw new _CodedError.default("Role to migrate to not found", 400, "ROLE|09"); - await role.setUsers(migrateToRoleObject.users); - await role.destroy(); - return role; + if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|09") + const role = await RoleModel.findOne({ where: { name: roleName } }) + if (!role) throw new CodedError("Role not found", 400, "ROLE|09") + + const migrateToRoleObject = await RoleModel.findOne({ where: { name: migrateToRole } }) + if (!migrateToRoleObject) throw new CodedError("Role to migrate to not found", 400, "ROLE|09") + + await role.setUsers(migrateToRoleObject.users) + await role.destroy() + return role } catch (error) { - throw new _CodedError.default(error.message, 400, "ROLE|10"); + throw new CodedError(error.message, 400, "ROLE|10") } } } -var _default = exports.default = Role; \ No newline at end of file + +export default Role diff --git a/api/classes/totpClass.js b/api/classes/totpClass.js index e2b12a6..32da995 100644 --- a/api/classes/totpClass.js +++ b/api/classes/totpClass.js @@ -1,19 +1,10 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var OTPAuth = _interopRequireWildcard(require("otpauth")); -var _crypto = _interopRequireDefault(require("crypto")); -var _hiBase = _interopRequireDefault(require("hi-base32")); -var _authFactorClass = _interopRequireDefault(require("./authFactorClass")); -var _User = _interopRequireDefault(require("../models/User")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } -function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /* eslint-disable import/no-extraneous-dependencies */ +import * as OTPAuth from "otpauth" +import crypto from "crypto" +import base32 from "hi-base32" +import AuthFactor from "./authFactorClass" +import User from "../models/User" +import CodedError from "../config/CodedError" /** * Methods for handling TOTP authentication @@ -27,12 +18,13 @@ class TOTP { */ generateSecret() { try { - const secret = _crypto.default.randomBytes(32).toString("hex"); - const encodedSecret = _hiBase.default.encode(secret); - console.log("HERE's YOUR SECRET", encodedSecret); - return encodedSecret; + const secret = crypto.randomBytes(32).toString("hex") + const encodedSecret = base32.encode(secret) + console.log("HERE's YOUR SECRET", encodedSecret) + + return encodedSecret } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|01"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|01") } } @@ -52,14 +44,16 @@ class TOTP { algorithm: "SHA256", secret, digits: 6, - period: 30 - }); - const uri = totp.toString(); - return uri; + period: 30, + }) + + const uri = totp.toString() + return uri } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|02"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|02") } } + generateRecoveryCodes() {} /** @@ -73,34 +67,32 @@ class TOTP { */ async verify(userId, code) { try { - const user = await _User.default.findOne({ - where: { - id: userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "TOTPc|03"); - const userAuthFactors = await user.getAuthFactors(); - const totpAuthFactor = userAuthFactors.find(authFactor => authFactor.factor === "TOTP"); - if (!totpAuthFactor) throw new _CodedError.default("TOTP auth factor not found", 404, "TOTPc|04"); + const user = await User.findOne({ where: { id: userId } }) + if (!user) throw new CodedError("User not found", 404, "TOTPc|03") + + const userAuthFactors = await user.getAuthFactors() + const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") + if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|04") + const totp = new OTPAuth.TOTP({ issuer: "Authenticator", label: user.email, algorithm: "SHA256", digits: 6, secret: totpAuthFactor.secret, - period: 30 - }); - const delta = totp.validate({ - token: code, - window: 1 - }); - const isValid = delta === 0 || delta === 1 || delta === -1; - if (!isValid) throw new _CodedError.default("Invalid code", 400, "TOTPc|051"); - return true; + period: 30, + }) + + const delta = totp.validate({ token: code, window: 1 }) + const isValid = delta === 0 || delta === 1 || delta === -1 + + if (!isValid) throw new CodedError("Invalid code", 400, "TOTPc|051") + return true } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|05"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|05") } } + verifyRecoveryCode() {} /** @@ -114,21 +106,20 @@ class TOTP { */ async createRecord(userId) { try { - const user = await _User.default.findOne({ - where: { - id: userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "TOTPc|06"); - const userAuthFactors = await user.getAuthFactors(); - const userHasTOTPFactor = userAuthFactors.some(authFactor => authFactor.factor === "TOTP"); - if (userHasTOTPFactor) throw new _CodedError.default("User already has a TOTP auth factor", 400, "TOTPc|07"); - const secret = this.generateSecret(); - const authFactorMethods = new _authFactorClass.default(); - const authFactor = await authFactorMethods.createRecord(userId, "TOTP", secret); - return authFactor; + const user = await User.findOne({ where: { id: userId } }) + if (!user) throw new CodedError("User not found", 404, "TOTPc|06") + + const userAuthFactors = await user.getAuthFactors() + const userHasTOTPFactor = userAuthFactors.some((authFactor) => authFactor.factor === "TOTP") + if (userHasTOTPFactor) throw new CodedError("User already has a TOTP auth factor", 400, "TOTPc|07") + + const secret = this.generateSecret() + const authFactorMethods = new AuthFactor() + const authFactor = await authFactorMethods.createRecord(userId, "TOTP", secret) + + return authFactor } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|08"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|08") } } @@ -143,22 +134,22 @@ class TOTP { */ async activateRecord(userId, code) { try { - const secretIsValid = await this.verify(userId, code); - if (!secretIsValid) throw new _CodedError.default("Invalid code", 400, "TOTPc|09"); - const user = await _User.default.findOne({ - where: { - id: userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "TOTPc|10"); - const userAuthFactors = await user.getAuthFactors(); - const totpAuthFactor = userAuthFactors.find(authFactor => authFactor.factor === "TOTP"); - if (!totpAuthFactor) throw new _CodedError.default("TOTP auth factor not found", 404, "TOTPc|11"); - const authFactorMethods = new _authFactorClass.default(); - const updated = await authFactorMethods.activateRecord(totpAuthFactor.id); - return updated; + const secretIsValid = await this.verify(userId, code) + if (!secretIsValid) throw new CodedError("Invalid code", 400, "TOTPc|09") + + const user = await User.findOne({ where: { id: userId } }) + if (!user) throw new CodedError("User not found", 404, "TOTPc|10") + + const userAuthFactors = await user.getAuthFactors() + const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") + if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|11") + + const authFactorMethods = new AuthFactor() + const updated = await authFactorMethods.activateRecord(totpAuthFactor.id) + + return updated } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|12"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|12") } } @@ -171,21 +162,21 @@ class TOTP { */ async deleteRecord(userId) { try { - const user = await _User.default.findOne({ - where: { - id: userId - } - }); - if (!user) throw new _CodedError.default("User not found", 404, "TOTPc|13"); - const userAuthFactors = await user.getAuthFactors(); - const totpAuthFactor = userAuthFactors.find(authFactor => authFactor.factor === "TOTP"); - if (!totpAuthFactor) throw new _CodedError.default("TOTP auth factor not found", 404, "TOTPc|14"); - const authFactorMethods = new _authFactorClass.default(); - const deleted = await authFactorMethods.deleteRecord(totpAuthFactor.id); - return deleted; + const user = await User.findOne({ where: { id: userId } }) + if (!user) throw new CodedError("User not found", 404, "TOTPc|13") + + const userAuthFactors = await user.getAuthFactors() + const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") + if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|14") + + const authFactorMethods = new AuthFactor() + const deleted = await authFactorMethods.deleteRecord(totpAuthFactor.id) + + return deleted } catch (error) { - throw new _CodedError.default(error.message, error.status ?? 500, error.location ?? "TOTPc|15"); + throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|15") } } } -var _default = exports.default = TOTP; \ No newline at end of file + +export default TOTP diff --git a/api/classes/usersClass.js b/api/classes/usersClass.js index 6b745b8..0d0241b 100644 --- a/api/classes/usersClass.js +++ b/api/classes/usersClass.js @@ -1,17 +1,11 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _bcryptjs = _interopRequireDefault(require("bcryptjs")); -var _isStrongPassword = _interopRequireDefault(require("validator/lib/isStrongPassword")); -var _isJWT = _interopRequireDefault(require("validator/lib/isJWT")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _jwtClass = _interopRequireDefault(require("./jwtClass")); -var _rolesClass = _interopRequireDefault(require("./rolesClass")); -var _User = _interopRequireDefault(require("../models/User")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +import bcrypt from "bcryptjs" +import isStrongPassword from "validator/lib/isStrongPassword" +import isJWT from "validator/lib/isJWT" +import CodedError from "../config/CodedError" +import JWT from "./jwtClass" +import Role from "./rolesClass" +import UserModel from "../models/User" + /** * Default data to create a new user * @typedef {Object} DefaultUser @@ -26,7 +20,7 @@ class User { * Class for handling User operations */ constructor() { - this.User = _User.default; + this.User = UserModel } /** @@ -38,16 +32,16 @@ class User { */ async getUsers(conditions) { try { - const users = await _User.default.findAll({ - where: conditions - }); - users.forEach(user => { + const users = await UserModel.findAll({ where: conditions }) + + users.forEach((user) => { // eslint-disable-next-line no-param-reassign - delete user.dataValues.password; - }); - return users; + delete user.dataValues.password + }) + + return users } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|00"); + throw new CodedError(error.message, 400, "USER|00") } } @@ -59,14 +53,12 @@ class User { */ async getUser(conditions = {}) { try { - const user = await _User.default.findOne({ - where: conditions - }); + const user = await UserModel.findOne({ where: conditions }) // if (user) delete user.dataValues.password - return user; + return user } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|01"); + throw new CodedError(error.message, 400, "USER|01") } } @@ -77,12 +69,13 @@ class User { */ async hashPassword(password) { try { - if (!password) throw new _CodedError.default("Password is required", 400, "USER|32"); - if (!(0, _isStrongPassword.default)(password)) throw new _CodedError.default("Password does not meet requirements", 400, "USER|33"); - const hashedPassword = await _bcryptjs.default.hash(password, 10); - return hashedPassword; + if (!password) throw new CodedError("Password is required", 400, "USER|32") + if (!isStrongPassword(password)) throw new CodedError("Password does not meet requirements", 400, "USER|33") + + const hashedPassword = await bcrypt.hash(password, 10) + return hashedPassword } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|34"); + throw new CodedError(error.message, 400, "USER|34") } } @@ -93,33 +86,35 @@ class User { * @returns {Promise} User */ async createUser(data = {}) { - const role = new _rolesClass.default(); + const role = new Role() + try { const createUserData = { - active: data.active ?? true - }; - if (!data.email) throw new _CodedError.default("Email is required", 400, "USER|02"); - createUserData.email = data.email; - const roleName = data.role || "User"; - const roleObject = await role.getRole({ - name: roleName - }); - createUserData.roleId = roleObject.id; + active: data.active ?? true, + } + + if (!data.email) throw new CodedError("Email is required", 400, "USER|02") + createUserData.email = data.email + + const roleName = data.role || "User" + const roleObject = await role.getRole({ name: roleName }) + createUserData.roleId = roleObject.id + if (!roleObject) { - if (roleName !== "User") throw new _CodedError.default("Role not found", 400, "USER|03"); - const newRole = await role.createRole({ - name: roleName - }); - createUserData.roleId = newRole.id; + if (roleName !== "User") throw new CodedError("Role not found", 400, "USER|03") + const newRole = await role.createRole({ name: roleName }) + createUserData.roleId = newRole.id } + if (data.password) { - const hashedPassword = await this.hashPassword(data.password); - createUserData.password = hashedPassword; + const hashedPassword = await this.hashPassword(data.password) + createUserData.password = hashedPassword } - const user = await _User.default.create(createUserData); - return user; + + const user = await UserModel.create(createUserData) + return user } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|04"); + throw new CodedError(error.message, 400, "USER|04") } } @@ -135,34 +130,37 @@ class User { async updateUser(data, conditions) { try { // get users - const user = await this.getUser(conditions); - if (!user.length) throw new _CodedError.default("No users found", 400, "USER|05"); + const user = await this.getUser(conditions) + + if (!user.length) throw new CodedError("No users found", 400, "USER|05") // prep data - const updateUserData = {}; + const updateUserData = {} + if (data.email) { - const userWithEmail = await this.getUser({ - email: data.email - }); - if (userWithEmail && user.dataValues.id !== userWithEmail.dataValues.id) throw new _CodedError.default("Email already taken", 400, "USER|06"); - updateUserData.email = data.email; + const userWithEmail = await this.getUser({ email: data.email }) + if (userWithEmail && user.dataValues.id !== userWithEmail.dataValues.id) throw new CodedError("Email already taken", 400, "USER|06") + updateUserData.email = data.email } - if (typeof data.active !== "undefined") updateUserData.active = data.active; - if (data.password) updateUserData.password = await this.hashPassword(data.password); + + if (typeof data.active !== "undefined") updateUserData.active = data.active + + if (data.password) updateUserData.password = await this.hashPassword(data.password) + if (data.role) { - const role = new _rolesClass.default(); - const roleObject = await role.getRole({ - name: data.role - }); - if (!roleObject) throw new _CodedError.default("Role not found", 400, "USER|07"); - updateUserData.roleId = roleObject.id; + const role = new Role() + + const roleObject = await role.getRole({ name: data.role }) + if (!roleObject) throw new CodedError("Role not found", 400, "USER|07") + + updateUserData.roleId = roleObject.id } // update user - const updatedUser = await user.update(updateUserData); - return updatedUser; + const updatedUser = await user.update(updateUserData) + return updatedUser } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|08"); + throw new CodedError(error.message, 400, "USER|08") } } @@ -174,12 +172,13 @@ class User { */ async deleteUser(conditions) { try { - const user = await this.getUser(conditions); - if (!user) throw new _CodedError.default("User not found", 400, "USER|09"); - await user.destroy(); - return user; + const user = await this.getUser(conditions) + if (!user) throw new CodedError("User not found", 400, "USER|09") + + await user.destroy() + return user } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|10"); + throw new CodedError(error.message, 400, "USER|10") } } @@ -191,7 +190,7 @@ class User { * @returns {Promise} - True if the user has the capability */ async hasCapabilities(userId, ...capabilities) { - return true; + return true // try { // return true // right now, all users have all capabilities @@ -221,14 +220,13 @@ class User { */ async checkPassword(userId, password) { try { - const user = await this.getUser({ - id: userId - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|13"); - const passwordMatches = await _bcryptjs.default.compare(password, user?.dataValues?.password); - return passwordMatches; + const user = await this.getUser({ id: userId }) + if (!user) throw new CodedError("User not found", 400, "USER|13") + + const passwordMatches = await bcrypt.compare(password, user?.dataValues?.password) + return passwordMatches } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|14"); + throw new CodedError(error.message, 400, "USER|14") } } @@ -240,23 +238,24 @@ class User { */ async createSessionToken(userId) { try { - const jwt = new _jwtClass.default(); - const user = await this.getUser({ - id: userId - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|15"); - const token = await jwt.sign({ - id: user.id, - email: user.email, - role: user?.dataValues?.roleId, - mfa: user?.dataValues?.mfa ?? false, - sessionState: "full" - }, "15m" // change to 15m in production - ); - - return token; + const jwt = new JWT() + + const user = await this.getUser({ id: userId }) + if (!user) throw new CodedError("User not found", 400, "USER|15") + + const token = await jwt.sign( + { + id: user.id, + email: user.email, + role: user?.dataValues?.roleId, + mfa: user?.dataValues?.mfa ?? false, + sessionState: "full", + }, + "15m" // change to 15m in production + ) + return token } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|16"); + throw new CodedError(error.message, 400, "USER|16") } } @@ -270,20 +269,23 @@ class User { */ async createHalfSessionToken(userId) { try { - const jwt = new _jwtClass.default(); - const user = await this.getUser({ - id: userId - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|15"); - const token = await jwt.sign({ - id: user.id, - email: user.email, - role: user?.dataValues?.roleId, - sessionState: "half" - }, "5m"); - return token; + const jwt = new JWT() + + const user = await this.getUser({ id: userId }) + if (!user) throw new CodedError("User not found", 400, "USER|15") + + const token = await jwt.sign( + { + id: user.id, + email: user.email, + role: user?.dataValues?.roleId, + sessionState: "half", + }, + "5m" + ) + return token } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|16"); + throw new CodedError(error.message, 400, "USER|16") } } @@ -295,21 +297,16 @@ class User { */ async createRefreshToken(userId) { try { - const jwt = new _jwtClass.default(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE); - const user = await this.getUser({ - id: userId - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|17"); + const jwt = new JWT(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE) + + const user = await this.getUser({ id: userId }) + if (!user) throw new CodedError("User not found", 400, "USER|17") // expires in 1 week - const token = await jwt.sign({ - id: user.id, - email: user.email, - role: user?.dataValues?.roleId - }, "1w"); - return token; + const token = await jwt.sign({ id: user.id, email: user.email, role: user?.dataValues?.roleId }, "1w") + return token } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|18"); + throw new CodedError(error.message, 400, "USER|18") } } @@ -323,17 +320,19 @@ class User { */ async checkSessionToken(token) { try { - const jwt = new _jwtClass.default(); - const decodedToken = await jwt.verify(token); - if (decodedToken instanceof _CodedError.default) throw decodedToken; - if (decodedToken.sessionState !== "full") throw new _CodedError.default("Session Token is invalid", 400, "USER|18"); - const user = await this.getUser({ - id: decodedToken.id - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|19"); - return true; + const jwt = new JWT() + + const decodedToken = await jwt.verify(token) + if (decodedToken instanceof CodedError) throw decodedToken + + if (decodedToken.sessionState !== "full") throw new CodedError("Session Token is invalid", 400, "USER|18") + + const user = await this.getUser({ id: decodedToken.id }) + if (!user) throw new CodedError("User not found", 400, "USER|19") + + return true } catch (error) { - return false; + return false } } @@ -345,17 +344,19 @@ class User { */ async checkHalfSessionToken(token) { try { - const jwt = new _jwtClass.default(); - const decodedToken = await jwt.verify(token); - if (decodedToken instanceof _CodedError.default) throw decodedToken; - if (decodedToken.sessionState !== "half") throw new _CodedError.default("Session Token is invalid", 400, "USER|18"); - const user = await this.getUser({ - id: decodedToken.id - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|19"); - return true; + const jwt = new JWT() + + const decodedToken = await jwt.verify(token) + if (decodedToken instanceof CodedError) throw decodedToken + + if (decodedToken.sessionState !== "half") throw new CodedError("Session Token is invalid", 400, "USER|18") + + const user = await this.getUser({ id: decodedToken.id }) + if (!user) throw new CodedError("User not found", 400, "USER|19") + + return true } catch (error) { - return false; + return false } } @@ -367,18 +368,20 @@ class User { */ async checkRefreshToken(token) { try { - if (!token) throw new _CodedError.default("Refresh Token is required", 400, "USER|20"); - if (!(0, _isJWT.default)(token)) throw new _CodedError.default("Token is invalid", 400, "USER|21"); - const jwt = new _jwtClass.default(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE); - const decodedToken = await jwt.verify(token); - if (decodedToken instanceof _CodedError.default) throw decodedToken; - const user = await this.getUser({ - id: decodedToken.id - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|21"); - return decodedToken; + if (!token) throw new CodedError("Refresh Token is required", 400, "USER|20") + if (!isJWT(token)) throw new CodedError("Token is invalid", 400, "USER|21") + + const jwt = new JWT(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE) + + const decodedToken = await jwt.verify(token) + if (decodedToken instanceof CodedError) throw decodedToken + + const user = await this.getUser({ id: decodedToken.id }) + if (!user) throw new CodedError("User not found", 400, "USER|21") + + return decodedToken } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|22"); + throw new CodedError(error.message, 400, "USER|22") } } @@ -390,18 +393,18 @@ class User { */ async refreshSessionToken(refreshToken) { try { - const jwt = new _jwtClass.default(); - const validRefreshToken = await this.checkRefreshToken(refreshToken); - const token = await this.createSessionToken(validRefreshToken.id); - const newRefreshToken = await this.createRefreshToken(validRefreshToken.id); - const blacklistOldToken = await jwt.blacklist(refreshToken); - if (!blacklistOldToken) throw new _CodedError.default("Could not blacklist old token", 500, "USER|23"); - return { - sessionToken: token, - refreshToken: newRefreshToken - }; + const jwt = new JWT() + const validRefreshToken = await this.checkRefreshToken(refreshToken) + + const token = await this.createSessionToken(validRefreshToken.id) + const newRefreshToken = await this.createRefreshToken(validRefreshToken.id) + + const blacklistOldToken = await jwt.blacklist(refreshToken) + if (!blacklistOldToken) throw new CodedError("Could not blacklist old token", 500, "USER|23") + + return { sessionToken: token, refreshToken: newRefreshToken } } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|24"); + throw new CodedError(error.message, 400, "USER|24") } } @@ -413,18 +416,15 @@ class User { */ async createPasswordResetToken(email) { try { - const jwt = new _jwtClass.default(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE); - const user = await this.getUser({ - email - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|25"); - const token = await jwt.sign({ - id: user.id, - email: user.email - }, "15m"); - return token; + const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) + + const user = await this.getUser({ email }) + if (!user) throw new CodedError("User not found", 400, "USER|25") + + const token = await jwt.sign({ id: user.id, email: user.email }, "15m") + return token } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|26"); + throw new CodedError(error.message, 400, "USER|26") } } @@ -436,16 +436,17 @@ class User { */ async checkPasswordResetToken(token) { try { - const jwt = new _jwtClass.default(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE); - const decodedToken = await jwt.verify(token); - if (decodedToken instanceof _CodedError.default) throw decodedToken; - const user = await this.getUser({ - id: decodedToken.id - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|27"); - return true; + const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) + + const decodedToken = await jwt.verify(token) + if (decodedToken instanceof CodedError) throw decodedToken + + const user = await this.getUser({ id: decodedToken.id }) + if (!user) throw new CodedError("User not found", 400, "USER|27") + + return true } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|28"); + throw new CodedError(error.message, 400, "USER|28") } } @@ -458,25 +459,28 @@ class User { */ async resetPassword(token, password) { try { - if (!token) throw new _CodedError.default("Password Token is required", 400, "USER|29"); - if (!password) throw new _CodedError.default("Password is required", 400, "USER|30"); - const jwt = new _jwtClass.default(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE); - const decodedToken = await jwt.verify(token); - if (decodedToken instanceof _CodedError.default) throw decodedToken; - const user = await this.getUser({ - id: decodedToken.id - }); - if (!user) throw new _CodedError.default("User not found", 400, "USER|29"); - const hashedPassword = await this.hashPassword(password); - await user.update({ - password: hashedPassword - }); - const blacklistOldToken = await jwt.blacklist(token); - if (!blacklistOldToken) throw new _CodedError.default("Could not blacklist old token", 400, "USER|30"); - return true; + if (!token) throw new CodedError("Password Token is required", 400, "USER|29") + if (!password) throw new CodedError("Password is required", 400, "USER|30") + + const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) + + const decodedToken = await jwt.verify(token) + if (decodedToken instanceof CodedError) throw decodedToken + + const user = await this.getUser({ id: decodedToken.id }) + if (!user) throw new CodedError("User not found", 400, "USER|29") + + const hashedPassword = await this.hashPassword(password) + await user.update({ password: hashedPassword }) + + const blacklistOldToken = await jwt.blacklist(token) + if (!blacklistOldToken) throw new CodedError("Could not blacklist old token", 400, "USER|30") + + return true } catch (error) { - throw new _CodedError.default(error.message, 400, "USER|31"); + throw new CodedError(error.message, 400, "USER|31") } } } -var _default = exports.default = User; \ No newline at end of file + +export default User diff --git a/api/config/CodedError.js b/api/config/CodedError.js index b2f0626..039d9c7 100644 --- a/api/config/CodedError.js +++ b/api/config/CodedError.js @@ -1,24 +1,16 @@ -"use strict"; +import dotenv from "dotenv" + +dotenv.config({ path: ".env" }) +dotenv.config({ path: ".env.secrets" }) -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _dotenv = _interopRequireDefault(require("dotenv")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -_dotenv.default.config({ - path: ".env" -}); -_dotenv.default.config({ - path: ".env.secrets" -}); class CodedError extends Error { constructor(message, status, location, data) { - super(message); // Human-readable message - this.name = this.constructor.name; - this.status = status; // HTTP status code - this.location = location; - this.data = data; + super(message) // Human-readable message + this.name = this.constructor.name + this.status = status // HTTP status code + this.location = location + this.data = data } } -var _default = exports.default = CodedError; \ No newline at end of file + +export default CodedError diff --git a/api/config/config.js b/api/config/config.js index d57108c..a071f83 100644 --- a/api/config/config.js +++ b/api/config/config.js @@ -1,10 +1,5 @@ -"use strict"; +import "dotenv/config" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -require("dotenv/config"); const config = { development: { username: process.env.DEV_DB_USER, @@ -12,7 +7,7 @@ const config = { database: process.env.DEV_DB_NAME, host: process.env.DEV_DB_HOST, port: process.env.DEV_DB_PORT, - dialect: "mysql" + dialect: "mysql", }, test: { username: process.env.TEST_DB_USER, @@ -20,7 +15,7 @@ const config = { database: process.env.TEST_DB_NAME, host: process.env.TEST_DB_HOST, port: process.env.TEST_DB_PORT, - dialect: "mysql" + dialect: "mysql", }, production: { username: process.env.PROD_DB_USER, @@ -28,7 +23,7 @@ const config = { database: process.env.PROD_DB_NAME, host: process.env.PROD_DB_HOST, port: process.env.PROD_DB_PORT, - dialect: "mysql" - } -}; -var _default = exports.default = config; \ No newline at end of file + dialect: "mysql", + }, +} +export default config diff --git a/api/config/database.js b/api/config/database.js index f6a85a0..bc11019 100644 --- a/api/config/database.js +++ b/api/config/database.js @@ -1,25 +1,15 @@ -"use strict"; +import "dotenv/config" +import { Sequelize } from "sequelize" +import config from "./config" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -require("dotenv/config"); -var _sequelize = require("sequelize"); -var _config2 = _interopRequireDefault(require("./config")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const nodeEnv = process.env.NODE_ENV || "development"; -const { - username: user, - password, - database: db, - host, - port -} = _config2.default[nodeEnv]; -const sequelize = new _sequelize.Sequelize(db, user, password, { +const nodeEnv = process.env.NODE_ENV || "development" +const { username: user, password, database: db, host, port } = config[nodeEnv] + +const sequelize = new Sequelize(db, user, password, { host, port, dialect: "mysql", - logging: false -}); -var _default = exports.default = sequelize; \ No newline at end of file + logging: false, +}) + +export default sequelize diff --git a/api/index.js b/api/index.js index 2e86da6..550bec1 100644 --- a/api/index.js +++ b/api/index.js @@ -1,48 +1,50 @@ -"use strict"; - -var _express = _interopRequireWildcard(require("express")); -require("dotenv/config"); -var _cors = _interopRequireDefault(require("cors")); -var _cookieParser = _interopRequireDefault(require("cookie-parser")); -var _helmet = _interopRequireDefault(require("helmet")); -var _database = _interopRequireDefault(require("./config/database")); -var _requireSSL = _interopRequireDefault(require("./middleware/requireSSL")); -var _baseRoutes = _interopRequireDefault(require("./routes/baseRoutes")); -var _registerRoutes = _interopRequireDefault(require("./routes/registerRoutes")); -var _loginRoutes = _interopRequireDefault(require("./routes/loginRoutes")); -var _passwordRoutes = _interopRequireDefault(require("./routes/passwordRoutes")); -var _userRoutes = _interopRequireDefault(require("./routes/userRoutes")); -var _refreshRoutes = _interopRequireDefault(require("./routes/refreshRoutes")); -var _totpRoutes = _interopRequireDefault(require("./routes/totpRoutes")); -require("./models/index"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } -function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } +import Express, { json } from "express" +import "dotenv/config" +import cors from "cors" +import cookieParser from "cookie-parser" +import helmet from "helmet" +import sequelize from "./config/database" +import requireSSL from "./middleware/requireSSL" + // Routes +import baseRoutes from "./routes/baseRoutes" +import registerRoutes from "./routes/registerRoutes" +import loginRoutes from "./routes/loginRoutes" +import passwordRoutes from "./routes/passwordRoutes" +import userRoutes from "./routes/userRoutes" +import refreshRoutes from "./routes/refreshRoutes" +import totpRoutes from "./routes/totpRoutes" + // DB Models -const PORT = process.env.PORT || 3000; -const app = (0, _express.default)(); -app.set("trust proxy", 1); -app.use((0, _express.json)()); -app.use((0, _cors.default)()); -app.use((0, _helmet.default)()); -app.use((0, _cookieParser.default)()); -app.use(_requireSSL.default); -app.use("/", _baseRoutes.default); -app.use("/register", _registerRoutes.default); -app.use("/login", _loginRoutes.default); -app.use("/password", _passwordRoutes.default); -app.use("/users", _userRoutes.default); -app.use("/refresh", _refreshRoutes.default); -app.use("/totp", _totpRoutes.default); +import "./models/index" + +const PORT = process.env.PORT || 3000 +const app = Express() + +app.set("trust proxy", 1) + +app.use(json()) +app.use(cors()) +app.use(helmet()) +app.use(cookieParser()) +app.use(requireSSL) + +app.use("/", baseRoutes) +app.use("/register", registerRoutes) +app.use("/login", loginRoutes) +app.use("/password", passwordRoutes) +app.use("/users", userRoutes) +app.use("/refresh", refreshRoutes) +app.use("/totp", totpRoutes) + app.listen(PORT, async () => { try { - await _database.default.sync(); + await sequelize.sync() } catch (error) { - if (process.env.NODE_ENV !== "development") return; + if (process.env.NODE_ENV !== "development") return // eslint-disable-next-line no-console - console.error("Error syncing database:", error.message); + console.error("Error syncing database:", error.message) } -}); \ No newline at end of file +}) diff --git a/api/middleware/errorMiddleware.js b/api/middleware/errorMiddleware.js index 4527b4c..76512c4 100644 --- a/api/middleware/errorMiddleware.js +++ b/api/middleware/errorMiddleware.js @@ -1,13 +1,8 @@ -"use strict"; +import Response from "../classes/responseClass" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const errorMiddleware = (err, req, res) => { - const response = new _responseClass.default(req, res); - response.error(err); -}; -var _default = exports.default = errorMiddleware; \ No newline at end of file + const response = new Response(req, res) + response.error(err) +} + +export default errorMiddleware diff --git a/api/middleware/rateLimiters.js b/api/middleware/rateLimiters.js index 1ed81a4..ff3e2d2 100644 --- a/api/middleware/rateLimiters.js +++ b/api/middleware/rateLimiters.js @@ -1,22 +1,15 @@ -"use strict"; +import rateLimit from "express-rate-limit" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.noAuthLimiter = exports.authLimiter = void 0; -var _expressRateLimit = _interopRequireDefault(require("express-rate-limit")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const noAuthLimiter = exports.noAuthLimiter = (0, _expressRateLimit.default)({ - windowMs: 15 * 60 * 1000, - // 15 minutes +export const noAuthLimiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes max: 50, standardHeaders: true, - legacyHeaders: false -}); -const authLimiter = exports.authLimiter = (0, _expressRateLimit.default)({ - windowMs: 5 * 60 * 1000, - // 5 minutes + legacyHeaders: false, +}) + +export const authLimiter = rateLimit({ + windowMs: 5 * 60 * 1000, // 5 minutes max: 100, standardHeaders: true, - legacyHeaders: false -}); \ No newline at end of file + legacyHeaders: false, +}) diff --git a/api/middleware/requireSSL.js b/api/middleware/requireSSL.js index a3ef724..1449d4f 100644 --- a/api/middleware/requireSSL.js +++ b/api/middleware/requireSSL.js @@ -1,17 +1,12 @@ -"use strict"; +import Response from "../classes/responseClass" + +export default function requireSSL(req, res, next) { + const response = new Response(req, res) -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = requireSSL; -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -function requireSSL(req, res, next) { - const response = new _responseClass.default(req, res); try { // if (!req.secure && process.env.NODE_ENV === "production") throw new Error("SSL required") - next(); + next() } catch (err) { - response.error(err); + response.error(err) } -} \ No newline at end of file +} diff --git a/api/middleware/verifyUser.js b/api/middleware/verifyUser.js index 69c99dd..75152a2 100644 --- a/api/middleware/verifyUser.js +++ b/api/middleware/verifyUser.js @@ -1,15 +1,9 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _isJWT = _interopRequireDefault(require("validator/lib/isJWT")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _cookiesClass = _interopRequireDefault(require("../classes/cookiesClass")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +import isJWT from "validator/lib/isJWT" +import CodedError from "../config/CodedError" +import User from "../classes/usersClass" +import Cookies from "../classes/cookiesClass" +import Response from "../classes/responseClass" + /** * Middleware to verify a user's session * @param {...string} capabilities - The capabilities required to access the route @@ -17,32 +11,42 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de function verifyUser(...capabilities) { return async function verifyUserInner(req, res, next) { - const response = new _responseClass.default(req, res); - let token = req.headers?.authorization?.split(" ")?.[1]; // Bearer + const response = new Response(req, res) + + let token = req.headers?.authorization?.split(" ")?.[1] // Bearer try { - if (!token) throw new _CodedError.default("Session Token is required", 400, "VERIFY|01"); - if (!(0, _isJWT.default)(token)) throw new _CodedError.default("Session Token is invalid", 400, "VERIFY|02"); - const userMethods = new _usersClass.default(); - const userTokenIsValid = await userMethods.checkSessionToken(token); - const tokenContent = JSON.parse(atob(token.split(".")[1])); - if (tokenContent.sessionState !== "full") throw new _CodedError.default("Session Token is invalid", 400, "VERIFY|021"); + if (!token) throw new CodedError("Session Token is required", 400, "VERIFY|01") + if (!isJWT(token)) throw new CodedError("Session Token is invalid", 400, "VERIFY|02") + + const userMethods = new User() + const userTokenIsValid = await userMethods.checkSessionToken(token) + + const tokenContent = JSON.parse(atob(token.split(".")[1])) + if (tokenContent.sessionState !== "full") throw new CodedError("Session Token is invalid", 400, "VERIFY|021") + if (!userTokenIsValid) { - const cookies = new _cookiesClass.default(req, res); - const refreshToken = cookies.getRefreshToken(); - const newTokens = await userMethods.refreshSessionToken(refreshToken); - token = newTokens.sessionToken; - req.token = newTokens.sessionToken; - cookies.setRefreshToken(newTokens.refreshToken); + const cookies = new Cookies(req, res) + const refreshToken = cookies.getRefreshToken() + const newTokens = await userMethods.refreshSessionToken(refreshToken) + + token = newTokens.sessionToken + req.token = newTokens.sessionToken + cookies.setRefreshToken(newTokens.refreshToken) } - req.user = JSON.parse(atob(token.split(".")[1])); - if (!capabilities.length) return next(); - const userHasCapabilities = await userMethods.hasCapabilities(req.user.id, ...capabilities); - if (!userHasCapabilities) throw new _CodedError.default("User does not have required capabilities", 403, "VERIFY|03"); - next(); + + req.user = JSON.parse(atob(token.split(".")[1])) + + if (!capabilities.length) return next() + + const userHasCapabilities = await userMethods.hasCapabilities(req.user.id, ...capabilities) + if (!userHasCapabilities) throw new CodedError("User does not have required capabilities", 403, "VERIFY|03") + + next() } catch (error) { - response.error(error); + response.error(error) } - }; + } } -var _default = exports.default = verifyUser; \ No newline at end of file + +export default verifyUser diff --git a/api/models/AuthFactor.js b/api/models/AuthFactor.js index 4f95018..8203600 100644 --- a/api/models/AuthFactor.js +++ b/api/models/AuthFactor.js @@ -1,35 +1,30 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // storing information used in 2-factor authentication (2FA) -const AuthFactor = _database.default.define("AuthFactor", { +const AuthFactor = sequelize.define("AuthFactor", { id: { - type: _sequelize.DataTypes.UUID, + type: DataTypes.UUID, primaryKey: true, - defaultValue: _sequelize.DataTypes.UUIDV4 + defaultValue: DataTypes.UUIDV4, }, factor: { - type: _sequelize.DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, secret: { - type: _sequelize.DataTypes.STRING, - allowNull: false + type: DataTypes.STRING, + allowNull: false, }, verified: { - type: _sequelize.DataTypes.BOOLEAN, + type: DataTypes.BOOLEAN, allowNull: false, - defaultValue: false + defaultValue: false, }, verifiedAt: { - type: _sequelize.DataTypes.DATE, - allowNull: true - } -}); -var _default = exports.default = AuthFactor; \ No newline at end of file + type: DataTypes.DATE, + allowNull: true, + }, +}) + +export default AuthFactor diff --git a/api/models/Capability.js b/api/models/Capability.js index 54debbb..1a1a25e 100644 --- a/api/models/Capability.js +++ b/api/models/Capability.js @@ -1,28 +1,21 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const Capability = _database.default.define("Capability", { +const Capability = sequelize.define("Capability", { id: { - type: _sequelize.DataTypes.INTEGER, - // or DataTypes.UUID + type: DataTypes.INTEGER, // or DataTypes.UUID primaryKey: true, - autoIncrement: true // set to false if using UUID + autoIncrement: true, // set to false if using UUID }, - name: { - type: _sequelize.DataTypes.STRING, + type: DataTypes.STRING, allowNull: false, - unique: true + unique: true, }, description: { - type: _sequelize.DataTypes.STRING, - allowNull: true - } -}); -var _default = exports.default = Capability; \ No newline at end of file + type: DataTypes.STRING, + allowNull: true, + }, +}) + +export default Capability diff --git a/api/models/Role.js b/api/models/Role.js index 225763a..51fb5ab 100644 --- a/api/models/Role.js +++ b/api/models/Role.js @@ -1,31 +1,32 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const Role = _database.default.define("Role", { - id: { - type: _sequelize.DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true +const Role = sequelize.define( + "Role", + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + }, + description: { + type: DataTypes.STRING, + allowNull: true, + }, }, - name: { - type: _sequelize.DataTypes.STRING, - allowNull: false, - unique: true - }, - description: { - type: _sequelize.DataTypes.STRING, - allowNull: true + { + indexes: [ + { + unique: true, + fields: ["name"], + }, + ], } -}, { - indexes: [{ - unique: true, - fields: ["name"] - }] -}); -var _default = exports.default = Role; \ No newline at end of file +) + +export default Role diff --git a/api/models/Role_Capability.js b/api/models/Role_Capability.js index 3357d86..3b3b6f2 100644 --- a/api/models/Role_Capability.js +++ b/api/models/Role_Capability.js @@ -1,17 +1,12 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const RoleCapability = _database.default.define("Role_Capability", { +const RoleCapability = sequelize.define("Role_Capability", { id: { - type: _sequelize.DataTypes.INTEGER, + type: DataTypes.INTEGER, primaryKey: true, - autoIncrement: true - } -}); -var _default = exports.default = RoleCapability; \ No newline at end of file + autoIncrement: true, + }, +}) + +export default RoleCapability diff --git a/api/models/Token.js b/api/models/Token.js index cfa65f4..b3f7fbf 100644 --- a/api/models/Token.js +++ b/api/models/Token.js @@ -1,30 +1,25 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const Token = _database.default.define("Token", { +const Token = sequelize.define("Token", { id: { - type: _sequelize.DataTypes.UUID, + type: DataTypes.UUID, primaryKey: true, - defaultValue: _sequelize.DataTypes.UUIDV4 + defaultValue: DataTypes.UUIDV4, }, token: { - type: _sequelize.DataTypes.STRING(1000), - allowNull: false + type: DataTypes.STRING(1000), + allowNull: false, }, expires: { - type: _sequelize.DataTypes.DATE, - allowNull: false + type: DataTypes.DATE, + allowNull: false, }, blacklisted: { - type: _sequelize.DataTypes.BOOLEAN, + type: DataTypes.BOOLEAN, allowNull: false, - defaultValue: false - } -}); -var _default = exports.default = Token; \ No newline at end of file + defaultValue: false, + }, +}) + +export default Token diff --git a/api/models/User.js b/api/models/User.js index bd23c61..7a03b20 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -1,41 +1,42 @@ -"use strict"; +import { DataTypes } from "sequelize" +import sequelize from "../config/database" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _sequelize = require("sequelize"); -var _database = _interopRequireDefault(require("../config/database")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const User = _database.default.define("User", { - id: { - type: _sequelize.DataTypes.UUID, - primaryKey: true, - defaultValue: _sequelize.DataTypes.UUIDV4 +const User = sequelize.define( + "User", + { + id: { + type: DataTypes.UUID, + primaryKey: true, + defaultValue: DataTypes.UUIDV4, + }, + email: { + type: DataTypes.STRING, + allowNull: false, + }, + password: { + type: DataTypes.STRING, + }, + active: { + type: DataTypes.BOOLEAN, + defaultValue: true, + }, + mfa: { + type: DataTypes.BOOLEAN, + defaultValue: false, + }, + primaryAuth: { + type: DataTypes.STRING, + defaultValue: "UNPW", + }, }, - email: { - type: _sequelize.DataTypes.STRING, - allowNull: false - }, - password: { - type: _sequelize.DataTypes.STRING - }, - active: { - type: _sequelize.DataTypes.BOOLEAN, - defaultValue: true - }, - mfa: { - type: _sequelize.DataTypes.BOOLEAN, - defaultValue: false - }, - primaryAuth: { - type: _sequelize.DataTypes.STRING, - defaultValue: "UNPW" + { + indexes: [ + { + unique: true, + fields: ["email"], + }, + ], } -}, { - indexes: [{ - unique: true, - fields: ["email"] - }] -}); -var _default = exports.default = User; \ No newline at end of file +) + +export default User diff --git a/api/models/associations.js b/api/models/associations.js index 97ae807..33b6ae7 100644 --- a/api/models/associations.js +++ b/api/models/associations.js @@ -1,58 +1,57 @@ -"use strict"; +import User from "./User" +import Role from "./Role" +import Capability from "./Capability" +import AuthFactor from "./AuthFactor" -var _User = _interopRequireDefault(require("./User")); -var _Role = _interopRequireDefault(require("./Role")); -var _Capability = _interopRequireDefault(require("./Capability")); -var _AuthFactor = _interopRequireDefault(require("./AuthFactor")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -_User.default.belongsTo(_Role.default, { - foreignKey: "roleId" -}); -_Role.default.hasMany(_User.default, { - foreignKey: "roleId" -}); -_User.default.hasMany(_AuthFactor.default, { - foreignKey: "userId" -}); -_AuthFactor.default.belongsTo(_User.default, { - foreignKey: "userId" -}); -_Role.default.belongsToMany(_Capability.default, { - through: "Role_Capability", - as: "capabilities" -}); -_Capability.default.belongsToMany(_Role.default, { - through: "Role_Capability", - as: "roles" -}); -_Role.default.addScope("defaultScope", { - include: [{ - model: _Capability.default, - as: "capabilities", - attributes: ["id", "name", "description"], - through: { - attributes: [] - } - }] -}, { - override: true -}); -_User.default.addScope("defaultScope", { - include: [{ - model: _Role.default, - attributes: ["name", "description"], - include: [{ - model: _Capability.default, - as: "capabilities", - attributes: ["name", "description"], - through: { - attributes: [] - } - }] - }, { - model: _AuthFactor.default, - attributes: ["factor", "verified", "verifiedAt"] - }] -}, { - override: true -}); \ No newline at end of file +User.belongsTo(Role, { foreignKey: "roleId" }) +Role.hasMany(User, { foreignKey: "roleId" }) + +User.hasMany(AuthFactor, { foreignKey: "userId" }) +AuthFactor.belongsTo(User, { foreignKey: "userId" }) + +Role.belongsToMany(Capability, { through: "Role_Capability", as: "capabilities" }) +Capability.belongsToMany(Role, { through: "Role_Capability", as: "roles" }) + +Role.addScope( + "defaultScope", + { + include: [ + { + model: Capability, + as: "capabilities", + attributes: ["id", "name", "description"], + through: { + attributes: [], + }, + }, + ], + }, + { override: true } +) + +User.addScope( + "defaultScope", + { + include: [ + { + model: Role, + attributes: ["name", "description"], + include: [ + { + model: Capability, + as: "capabilities", + attributes: ["name", "description"], + through: { + attributes: [], + }, + }, + ], + }, + { + model: AuthFactor, + attributes: ["factor", "verified", "verifiedAt"], + }, + ], + }, + { override: true } +) diff --git a/api/models/index.js b/api/models/index.js index 3863a72..e2fa6cb 100644 --- a/api/models/index.js +++ b/api/models/index.js @@ -1,17 +1,22 @@ -"use strict"; +const fs = require("fs") -const fs = require("fs"); -const path = require("path"); -const basename = path.basename(__filename); -fs.readdirSync(__dirname).filter(file => { - const isJavaScriptFile = file.indexOf(".") !== 0; - const isNotCurrentFile = file !== basename; - const isJavaScriptExtension = file.slice(-3) === ".js"; - const isNotTestFile = file.indexOf(".test.js") === -1; - const isNotAssociationFile = file !== "associations.js"; - return isJavaScriptFile && isNotCurrentFile && isJavaScriptExtension && isNotTestFile && isNotAssociationFile; -}).forEach(file => { - // eslint-disable-next-line import/no-dynamic-require, global-require - require(path.join(__dirname, file)); -}); -require("./associations"); \ No newline at end of file +const path = require("path") + +const basename = path.basename(__filename) + +fs.readdirSync(__dirname) + .filter((file) => { + const isJavaScriptFile = file.indexOf(".") !== 0 + const isNotCurrentFile = file !== basename + const isJavaScriptExtension = file.slice(-3) === ".js" + const isNotTestFile = file.indexOf(".test.js") === -1 + const isNotAssociationFile = file !== "associations.js" + + return isJavaScriptFile && isNotCurrentFile && isJavaScriptExtension && isNotTestFile && isNotAssociationFile + }) + .forEach((file) => { + // eslint-disable-next-line import/no-dynamic-require, global-require + require(path.join(__dirname, file)) + }) + +require("./associations") diff --git a/api/routes/baseRoutes.js b/api/routes/baseRoutes.js index 124a3b5..42060f2 100644 --- a/api/routes/baseRoutes.js +++ b/api/routes/baseRoutes.js @@ -1,23 +1,18 @@ -"use strict"; +import express from "express" +import verifyUser from "../middleware/verifyUser" +import Response from "../classes/responseClass" + +const router = express.Router() + +router.get("/", verifyUser(), async (req, res) => { + const response = new Response(req, res) -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _verifyUser = _interopRequireDefault(require("../middleware/verifyUser")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -router.get("/", (0, _verifyUser.default)(), async (req, res) => { - const response = new _responseClass.default(req, res); try { - response.success({ - message: "Hello, world!" - }); + response.success({ message: "Hello, world!" }) } catch (error) { - response.error(error); + response.error(error) } -}); -const baseRoutes = router; -var _default = exports.default = baseRoutes; \ No newline at end of file +}) + +const baseRoutes = router +export default baseRoutes diff --git a/api/routes/loginRoutes.js b/api/routes/loginRoutes.js index 30dae9d..de600d5 100644 --- a/api/routes/loginRoutes.js +++ b/api/routes/loginRoutes.js @@ -1,59 +1,53 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _isEmail = _interopRequireDefault(require("validator/lib/isEmail")); -var _normalizeEmail = _interopRequireDefault(require("validator/lib/normalizeEmail")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _rateLimiters = require("../middleware/rateLimiters"); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _cookiesClass = _interopRequireDefault(require("../classes/cookiesClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -router.post("/", _rateLimiters.noAuthLimiter, async (req, res) => { - let { - email, - password - } = req.body; - const response = new _responseClass.default(req, res); +import express from "express" +import isEmail from "validator/lib/isEmail" +import normalizeEmail from "validator/lib/normalizeEmail" +import CodedError from "../config/CodedError" +import User from "../classes/usersClass" +import { noAuthLimiter } from "../middleware/rateLimiters" +import Response from "../classes/responseClass" +import Cookies from "../classes/cookiesClass" + +const router = express.Router() + +router.post("/", noAuthLimiter, async (req, res) => { + let { email, password } = req.body + + const response = new Response(req, res) + try { - if (!password) throw new _CodedError.default("Invalid Password", 400, "LOG|02"); - if (!email || !(0, _isEmail.default)(email)) throw new _CodedError.default("Invalid Email", 400, "LOG|01"); - email = (0, _normalizeEmail.default)(email, { - gmail_remove_subaddress: false - }); - const userMethods = new _usersClass.default(); - const user = await userMethods.getUser({ - email - }); - if (!user) throw new _CodedError.default("User not found", 400, "LOG|03"); - const userId = user.id; - const isPasswordValid = await userMethods.checkPassword(userId, password); - if (!isPasswordValid) throw new _CodedError.default("Password is incorrect", 400, "LOG|04"); - let token; + if (!password) throw new CodedError("Invalid Password", 400, "LOG|02") + if (!email || !isEmail(email)) throw new CodedError("Invalid Email", 400, "LOG|01") + email = normalizeEmail(email, { gmail_remove_subaddress: false }) + + const userMethods = new User() + const user = await userMethods.getUser({ email }) + if (!user) throw new CodedError("User not found", 400, "LOG|03") + + const userId = user.id + const isPasswordValid = await userMethods.checkPassword(userId, password) + if (!isPasswordValid) throw new CodedError("Password is incorrect", 400, "LOG|04") + + let token + if (user?.dataValues?.mfa) { - token = await userMethods.createHalfSessionToken(userId); + token = await userMethods.createHalfSessionToken(userId) } else { - const cookies = new _cookiesClass.default(req, res); - token = await userMethods.createSessionToken(userId); - cookies.setSessionCookie(token); - const refreshToken = await userMethods.createRefreshToken(userId); - if (!refreshToken) throw new _CodedError.default("Error creating refresh token", 500, "REG|06"); - cookies.setRefreshToken(refreshToken); + const cookies = new Cookies(req, res) + token = await userMethods.createSessionToken(userId) + cookies.setSessionCookie(token) + + const refreshToken = await userMethods.createRefreshToken(userId) + if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "REG|06") + cookies.setRefreshToken(refreshToken) } - const tokenBody = JSON.parse(atob(token.split(".")[1])); - response.setToken(token); - response.success({ - message: "Login successful", - data: tokenBody - }); + const tokenBody = JSON.parse(atob(token.split(".")[1])) + + response.setToken(token) + response.success({ message: "Login successful", data: tokenBody }) } catch (error) { - response.error(error); + response.error(error) } -}); -const loginRoutes = router; -var _default = exports.default = loginRoutes; \ No newline at end of file +}) + +const loginRoutes = router +export default loginRoutes diff --git a/api/routes/passwordRoutes.js b/api/routes/passwordRoutes.js index 03edbe0..a21d800 100644 --- a/api/routes/passwordRoutes.js +++ b/api/routes/passwordRoutes.js @@ -1,99 +1,91 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _isEmail = _interopRequireDefault(require("validator/lib/isEmail")); -var _isJWT = _interopRequireDefault(require("validator/lib/isJWT")); -var _normalizeEmail = _interopRequireDefault(require("validator/lib/normalizeEmail")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _jwtClass = _interopRequireDefault(require("../classes/jwtClass")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _sendEmail = _interopRequireDefault(require("../utils/sendEmail")); -var _rateLimiters = require("../middleware/rateLimiters"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -router.post("/forgot", _rateLimiters.noAuthLimiter, async (req, res) => { - const response = new _responseClass.default(req, res); - let { - email - } = req.body; +import express from "express" +import isEmail from "validator/lib/isEmail" +import isJWT from "validator/lib/isJWT" +import normalizeEmail from "validator/lib/normalizeEmail" +import CodedError from "../config/CodedError" +import Response from "../classes/responseClass" +import JWT from "../classes/jwtClass" +import User from "../classes/usersClass" +import sendEmail from "../utils/sendEmail" +import { noAuthLimiter } from "../middleware/rateLimiters" + +const router = express.Router() + +router.post("/forgot", noAuthLimiter, async (req, res) => { + const response = new Response(req, res) + let { email } = req.body + try { - if (!email) throw new _CodedError.default("Email is required", 400, "PAS|01"); - if (!(0, _isEmail.default)(email)) throw new _CodedError.default("Invalid Email", 400, "PAS|02"); - email = (0, _normalizeEmail.default)(email, { - gmail_remove_subaddress: false - }); - const userMethods = new _usersClass.default(); - const user = await userMethods.getUser({ - email - }); - if (!user) throw new _CodedError.default("User not found", 400, "PAS|02"); - const token = await userMethods.createPasswordResetToken(email); - const emailSent = await (0, _sendEmail.default)({ + if (!email) throw new CodedError("Email is required", 400, "PAS|01") + if (!isEmail(email)) throw new CodedError("Invalid Email", 400, "PAS|02") + email = normalizeEmail(email, { gmail_remove_subaddress: false }) + + const userMethods = new User() + const user = await userMethods.getUser({ email }) + if (!user) throw new CodedError("User not found", 400, "PAS|02") + + const token = await userMethods.createPasswordResetToken(email) + + const emailSent = await sendEmail({ to: email, subject: "Password Reset", - text: `Click here to reset your password: ${process.env.PASSWORD_RESET_URL}/${token}` - }); - if (!emailSent) throw new _CodedError.default("Email failed to send", 500, "PAS|03"); - response.success({ - message: "Email sent" - }); + text: `Click here to reset your password: ${process.env.PASSWORD_RESET_URL}/${token}`, + }) + if (!emailSent) throw new CodedError("Email failed to send", 500, "PAS|03") + + response.success({ message: "Email sent" }) } catch (error) { - response.error(error); + response.error(error) } -}); -router.get("/reset/:token", _rateLimiters.noAuthLimiter, async (req, res) => { - const response = new _responseClass.default(req, res); - const { - token - } = req.params; +}) + +router.get("/reset/:token", noAuthLimiter, async (req, res) => { + const response = new Response(req, res) + const { token } = req.params + try { - if (!token) throw new _CodedError.default("Token is required", 400, "PAS|04"); - if (!(0, _isJWT.default)(token)) throw new _CodedError.default("Invalid Token", 400, "PAS|05"); - const jwt = new _jwtClass.default(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE); - let decoded; + if (!token) throw new CodedError("Token is required", 400, "PAS|04") + if (!isJWT(token)) throw new CodedError("Invalid Token", 400, "PAS|05") + + const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) + + let decoded try { - decoded = await jwt.verify(token); + decoded = await jwt.verify(token) } catch (error) { - throw new _CodedError.default("Verification Failed", 400, "PAS|05"); + throw new CodedError("Verification Failed", 400, "PAS|05") } - const userMethods = new _usersClass.default(); - const user = await userMethods.getUser({ - email: decoded.email - }); - if (!user) throw new _CodedError.default("User not found", 400, "PAS|06"); - response.success({ - message: "Token is valid" - }); + + const userMethods = new User() + const user = await userMethods.getUser({ email: decoded.email }) + if (!user) throw new CodedError("User not found", 400, "PAS|06") + + response.success({ message: "Token is valid" }) } catch (error) { - response.error(error); + response.error(error) } -}); -router.post("/reset/:token", _rateLimiters.noAuthLimiter, async (req, res) => { - const response = new _responseClass.default(req, res); - const { - token - } = req.params; - const { - password - } = req.body; +}) + +router.post("/reset/:token", noAuthLimiter, async (req, res) => { + const response = new Response(req, res) + const { token } = req.params + const { password } = req.body + try { - if (!token) throw new _CodedError.default("Token is required", 400, "PAS|07"); - if (!(0, _isJWT.default)(token)) throw new _CodedError.default("Invalid Token", 400, "PAS|07"); - if (!password) throw new _CodedError.default("Password is required", 400, "PAS|08"); - const userMethods = new _usersClass.default(); - const resetPassword = await userMethods.resetPassword(token, password); - if (!resetPassword) throw new _CodedError.default("Password reset failed", 500, "PAS|09"); - response.success({ - message: "Password reset successful" - }); + if (!token) throw new CodedError("Token is required", 400, "PAS|07") + if (!isJWT(token)) throw new CodedError("Invalid Token", 400, "PAS|07") + if (!password) throw new CodedError("Password is required", 400, "PAS|08") + + const userMethods = new User() + + const resetPassword = await userMethods.resetPassword(token, password) + if (!resetPassword) throw new CodedError("Password reset failed", 500, "PAS|09") + + response.success({ message: "Password reset successful" }) } catch (error) { - response.error(error); + response.error(error) } -}); -const passwordRoutes = router; -var _default = exports.default = passwordRoutes; \ No newline at end of file +}) + +const passwordRoutes = router +export default passwordRoutes diff --git a/api/routes/refreshRoutes.js b/api/routes/refreshRoutes.js index 34258b1..b19b208 100644 --- a/api/routes/refreshRoutes.js +++ b/api/routes/refreshRoutes.js @@ -1,38 +1,32 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _cookiesClass = _interopRequireDefault(require("../classes/cookiesClass")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); +import express from "express" +import Response from "../classes/responseClass" +import User from "../classes/usersClass" +import Cookies from "../classes/cookiesClass" +import CodedError from "../config/CodedError" + +const router = express.Router() + router.post("/", async (req, res) => { - const response = new _responseClass.default(req, res); - const { - refreshToken - } = req.body; + const response = new Response(req, res) + const { refreshToken } = req.body + try { - const cookies = new _cookiesClass.default(req, res); - if (!refreshToken) throw new _CodedError.default("Token not found", 401, "REFRESH|01"); - const userMethods = new _usersClass.default(); - const { - sessionToken, - refreshToken: newRefreshToken - } = await userMethods.refreshSessionToken(refreshToken); - const refreshTokenSet = cookies.setRefreshToken(newRefreshToken); - if (!refreshTokenSet) throw new _CodedError.default("Token not set", 500, "REFRESH|02"); - response.setToken(sessionToken); - response.success({ - refreshToken: newRefreshToken - }); + const cookies = new Cookies(req, res) + + if (!refreshToken) throw new CodedError("Token not found", 401, "REFRESH|01") + + const userMethods = new User() + const { sessionToken, refreshToken: newRefreshToken } = await userMethods.refreshSessionToken(refreshToken) + + const refreshTokenSet = cookies.setRefreshToken(newRefreshToken) + if (!refreshTokenSet) throw new CodedError("Token not set", 500, "REFRESH|02") + + response.setToken(sessionToken) + response.success({ refreshToken: newRefreshToken }) } catch (error) { - response.error(error); + response.error(error) } -}); -const refreshRoutes = router; -var _default = exports.default = refreshRoutes; \ No newline at end of file +}) + +const refreshRoutes = router +export default refreshRoutes diff --git a/api/routes/registerRoutes.js b/api/routes/registerRoutes.js index 750017b..fa5302b 100644 --- a/api/routes/registerRoutes.js +++ b/api/routes/registerRoutes.js @@ -1,58 +1,49 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _isEmail = _interopRequireDefault(require("validator/lib/isEmail")); -var _normalizeEmail = _interopRequireDefault(require("validator/lib/normalizeEmail")); -var _isStrongPassword = _interopRequireDefault(require("validator/lib/isStrongPassword")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _cookiesClass = _interopRequireDefault(require("../classes/cookiesClass")); -var _rateLimiters = require("../middleware/rateLimiters"); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -router.post("/", _rateLimiters.noAuthLimiter, async (req, res) => { - const response = new _responseClass.default(req, res); - let { - email, - password - } = req.body; +import express from "express" +import isEmail from "validator/lib/isEmail" +import normalizeEmail from "validator/lib/normalizeEmail" +import isStrongPassword from "validator/lib/isStrongPassword" +import CodedError from "../config/CodedError" +import User from "../classes/usersClass" +import Response from "../classes/responseClass" +import Cookies from "../classes/cookiesClass" +import { noAuthLimiter } from "../middleware/rateLimiters" + +const router = express.Router() + +router.post("/", noAuthLimiter, async (req, res) => { + const response = new Response(req, res) + let { email, password } = req.body + try { - const user = new _usersClass.default(); - if (!email || !password) throw new _CodedError.default("Email and password are required", 400, "REG|01"); - if (!(0, _isEmail.default)(email)) throw new _CodedError.default("Invalid Email", 400, "REG|02"); - if (!(0, _isStrongPassword.default)(password)) throw new _CodedError.default("Invalid Password", 400, "REG|02"); - email = (0, _normalizeEmail.default)(email, { - gmail_remove_subaddress: false - }); - const existingUser = await user.getUser({ - email - }); - if (existingUser) throw new _CodedError.default("Email already exists", 500, "REG|03"); - const newUser = await user.createUser({ - email, - password - }); - if (!newUser) throw new _CodedError.default("Error creating user", 500, "REG|04"); - const token = await user.createSessionToken(newUser.id); - if (!token) throw new _CodedError.default("Error creating session token", 500, "REG|05"); - const refreshToken = await user.createRefreshToken(newUser.id); - if (!refreshToken) throw new _CodedError.default("Error creating refresh token", 500, "REG|06"); - const cookie = new _cookiesClass.default(req, res); - const setRefreshCookie = cookie.setRefreshToken(refreshToken); - if (!setRefreshCookie) throw new _CodedError.default("Error setting refresh token", 500, "REG|07"); - response.setToken(token); - response.success({ - message: "User created", - data: newUser - }); + const user = new User() + + if (!email || !password) throw new CodedError("Email and password are required", 400, "REG|01") + if (!isEmail(email)) throw new CodedError("Invalid Email", 400, "REG|02") + if (!isStrongPassword(password)) throw new CodedError("Invalid Password", 400, "REG|02") + email = normalizeEmail(email, { gmail_remove_subaddress: false }) + + const existingUser = await user.getUser({ email }) + if (existingUser) throw new CodedError("Email already exists", 500, "REG|03") + + const newUser = await user.createUser({ email, password }) + if (!newUser) throw new CodedError("Error creating user", 500, "REG|04") + + const token = await user.createSessionToken(newUser.id) + if (!token) throw new CodedError("Error creating session token", 500, "REG|05") + + const refreshToken = await user.createRefreshToken(newUser.id) + if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "REG|06") + + const cookie = new Cookies(req, res) + const setRefreshCookie = cookie.setRefreshToken(refreshToken) + if (!setRefreshCookie) throw new CodedError("Error setting refresh token", 500, "REG|07") + + response.setToken(token) + response.success({ message: "User created", data: newUser }) } catch (error) { - response.error(error); + response.error(error) } -}); -const registerRoutes = router; -var _default = exports.default = registerRoutes; \ No newline at end of file +}) + +const registerRoutes = router +export default registerRoutes diff --git a/api/routes/testRoutes.js b/api/routes/testRoutes.js index 78d626e..29ad4e4 100644 --- a/api/routes/testRoutes.js +++ b/api/routes/testRoutes.js @@ -1,11 +1,6 @@ -"use strict"; +import express from "express" -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -const testRoutes = router; -var _default = exports.default = testRoutes; \ No newline at end of file +const router = express.Router() + +const testRoutes = router +export default testRoutes diff --git a/api/routes/totpRoutes.js b/api/routes/totpRoutes.js index 8ac5b59..41e1dc0 100644 --- a/api/routes/totpRoutes.js +++ b/api/routes/totpRoutes.js @@ -1,121 +1,113 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _totpClass = _interopRequireDefault(require("../classes/totpClass")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -var _verifyUser = _interopRequireDefault(require("../middleware/verifyUser")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _cookiesClass = _interopRequireDefault(require("../classes/cookiesClass")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); +import express from "express" +import TOTP from "../classes/totpClass" +import Response from "../classes/responseClass" +import CodedError from "../config/CodedError" +import verifyUser from "../middleware/verifyUser" +import User from "../classes/usersClass" +import Cookies from "../classes/cookiesClass" + +const router = express.Router() /** * Create TOTP auth factor for user and return URI for QR code */ -router.post("/create", (0, _verifyUser.default)(), async (req, res) => { - const response = new _responseClass.default(req, res); +router.post("/create", verifyUser(), async (req, res) => { + const response = new Response(req, res) + try { - const { - user - } = req; - const totp = new _totpClass.default(); - const totpRecord = await totp.createRecord(user.id); - const totpURI = totp.getURI(user.email, "Authenticator", totpRecord?.secret); - response.success({ - message: "TOTP created", - data: totpURI - }); + const { user } = req + const totp = new TOTP() + const totpRecord = await totp.createRecord(user.id) + const totpURI = totp.getURI(user.email, "Authenticator", totpRecord?.secret) + + response.success({ message: "TOTP created", data: totpURI }) } catch (error) { - response.error(error); + response.error(error) } -}); +}) /** * Activate TOTP auth factor for user */ -router.post("/activate", (0, _verifyUser.default)(), async (req, res) => { - const response = new _responseClass.default(req, res); - const { - code - } = req.body; +router.post("/activate", verifyUser(), async (req, res) => { + const response = new Response(req, res) + const { code } = req.body + try { - const { - user - } = req; - const totp = new _totpClass.default(); - const activated = await totp.activateRecord(user.id, code); - if (!activated) throw new _CodedError.default("Error activating TOTP", 500, "TOTP|07"); + const { user } = req + const totp = new TOTP() + + const activated = await totp.activateRecord(user.id, code) + if (!activated) throw new CodedError("Error activating TOTP", 500, "TOTP|07") // give user new session token with mfa flag - const userMethods = new _usersClass.default(); - const sessionToken = await userMethods.createSessionToken(user.id); - response.setToken(sessionToken); - response.success({ - message: "TOTP activated" - }); + const userMethods = new User() + const sessionToken = await userMethods.createSessionToken(user.id) + + response.setToken(sessionToken) + response.success({ message: "TOTP activated" }) } catch (error) { - response.error(error); + response.error(error) } -}); +}) /** * Verify TOTP auth factor for user, and authenticate the user * - User must have a valid session token with "sessionState": "half" */ router.post("/verify", async (req, res) => { - const response = new _responseClass.default(req, res); - const { - code - } = req.body; + const response = new Response(req, res) + const { code } = req.body + try { - const sessionToken = req.headers.authorization.split(" ")[1]; - const userMethods = new _usersClass.default(); - const halfSessionTokenIsValid = await userMethods.checkHalfSessionToken(sessionToken); - if (!halfSessionTokenIsValid) throw new _CodedError.default("Invalid session token", 400, "TOTP|05"); - const userId = JSON.parse(atob(sessionToken.split(".")[1])).id; - const totp = new _totpClass.default(); - const isValid = await totp.verify(userId, code); - if (!isValid) throw new _CodedError.default("Invalid code", 400, "TOTP|08"); - const cookies = new _cookiesClass.default(req, res); - const fullSessionToken = await userMethods.createSessionToken(userId); - if (!sessionToken) throw new _CodedError.default("Error creating session token", 500, "TOTP|09"); - cookies.setSessionCookie(fullSessionToken); - const refreshToken = await userMethods.createRefreshToken(userId); - if (!refreshToken) throw new _CodedError.default("Error creating refresh token", 500, "TOTP|10"); - const setRefreshCookie = cookies.setRefreshToken(refreshToken); - if (!setRefreshCookie) throw new _CodedError.default("Error setting refresh token", 500, "TOTP|11"); - response.setToken(fullSessionToken); - response.success({ - message: "User Session verified" - }); + const sessionToken = req.headers.authorization.split(" ")[1] + const userMethods = new User() + + const halfSessionTokenIsValid = await userMethods.checkHalfSessionToken(sessionToken) + if (!halfSessionTokenIsValid) throw new CodedError("Invalid session token", 400, "TOTP|05") + + const userId = JSON.parse(atob(sessionToken.split(".")[1])).id + + const totp = new TOTP() + const isValid = await totp.verify(userId, code) + if (!isValid) throw new CodedError("Invalid code", 400, "TOTP|08") + + const cookies = new Cookies(req, res) + + const fullSessionToken = await userMethods.createSessionToken(userId) + if (!sessionToken) throw new CodedError("Error creating session token", 500, "TOTP|09") + cookies.setSessionCookie(fullSessionToken) + + const refreshToken = await userMethods.createRefreshToken(userId) + if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "TOTP|10") + + const setRefreshCookie = cookies.setRefreshToken(refreshToken) + if (!setRefreshCookie) throw new CodedError("Error setting refresh token", 500, "TOTP|11") + + response.setToken(fullSessionToken) + response.success({ message: "User Session verified" }) } catch (error) { - response.error(error); + response.error(error) } -}); +}) /** * Deactivate TOTP auth factor for user */ -router.post("/disable", (0, _verifyUser.default)(), async (req, res) => { - const response = new _responseClass.default(req, res); +router.post("/disable", verifyUser(), async (req, res) => { + const response = new Response(req, res) + try { - const { - user - } = req; - const totp = new _totpClass.default(); - const deactivated = await totp.deleteRecord(user.id); - if (!deactivated) throw new _CodedError.default("Error deactivating TOTP", 500, "TOTP|12"); - response.success({ - message: "TOTP deactivated" - }); + const { user } = req + const totp = new TOTP() + const deactivated = await totp.deleteRecord(user.id) + if (!deactivated) throw new CodedError("Error deactivating TOTP", 500, "TOTP|12") + + response.success({ message: "TOTP deactivated" }) } catch (error) { - response.error(error); + response.error(error) } -}); -const totpRoutes = router; -var _default = exports.default = totpRoutes; \ No newline at end of file +}) + +const totpRoutes = router +export default totpRoutes diff --git a/api/routes/userRoutes.js b/api/routes/userRoutes.js index 7babcb7..9c566e4 100644 --- a/api/routes/userRoutes.js +++ b/api/routes/userRoutes.js @@ -1,120 +1,113 @@ -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = void 0; -var _express = _interopRequireDefault(require("express")); -var _verifyUser = _interopRequireDefault(require("../middleware/verifyUser")); -var _usersClass = _interopRequireDefault(require("../classes/usersClass")); -var _responseClass = _interopRequireDefault(require("../classes/responseClass")); -var _CodedError = _interopRequireDefault(require("../config/CodedError")); -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const router = _express.default.Router(); -router.get("/:id", (0, _verifyUser.default)("users_read"), async (req, res) => { - const response = new _responseClass.default(req, res); - const { - id - } = req.params; +import express from "express" +import verifyUser from "../middleware/verifyUser" +import User from "../classes/usersClass" +import Response from "../classes/responseClass" +import CodedError from "../config/CodedError" + +const router = express.Router() + +router.get("/:id", verifyUser("users_read"), async (req, res) => { + const response = new Response(req, res) + const { id } = req.params + try { - const userMethods = new _usersClass.default(); - const { - user - } = req; - const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all"); - const users = await userMethods.getUser({ - id - }); + const userMethods = new User() + const { user } = req + + const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all") + const users = await userMethods.getUser({ id }) + if (!userCanReadAllUsers) { - if (users?.dataValues?.id === user.id) return response.success(users); - throw new _CodedError.default("You are not allowed to read this user", 403, "USERS|01"); + if (users?.dataValues?.id === user.id) return response.success(users) + throw new CodedError("You are not allowed to read this user", 403, "USERS|01") } - response.success(users); + + response.success(users) } catch (error) { - response.error(error); + response.error(error) } -}); -router.get("/", (0, _verifyUser.default)("users_read"), async (req, res) => { - const response = new _responseClass.default(req, res); +}) + +router.get("/", verifyUser("users_read"), async (req, res) => { + const response = new Response(req, res) + try { - const userMethods = new _usersClass.default(); - const { - user - } = req; - const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all"); + const userMethods = new User() + const { user } = req + + const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all") if (!userCanReadAllUsers) { - const users = await userMethods.getUsers({ - id: user.id - }); - return response.success(users); + const users = await userMethods.getUsers({ id: user.id }) + return response.success(users) } - const users = await userMethods.getUsers(); - response.success(users); + + const users = await userMethods.getUsers() + response.success(users) } catch (error) { - response.error(error); + response.error(error) } -}); -router.post("/", (0, _verifyUser.default)("users_create"), async (req, res) => { - const response = new _responseClass.default(req, res); - const { - body - } = req; +}) + +router.post("/", verifyUser("users_create"), async (req, res) => { + const response = new Response(req, res) + const { body } = req + try { - const userMethods = new _usersClass.default(); - const { - user - } = req; - delete body.id; - delete body.password; // You can't set the password when creating a user. - - const userCanCreateUsers = await userMethods.hasCapabilities(user.id, "users_create"); - if (!userCanCreateUsers) throw new _CodedError.default("You are not allowed to create users", 403, "USERS|02"); - const newUser = await userMethods.createUser(body); - response.success(newUser); + const userMethods = new User() + const { user } = req + + delete body.id + delete body.password // You can't set the password when creating a user. + + const userCanCreateUsers = await userMethods.hasCapabilities(user.id, "users_create") + if (!userCanCreateUsers) throw new CodedError("You are not allowed to create users", 403, "USERS|02") + + const newUser = await userMethods.createUser(body) + response.success(newUser) } catch (error) { - response.error(error); + response.error(error) } -}); -router.put("/:id", (0, _verifyUser.default)("users_update"), async (req, res) => { - const response = new _responseClass.default(req, res); - const { - body - } = req; - const { - id - } = req.params; +}) + +router.put("/:id", verifyUser("users_update"), async (req, res) => { + const response = new Response(req, res) + const { body } = req + const { id } = req.params + try { - const userMethods = new _usersClass.default(); - const { - user - } = req; - delete body.password; - delete body.id; - const userCanUpdateUsers = await userMethods.hasCapabilities(user.id, "users_update"); - if (!userCanUpdateUsers) throw new _CodedError.default("You are not allowed to update users", 403, "USERS|03"); - const updatedUser = await userMethods.updateUser(id, body); - response.success(updatedUser); + const userMethods = new User() + const { user } = req + + delete body.password + delete body.id + + const userCanUpdateUsers = await userMethods.hasCapabilities(user.id, "users_update") + if (!userCanUpdateUsers) throw new CodedError("You are not allowed to update users", 403, "USERS|03") + + const updatedUser = await userMethods.updateUser(id, body) + response.success(updatedUser) } catch (error) { - response.error(error); + response.error(error) } -}); -router.delete("/:id", (0, _verifyUser.default)("users_delete"), async (req, res) => { - const response = new _responseClass.default(req, res); - const { - id - } = req.params; +}) + +router.delete("/:id", verifyUser("users_delete"), async (req, res) => { + const response = new Response(req, res) + const { id } = req.params + try { - const userMethods = new _usersClass.default(); - const { - user - } = req; - const userCanDeleteUsers = await userMethods.hasCapabilities(user.id, "users_delete"); - if (!userCanDeleteUsers) throw new _CodedError.default("You are not allowed to delete users", 403, "USERS|04"); - await userMethods.deleteUser(id); - response.success("User deleted"); + const userMethods = new User() + const { user } = req + + const userCanDeleteUsers = await userMethods.hasCapabilities(user.id, "users_delete") + if (!userCanDeleteUsers) throw new CodedError("You are not allowed to delete users", 403, "USERS|04") + + await userMethods.deleteUser(id) + response.success("User deleted") } catch (error) { - response.error(error); + response.error(error) } -}); -const baseRoutes = router; -var _default = exports.default = baseRoutes; \ No newline at end of file +}) + +const baseRoutes = router +export default baseRoutes diff --git a/api/utils/sendEmail.js b/api/utils/sendEmail.js index 3c80d13..622247d 100644 --- a/api/utils/sendEmail.js +++ b/api/utils/sendEmail.js @@ -1,20 +1,11 @@ -"use strict"; +export default async function sendEmail(email) { + const { to, subject, text, html } = email -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.default = sendEmail; -async function sendEmail(email) { - const { - to, - subject, - text, - html - } = email; - console.log("Email sending is not implemented yet"); - console.log(`To: ${to}`); - console.log(`Subject: ${subject}`); - console.log(`Text: ${text}`); - console.log(`HTML: ${html}`); - return true; -} \ No newline at end of file + console.log("Email sending is not implemented yet") + console.log(`To: ${to}`) + console.log(`Subject: ${subject}`) + console.log(`Text: ${text}`) + console.log(`HTML: ${html}`) + + return true +} diff --git a/package.json b/package.json index b939605..5bb8887 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,8 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "rimraf api && babel src --out-dir api --copy-files", "start": "node api/index.js", - "dev": "nodemon --exec babel-node src/index.js" + "dev": "nodemon --exec babel-node api/index.js" }, "keywords": [], "author": "", diff --git a/public/test.txt b/public/test.txt deleted file mode 100644 index e69de29..0000000 diff --git a/src/classes/authFactorClass.js b/src/classes/authFactorClass.js deleted file mode 100644 index 9bdda12..0000000 --- a/src/classes/authFactorClass.js +++ /dev/null @@ -1,137 +0,0 @@ -import AuthFactorModel from "../models/AuthFactor" -import User from "../models/User" -import CodedError from "../config/CodedError" - -/** - * @typedef {Object} AuthFactorType - * Options - * - TOTP - * - WEBAUTHN - * - OAUTH - */ - -/** - * @typedef {Object} AuthFactor - * @property {string} id - The id of the auth factor (UUID, set by the database) - * @property {string} userId - The id of the user that owns the auth factor - * @property {AuthFactorType} factor - The type of auth factor (TOTP, SMS, etc.) - * @property {string} secret - The secret that is used to generate the TOTP - */ - -/** - * Handles the methods for interacitng with the AuthFactor data - */ -class AuthFactor { - /** - * Creates an auth record in the database with a secret - * - Is inactive when created because the user needs to verify it - * - * @async - * @param {string} userId - The id of the user that owns the auth factor - * @param {AuthFactorType} factor - The type of auth factor (TOTP, SMS, etc.) - * @param {string} secret - The secret that is used to generate the TOTP - * @returns {Promise} The id of the auth factor record or false if it fails - */ - async createRecord(userId, factor, secret) { - try { - const authFactor = await AuthFactorModel.create({ - userId, - factor, - secret, - verified: false, - }) - - return authFactor.dataValues - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|01") - } - } - - /** - * Activates an auth factor record - * - Sets the verified flag to true - * - Sets the verifiedAt date to the current date - * - * @async - * @param {string} id - The id of the auth factor record - * @returns {Promise} True if the record was activated, false if it fails - */ - async activateRecord(id) { - try { - const authFactor = await AuthFactorModel.findOne({ where: { id } }) - if (!authFactor) throw new CodedError("Auth factor not found", 404, "AUTHFACTOR|02") - - await authFactor.update({ - verified: true, - verifiedAt: new Date(), - }) - - // update user mfa flag - const user = await User.findOne({ where: { id: authFactor.userId } }) - if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|03") - - await user.update({ mfa: true }) - - return true - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|03") - } - } - - /** - * Deletes an auth factor record - * - if after deletion the user has no auth factors, the user's mfa flag is set to false - * @async - * @param {string} id - The id of the auth factor record - * @returns {Promise} True if the record was deleted, false if it fails - */ - async deleteRecord(id) { - try { - const authFactor = await AuthFactorModel.findOne({ where: { id } }) - if (!authFactor) throw new CodedError("Auth factor not found", 404, "AUTHFACTOR|04") - - await authFactor.destroy() - - const user = await User.findOne({ where: { id: authFactor.userId } }) - if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|05") - - const userAuthFactors = await user.getAuthFactors() - if (userAuthFactors.length === 0) { - await user.update({ mfa: false }) - } - - return true - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|05") - } - } - - /** - * Deletes all auth factor records for a user and sets the user's mfa flag to false - * - * @async - * @param {string} userId - The id of the user that owns the auth factor - * @returns {Promise} True if the records were deleted, false if it fails - */ - async disableMFA(userId) { - try { - const user = await User.findOne({ where: { id: userId } }) - if (!user) throw new CodedError("User not found", 404, "AUTHFACTOR|06") - - await user.update({ mfa: false }) - - const authFactors = await AuthFactorModel.getAll({ where: { userId } }) - if (!authFactors) throw new CodedError("Auth factors not found", 404, "AUTHFACTOR|07") - - authFactors.forEach(async (authFactor) => { - await authFactor.destroy() - }) - - return true - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "AUTHFACTOR|08") - } - } -} - -export default AuthFactor diff --git a/src/classes/capabilitiesClass.js b/src/classes/capabilitiesClass.js deleted file mode 100644 index 65ca799..0000000 --- a/src/classes/capabilitiesClass.js +++ /dev/null @@ -1,88 +0,0 @@ -import CapabilityModel from "../models/Capability" -import RoleModel from "../models/Role" -import CodedError from "../config/CodedError" - -class Capability { - /** - * Get all capabilities that match the conditions - * @async - * @param {*} conditions - * @returns {Promise} - */ - async getCapabilities(conditions = {}) { - try { - const capabilities = await CapabilityModel.findAll({ where: conditions }) - return capabilities - } catch (error) { - throw new CodedError(error.message, 400, "CAPABILITY|00") - } - } - - /** - * Returns the first capability that matches the conditions - */ - async getCapability(conditions = {}) { - try { - const capability = await CapabilityModel.findOne({ where: conditions }) - return capability - } catch (error) { - throw new CodedError(error.message, 400, "CAPABILITY|01") - } - } - - /** - * Create a new capability - * @async - * @param {Object} data - * @param {Object} options - * - - * - roles: Array - The names of the roles to assign the capability to - * @returns {Promise} - */ - async createCapability(data = {}, options = {}) { - const { name, description } = data - const { roles } = options - - try { - const capability = await CapabilityModel.create({ name, description }) - - if (roles) { - await capability.setRoles(roles) - } - - return capability - } catch (error) { - throw new CodedError(error.message, 400, "CAPABILITY|02") - } - } - - /** - * Delete capabilities - * - Will do nothing if no capabilities are provided - * - Removes the capability from all roles - * @async - * @param {Array} capabilities - The names of the capabilities to delete - * @returns {Promise} - True if the capabilities were deleted - */ - async deleteCapabilities(capabilities) { - try { - if (!capabilities) throw new CodedError("No capabilities provided", 400, "CAPABILITY|02") - if (!Array.isArray(capabilities)) throw new CodedError("Capabilities must be an array", 400, "CAPABILITY|03") - await CapabilityModel.destroy({ where: { name: capabilities } }) - - // Remove the capabilities from all roles - const roles = await RoleModel.findAll({ include: [{ model: CapabilityModel, as: "capabilities" }] }) - roles.forEach(async (role) => { - const roleCapabilities = role.capabilities.map((capability) => capability.name) - const updatedCapabilities = roleCapabilities.filter((capability) => !capabilities.includes(capability)) - await role.setCapabilities(updatedCapabilities) - }) - - return true - } catch (error) { - throw new CodedError(error.message, 400, "CAPABILITY|03") - } - } -} - -export default Capability diff --git a/src/classes/cookiesClass.js b/src/classes/cookiesClass.js deleted file mode 100644 index e454f07..0000000 --- a/src/classes/cookiesClass.js +++ /dev/null @@ -1,80 +0,0 @@ -import CodedError from "../config/CodedError" - -class Cookies { - constructor(req, res) { - this.req = req - this.res = res - } - - /** - * Sets a cookie in the response - */ - set(name, value, options = {}) { - try { - this.res.cookie(name, value, options) - return true - } catch (error) { - throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") - } - } - - setSessionCookie(value) { - this.set("session", value, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - }) - } - - /** - * Gets the refresh token from the request cookies - * @returns {String} - The refresh token or false if not found - */ - getRefreshToken() { - try { - const token = this.req?.cookies?.refreshToken - return token - } catch (error) { - return false - } - } - - /** - * Adds an httpOnly cookie to the response - * @param {string} name - The name of the cookie - * @param {*} value - The value of the cookie - * @param {*} options - The options of the cookie - * @returns true if the cookie was added, throws an error otherwise - */ - setHttpOnly(name, value, options = {}) { - try { - this.res.cookie(name, value, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - ...options, - }) - return true - } catch (error) { - throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") - } - } - - /** - * Sets the refresh token in the response cookies - * - * @param {*} token - The refresh token - * @returns true if the cookie was added, throws an error otherwise - */ - setRefreshToken(token) { - try { - this.setHttpOnly("refreshToken", token, { - maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days - }) - return true - } catch (error) { - throw new CodedError(`Error setting cookie: ${error.message}`, 500, "LOG|05") - } - } -} - -export default Cookies diff --git a/src/classes/jwtClass.js b/src/classes/jwtClass.js deleted file mode 100644 index 1d2d30b..0000000 --- a/src/classes/jwtClass.js +++ /dev/null @@ -1,94 +0,0 @@ -import jwt from "jsonwebtoken" -import CodedError from "../config/CodedError" -import Token from "../models/Token" - -/** - * The Standard payload for a JWT token - * @typedef {Object} JWTToken - * @property {String} id - The user id - * @property {String} email - The user email - * @property {String} role - The user role - */ - -class JWT { - /** - * Creates a new JWT instance - * @param {String} secret - The secret to sign the JWT token (default: process.env.JWT_SECRET) - * @returns {JWT} - The JWT instance - */ - - constructor(publicKey, privateKey) { - this.publicKey = publicKey || process.env.JWT_PUBLIC - this.privateKey = privateKey || process.env.JWT_PRIVATE - - if (!this.publicKey || !this.privateKey) throw new CodedError("Invalid key configuration", 500, "JWT|00") - } - - /** - * Returns a signed JWT token - * @async - * @param {JWTToken} payload - The payload to be signed: { id: String, email: String, role: String } - * @param {String} expiresIn - The expiration time of the token (default: "1h") - * @returns {Promise} - The signed JWT token - */ - async sign(payload, expiresIn = "1m") { - const token = jwt.sign(payload, this.privateKey, { expiresIn, algorithm: "RS256" }) - const expiresUnix = jwt.decode(token).exp - const expires = new Date(expiresUnix * 1000) - - const logToken = await Token.create({ - token, - expires, - }) - if (!logToken) throw new CodedError("Error logging token", 500, "JWT|05") - - return token - } - - /** - * Returns the payload of a JWT token - * @async - * @param {String} token - The JWT token - * @returns {Promise} - The payload of the token - */ - async verify(token) { - try { - const tokenExists = await Token.findOne({ where: { token } }) - if (!tokenExists) throw new CodedError("Token not found", 400, "JWT|02") - - if (tokenExists.blacklisted) throw new CodedError("Token is blacklisted", 400, "JWT|03") - - return jwt.verify(token, this.publicKey, { algorithms: ["RS256"] }) - } catch (error) { - return new CodedError(error.message, 400, "JWT|01") - } - } - - /** - * Blacklists a JWT token - * @async - * @param {String} token - The JWT token - * @returns {Promise} - True if the token was blacklisted - */ - async blacklist(token) { - try { - let tokenExists = await Token.findOne({ where: { token } }) - if (!tokenExists) { - const decodedToken = jwt.decode(token) - tokenExists = await Token.create({ - token, - expires: decodedToken.exp, - }) - } - - tokenExists.blacklisted = true - await tokenExists.save() - - return true - } catch (error) { - throw new CodedError(error.message, 400, "JWT|04") - } - } -} - -export default JWT diff --git a/src/classes/responseClass.js b/src/classes/responseClass.js deleted file mode 100644 index 55875b6..0000000 --- a/src/classes/responseClass.js +++ /dev/null @@ -1,77 +0,0 @@ -import CodedError from "../config/CodedError" - -class Response { - /** - * Used to normalize the response - * - * @param {*} req - * @param {*} res - */ - constructor(req, res) { - this.req = req - this.res = res - } - - /** - * Called when the request is successful - * @param {*} data - * @returns {boolean} true - */ - success(data) { - const response = { success: true } - - if (this.req.token) response.token = this.req.token - if (data) response.data = data - - this.res.status(this.req.status ?? 200).json(response) - - return true - } - - /** - * Called when the request is unsuccessful - * @param {*} error - * @returns {boolean} true - */ - error(error) { - const isDev = process.env.NODE_ENV === "development" - if (isDev) console.error(error) - if (error instanceof CodedError) { - const responseBody = { - error: true, - success: false, - message: error.message, - location: isDev ? error.location : undefined, - req: isDev ? this.req.body : undefined, - data: error.data, - } - this.res.status(error.status ?? 500).json(responseBody) - return true - } - - this.res.status(500).json({ success: false, error: true, message: error.message }) - return true - } - - /** - * Sets the status code for the response - * @param {*} status - * @returns {boolean} - true - */ - setStatus(status) { - this.req.status = status - return true - } - - /** - * Sets the token for the response - * @param {*} token - * @returns {boolean} - true - */ - setToken(token) { - this.req.token = token - return true - } -} - -export default Response diff --git a/src/classes/rolesClass.js b/src/classes/rolesClass.js deleted file mode 100644 index 1b9dae5..0000000 --- a/src/classes/rolesClass.js +++ /dev/null @@ -1,189 +0,0 @@ -import RoleModel from "../models/Role" -import CapabilityModel from "../models/Capability" -import Capability from "./capabilitiesClass" -import CodedError from "../config/CodedError" - -class Role { - /** - * Get all roles that match the conditions - * - Gets all roles if no conditions are provided - * @async - * @param {*} conditions - * @returns {Promise} - */ - async getRoles(conditions = {}) { - try { - const roles = await RoleModel.findAll({ where: conditions }) - return roles - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|00") - } - } - - /** - * Gets a single role - * @async - * @param {*} conditions - * @returns {Promise} - */ - async getRole(conditions = {}) { - try { - const role = await RoleModel.findOne({ where: conditions }) - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|01") - } - } - - /** - * Creates a new role - * @async - * @param {*} data - * @param {Object} options - custom options - * - copyFrom: String - The name of the role to copy capabilities from - * @returns {Promise} - */ - async createRole(data = {}, options = {}) { - const { name, description, capabilities: cababilityNames } = data - const { copyFrom } = options - - try { - const role = await RoleModel.create({ name, description }) - const capability = new Capability() - - const SETTING_NEW_CAPABILITIES = cababilityNames && cababilityNames.length - const capabilityObjects = SETTING_NEW_CAPABILITIES ? await capability.getCapabilities({ name: cababilityNames }) : [] - - if (copyFrom) { - const existingRole = await RoleModel.findOne({ - where: { name: copyFrom }, - include: [{ model: CapabilityModel, as: "capabilities" }], - }) - - if (!existingRole?.capabilities) return role - const capabilitiesList = [...existingRole.capabilities, ...capabilityObjects] - await role.setCapabilities(capabilitiesList) - } else { - if (!capabilityObjects.length) return role - await role.setCapabilities(capabilityObjects) - } - - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|02") - } - } - - /** - * Updates a role - * @async - * @param {*} data - * - Can use name or id to select the role to update - * - If capabilities are provided, it will replace the existing capabilities - * @returns {Promise} - */ - async updateRole(data = {}) { - const { id, name, description, capabilities: capabilityNames } = data - const capability = new Capability() - - try { - const condition = id ? { id } : { name } - const role = await RoleModel.findOne({ where: condition }) - if (!role) throw new CodedError("Role not found", 400, "ROLE|03") - - await role.update({ name, description }) - - if (Array.isArray(capabilityNames) && !capabilityNames.length) { - await role.setCapabilities([]) - return role - } - - if (capabilityNames) { - const capabilities = await capability.getCapabilities({ name: capabilityNames }) - if (!capabilities.length) return role - await role.setCapabilities(capabilities) - } - - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|04") - } - } - - /** - * Adds capabilities to a role - * @async - * @param {String} roleName - * @param {String[]} capabilities - array of capability names - * @returns {Promise} - */ - async addCapabilities(roleName, capabilities = []) { - const capability = new Capability() - - try { - if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|05") - const role = await RoleModel.findOne({ where: { name: roleName } }) - if (!role) throw new CodedError("Role not found", 400, "ROLE|05") - - const capabilityObjects = await capability.getCapabilities({ name: capabilities }) - if (!capabilityObjects.length) return role - - await role.addCapabilities(capabilityObjects) - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|06") - } - } - - /** - * Removes capabilities from a role - * @async - * @param {String} roleName - * @param {String[]} capabilities - array of capability names to remove - * @returns {Promise} - */ - async removeCapabilities(roleName, capabilities = []) { - const capability = new Capability() - - try { - if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|07") - const role = await RoleModel.findOne({ where: { name: roleName } }) - if (!role) throw new CodedError("Role not found", 400, "ROLE|07") - - const capabilityObjects = await capability.getCapabilities({ name: capabilities }) - if (!capabilityObjects.length) return role - - await role.removeCapabilities(capabilityObjects) - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|08") - } - } - - /** - * Deletes a role - * - Migrates all users with the role to another role - * @async - * @param {String} roleName - * @param {String} migrateToRole - The role to migrate users to - * @returns {Promise} - */ - async deleteRole(roleName, migrateToRole) { - try { - if (typeof roleName !== "string") throw new CodedError("Role name is required", 400, "ROLE|09") - const role = await RoleModel.findOne({ where: { name: roleName } }) - if (!role) throw new CodedError("Role not found", 400, "ROLE|09") - - const migrateToRoleObject = await RoleModel.findOne({ where: { name: migrateToRole } }) - if (!migrateToRoleObject) throw new CodedError("Role to migrate to not found", 400, "ROLE|09") - - await role.setUsers(migrateToRoleObject.users) - await role.destroy() - return role - } catch (error) { - throw new CodedError(error.message, 400, "ROLE|10") - } - } -} - -export default Role diff --git a/src/classes/totpClass.js b/src/classes/totpClass.js deleted file mode 100644 index 32da995..0000000 --- a/src/classes/totpClass.js +++ /dev/null @@ -1,182 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import * as OTPAuth from "otpauth" -import crypto from "crypto" -import base32 from "hi-base32" -import AuthFactor from "./authFactorClass" -import User from "../models/User" -import CodedError from "../config/CodedError" - -/** - * Methods for handling TOTP authentication - */ -class TOTP { - /** - * Generates a secret that is used to generate the TOTP - * - The secret will be stored in the AuthFactor table and associated with a user - * - * @returns {string} The secret - */ - generateSecret() { - try { - const secret = crypto.randomBytes(32).toString("hex") - const encodedSecret = base32.encode(secret) - console.log("HERE's YOUR SECRET", encodedSecret) - - return encodedSecret - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|01") - } - } - - /** - * Generates a URI that is used to generate the QR code - * - does not include the secret - * - * @param {string} label - The label that will be displayed on the authenticator app - * @param {string} issuer - The issuer that will be displayed on the authenticator app - * @returns {string} The URI - */ - getURI(label, issuer, secret) { - try { - const totp = new OTPAuth.TOTP({ - issuer, - label, - algorithm: "SHA256", - secret, - digits: 6, - period: 30, - }) - - const uri = totp.toString() - return uri - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|02") - } - } - - generateRecoveryCodes() {} - - /** - * Verifies a TOTP code - * - Checks if the code is valid - * - * @async - * @param {string} userId - The id of the user that owns the auth factor - * @param {string} code - The code that will be verified - * @returns {Promise} True if the code is valid, false if it is not - */ - async verify(userId, code) { - try { - const user = await User.findOne({ where: { id: userId } }) - if (!user) throw new CodedError("User not found", 404, "TOTPc|03") - - const userAuthFactors = await user.getAuthFactors() - const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") - if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|04") - - const totp = new OTPAuth.TOTP({ - issuer: "Authenticator", - label: user.email, - algorithm: "SHA256", - digits: 6, - secret: totpAuthFactor.secret, - period: 30, - }) - - const delta = totp.validate({ token: code, window: 1 }) - const isValid = delta === 0 || delta === 1 || delta === -1 - - if (!isValid) throw new CodedError("Invalid code", 400, "TOTPc|051") - return true - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|05") - } - } - - verifyRecoveryCode() {} - - /** - * Creates a TOTP auth factor record for a user - * - Is inactive when created because the user needs to verify it - * - Verifies the user exists, and does not already have a TOTP auth factor - * - * @async - * @param {*} userId - * @returns {Promise} The id of the auth factor record or false if it fails - */ - async createRecord(userId) { - try { - const user = await User.findOne({ where: { id: userId } }) - if (!user) throw new CodedError("User not found", 404, "TOTPc|06") - - const userAuthFactors = await user.getAuthFactors() - const userHasTOTPFactor = userAuthFactors.some((authFactor) => authFactor.factor === "TOTP") - if (userHasTOTPFactor) throw new CodedError("User already has a TOTP auth factor", 400, "TOTPc|07") - - const secret = this.generateSecret() - const authFactorMethods = new AuthFactor() - const authFactor = await authFactorMethods.createRecord(userId, "TOTP", secret) - - return authFactor - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|08") - } - } - - /** - * User must verify the TOTP auth factor before it can be used - * - Sets the verified flag to true - * - * @async - * @param {string} userId - The id of the user that owns the auth factor - * @param {string} code - The code that will be verified - * @returns {Promise} True if the record was activated, false if it fails - */ - async activateRecord(userId, code) { - try { - const secretIsValid = await this.verify(userId, code) - if (!secretIsValid) throw new CodedError("Invalid code", 400, "TOTPc|09") - - const user = await User.findOne({ where: { id: userId } }) - if (!user) throw new CodedError("User not found", 404, "TOTPc|10") - - const userAuthFactors = await user.getAuthFactors() - const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") - if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|11") - - const authFactorMethods = new AuthFactor() - const updated = await authFactorMethods.activateRecord(totpAuthFactor.id) - - return updated - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|12") - } - } - - /** - * Deletes a TOTP auth factor record for a user - * - * @async - * @param {*} userId - * @returns {Promise} True if the record was deleted, false if it fails - */ - async deleteRecord(userId) { - try { - const user = await User.findOne({ where: { id: userId } }) - if (!user) throw new CodedError("User not found", 404, "TOTPc|13") - - const userAuthFactors = await user.getAuthFactors() - const totpAuthFactor = userAuthFactors.find((authFactor) => authFactor.factor === "TOTP") - if (!totpAuthFactor) throw new CodedError("TOTP auth factor not found", 404, "TOTPc|14") - - const authFactorMethods = new AuthFactor() - const deleted = await authFactorMethods.deleteRecord(totpAuthFactor.id) - - return deleted - } catch (error) { - throw new CodedError(error.message, error.status ?? 500, error.location ?? "TOTPc|15") - } - } -} - -export default TOTP diff --git a/src/classes/usersClass.js b/src/classes/usersClass.js deleted file mode 100644 index 0d0241b..0000000 --- a/src/classes/usersClass.js +++ /dev/null @@ -1,486 +0,0 @@ -import bcrypt from "bcryptjs" -import isStrongPassword from "validator/lib/isStrongPassword" -import isJWT from "validator/lib/isJWT" -import CodedError from "../config/CodedError" -import JWT from "./jwtClass" -import Role from "./rolesClass" -import UserModel from "../models/User" - -/** - * Default data to create a new user - * @typedef {Object} DefaultUser - * @property {String} email - The user email - * @property {String} password - The user password - * @property {Boolean} active - The user status - * @property {String} role - The name of the role - */ - -class User { - /** - * Class for handling User operations - */ - constructor() { - this.User = UserModel - } - - /** - * Get all users that match the conditions - * - Gets all users if no conditions are provided - * @async - * @param {object} conditions - Conditions to filter the users - * @returns {Promise} Users - */ - async getUsers(conditions) { - try { - const users = await UserModel.findAll({ where: conditions }) - - users.forEach((user) => { - // eslint-disable-next-line no-param-reassign - delete user.dataValues.password - }) - - return users - } catch (error) { - throw new CodedError(error.message, 400, "USER|00") - } - } - - /** - * Get a single user - * @async - * @param {object} conditions - Conditions to filter the user - * @returns {Promise} User - */ - async getUser(conditions = {}) { - try { - const user = await UserModel.findOne({ where: conditions }) - // if (user) delete user.dataValues.password - - return user - } catch (error) { - throw new CodedError(error.message, 400, "USER|01") - } - } - - /** - * Hashes a password - * - Also ensures the password complies with the password policy - * @param {*} password - */ - async hashPassword(password) { - try { - if (!password) throw new CodedError("Password is required", 400, "USER|32") - if (!isStrongPassword(password)) throw new CodedError("Password does not meet requirements", 400, "USER|33") - - const hashedPassword = await bcrypt.hash(password, 10) - return hashedPassword - } catch (error) { - throw new CodedError(error.message, 400, "USER|34") - } - } - - /** - * Creates a new User - * @async - * @param {object} data - Data to create the user - * @returns {Promise} User - */ - async createUser(data = {}) { - const role = new Role() - - try { - const createUserData = { - active: data.active ?? true, - } - - if (!data.email) throw new CodedError("Email is required", 400, "USER|02") - createUserData.email = data.email - - const roleName = data.role || "User" - const roleObject = await role.getRole({ name: roleName }) - createUserData.roleId = roleObject.id - - if (!roleObject) { - if (roleName !== "User") throw new CodedError("Role not found", 400, "USER|03") - const newRole = await role.createRole({ name: roleName }) - createUserData.roleId = newRole.id - } - - if (data.password) { - const hashedPassword = await this.hashPassword(data.password) - createUserData.password = hashedPassword - } - - const user = await UserModel.create(createUserData) - return user - } catch (error) { - throw new CodedError(error.message, 400, "USER|04") - } - } - - /** - * Updates a single user - * - Can be used to update a user's password - * - Can be used to update a user's role - * @async - * @param {object} data - Data to update the user - * @param {object} conditions - Conditions to filter the user - * @returns {Promise} User - */ - async updateUser(data, conditions) { - try { - // get users - const user = await this.getUser(conditions) - - if (!user.length) throw new CodedError("No users found", 400, "USER|05") - - // prep data - const updateUserData = {} - - if (data.email) { - const userWithEmail = await this.getUser({ email: data.email }) - if (userWithEmail && user.dataValues.id !== userWithEmail.dataValues.id) throw new CodedError("Email already taken", 400, "USER|06") - updateUserData.email = data.email - } - - if (typeof data.active !== "undefined") updateUserData.active = data.active - - if (data.password) updateUserData.password = await this.hashPassword(data.password) - - if (data.role) { - const role = new Role() - - const roleObject = await role.getRole({ name: data.role }) - if (!roleObject) throw new CodedError("Role not found", 400, "USER|07") - - updateUserData.roleId = roleObject.id - } - - // update user - const updatedUser = await user.update(updateUserData) - return updatedUser - } catch (error) { - throw new CodedError(error.message, 400, "USER|08") - } - } - - /** - * Deletes a single user - * @async - * @param {object} conditions - Will delete the first user that matches the conditions - * @returns {Promise} User - */ - async deleteUser(conditions) { - try { - const user = await this.getUser(conditions) - if (!user) throw new CodedError("User not found", 400, "USER|09") - - await user.destroy() - return user - } catch (error) { - throw new CodedError(error.message, 400, "USER|10") - } - } - - /** - * Checks if a user has a capability - * @async - * @param {Number} userId - The user id - * @param {String} capability - The capability to check - * @returns {Promise} - True if the user has the capability - */ - async hasCapabilities(userId, ...capabilities) { - return true - // try { - // return true // right now, all users have all capabilities - - // // if (!userId) throw new CodedError("User ID is required", 400, "USER|11") - // // if (!capabilities.length) throw new CodedError("At least 1 capability is required", 400, "USER|12") - - // // const user = await this.getUser({ id: userId }) - // // if (!user) throw new CodedError("User not found", 400, "USER|13") - - // // const userRole = await user.getRole() - // // const userRoleData = userRole.get() - // // const userCapabilities = userRoleData.capabilities.map((capability) => capability.dataValues?.name) - // // const userHasAllCapabilities = capabilities.every((capability) => userCapabilities.includes(capability)) - - // // return userHasAllCapabilities - // } catch (error) { - // // throw new CodedError(error.message, error.status ?? 500, error.location ?? "USER|15") - // } - } - - /** - * Checks if a password matches the user's password - * @async - * @param {String} userId - The user id - * @param {String} password - The password to check - * @returns {Promise} - True if the password matches - */ - async checkPassword(userId, password) { - try { - const user = await this.getUser({ id: userId }) - if (!user) throw new CodedError("User not found", 400, "USER|13") - - const passwordMatches = await bcrypt.compare(password, user?.dataValues?.password) - return passwordMatches - } catch (error) { - throw new CodedError(error.message, 400, "USER|14") - } - } - - /** - * Generate a session token for a user - * @async - * @param {Number} userId - The user id - * @returns {Promise} - The session token - */ - async createSessionToken(userId) { - try { - const jwt = new JWT() - - const user = await this.getUser({ id: userId }) - if (!user) throw new CodedError("User not found", 400, "USER|15") - - const token = await jwt.sign( - { - id: user.id, - email: user.email, - role: user?.dataValues?.roleId, - mfa: user?.dataValues?.mfa ?? false, - sessionState: "full", - }, - "15m" // change to 15m in production - ) - return token - } catch (error) { - throw new CodedError(error.message, 400, "USER|16") - } - } - - /** - * Create a 'half' session token for a user - * - This token signifies that the user has input their password correctly - * - but has not yet completed 2FA - * @async - * @param {Number} userId - The user id - * @returns {Promise} - The session token - */ - async createHalfSessionToken(userId) { - try { - const jwt = new JWT() - - const user = await this.getUser({ id: userId }) - if (!user) throw new CodedError("User not found", 400, "USER|15") - - const token = await jwt.sign( - { - id: user.id, - email: user.email, - role: user?.dataValues?.roleId, - sessionState: "half", - }, - "5m" - ) - return token - } catch (error) { - throw new CodedError(error.message, 400, "USER|16") - } - } - - /** - * Generates a refresh token for a user - * @async - * @param {Number} userId - The user id - * @returns {Promise} - The refresh token - */ - async createRefreshToken(userId) { - try { - const jwt = new JWT(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE) - - const user = await this.getUser({ id: userId }) - if (!user) throw new CodedError("User not found", 400, "USER|17") - - // expires in 1 week - const token = await jwt.sign({ id: user.id, email: user.email, role: user?.dataValues?.roleId }, "1w") - return token - } catch (error) { - throw new CodedError(error.message, 400, "USER|18") - } - } - - /** - * Checks if a session token is valid - * - Session token needs to be a 'full' session token - * - Half session tokens are not valid - * @async - * @param {String} token - The session token - * @returns {Promise} - True if the token is valid - */ - async checkSessionToken(token) { - try { - const jwt = new JWT() - - const decodedToken = await jwt.verify(token) - if (decodedToken instanceof CodedError) throw decodedToken - - if (decodedToken.sessionState !== "full") throw new CodedError("Session Token is invalid", 400, "USER|18") - - const user = await this.getUser({ id: decodedToken.id }) - if (!user) throw new CodedError("User not found", 400, "USER|19") - - return true - } catch (error) { - return false - } - } - - /** - * Checks if a half session token is valid - * @async - * @param {String} token - The session token - * @returns {Promise} - True if the token is valid - */ - async checkHalfSessionToken(token) { - try { - const jwt = new JWT() - - const decodedToken = await jwt.verify(token) - if (decodedToken instanceof CodedError) throw decodedToken - - if (decodedToken.sessionState !== "half") throw new CodedError("Session Token is invalid", 400, "USER|18") - - const user = await this.getUser({ id: decodedToken.id }) - if (!user) throw new CodedError("User not found", 400, "USER|19") - - return true - } catch (error) { - return false - } - } - - /** - * Checks if a refresh token is valid - * @async - * @param {String} token - The refresh token - * @returns {Promise} - An object containing the data from the token - */ - async checkRefreshToken(token) { - try { - if (!token) throw new CodedError("Refresh Token is required", 400, "USER|20") - if (!isJWT(token)) throw new CodedError("Token is invalid", 400, "USER|21") - - const jwt = new JWT(process.env.REFRESH_JWT_PUBLIC, process.env.REFRESH_JWT_PRIVATE) - - const decodedToken = await jwt.verify(token) - if (decodedToken instanceof CodedError) throw decodedToken - - const user = await this.getUser({ id: decodedToken.id }) - if (!user) throw new CodedError("User not found", 400, "USER|21") - - return decodedToken - } catch (error) { - throw new CodedError(error.message, 400, "USER|22") - } - } - - /** - * Refreshes a session token - * @async - * @param {String} refreshToken - The refresh token - * @returns {Promise} - An object containing the new session token and new refresh token - */ - async refreshSessionToken(refreshToken) { - try { - const jwt = new JWT() - const validRefreshToken = await this.checkRefreshToken(refreshToken) - - const token = await this.createSessionToken(validRefreshToken.id) - const newRefreshToken = await this.createRefreshToken(validRefreshToken.id) - - const blacklistOldToken = await jwt.blacklist(refreshToken) - if (!blacklistOldToken) throw new CodedError("Could not blacklist old token", 500, "USER|23") - - return { sessionToken: token, refreshToken: newRefreshToken } - } catch (error) { - throw new CodedError(error.message, 400, "USER|24") - } - } - - /** - * Issues a password reset token - * @async - * @param {String} email - The user email - * @returns {Promise} - The password reset token - */ - async createPasswordResetToken(email) { - try { - const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) - - const user = await this.getUser({ email }) - if (!user) throw new CodedError("User not found", 400, "USER|25") - - const token = await jwt.sign({ id: user.id, email: user.email }, "15m") - return token - } catch (error) { - throw new CodedError(error.message, 400, "USER|26") - } - } - - /** - * Checks if a password reset token is valid - * @async - * @param {String} token - The password reset token - * @returns {Promise} - True if the token is valid - */ - async checkPasswordResetToken(token) { - try { - const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) - - const decodedToken = await jwt.verify(token) - if (decodedToken instanceof CodedError) throw decodedToken - - const user = await this.getUser({ id: decodedToken.id }) - if (!user) throw new CodedError("User not found", 400, "USER|27") - - return true - } catch (error) { - throw new CodedError(error.message, 400, "USER|28") - } - } - - /** - * Resets a user's password - * @async - * @param {String} token - The password reset token - * @param {String} password - The new password - * @returns {Promise} - True if the password was reset - */ - async resetPassword(token, password) { - try { - if (!token) throw new CodedError("Password Token is required", 400, "USER|29") - if (!password) throw new CodedError("Password is required", 400, "USER|30") - - const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) - - const decodedToken = await jwt.verify(token) - if (decodedToken instanceof CodedError) throw decodedToken - - const user = await this.getUser({ id: decodedToken.id }) - if (!user) throw new CodedError("User not found", 400, "USER|29") - - const hashedPassword = await this.hashPassword(password) - await user.update({ password: hashedPassword }) - - const blacklistOldToken = await jwt.blacklist(token) - if (!blacklistOldToken) throw new CodedError("Could not blacklist old token", 400, "USER|30") - - return true - } catch (error) { - throw new CodedError(error.message, 400, "USER|31") - } - } -} - -export default User diff --git a/src/config/CodedError.js b/src/config/CodedError.js deleted file mode 100644 index 039d9c7..0000000 --- a/src/config/CodedError.js +++ /dev/null @@ -1,16 +0,0 @@ -import dotenv from "dotenv" - -dotenv.config({ path: ".env" }) -dotenv.config({ path: ".env.secrets" }) - -class CodedError extends Error { - constructor(message, status, location, data) { - super(message) // Human-readable message - this.name = this.constructor.name - this.status = status // HTTP status code - this.location = location - this.data = data - } -} - -export default CodedError diff --git a/src/config/config.js b/src/config/config.js deleted file mode 100644 index a071f83..0000000 --- a/src/config/config.js +++ /dev/null @@ -1,29 +0,0 @@ -import "dotenv/config" - -const config = { - development: { - username: process.env.DEV_DB_USER, - password: process.env.DEV_DB_PASS, - database: process.env.DEV_DB_NAME, - host: process.env.DEV_DB_HOST, - port: process.env.DEV_DB_PORT, - dialect: "mysql", - }, - test: { - username: process.env.TEST_DB_USER, - password: process.env.TEST_DB_PASS, - database: process.env.TEST_DB_NAME, - host: process.env.TEST_DB_HOST, - port: process.env.TEST_DB_PORT, - dialect: "mysql", - }, - production: { - username: process.env.PROD_DB_USER, - password: process.env.PROD_DB_PASS, - database: process.env.PROD_DB_NAME, - host: process.env.PROD_DB_HOST, - port: process.env.PROD_DB_PORT, - dialect: "mysql", - }, -} -export default config diff --git a/src/config/database.js b/src/config/database.js deleted file mode 100644 index bc11019..0000000 --- a/src/config/database.js +++ /dev/null @@ -1,15 +0,0 @@ -import "dotenv/config" -import { Sequelize } from "sequelize" -import config from "./config" - -const nodeEnv = process.env.NODE_ENV || "development" -const { username: user, password, database: db, host, port } = config[nodeEnv] - -const sequelize = new Sequelize(db, user, password, { - host, - port, - dialect: "mysql", - logging: false, -}) - -export default sequelize diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 550bec1..0000000 --- a/src/index.js +++ /dev/null @@ -1,50 +0,0 @@ -import Express, { json } from "express" -import "dotenv/config" -import cors from "cors" -import cookieParser from "cookie-parser" -import helmet from "helmet" -import sequelize from "./config/database" -import requireSSL from "./middleware/requireSSL" - -// Routes - -import baseRoutes from "./routes/baseRoutes" -import registerRoutes from "./routes/registerRoutes" -import loginRoutes from "./routes/loginRoutes" -import passwordRoutes from "./routes/passwordRoutes" -import userRoutes from "./routes/userRoutes" -import refreshRoutes from "./routes/refreshRoutes" -import totpRoutes from "./routes/totpRoutes" - -// DB Models - -import "./models/index" - -const PORT = process.env.PORT || 3000 -const app = Express() - -app.set("trust proxy", 1) - -app.use(json()) -app.use(cors()) -app.use(helmet()) -app.use(cookieParser()) -app.use(requireSSL) - -app.use("/", baseRoutes) -app.use("/register", registerRoutes) -app.use("/login", loginRoutes) -app.use("/password", passwordRoutes) -app.use("/users", userRoutes) -app.use("/refresh", refreshRoutes) -app.use("/totp", totpRoutes) - -app.listen(PORT, async () => { - try { - await sequelize.sync() - } catch (error) { - if (process.env.NODE_ENV !== "development") return - // eslint-disable-next-line no-console - console.error("Error syncing database:", error.message) - } -}) diff --git a/src/middleware/errorMiddleware.js b/src/middleware/errorMiddleware.js deleted file mode 100644 index 76512c4..0000000 --- a/src/middleware/errorMiddleware.js +++ /dev/null @@ -1,8 +0,0 @@ -import Response from "../classes/responseClass" - -const errorMiddleware = (err, req, res) => { - const response = new Response(req, res) - response.error(err) -} - -export default errorMiddleware diff --git a/src/middleware/rateLimiters.js b/src/middleware/rateLimiters.js deleted file mode 100644 index ff3e2d2..0000000 --- a/src/middleware/rateLimiters.js +++ /dev/null @@ -1,15 +0,0 @@ -import rateLimit from "express-rate-limit" - -export const noAuthLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 50, - standardHeaders: true, - legacyHeaders: false, -}) - -export const authLimiter = rateLimit({ - windowMs: 5 * 60 * 1000, // 5 minutes - max: 100, - standardHeaders: true, - legacyHeaders: false, -}) diff --git a/src/middleware/requireSSL.js b/src/middleware/requireSSL.js deleted file mode 100644 index 1449d4f..0000000 --- a/src/middleware/requireSSL.js +++ /dev/null @@ -1,12 +0,0 @@ -import Response from "../classes/responseClass" - -export default function requireSSL(req, res, next) { - const response = new Response(req, res) - - try { - // if (!req.secure && process.env.NODE_ENV === "production") throw new Error("SSL required") - next() - } catch (err) { - response.error(err) - } -} diff --git a/src/middleware/verifyUser.js b/src/middleware/verifyUser.js deleted file mode 100644 index 75152a2..0000000 --- a/src/middleware/verifyUser.js +++ /dev/null @@ -1,52 +0,0 @@ -import isJWT from "validator/lib/isJWT" -import CodedError from "../config/CodedError" -import User from "../classes/usersClass" -import Cookies from "../classes/cookiesClass" -import Response from "../classes/responseClass" - -/** - * Middleware to verify a user's session - * @param {...string} capabilities - The capabilities required to access the route - */ - -function verifyUser(...capabilities) { - return async function verifyUserInner(req, res, next) { - const response = new Response(req, res) - - let token = req.headers?.authorization?.split(" ")?.[1] // Bearer - - try { - if (!token) throw new CodedError("Session Token is required", 400, "VERIFY|01") - if (!isJWT(token)) throw new CodedError("Session Token is invalid", 400, "VERIFY|02") - - const userMethods = new User() - const userTokenIsValid = await userMethods.checkSessionToken(token) - - const tokenContent = JSON.parse(atob(token.split(".")[1])) - if (tokenContent.sessionState !== "full") throw new CodedError("Session Token is invalid", 400, "VERIFY|021") - - if (!userTokenIsValid) { - const cookies = new Cookies(req, res) - const refreshToken = cookies.getRefreshToken() - const newTokens = await userMethods.refreshSessionToken(refreshToken) - - token = newTokens.sessionToken - req.token = newTokens.sessionToken - cookies.setRefreshToken(newTokens.refreshToken) - } - - req.user = JSON.parse(atob(token.split(".")[1])) - - if (!capabilities.length) return next() - - const userHasCapabilities = await userMethods.hasCapabilities(req.user.id, ...capabilities) - if (!userHasCapabilities) throw new CodedError("User does not have required capabilities", 403, "VERIFY|03") - - next() - } catch (error) { - response.error(error) - } - } -} - -export default verifyUser diff --git a/src/models/AuthFactor.js b/src/models/AuthFactor.js deleted file mode 100644 index 8203600..0000000 --- a/src/models/AuthFactor.js +++ /dev/null @@ -1,30 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -// storing information used in 2-factor authentication (2FA) -const AuthFactor = sequelize.define("AuthFactor", { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - }, - factor: { - type: DataTypes.STRING, - allowNull: false, - }, - secret: { - type: DataTypes.STRING, - allowNull: false, - }, - verified: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, - verifiedAt: { - type: DataTypes.DATE, - allowNull: true, - }, -}) - -export default AuthFactor diff --git a/src/models/Capability.js b/src/models/Capability.js deleted file mode 100644 index 1a1a25e..0000000 --- a/src/models/Capability.js +++ /dev/null @@ -1,21 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -const Capability = sequelize.define("Capability", { - id: { - type: DataTypes.INTEGER, // or DataTypes.UUID - primaryKey: true, - autoIncrement: true, // set to false if using UUID - }, - name: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - }, - description: { - type: DataTypes.STRING, - allowNull: true, - }, -}) - -export default Capability diff --git a/src/models/Role.js b/src/models/Role.js deleted file mode 100644 index 51fb5ab..0000000 --- a/src/models/Role.js +++ /dev/null @@ -1,32 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -const Role = sequelize.define( - "Role", - { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, - name: { - type: DataTypes.STRING, - allowNull: false, - unique: true, - }, - description: { - type: DataTypes.STRING, - allowNull: true, - }, - }, - { - indexes: [ - { - unique: true, - fields: ["name"], - }, - ], - } -) - -export default Role diff --git a/src/models/Role_Capability.js b/src/models/Role_Capability.js deleted file mode 100644 index 3b3b6f2..0000000 --- a/src/models/Role_Capability.js +++ /dev/null @@ -1,12 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -const RoleCapability = sequelize.define("Role_Capability", { - id: { - type: DataTypes.INTEGER, - primaryKey: true, - autoIncrement: true, - }, -}) - -export default RoleCapability diff --git a/src/models/Token.js b/src/models/Token.js deleted file mode 100644 index b3f7fbf..0000000 --- a/src/models/Token.js +++ /dev/null @@ -1,25 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -const Token = sequelize.define("Token", { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - }, - token: { - type: DataTypes.STRING(1000), - allowNull: false, - }, - expires: { - type: DataTypes.DATE, - allowNull: false, - }, - blacklisted: { - type: DataTypes.BOOLEAN, - allowNull: false, - defaultValue: false, - }, -}) - -export default Token diff --git a/src/models/User.js b/src/models/User.js deleted file mode 100644 index 7a03b20..0000000 --- a/src/models/User.js +++ /dev/null @@ -1,42 +0,0 @@ -import { DataTypes } from "sequelize" -import sequelize from "../config/database" - -const User = sequelize.define( - "User", - { - id: { - type: DataTypes.UUID, - primaryKey: true, - defaultValue: DataTypes.UUIDV4, - }, - email: { - type: DataTypes.STRING, - allowNull: false, - }, - password: { - type: DataTypes.STRING, - }, - active: { - type: DataTypes.BOOLEAN, - defaultValue: true, - }, - mfa: { - type: DataTypes.BOOLEAN, - defaultValue: false, - }, - primaryAuth: { - type: DataTypes.STRING, - defaultValue: "UNPW", - }, - }, - { - indexes: [ - { - unique: true, - fields: ["email"], - }, - ], - } -) - -export default User diff --git a/src/models/associations.js b/src/models/associations.js deleted file mode 100644 index 33b6ae7..0000000 --- a/src/models/associations.js +++ /dev/null @@ -1,57 +0,0 @@ -import User from "./User" -import Role from "./Role" -import Capability from "./Capability" -import AuthFactor from "./AuthFactor" - -User.belongsTo(Role, { foreignKey: "roleId" }) -Role.hasMany(User, { foreignKey: "roleId" }) - -User.hasMany(AuthFactor, { foreignKey: "userId" }) -AuthFactor.belongsTo(User, { foreignKey: "userId" }) - -Role.belongsToMany(Capability, { through: "Role_Capability", as: "capabilities" }) -Capability.belongsToMany(Role, { through: "Role_Capability", as: "roles" }) - -Role.addScope( - "defaultScope", - { - include: [ - { - model: Capability, - as: "capabilities", - attributes: ["id", "name", "description"], - through: { - attributes: [], - }, - }, - ], - }, - { override: true } -) - -User.addScope( - "defaultScope", - { - include: [ - { - model: Role, - attributes: ["name", "description"], - include: [ - { - model: Capability, - as: "capabilities", - attributes: ["name", "description"], - through: { - attributes: [], - }, - }, - ], - }, - { - model: AuthFactor, - attributes: ["factor", "verified", "verifiedAt"], - }, - ], - }, - { override: true } -) diff --git a/src/models/index.js b/src/models/index.js deleted file mode 100644 index e2fa6cb..0000000 --- a/src/models/index.js +++ /dev/null @@ -1,22 +0,0 @@ -const fs = require("fs") - -const path = require("path") - -const basename = path.basename(__filename) - -fs.readdirSync(__dirname) - .filter((file) => { - const isJavaScriptFile = file.indexOf(".") !== 0 - const isNotCurrentFile = file !== basename - const isJavaScriptExtension = file.slice(-3) === ".js" - const isNotTestFile = file.indexOf(".test.js") === -1 - const isNotAssociationFile = file !== "associations.js" - - return isJavaScriptFile && isNotCurrentFile && isJavaScriptExtension && isNotTestFile && isNotAssociationFile - }) - .forEach((file) => { - // eslint-disable-next-line import/no-dynamic-require, global-require - require(path.join(__dirname, file)) - }) - -require("./associations") diff --git a/src/routes/baseRoutes.js b/src/routes/baseRoutes.js deleted file mode 100644 index 42060f2..0000000 --- a/src/routes/baseRoutes.js +++ /dev/null @@ -1,18 +0,0 @@ -import express from "express" -import verifyUser from "../middleware/verifyUser" -import Response from "../classes/responseClass" - -const router = express.Router() - -router.get("/", verifyUser(), async (req, res) => { - const response = new Response(req, res) - - try { - response.success({ message: "Hello, world!" }) - } catch (error) { - response.error(error) - } -}) - -const baseRoutes = router -export default baseRoutes diff --git a/src/routes/loginRoutes.js b/src/routes/loginRoutes.js deleted file mode 100644 index de600d5..0000000 --- a/src/routes/loginRoutes.js +++ /dev/null @@ -1,53 +0,0 @@ -import express from "express" -import isEmail from "validator/lib/isEmail" -import normalizeEmail from "validator/lib/normalizeEmail" -import CodedError from "../config/CodedError" -import User from "../classes/usersClass" -import { noAuthLimiter } from "../middleware/rateLimiters" -import Response from "../classes/responseClass" -import Cookies from "../classes/cookiesClass" - -const router = express.Router() - -router.post("/", noAuthLimiter, async (req, res) => { - let { email, password } = req.body - - const response = new Response(req, res) - - try { - if (!password) throw new CodedError("Invalid Password", 400, "LOG|02") - if (!email || !isEmail(email)) throw new CodedError("Invalid Email", 400, "LOG|01") - email = normalizeEmail(email, { gmail_remove_subaddress: false }) - - const userMethods = new User() - const user = await userMethods.getUser({ email }) - if (!user) throw new CodedError("User not found", 400, "LOG|03") - - const userId = user.id - const isPasswordValid = await userMethods.checkPassword(userId, password) - if (!isPasswordValid) throw new CodedError("Password is incorrect", 400, "LOG|04") - - let token - - if (user?.dataValues?.mfa) { - token = await userMethods.createHalfSessionToken(userId) - } else { - const cookies = new Cookies(req, res) - token = await userMethods.createSessionToken(userId) - cookies.setSessionCookie(token) - - const refreshToken = await userMethods.createRefreshToken(userId) - if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "REG|06") - cookies.setRefreshToken(refreshToken) - } - const tokenBody = JSON.parse(atob(token.split(".")[1])) - - response.setToken(token) - response.success({ message: "Login successful", data: tokenBody }) - } catch (error) { - response.error(error) - } -}) - -const loginRoutes = router -export default loginRoutes diff --git a/src/routes/passwordRoutes.js b/src/routes/passwordRoutes.js deleted file mode 100644 index a21d800..0000000 --- a/src/routes/passwordRoutes.js +++ /dev/null @@ -1,91 +0,0 @@ -import express from "express" -import isEmail from "validator/lib/isEmail" -import isJWT from "validator/lib/isJWT" -import normalizeEmail from "validator/lib/normalizeEmail" -import CodedError from "../config/CodedError" -import Response from "../classes/responseClass" -import JWT from "../classes/jwtClass" -import User from "../classes/usersClass" -import sendEmail from "../utils/sendEmail" -import { noAuthLimiter } from "../middleware/rateLimiters" - -const router = express.Router() - -router.post("/forgot", noAuthLimiter, async (req, res) => { - const response = new Response(req, res) - let { email } = req.body - - try { - if (!email) throw new CodedError("Email is required", 400, "PAS|01") - if (!isEmail(email)) throw new CodedError("Invalid Email", 400, "PAS|02") - email = normalizeEmail(email, { gmail_remove_subaddress: false }) - - const userMethods = new User() - const user = await userMethods.getUser({ email }) - if (!user) throw new CodedError("User not found", 400, "PAS|02") - - const token = await userMethods.createPasswordResetToken(email) - - const emailSent = await sendEmail({ - to: email, - subject: "Password Reset", - text: `Click here to reset your password: ${process.env.PASSWORD_RESET_URL}/${token}`, - }) - if (!emailSent) throw new CodedError("Email failed to send", 500, "PAS|03") - - response.success({ message: "Email sent" }) - } catch (error) { - response.error(error) - } -}) - -router.get("/reset/:token", noAuthLimiter, async (req, res) => { - const response = new Response(req, res) - const { token } = req.params - - try { - if (!token) throw new CodedError("Token is required", 400, "PAS|04") - if (!isJWT(token)) throw new CodedError("Invalid Token", 400, "PAS|05") - - const jwt = new JWT(process.env.PASSWORD_JWT_PUBLIC, process.env.PASSWORD_JWT_PRIVATE) - - let decoded - try { - decoded = await jwt.verify(token) - } catch (error) { - throw new CodedError("Verification Failed", 400, "PAS|05") - } - - const userMethods = new User() - const user = await userMethods.getUser({ email: decoded.email }) - if (!user) throw new CodedError("User not found", 400, "PAS|06") - - response.success({ message: "Token is valid" }) - } catch (error) { - response.error(error) - } -}) - -router.post("/reset/:token", noAuthLimiter, async (req, res) => { - const response = new Response(req, res) - const { token } = req.params - const { password } = req.body - - try { - if (!token) throw new CodedError("Token is required", 400, "PAS|07") - if (!isJWT(token)) throw new CodedError("Invalid Token", 400, "PAS|07") - if (!password) throw new CodedError("Password is required", 400, "PAS|08") - - const userMethods = new User() - - const resetPassword = await userMethods.resetPassword(token, password) - if (!resetPassword) throw new CodedError("Password reset failed", 500, "PAS|09") - - response.success({ message: "Password reset successful" }) - } catch (error) { - response.error(error) - } -}) - -const passwordRoutes = router -export default passwordRoutes diff --git a/src/routes/refreshRoutes.js b/src/routes/refreshRoutes.js deleted file mode 100644 index b19b208..0000000 --- a/src/routes/refreshRoutes.js +++ /dev/null @@ -1,32 +0,0 @@ -import express from "express" -import Response from "../classes/responseClass" -import User from "../classes/usersClass" -import Cookies from "../classes/cookiesClass" -import CodedError from "../config/CodedError" - -const router = express.Router() - -router.post("/", async (req, res) => { - const response = new Response(req, res) - const { refreshToken } = req.body - - try { - const cookies = new Cookies(req, res) - - if (!refreshToken) throw new CodedError("Token not found", 401, "REFRESH|01") - - const userMethods = new User() - const { sessionToken, refreshToken: newRefreshToken } = await userMethods.refreshSessionToken(refreshToken) - - const refreshTokenSet = cookies.setRefreshToken(newRefreshToken) - if (!refreshTokenSet) throw new CodedError("Token not set", 500, "REFRESH|02") - - response.setToken(sessionToken) - response.success({ refreshToken: newRefreshToken }) - } catch (error) { - response.error(error) - } -}) - -const refreshRoutes = router -export default refreshRoutes diff --git a/src/routes/registerRoutes.js b/src/routes/registerRoutes.js deleted file mode 100644 index fa5302b..0000000 --- a/src/routes/registerRoutes.js +++ /dev/null @@ -1,49 +0,0 @@ -import express from "express" -import isEmail from "validator/lib/isEmail" -import normalizeEmail from "validator/lib/normalizeEmail" -import isStrongPassword from "validator/lib/isStrongPassword" -import CodedError from "../config/CodedError" -import User from "../classes/usersClass" -import Response from "../classes/responseClass" -import Cookies from "../classes/cookiesClass" -import { noAuthLimiter } from "../middleware/rateLimiters" - -const router = express.Router() - -router.post("/", noAuthLimiter, async (req, res) => { - const response = new Response(req, res) - let { email, password } = req.body - - try { - const user = new User() - - if (!email || !password) throw new CodedError("Email and password are required", 400, "REG|01") - if (!isEmail(email)) throw new CodedError("Invalid Email", 400, "REG|02") - if (!isStrongPassword(password)) throw new CodedError("Invalid Password", 400, "REG|02") - email = normalizeEmail(email, { gmail_remove_subaddress: false }) - - const existingUser = await user.getUser({ email }) - if (existingUser) throw new CodedError("Email already exists", 500, "REG|03") - - const newUser = await user.createUser({ email, password }) - if (!newUser) throw new CodedError("Error creating user", 500, "REG|04") - - const token = await user.createSessionToken(newUser.id) - if (!token) throw new CodedError("Error creating session token", 500, "REG|05") - - const refreshToken = await user.createRefreshToken(newUser.id) - if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "REG|06") - - const cookie = new Cookies(req, res) - const setRefreshCookie = cookie.setRefreshToken(refreshToken) - if (!setRefreshCookie) throw new CodedError("Error setting refresh token", 500, "REG|07") - - response.setToken(token) - response.success({ message: "User created", data: newUser }) - } catch (error) { - response.error(error) - } -}) - -const registerRoutes = router -export default registerRoutes diff --git a/src/routes/testRoutes.js b/src/routes/testRoutes.js deleted file mode 100644 index 29ad4e4..0000000 --- a/src/routes/testRoutes.js +++ /dev/null @@ -1,6 +0,0 @@ -import express from "express" - -const router = express.Router() - -const testRoutes = router -export default testRoutes diff --git a/src/routes/totpRoutes.js b/src/routes/totpRoutes.js deleted file mode 100644 index 41e1dc0..0000000 --- a/src/routes/totpRoutes.js +++ /dev/null @@ -1,113 +0,0 @@ -import express from "express" -import TOTP from "../classes/totpClass" -import Response from "../classes/responseClass" -import CodedError from "../config/CodedError" -import verifyUser from "../middleware/verifyUser" -import User from "../classes/usersClass" -import Cookies from "../classes/cookiesClass" - -const router = express.Router() - -/** - * Create TOTP auth factor for user and return URI for QR code - */ -router.post("/create", verifyUser(), async (req, res) => { - const response = new Response(req, res) - - try { - const { user } = req - const totp = new TOTP() - const totpRecord = await totp.createRecord(user.id) - const totpURI = totp.getURI(user.email, "Authenticator", totpRecord?.secret) - - response.success({ message: "TOTP created", data: totpURI }) - } catch (error) { - response.error(error) - } -}) - -/** - * Activate TOTP auth factor for user - */ -router.post("/activate", verifyUser(), async (req, res) => { - const response = new Response(req, res) - const { code } = req.body - - try { - const { user } = req - const totp = new TOTP() - - const activated = await totp.activateRecord(user.id, code) - if (!activated) throw new CodedError("Error activating TOTP", 500, "TOTP|07") - - // give user new session token with mfa flag - const userMethods = new User() - const sessionToken = await userMethods.createSessionToken(user.id) - - response.setToken(sessionToken) - response.success({ message: "TOTP activated" }) - } catch (error) { - response.error(error) - } -}) - -/** - * Verify TOTP auth factor for user, and authenticate the user - * - User must have a valid session token with "sessionState": "half" - */ -router.post("/verify", async (req, res) => { - const response = new Response(req, res) - const { code } = req.body - - try { - const sessionToken = req.headers.authorization.split(" ")[1] - const userMethods = new User() - - const halfSessionTokenIsValid = await userMethods.checkHalfSessionToken(sessionToken) - if (!halfSessionTokenIsValid) throw new CodedError("Invalid session token", 400, "TOTP|05") - - const userId = JSON.parse(atob(sessionToken.split(".")[1])).id - - const totp = new TOTP() - const isValid = await totp.verify(userId, code) - if (!isValid) throw new CodedError("Invalid code", 400, "TOTP|08") - - const cookies = new Cookies(req, res) - - const fullSessionToken = await userMethods.createSessionToken(userId) - if (!sessionToken) throw new CodedError("Error creating session token", 500, "TOTP|09") - cookies.setSessionCookie(fullSessionToken) - - const refreshToken = await userMethods.createRefreshToken(userId) - if (!refreshToken) throw new CodedError("Error creating refresh token", 500, "TOTP|10") - - const setRefreshCookie = cookies.setRefreshToken(refreshToken) - if (!setRefreshCookie) throw new CodedError("Error setting refresh token", 500, "TOTP|11") - - response.setToken(fullSessionToken) - response.success({ message: "User Session verified" }) - } catch (error) { - response.error(error) - } -}) - -/** - * Deactivate TOTP auth factor for user - */ -router.post("/disable", verifyUser(), async (req, res) => { - const response = new Response(req, res) - - try { - const { user } = req - const totp = new TOTP() - const deactivated = await totp.deleteRecord(user.id) - if (!deactivated) throw new CodedError("Error deactivating TOTP", 500, "TOTP|12") - - response.success({ message: "TOTP deactivated" }) - } catch (error) { - response.error(error) - } -}) - -const totpRoutes = router -export default totpRoutes diff --git a/src/routes/userRoutes.js b/src/routes/userRoutes.js deleted file mode 100644 index 9c566e4..0000000 --- a/src/routes/userRoutes.js +++ /dev/null @@ -1,113 +0,0 @@ -import express from "express" -import verifyUser from "../middleware/verifyUser" -import User from "../classes/usersClass" -import Response from "../classes/responseClass" -import CodedError from "../config/CodedError" - -const router = express.Router() - -router.get("/:id", verifyUser("users_read"), async (req, res) => { - const response = new Response(req, res) - const { id } = req.params - - try { - const userMethods = new User() - const { user } = req - - const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all") - const users = await userMethods.getUser({ id }) - - if (!userCanReadAllUsers) { - if (users?.dataValues?.id === user.id) return response.success(users) - throw new CodedError("You are not allowed to read this user", 403, "USERS|01") - } - - response.success(users) - } catch (error) { - response.error(error) - } -}) - -router.get("/", verifyUser("users_read"), async (req, res) => { - const response = new Response(req, res) - - try { - const userMethods = new User() - const { user } = req - - const userCanReadAllUsers = await userMethods.hasCapabilities(user.id, "users_read_all") - if (!userCanReadAllUsers) { - const users = await userMethods.getUsers({ id: user.id }) - return response.success(users) - } - - const users = await userMethods.getUsers() - response.success(users) - } catch (error) { - response.error(error) - } -}) - -router.post("/", verifyUser("users_create"), async (req, res) => { - const response = new Response(req, res) - const { body } = req - - try { - const userMethods = new User() - const { user } = req - - delete body.id - delete body.password // You can't set the password when creating a user. - - const userCanCreateUsers = await userMethods.hasCapabilities(user.id, "users_create") - if (!userCanCreateUsers) throw new CodedError("You are not allowed to create users", 403, "USERS|02") - - const newUser = await userMethods.createUser(body) - response.success(newUser) - } catch (error) { - response.error(error) - } -}) - -router.put("/:id", verifyUser("users_update"), async (req, res) => { - const response = new Response(req, res) - const { body } = req - const { id } = req.params - - try { - const userMethods = new User() - const { user } = req - - delete body.password - delete body.id - - const userCanUpdateUsers = await userMethods.hasCapabilities(user.id, "users_update") - if (!userCanUpdateUsers) throw new CodedError("You are not allowed to update users", 403, "USERS|03") - - const updatedUser = await userMethods.updateUser(id, body) - response.success(updatedUser) - } catch (error) { - response.error(error) - } -}) - -router.delete("/:id", verifyUser("users_delete"), async (req, res) => { - const response = new Response(req, res) - const { id } = req.params - - try { - const userMethods = new User() - const { user } = req - - const userCanDeleteUsers = await userMethods.hasCapabilities(user.id, "users_delete") - if (!userCanDeleteUsers) throw new CodedError("You are not allowed to delete users", 403, "USERS|04") - - await userMethods.deleteUser(id) - response.success("User deleted") - } catch (error) { - response.error(error) - } -}) - -const baseRoutes = router -export default baseRoutes diff --git a/src/utils/sendEmail.js b/src/utils/sendEmail.js deleted file mode 100644 index 622247d..0000000 --- a/src/utils/sendEmail.js +++ /dev/null @@ -1,11 +0,0 @@ -export default async function sendEmail(email) { - const { to, subject, text, html } = email - - console.log("Email sending is not implemented yet") - console.log(`To: ${to}`) - console.log(`Subject: ${subject}`) - console.log(`Text: ${text}`) - console.log(`HTML: ${html}`) - - return true -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 3ac0370..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./api", // specify the output directory - "noImplicitAny": true, // raise error on expressions and declarations with an implied 'any' type - "module": "esnext", // specify module code generation - "target": "es5", // specify ECMAScript target version - "jsx": "preserve", // preserve JSX to be further transformed by Babel - "allowJs": true, // allow JavaScript files to be compiled - "allowSyntheticDefaultImports": true, - "esModuleInterop": true - }, - "include": [ - "src/**/*", // specify files to be included - "models/associations.js", - "config/database.js", - "models/Capability.js", - "models/Role_Capability.js", - "models/Role.js", - "models/Token.js", - "models/User_Role.js", - "models/User.js" - ] -}