diff --git a/modulo7/introducao-autenticacao/.gitignore b/modulo7/introducao-autenticacao/.gitignore new file mode 100644 index 0000000..eb337e0 --- /dev/null +++ b/modulo7/introducao-autenticacao/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json + build + .env diff --git a/modulo7/introducao-autenticacao/package.json b/modulo7/introducao-autenticacao/package.json new file mode 100644 index 0000000..6e8daed --- /dev/null +++ b/modulo7/introducao-autenticacao/package.json @@ -0,0 +1,31 @@ +{ + "name": "to-do-list", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "tsc && node --inspect ./build/index.js", + "dev": "tsnd --transpile-only --ignore-watch node_modules ./src/index.ts", + "test": "ts-node-dev ./src/services/authenticator.ts" + }, + "author": "Labenu", + "license": "ISC", + "dependencies": { + "@types/jsonwebtoken": "^8.5.8", + "@types/uuid": "^8.3.4", + "cors": "^2.8.5", + "dotenv": "^8.2.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "knex": "^0.21.5", + "mysql": "^2.18.1", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/cors": "^2.8.8", + "@types/express": "^4.17.8", + "@types/knex": "^0.16.1", + "@types/node": "^14.11.2", + "ts-node-dev": "^1.0.0-pre.63", + "typescript": "4.6.4" + } +} diff --git a/modulo7/introducao-autenticacao/requests.rest b/modulo7/introducao-autenticacao/requests.rest new file mode 100644 index 0000000..fa18261 --- /dev/null +++ b/modulo7/introducao-autenticacao/requests.rest @@ -0,0 +1,32 @@ + +POST http://localhost:3003/user/signup +Content-Type: application/json + +{ + "name": "Locke", + "nickname": "barrigudinho", + "email": "locke@gmail.com" , + "password": "156374" +} + +### + +POST http://localhost:3003/user/login +Content-Type: application/json + +{ + + "email": "locke@gmail.com" , + "password": "156374" +} + +### + +PUT http://localhost:3003/user/edit/ +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjdlYTk2Yzg1LTZlNDItNGU2Ny04MmYwLWUwMmVmMzUyM2M3YiIsImlhdCI6MTY2MDYwNjU5NywiZXhwIjoxNjYwNjEwMTk3fQ._v-3r5TVEx4WwRQIu6fFDV-l7uGyN-MOSvMToHL-IPA +Content-Type: application/json + +{ + "name": "Lolo", + "nickname": "lambelambe" +} \ No newline at end of file diff --git a/modulo7/introducao-autenticacao/src/business/UserBusiness.ts b/modulo7/introducao-autenticacao/src/business/UserBusiness.ts new file mode 100644 index 0000000..67faed4 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/business/UserBusiness.ts @@ -0,0 +1,107 @@ +import { UserDatabase } from "../data/UserDatabase"; +import {CustomError,InvalidEmail,InvalidName,InvalidPassword,UserNotFound,} from "../error/customError"; +import {UserInputDTO,user,EditUserInputDTO,EditUserInput,} from "../model/user"; +import { Authenticator } from "../services/Authenticator"; +import { IdGenerator } from "../services/IdGenerator"; + +const idGenerator = new IdGenerator(); +const authenticator = new Authenticator(); + +export class UserBusiness { + public signup = async (input: UserInputDTO): Promise => { + try { + const { name, nickname, email, password } = input; + + if (!name || !nickname || !email || !password) { + throw new CustomError( + 400, + 'Preencha os campos "name","nickname", "email" e "password"' + ); + } + + if (name.length < 4) { + throw new InvalidName(); + } + + if (!email.includes("@")) { + throw new InvalidEmail(); + } + + const id: string = idGenerator.generateId(); + + const user: user = { + id, + name, + nickname, + email, + password, + }; + const userDatabase = new UserDatabase(); + await userDatabase.insertUser(user); + + const token = authenticator.generateToken({ id }); + return token; + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; + + public login = async (input: any): Promise => { + try { + const { email, password } = input; + + if (!email || !password) { + throw new CustomError(400, 'Preencha os campos "email" e "password"'); + } + + if (!email.includes("@")) { + throw new InvalidEmail(); + } + const userDatabase = new UserDatabase(); + const user = await userDatabase.findUserByEmail(email); + + if (!user) { + throw new UserNotFound(); + } + + if (user.password !== password) { + throw new InvalidPassword(); + } + + const id = user.id + const token = authenticator.generateToken({ id}); + return token; + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; + + public editUser = async (input: EditUserInputDTO) => { + try { + const { name, nickname, token } = input; + + if (!name || !nickname) { + throw new CustomError( + 400, + 'Preencha os campos , "name" e "nickname"' + ); + } + const {id} = authenticator.getTokenData(token) + + if (name.length < 4) { + throw new InvalidName(); + } + + const editUserInput: EditUserInput = { + id, + name, + nickname, + }; + + const userDatabase = new UserDatabase(); + await userDatabase.editUser(editUserInput); + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; +} diff --git a/modulo7/introducao-autenticacao/src/controller/UserController.ts b/modulo7/introducao-autenticacao/src/controller/UserController.ts new file mode 100644 index 0000000..a2c19ea --- /dev/null +++ b/modulo7/introducao-autenticacao/src/controller/UserController.ts @@ -0,0 +1,65 @@ +import { Request, Response } from "express"; +import { UserBusiness } from "../business/UserBusiness"; +import { EditUserInputDTO, LoginInputDTO, UserInputDTO } from "../model/user"; + +export class UserController { + + public signup = async (req: Request, res: Response) => { + try { + const { name, nickname, email, password } = req.body; + + const input: UserInputDTO = { + name, + nickname, + email, + password, + }; + const userBusiness = new UserBusiness() + const token = await userBusiness.signup(input); + + res.status(201).send({ message: "Usuário criado!", token }); + } catch (error: any) { + res.status(400).send(error.message); + } + }; + + public login = async (req: Request, res: Response) => { + try { + const { email, password } = req.body; + + const input: LoginInputDTO = { + email, + password, + }; + + const userBusiness = new UserBusiness() + const token = await userBusiness.login(input); + + res.status(200).send({token}); + } catch (error: any) { + res.status(400).send(error.message); + } + }; + + public editUser = async (req: Request, res: Response) => { + try { + + const input: EditUserInputDTO = { + name: req.body.name, + nickname: req.body.nickname, + token: req.headers.authorization as string + }; + + const userBusiness = new UserBusiness() + await userBusiness.editUser(input); + + res.status(201).send({ message: "Usuário alterado!" }); + } catch (error: any) { + res.status(400).send(error.message); + } + }; + + + + +} diff --git a/modulo7/introducao-autenticacao/src/controller/app.ts b/modulo7/introducao-autenticacao/src/controller/app.ts new file mode 100644 index 0000000..32e736f --- /dev/null +++ b/modulo7/introducao-autenticacao/src/controller/app.ts @@ -0,0 +1,13 @@ +import express from 'express' +import cors from 'cors' + +const app = express() + +app.use(express.json()) +app.use(cors()) + +app.listen(3003, ()=>{ + console.log('Servidor rodando na porta 3003') +}) + +export default app \ No newline at end of file diff --git a/modulo7/introducao-autenticacao/src/controller/userRouter.ts b/modulo7/introducao-autenticacao/src/controller/userRouter.ts new file mode 100644 index 0000000..a225cb0 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/controller/userRouter.ts @@ -0,0 +1,12 @@ +import express from "express"; + +import { UserController } from "../controller/UserController"; + +export const userRouter = express.Router() + +const userController = new UserController() + +userRouter.post('/signup', userController.signup) +userRouter.post('/login', userController.login) + +userRouter.put('/edit',userController.editUser ) diff --git a/modulo7/introducao-autenticacao/src/data/BaseDatabase.ts b/modulo7/introducao-autenticacao/src/data/BaseDatabase.ts new file mode 100644 index 0000000..c1578b7 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/data/BaseDatabase.ts @@ -0,0 +1,21 @@ +import knex from 'knex' +import dotenv from 'dotenv' + +dotenv.config() + +export class BaseDatabase { + + protected static connection = knex({ + client: 'mysql', + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_SCHEMA, + port: 3306, + multipleStatements: true + } + }) + +} + diff --git a/modulo7/introducao-autenticacao/src/data/UserDatabase.ts b/modulo7/introducao-autenticacao/src/data/UserDatabase.ts new file mode 100644 index 0000000..ec68130 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/data/UserDatabase.ts @@ -0,0 +1,44 @@ +import { CustomError } from "../error/customError"; +import { EditUserInput, user } from "../model/user"; +import { BaseDatabase } from "./BaseDatabase"; + +export class UserDatabase extends BaseDatabase { + public insertUser = async (user: user) => { + try { + await UserDatabase.connection + .insert({ + id: user.id, + name: user.name, + nickname: user.nickname, + email: user.email, + password: user.password, + }) + .into("Auth_users"); + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; + + public editUser = async (user: EditUserInput) => { + try { + await UserDatabase.connection + .update({ name: user.name, nickname: user.nickname }) + .where({ id: user.id }) + .into("Auth_users"); + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; + + public findUserByEmail = async (email: string): Promise => { + try { + const result = await UserDatabase.connection("Auth_users") + .select() + .where({ email }); + + return result[0]; + } catch (error: any) { + throw new CustomError(400, error.message); + } + }; +} diff --git a/modulo7/introducao-autenticacao/src/error/customError.ts b/modulo7/introducao-autenticacao/src/error/customError.ts new file mode 100644 index 0000000..370d3e1 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/error/customError.ts @@ -0,0 +1,35 @@ +export class CustomError extends Error { + constructor(statusCode: number, message: string){ + super(message) + } +} + +export class InvalidName extends CustomError{ + constructor(){ + super(400, "Nome inválido") + } +} + +export class InvalidEmail extends CustomError{ + constructor(){ + super(400, "Email inválido") + } +} + +export class InvalidPassword extends CustomError{ + constructor(){ + super(400, "Senha inválida") + } +} + +export class UserNotFound extends CustomError{ + constructor(){ + super(404, "Usuário não encontrado") + } +} + +export class Unauthorized extends CustomError{ + constructor(){ + super(401, "Usuário não não authorizado, faça login novamente") + } +} \ No newline at end of file diff --git a/modulo7/introducao-autenticacao/src/index.ts b/modulo7/introducao-autenticacao/src/index.ts new file mode 100644 index 0000000..aa04b7d --- /dev/null +++ b/modulo7/introducao-autenticacao/src/index.ts @@ -0,0 +1,5 @@ +import app from "./controller/app" +import { userRouter } from "./controller/userRouter" + + +app.use('/user/', userRouter) diff --git a/modulo7/introducao-autenticacao/src/model/user.ts b/modulo7/introducao-autenticacao/src/model/user.ts new file mode 100644 index 0000000..35ca925 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/model/user.ts @@ -0,0 +1,31 @@ +export type user = { + id: string + email: string + password: string + name: string + nickname: string +} + +export interface UserInputDTO { + name: string, + nickname: string, + email: string, + password: string +} + +export interface EditUserInputDTO { + name: string, + nickname: string, + token: string +} + +export interface EditUserInput { + name: string, + nickname: string, + id: string +} + +export interface LoginInputDTO { + email: string, + password: string +} \ No newline at end of file diff --git a/modulo7/introducao-autenticacao/src/services/Authenticator.ts b/modulo7/introducao-autenticacao/src/services/Authenticator.ts new file mode 100644 index 0000000..4be57d9 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/services/Authenticator.ts @@ -0,0 +1,29 @@ +import * as jwt from "jsonwebtoken"; +import { Unauthorized } from "../error/customError"; + +export type AuthenticationData = { + id: string, +}; + +export class Authenticator { + generateToken = ({ id }: AuthenticationData): string => { + const token = jwt.sign({ id }, process.env.JWT_KEY as string, { + expiresIn: process.env.JWT_DURATION, + }); + return token; + }; + + getTokenData = (token: string): AuthenticationData => { + try { + const payload = jwt.verify( + token, + process.env.JWT_KEY as string + ) as AuthenticationData; + return payload; + } catch (error: any) { + console.log(error.message) + throw new Unauthorized() + } + + }; +} diff --git a/modulo7/introducao-autenticacao/src/services/IdGenerator.ts b/modulo7/introducao-autenticacao/src/services/IdGenerator.ts new file mode 100644 index 0000000..7574c54 --- /dev/null +++ b/modulo7/introducao-autenticacao/src/services/IdGenerator.ts @@ -0,0 +1,8 @@ +import { v4 } from "uuid" + + +export class IdGenerator { + generateId = () => { + return v4() + } +} \ No newline at end of file diff --git a/modulo7/introducao-autenticacao/tables.sql b/modulo7/introducao-autenticacao/tables.sql new file mode 100644 index 0000000..6787d50 --- /dev/null +++ b/modulo7/introducao-autenticacao/tables.sql @@ -0,0 +1,27 @@ + +CREATE TABLE IF NOT EXISTS Auth_users ( + id VARCHAR(64) PRIMARY KEY, + name VARCHAR(64) NOT NULL, + nickname VARCHAR(64) NOT NULL, + email VARCHAR(64) NOT NULL, + password VARCHAR(64) NOT NULL +); + +CREATE TABLE IF NOT EXISTS Auth_tasks ( + id VARCHAR(64) PRIMARY KEY, + title VARCHAR(64) NOT NULL, + description VARCHAR(1024) DEFAULT "No description provided", + deadline DATE, + status ENUM("TO_DO", "DOING", "DONE") DEFAULT "TO_DO", + author_id VARCHAR(64), + FOREIGN KEY (author_id) REFERENCES Auth_users(id) +); + +CREATE TABLE IF NOT EXISTS Auth_assignees ( + task_id VARCHAR(64), + assignee_id VARCHAR(64), + PRIMARY KEY (task_id, assignee_id), + FOREIGN KEY (task_id) REFERENCES Auth_tasks(id), + FOREIGN KEY (assignee_id) REFERENCES Auth_users(id) +); + diff --git a/modulo7/introducao-autenticacao/tsconfig.json b/modulo7/introducao-autenticacao/tsconfig.json new file mode 100644 index 0000000..8c020cc --- /dev/null +++ b/modulo7/introducao-autenticacao/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "outDir": "./build", + "rootDir": "./src", + "removeComments": true, + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } +} \ No newline at end of file