diff --git a/.env-example b/.env-example index afc167f..79b99c1 100644 --- a/.env-example +++ b/.env-example @@ -1 +1,2 @@ SALT_ROUNDS = +SECRET = diff --git a/package-lock.json b/package-lock.json index b328267..bc5ed02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1526,6 +1526,11 @@ "node-int64": "^0.4.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2371,6 +2376,14 @@ "safer-buffer": "^2.1.0" } }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -4538,6 +4551,30 @@ "minimist": "^1.2.5" } }, + "jsonwebtoken": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", + "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", + "requires": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -4550,6 +4587,25 @@ "verror": "1.10.0" } }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -4650,6 +4706,41 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" + }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", diff --git a/package.json b/package.json index 0cf4eca..8cd9566 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "bcrypt": "^5.0.1", "dotenv": "^8.2.0", "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", "pg": "^8.5.1", "pg-hstore": "^2.3.3", "sequelize": "^6.5.0" diff --git a/src/controllers/SessionController.js b/src/controllers/SessionController.js new file mode 100644 index 0000000..1cf717a --- /dev/null +++ b/src/controllers/SessionController.js @@ -0,0 +1,27 @@ +import User from '../models/User.js'; +import bcrypt from 'bcrypt'; +import generateToken from '../services/auth.js'; + +export default { + async create(request, response) { + const { useremail, password } = request.body; + + try { + const user = await User.findOne({ + where: { + useremail, + }, + }); + + if (!user || !(await bcrypt.compare(password, user.password))) { + return response.status(404).json({ error: 'Usuário/Senha inválidos' }); + } + + const token = generateToken({ useremail }); + + return response.status(201).json(token); + } catch (error) { + return response.status(500).json({ error: error.message }); + } + }, +}; diff --git a/src/controllers/UserController.js b/src/controllers/UserController.js index 96a2803..73a3e05 100644 --- a/src/controllers/UserController.js +++ b/src/controllers/UserController.js @@ -1,5 +1,6 @@ import User from '../models/User.js'; import bcrypt from 'bcrypt'; +import generateToken from '../services/auth.js'; const saltRounds = process.env.SALT_ROUNDS; @@ -38,7 +39,9 @@ export default { longitude, }); - return response.status(201).json(user); + const token = generateToken({ useremail }); + + return response.status(201).json({ user, token }); } catch (error) { return response.status(500).json({ error: error.message }); } diff --git a/src/middlewares/auth.js b/src/middlewares/auth.js new file mode 100644 index 0000000..f1da665 --- /dev/null +++ b/src/middlewares/auth.js @@ -0,0 +1,32 @@ +import jwt from 'jsonwebtoken'; + +function verifyToken(request, response, next) { + const authHeader = request.headers.authorization; + + if (!authHeader) { + return response.status(400).json({ error: 'Token não encontrado' }); + } + + const parts = authHeader.split(' '); + + if (!parts.lenght == 2) { + return response.status(401).json({ error: 'Erro no token' }); + } + + const [scheme, token] = parts; + + if (!/^Bearer$/i.test(scheme)) { + return response.status(401).json({ error: 'Token mal formatado' }); + } + + jwt.verify(token, process.env.SECRET, (err, decoded) => { + if (err) { + return response.status(401).json({ error: 'Token inválido' }); + } + + request.useremail = decoded.useremail; + return next(); + }); +} + +export default verifyToken; diff --git a/src/routes.js b/src/routes.js index df2b6b1..3c74c66 100644 --- a/src/routes.js +++ b/src/routes.js @@ -1,9 +1,11 @@ import { Router } from 'express'; import userRouter from './routes/user.routes.js'; +import sessionRouter from './routes/session.routes.js'; const routes = Router(); routes.use('/users', userRouter); +routes.use('/session', sessionRouter); export default routes; diff --git a/src/routes/session.routes.js b/src/routes/session.routes.js new file mode 100644 index 0000000..cac88fe --- /dev/null +++ b/src/routes/session.routes.js @@ -0,0 +1,9 @@ +import { Router } from 'express'; + +import SessionController from '../controllers/SessionController.js'; + +const sessionRouter = Router(); + +sessionRouter.post('/', SessionController.create); + +export default sessionRouter; diff --git a/src/services/auth.js b/src/services/auth.js new file mode 100644 index 0000000..702da69 --- /dev/null +++ b/src/services/auth.js @@ -0,0 +1,9 @@ +import jwt from 'jsonwebtoken'; + +function generateToken(params = {}) { + return jwt.sign(params, process.env.SECRET, { + expiresIn: 86400, + }); +} + +export default generateToken;