From 7f33e33078ec3b9d973ebc3dae78cb52142976b3 Mon Sep 17 00:00:00 2001 From: canove Date: Mon, 14 Sep 2020 18:54:36 -0300 Subject: [PATCH] finished user store in typscript --- backend/package.json | 1 + backend/src/config/auth.js | 4 -- backend/src/config/auth.ts | 4 ++ backend/src/controllers/SessionController.ts | 16 +++++ backend/src/controllers/UserController.ts | 71 ++++++++++--------- .../ContactController.js | 0 .../ImportPhoneContactsController.js | 0 .../MessageController.js | 0 .../OldSessionController.js} | 0 .../OldSettingController.js} | 0 .../OldUserController.js | 0 .../TicketController.js | 0 .../WhatsAppController.js | 0 .../WhatsAppSessionController.js | 0 backend/src/database/index.ts | 6 +- backend/src/errors/AppError.ts | 1 + backend/src/helpers/CheckSettingsHelper.ts | 6 +- backend/src/middleware/is-auth.js | 36 ---------- backend/src/middleware/isAuth.ts | 35 +++++++++ backend/src/models/Setting.ts | 26 +++++++ backend/src/models/User.ts | 14 ++-- backend/src/routes/authRoutes.ts | 11 +++ backend/src/routes/index.ts | 4 +- backend/src/routes/userRoutes.ts | 4 +- backend/src/server.ts | 14 +++- backend/src/services/AuthUserSerice.ts | 48 +++++++++++++ backend/src/services/CreateUserService.ts | 65 ++++++++++------- backend/src/services/FindUserService.ts | 20 +++--- backend/yarn.lock | 7 ++ 29 files changed, 260 insertions(+), 133 deletions(-) delete mode 100644 backend/src/config/auth.js create mode 100644 backend/src/config/auth.ts create mode 100644 backend/src/controllers/SessionController.ts rename backend/src/{controllers => controllersOld}/ContactController.js (100%) rename backend/src/{controllers => controllersOld}/ImportPhoneContactsController.js (100%) rename backend/src/{controllers => controllersOld}/MessageController.js (100%) rename backend/src/{controllers/SessionController.js => controllersOld/OldSessionController.js} (100%) rename backend/src/{controllers/SettingController.js => controllersOld/OldSettingController.js} (100%) rename backend/src/{controllers => controllersOld}/OldUserController.js (100%) rename backend/src/{controllers => controllersOld}/TicketController.js (100%) rename backend/src/{controllers => controllersOld}/WhatsAppController.js (100%) rename backend/src/{controllers => controllersOld}/WhatsAppSessionController.js (100%) delete mode 100644 backend/src/middleware/is-auth.js create mode 100644 backend/src/middleware/isAuth.ts create mode 100644 backend/src/models/Setting.ts create mode 100644 backend/src/routes/authRoutes.ts create mode 100644 backend/src/services/AuthUserSerice.ts diff --git a/backend/package.json b/backend/package.json index 46fec36..739d214 100644 --- a/backend/package.json +++ b/backend/package.json @@ -34,6 +34,7 @@ "@types/bluebird": "^3.5.32", "@types/cors": "^2.8.7", "@types/express": "^4.17.8", + "@types/jsonwebtoken": "^8.5.0", "@types/multer": "^1.4.4", "@types/node": "^14.10.1", "@types/validator": "^13.1.0", diff --git a/backend/src/config/auth.js b/backend/src/config/auth.js deleted file mode 100644 index 77c2494..0000000 --- a/backend/src/config/auth.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - secret: "mysecret", - expiresIn: "7d", -}; diff --git a/backend/src/config/auth.ts b/backend/src/config/auth.ts new file mode 100644 index 0000000..a0f3b9e --- /dev/null +++ b/backend/src/config/auth.ts @@ -0,0 +1,4 @@ +export default { + secret: "mysecret", + expiresIn: "7d" +}; diff --git a/backend/src/controllers/SessionController.ts b/backend/src/controllers/SessionController.ts new file mode 100644 index 0000000..73b3646 --- /dev/null +++ b/backend/src/controllers/SessionController.ts @@ -0,0 +1,16 @@ +import { Request, Response } from "express"; + +import AuthUserService from "../services/AuthUserSerice"; + +const store = async (req: Request, res: Response): Promise => { + const { email, password } = req.body; + + const { user, token } = await AuthUserService({ email, password }); + + return res.status(200).json({ + user, + token + }); +}; + +export default store; diff --git a/backend/src/controllers/UserController.ts b/backend/src/controllers/UserController.ts index 46d81dd..f0682c5 100644 --- a/backend/src/controllers/UserController.ts +++ b/backend/src/controllers/UserController.ts @@ -1,6 +1,6 @@ import { Request, Response } from "express"; -// import CheckSettingsHelper from "../helpers/CheckSettingsHelper"; +import CheckSettingsHelper from "../helpers/CheckSettingsHelper"; import AppError from "../errors/AppError"; import CreateUserService from "../services/CreateUserService"; @@ -9,30 +9,31 @@ import CreateUserService from "../services/CreateUserService"; // import FindUserService from "../services/FindUserService"; export const index = async (req: Request, res: Response): Promise => { - if (req.user.profile !== "admin") { - throw new AppError("Only administrators can access this route.", 403); // should be handled better. - } - const { searchParam, pageNumber } = req.query as any; + // if (req.user.profile !== "admin") { + // throw new AppError("Only administrators can access this route.", 403); // should be handled better. + // } + // const { searchParam, pageNumber } = req.query as any; - const { users, count, hasMore } = await ListUsersService({ - searchParam, - pageNumber - }); + // const { users, count, hasMore } = await ListUsersService({ + // searchParam, + // pageNumber + // }); - return res.json({ users, count, hasMore }); + // return res.json({ users, count, hasMore }); + return res.json({ ok: "ok" }); }; export const store = async (req: Request, res: Response): Promise => { const { email, password, name, profile } = req.body; - // if ( - // req.url === "/signup" && - // (await CheckSettingsHelper("userCreation")) === "disabled" - // ) { - // throw new AppError("User creation is disabled by administrator.", 403); - // } else if (req.user.profile !== "admin") { - // throw new AppError("Only administrators can create users.", 403); - // } + if ( + req.url === "/signup" && + (await CheckSettingsHelper("userCreation")) === "disabled" + ) { + throw new AppError("User creation is disabled by administrator.", 403); + } else if (req.url !== "/signup" && req.user.profile !== "admin") { + throw new AppError("Only administrators can create users.", 403); + } const user = await CreateUserService({ email, @@ -44,26 +45,26 @@ export const store = async (req: Request, res: Response): Promise => { return res.status(200).json(user); }; -export const show = async (req: Request, res: Response): Promise => { - const { userId } = req.params; +// export const show = async (req: Request, res: Response): Promise => { +// const { userId } = req.params; - const user = await FindUserService(userId); +// const user = await FindUserService(userId); - return res.status(200).json(user); -}; +// return res.status(200).json(user); +// }; -export const update = async ( - req: Request, - res: Response -): Promise => { - if (req.user.profile !== "admin") { - throw new AppError("Only administrators can edit users.", 403); - } +// export const update = async ( +// req: Request, +// res: Response +// ): Promise => { +// if (req.user.profile !== "admin") { +// throw new AppError("Only administrators can edit users.", 403); +// } - const { userId } = req.params; - const userData = req.body; +// const { userId } = req.params; +// const userData = req.body; - const user = await UpdateUserService({ userData, userId }); +// const user = await UpdateUserService({ userData, userId }); - return res.status(200).json(user); -}; +// return res.status(200).json(user); +// }; diff --git a/backend/src/controllers/ContactController.js b/backend/src/controllersOld/ContactController.js similarity index 100% rename from backend/src/controllers/ContactController.js rename to backend/src/controllersOld/ContactController.js diff --git a/backend/src/controllers/ImportPhoneContactsController.js b/backend/src/controllersOld/ImportPhoneContactsController.js similarity index 100% rename from backend/src/controllers/ImportPhoneContactsController.js rename to backend/src/controllersOld/ImportPhoneContactsController.js diff --git a/backend/src/controllers/MessageController.js b/backend/src/controllersOld/MessageController.js similarity index 100% rename from backend/src/controllers/MessageController.js rename to backend/src/controllersOld/MessageController.js diff --git a/backend/src/controllers/SessionController.js b/backend/src/controllersOld/OldSessionController.js similarity index 100% rename from backend/src/controllers/SessionController.js rename to backend/src/controllersOld/OldSessionController.js diff --git a/backend/src/controllers/SettingController.js b/backend/src/controllersOld/OldSettingController.js similarity index 100% rename from backend/src/controllers/SettingController.js rename to backend/src/controllersOld/OldSettingController.js diff --git a/backend/src/controllers/OldUserController.js b/backend/src/controllersOld/OldUserController.js similarity index 100% rename from backend/src/controllers/OldUserController.js rename to backend/src/controllersOld/OldUserController.js diff --git a/backend/src/controllers/TicketController.js b/backend/src/controllersOld/TicketController.js similarity index 100% rename from backend/src/controllers/TicketController.js rename to backend/src/controllersOld/TicketController.js diff --git a/backend/src/controllers/WhatsAppController.js b/backend/src/controllersOld/WhatsAppController.js similarity index 100% rename from backend/src/controllers/WhatsAppController.js rename to backend/src/controllersOld/WhatsAppController.js diff --git a/backend/src/controllers/WhatsAppSessionController.js b/backend/src/controllersOld/WhatsAppSessionController.js similarity index 100% rename from backend/src/controllers/WhatsAppSessionController.js rename to backend/src/controllersOld/WhatsAppSessionController.js diff --git a/backend/src/database/index.ts b/backend/src/database/index.ts index eb4b24a..60eb3dd 100644 --- a/backend/src/database/index.ts +++ b/backend/src/database/index.ts @@ -1,5 +1,6 @@ import { Sequelize } from "sequelize-typescript"; import User from "../models/User"; +import Setting from "../models/Setting"; // eslint-disable-next-line const dbConfig = require("../config/database"); @@ -10,18 +11,17 @@ const dbConfig = require("../config/database"); // const Message = require("../models/Message"); // const Whatsapp = require("../models/Whatsapp"); // const ContactCustomField = require("../models/ContactCustomField"); -// const Setting = require("../models/Setting"); const sequelize = new Sequelize(dbConfig); const models = [ - User + User, // Contact, // Ticket, // Message, // Whatsapp, // ContactCustomField, - // Setting, + Setting ]; sequelize.addModels(models); diff --git a/backend/src/errors/AppError.ts b/backend/src/errors/AppError.ts index 28e6c22..a8b1209 100644 --- a/backend/src/errors/AppError.ts +++ b/backend/src/errors/AppError.ts @@ -1,5 +1,6 @@ class AppError { public readonly message: string; + public readonly statusCode: number; constructor(message: string, statusCode = 400) { diff --git a/backend/src/helpers/CheckSettingsHelper.ts b/backend/src/helpers/CheckSettingsHelper.ts index 39e8a95..c398ea0 100644 --- a/backend/src/helpers/CheckSettingsHelper.ts +++ b/backend/src/helpers/CheckSettingsHelper.ts @@ -1,10 +1,8 @@ -import AppError from "../errors/AppError"; import Setting from "../models/Setting"; +import AppError from "../errors/AppError"; const CheckSettings = async (key: string): Promise => { - const settingsRepository = getRepository(Setting); - - const setting = await settingsRepository.findOne({ + const setting = await Setting.findOne({ where: { key } }); diff --git a/backend/src/middleware/is-auth.js b/backend/src/middleware/is-auth.js deleted file mode 100644 index 596d737..0000000 --- a/backend/src/middleware/is-auth.js +++ /dev/null @@ -1,36 +0,0 @@ -const jwt = require("jsonwebtoken"); -const util = require("util"); - -const User = require("../models/User"); -const authConfig = require("../config/auth"); - -module.exports = async (req, res, next) => { - const authHeader = req.headers.authorization; - - if (!authHeader) { - return res.status(401).json({ error: "Token not provided" }); - } - - const [, token] = authHeader.split(" "); - - try { - const decoded = await util.promisify(jwt.verify)(token, authConfig.secret); - - const user = await User.findByPk(decoded.userId, { - attributes: ["id", "name", "profile", "email"], - }); - - if (!user) { - return res - .status(401) - .json({ error: "The token corresponding user does not exists." }); - } - - req.user = user; - - return next(); - } catch (err) { - console.log(err); - return res.status(401).json({ error: "Invalid Token" }); - } -}; diff --git a/backend/src/middleware/isAuth.ts b/backend/src/middleware/isAuth.ts new file mode 100644 index 0000000..cc9d0be --- /dev/null +++ b/backend/src/middleware/isAuth.ts @@ -0,0 +1,35 @@ +import { verify } from "jsonwebtoken"; +import { Request, Response, NextFunction } from "express"; + +import AppError from "../errors/AppError"; +import authConfig from "../config/auth"; + +interface TokenPayload { + id: string; + username: string; + profile: string; + iat: number; + exp: number; +} + +const isAuth = (req: Request, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + throw new AppError("Token not provided.", 403); + } + + const [, token] = authHeader.split(" "); + + const decoded = verify(token, authConfig.secret); + const { id, profile } = decoded as TokenPayload; + + req.user = { + id, + profile + }; + + return next(); +}; + +export default isAuth; diff --git a/backend/src/models/Setting.ts b/backend/src/models/Setting.ts new file mode 100644 index 0000000..b58e57a --- /dev/null +++ b/backend/src/models/Setting.ts @@ -0,0 +1,26 @@ +import { + Table, + Column, + CreatedAt, + UpdatedAt, + Model, + PrimaryKey +} from "sequelize-typescript"; + +@Table +class Setting extends Model { + @PrimaryKey + @Column + key: string; + + @Column + value: string; + + @CreatedAt + createdAt: Date; + + @UpdatedAt + updatedAt: Date; +} + +export default Setting; diff --git a/backend/src/models/User.ts b/backend/src/models/User.ts index ad08c66..b61a6bf 100644 --- a/backend/src/models/User.ts +++ b/backend/src/models/User.ts @@ -15,7 +15,7 @@ class User extends Model { @Column name: string; - @Column + @Column(DataType.STRING) email: string; @Column(DataType.VIRTUAL) @@ -43,12 +43,12 @@ class User extends Model { } }; - // static checkPassword = async ( // maybe not work like this. - // instance: User, - // password: string - // ): Promise => { - // return compare(password, instance.passwordHash); - // }; + public checkPassword = async ( + // maybe not work like this. + password: string + ): Promise => { + return compare(password, this.getDataValue("passwordHash")); + }; } export default User; diff --git a/backend/src/routes/authRoutes.ts b/backend/src/routes/authRoutes.ts new file mode 100644 index 0000000..6f3b7da --- /dev/null +++ b/backend/src/routes/authRoutes.ts @@ -0,0 +1,11 @@ +import { Router } from "express"; +import SessionController from "../controllers/SessionController"; +import * as UserController from "../controllers/UserController"; + +const authRoutes = Router(); + +authRoutes.post("/signup", UserController.store); + +authRoutes.post("/login", SessionController); + +export default authRoutes; diff --git a/backend/src/routes/index.ts b/backend/src/routes/index.ts index 1dffd0a..2d7b26b 100644 --- a/backend/src/routes/index.ts +++ b/backend/src/routes/index.ts @@ -1,8 +1,8 @@ import { Router } from "express"; import userRoutes from "./userRoutes"; +import authRoutes from "./authRoutes"; -// const AuthRoutes = require("./routes/auth"); // const TicketsRoutes = require("./routes/tickets"); // const MessagesRoutes = require("./routes/messages"); // const ContactsRoutes = require("./routes/contacts"); @@ -13,7 +13,7 @@ import userRoutes from "./userRoutes"; const routes = Router(); routes.use(userRoutes); -// routes.use("/auth", AuthRoutes); +routes.use("/auth", authRoutes); // routes.use(TicketsRoutes); // routes.use(MessagesRoutes); // routes.use(ContactsRoutes); diff --git a/backend/src/routes/userRoutes.ts b/backend/src/routes/userRoutes.ts index e9526a0..a165db5 100644 --- a/backend/src/routes/userRoutes.ts +++ b/backend/src/routes/userRoutes.ts @@ -1,6 +1,6 @@ import { Router } from "express"; -// const isAuth = require("../../middleware/is-auth"); +import isAuth from "../middleware/isAuth"; import * as UserController from "../controllers/UserController"; const userRoutes = Router(); @@ -9,7 +9,7 @@ userRoutes.get("/users", (req, res) => res.json({ meessage: "lets do some prettier shit here" }) ); -userRoutes.post("/users", UserController.store); +userRoutes.post("/users", isAuth, UserController.store); // userRoutes.put("/users/:userId", isAuth, UserController.update); diff --git a/backend/src/server.ts b/backend/src/server.ts index 8dea37a..4914f24 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -1,7 +1,8 @@ import "dotenv/config"; import "express-async-errors"; -import express from "express"; +import express, { Request, Response, NextFunction } from "express"; import cors from "cors"; +import AppError from "./errors/AppError"; import routes from "./routes"; @@ -15,14 +16,21 @@ import "./database"; // const wbotMonitor = require("./services/wbotMonitor"); // const Whatsapp = require("./models/Whatsapp"); -// const Router = require("./router"); - const app = express(); app.use(cors()); app.use(express.json()); app.use(routes); +app.use(async (err: Error, req: Request, res: Response, next: NextFunction) => { + if (err instanceof AppError) { + return res.status(err.statusCode).json({ error: err.message }); + } + + console.error(err); + return res.status(500).json({ error: "Internal server error" }); +}); + const server = app.listen(process.env.PORT, () => { console.log(`Server started on port: ${process.env.PORT}`); }); diff --git a/backend/src/services/AuthUserSerice.ts b/backend/src/services/AuthUserSerice.ts new file mode 100644 index 0000000..7b12496 --- /dev/null +++ b/backend/src/services/AuthUserSerice.ts @@ -0,0 +1,48 @@ +import { sign } from "jsonwebtoken"; + +import User from "../models/User"; +import AppError from "../errors/AppError"; +import authConfig from "../config/auth"; + +interface Request { + email: string; + password: string; +} + +interface Response { + user: User; + token: string; +} + +const AuthUserService = async ({ + email, + password +}: Request): Promise => { + const user = await User.findOne({ + where: { email } + }); + + if (!user) { + throw new AppError("Incorrect user/password combination.", 401); + } + + if (!(await user.checkPassword(password))) { + throw new AppError("Incorrect user/password combination.", 401); + } + + const { secret, expiresIn } = authConfig; + const token = sign( + { usarname: user.name, profile: user.profile, id: user.id }, + secret, + { + expiresIn + } + ); + + return { + user, + token + }; +}; + +export default AuthUserService; diff --git a/backend/src/services/CreateUserService.ts b/backend/src/services/CreateUserService.ts index 65363bf..6c06673 100644 --- a/backend/src/services/CreateUserService.ts +++ b/backend/src/services/CreateUserService.ts @@ -10,44 +10,59 @@ interface Request { profile?: string; } +interface Response { + email: string; + name: string; + id: number; + profile: string; +} + const CreateUserService = async ({ email, password, name, profile = "admin" -}: Request): Promise => { - // const schema = Yup.object().shape({ - // name: Yup.string().required().min(2), - // email: Yup.string() - // .email() - // .required() - // .test( - // "Check-email", - // "An user with this email already exists.", - // async value => { - // const emailExists = await User.findOne({ - // where: { email: value } - // }); - // return !Boolean(emailExists); - // } - // ), - // password: Yup.string().required().min(5) - // }); +}: Request): Promise => { + const schema = Yup.object().shape({ + name: Yup.string().required().min(2), + email: Yup.string() + .email() + .required() + .test( + "Check-email", + "An user with this email already exists.", + async value => { + if (value) { + const emailExists = await User.findOne({ + where: { email: value } + }); + return !emailExists; + } + return false; + } + ), + password: Yup.string().required().min(5) + }); - // try { - // await schema.validate({ email, password, name }); - // } catch (err) { - // throw new AppError(err.message); - // } + try { + await schema.validate({ email, password, name }); + } catch (err) { + throw new AppError(err.message); + } - const user = User.create({ + const user = await User.create({ email, password, name, profile }); - return user; + return { + id: user.id, + name: user.name, + email: user.email, + profile: user.profile + }; }; export default CreateUserService; diff --git a/backend/src/services/FindUserService.ts b/backend/src/services/FindUserService.ts index 17ab763..2a5c350 100644 --- a/backend/src/services/FindUserService.ts +++ b/backend/src/services/FindUserService.ts @@ -1,21 +1,17 @@ -import { getRepository, Raw } from "typeorm"; - import User from "../models/User"; import AppError from "../errors/AppError"; const FindUserService = async (id: string): Promise => { - const usersRepository = getRepository(User); + const user = await User.findOne({ + where: { id }, + attributes: ["name", "id", "email", "profile"] + }); - const user = await usersRepository.findOne({ - where: { id }, - select: ["name", "id", "email", "profile"], - }); + if (!user) { + throw new AppError("No user found with this ID.", 404); + } - if (!user) { - throw new AppError("No user found with this ID.", 404); - } - - return user; + return user; }; export default FindUserService; diff --git a/backend/yarn.lock b/backend/yarn.lock index f19d8b9..b3bac1c 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -218,6 +218,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/jsonwebtoken@^8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#2531d5e300803aa63279b232c014acf780c981c5" + integrity sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg== + dependencies: + "@types/node" "*" + "@types/mime@*": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a"