diff --git a/semana19/semana19-revisao/.gitignore b/semana19/semana19-revisao/.gitignore new file mode 100644 index 0000000..8ece3ba --- /dev/null +++ b/semana19/semana19-revisao/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +build +.env \ No newline at end of file diff --git a/semana19/semana19-revisao/package.json b/semana19/semana19-revisao/package.json new file mode 100644 index 0000000..64a3aed --- /dev/null +++ b/semana19/semana19-revisao/package.json @@ -0,0 +1,34 @@ +{ + "name": "semana19-revisao", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "ts-node-dev . /src/index.ts", + "start": "tsc && node . /build/index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/knex": "^0.16.1", + "@types/uuid": "^8.3.3", + "ts-node-dev": "^1.1.8", + "typescript": "^4.5.4" + }, + "dependencies": { + "@types/jsonwebtoken": "^8.5.6", + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^10.0.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "knex": "^0.95.14", + "mysql": "^2.18.1", + "uuid": "^8.3.2" + } +} diff --git a/semana19/semana19-revisao/src/app.ts b/semana19/semana19-revisao/src/app.ts new file mode 100644 index 0000000..f8d4335 --- /dev/null +++ b/semana19/semana19-revisao/src/app.ts @@ -0,0 +1,19 @@ +import express, { Express } from "express"; +import cors from "cors"; +import { AddressInfo } from "net"; +import dotenv from "dotenv"; + +dotenv.config(); + +export const app: Express = express(); +app.use(express.json()); +app.use(cors()); + +const server = app.listen(process.env.PORT || 3003, () => { + if (server) { + const address = server.address() as AddressInfo; + console.log(`Server is running in http://localhost: ${address.port}`); + } else { + console.error(`Failure upon starting server.`); + } +}); diff --git a/semana19/semana19-revisao/src/data/BaseDatabase.ts b/semana19/semana19-revisao/src/data/BaseDatabase.ts new file mode 100644 index 0000000..56a4f9b --- /dev/null +++ b/semana19/semana19-revisao/src/data/BaseDatabase.ts @@ -0,0 +1,15 @@ +import knex from "knex"; + +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/semana19/semana19-revisao/src/data/UserDatabase.ts b/semana19/semana19-revisao/src/data/UserDatabase.ts new file mode 100644 index 0000000..059a328 --- /dev/null +++ b/semana19/semana19-revisao/src/data/UserDatabase.ts @@ -0,0 +1,45 @@ +import { BaseDatabase } from "./BaseDatabase"; +import { User } from "../types/User"; + +export class UserDatabase extends BaseDatabase { + async createUser(user: User) { + try { + await BaseDatabase.connection("lbn_user").insert({ + id: user.getId(), + name: user.getName(), + email: user.getEmail(), + password: user.getPassword(), + role: user.getRole(), + }); + } catch (error) { + throw new Error((error as any).sqlMessage || (error as any).message); + } + } + + async findUserByEmail(email: string): Promise { + try { + const user = await BaseDatabase.connection("lbn_user") + .select("*") + .where({ email }); + return user[0] && User.toUserModel(user[0]); + //A função toUserModel recebe um usuário e cria uma nova instância do usuário através das informações que vêm do banco de dados + } catch (error) { + throw new Error((error as any).sqlMessage || (error as any).message); + } + } + + async getAllUsers(): Promise { + try { + const users = await BaseDatabase.connection("lbn_user").select( + "id", + "name", + "email", + "role" + ); + return users.map((user) => User.toUserModel(user)); + //Dou um map e para cada usuário que estiver dentro do meu array, eu quero retornar um User.ToUserModel + } catch (error) { + throw new Error((error as any).sqlMessage || (error as any).message); + } + } +} diff --git a/semana19/semana19-revisao/src/endpoints/getAllCharacters.ts b/semana19/semana19-revisao/src/endpoints/getAllCharacters.ts new file mode 100644 index 0000000..cda0a94 --- /dev/null +++ b/semana19/semana19-revisao/src/endpoints/getAllCharacters.ts @@ -0,0 +1,31 @@ +import { Request, Response } from "express"; +import { Authenticator } from "../services/Authenticator"; +import { UserDatabase } from "../data/UserDatabase"; + +export async function getAllCharacters(req: Request, res: Response) { + try { + const token = req.headers.authorization; + + if (!token) { + res + .status(422) + .send("Esse endpoint exige uma autorização a ser passada nos headers"); + } + + const authenticator = new Authenticator(); + const tokenData = authenticator.getTokenData(token as string); + + if (tokenData.role !== "ADMIN") { + res + .status(401) + .send("Somente administradores podem acessar essa funcionalidade"); + } + + const userDatabase = new UserDatabase(); + const users = await userDatabase.getAllUsers(); + + res.status(200).send(users); + } catch (error) { + res.status(400).send((error as any).message); + } +} diff --git a/semana19/semana19-revisao/src/endpoints/login.ts b/semana19/semana19-revisao/src/endpoints/login.ts new file mode 100644 index 0000000..ad92e83 --- /dev/null +++ b/semana19/semana19-revisao/src/endpoints/login.ts @@ -0,0 +1,43 @@ +import { Request, Response } from "express"; +import { UserDatabase } from "../data/UserDatabase"; +import { HashManager } from "../services/HashManager"; +import { Authenticator } from "../services/Authenticator"; + +export async function login(req: Request, res: Response) { + try { + const { email, password } = req.body; + + if (!email || !password) { + res + .status(422) + .send( + "Insira corretamente as informações de 'name', 'email', 'password' e 'role'" + ); + } + + const userDatabase = new UserDatabase(); + const user = await userDatabase.findUserByEmail(email); + + if (!user) { + res.status(404).send("Esse email não está cadastrado!"); + } + + const hashManager = new HashManager(); + const passwordIsCorrect = hashManager.compare(password, user.getPassword()); + //A função irá comparar a senha que o usuário enviou com a senha "hasheada" + + if (!passwordIsCorrect) { + res.status(401).send("Email ou senha incorretos."); + } + + const authenticator = new Authenticator(); + const token = authenticator.generate({ + id: user.getId(), + role: user.getRole(), + }); + + res.status(200).send({ message: "Usuário logado com sucesso", token }); + } catch (error) { + res.status(400).send((error as Error).message); + } +} diff --git a/semana19/semana19-revisao/src/endpoints/signup.ts b/semana19/semana19-revisao/src/endpoints/signup.ts new file mode 100644 index 0000000..92c82ba --- /dev/null +++ b/semana19/semana19-revisao/src/endpoints/signup.ts @@ -0,0 +1,43 @@ +import { Request, Response } from "express"; +import { IdGenerator } from "../services/IdGenerator"; +import { UserDatabase } from "../data/UserDatabase"; +import { HashManager } from "../services/HashManager"; +import { User } from "../types/User"; +import { Authenticator } from "../services/Authenticator"; + +export async function signup(req: Request, res: Response) { + try { + const { name, email, password, role } = req.body; + + if (!name || !email || !password || !role) { + res + .status(422) + .send( + "Insira corretamente as informações de 'name', 'email', 'password' e 'role'" + ); + } + + const userDatabase = new UserDatabase(); + const user = await userDatabase.findUserByEmail(email); + + if (user as any) { + res.status(409).send("Esse email já está cadastrado!"); + } + + const idGenerator = new IdGenerator(); + const id = idGenerator.generate(); + + const hashManager = new HashManager(); + const hashPassword = await hashManager.hash(password); + + const newUser = new User(id, name, email, hashPassword, role); + await userDatabase.createUser(newUser); + + const authenticator = new Authenticator(); + const token = authenticator.generate({ id, role }); + + res.status(200).send({ message: "Usuário criado com sucesso", token }); + } catch (error) { + res.status(400).send((error as Error).message); + } +} diff --git a/semana19/semana19-revisao/src/index.ts b/semana19/semana19-revisao/src/index.ts new file mode 100644 index 0000000..b2ba4ff --- /dev/null +++ b/semana19/semana19-revisao/src/index.ts @@ -0,0 +1,8 @@ +import { app } from "./app"; +import { signup } from "./endpoints/signup"; +import { login } from "./endpoints/login"; +import { getAllCharacters } from "./endpoints/getAllCharacters"; + +app.get("/user", getAllCharacters); +app.post("/user", signup); +app.post("/user/login", login); diff --git a/semana19/semana19-revisao/src/services/Authenticator.ts b/semana19/semana19-revisao/src/services/Authenticator.ts new file mode 100644 index 0000000..3573eb1 --- /dev/null +++ b/semana19/semana19-revisao/src/services/Authenticator.ts @@ -0,0 +1,22 @@ +import * as jwt from "jsonwebtoken"; +import { USER_ROLES } from "../types/User"; + +export interface AuthenticationData { + id: string; + role: USER_ROLES; +} + +export class Authenticator { + generate(input: AuthenticationData): string { + const token = jwt.sign(input, process.env.JWT_KEY as any, { + expiresIn: process.env.ACCESS_TOKEN_EXPIRES_IN, + }); + + return token; + } + + getTokenData(token: string): AuthenticationData { + const data = jwt.verify(token, process.env.JWT_KEY as any); + return data as AuthenticationData; + } +} diff --git a/semana19/semana19-revisao/src/services/HashManager.ts b/semana19/semana19-revisao/src/services/HashManager.ts new file mode 100644 index 0000000..c49abc1 --- /dev/null +++ b/semana19/semana19-revisao/src/services/HashManager.ts @@ -0,0 +1,15 @@ +import * as bcrypt from "bcryptjs"; + +export class HashManager { + async hash(text: string): Promise { + const rounds = Number(process.env.BCRYPT_COST); + const salt = await bcrypt.genSalt(rounds); + return bcrypt.hash(text, salt); + } + + async compare(text: string, hash: string): Promise { + return bcrypt.compare(text, hash); + } +} + +//OBS: O que veio antes do async é o método ex: async hash e async compare diff --git a/semana19/semana19-revisao/src/services/IdGenerator.ts b/semana19/semana19-revisao/src/services/IdGenerator.ts new file mode 100644 index 0000000..18fc6c9 --- /dev/null +++ b/semana19/semana19-revisao/src/services/IdGenerator.ts @@ -0,0 +1,5 @@ +import { v4 } from "uuid"; + +export class IdGenerator { + generate = (): string => v4(); +} diff --git a/semana19/semana19-revisao/src/types/User.ts b/semana19/semana19-revisao/src/types/User.ts new file mode 100644 index 0000000..03f5e85 --- /dev/null +++ b/semana19/semana19-revisao/src/types/User.ts @@ -0,0 +1,33 @@ +export enum USER_ROLES { + NORMAL = "NORMAL", + ADMIN = "ADMIN", +} + +export class User { + constructor( + private id: string, + private name: string, + private email: string, + private password: string, + private role: USER_ROLES + ) {} + public getId() { + return this.id; + } + public getName() { + return this.name; + } + public getEmail() { + return this.email; + } + public getPassword() { + return this.password; + } + public getRole() { + return this.role; + } + + static toUserModel(data: any): User { + return new User(data.id, data.name, data.email, data.password, data.role); + } +} diff --git a/semana19/semana19-revisao/tsconfig.json b/semana19/semana19-revisao/tsconfig.json new file mode 100644 index 0000000..4c7ff71 --- /dev/null +++ b/semana19/semana19-revisao/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs" /* Specify what module code is generated. */, + "rootDir": "./src" /* Specify the root folder within your source files. */, + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true /* Create source map files for emitted JavaScript files. */, + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./build" /* Specify an output folder for all emitted files. */, + "removeComments": true /* Disable emitting comments. */, + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied `any` type.. */, + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}