diff --git a/.dockerignore b/.dockerignore index 7590b84..cb8967a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,4 @@ !app !bootstrap !config +!services diff --git a/.env.example b/.env.example index 145b9f1..94f3dc4 100644 --- a/.env.example +++ b/.env.example @@ -11,3 +11,9 @@ DB_PORT=27017 DB_NAME= DB_USERNAME=arrecadar DB_PASSWORD=secret + +MAIL_PORT= +MAIL_HOST= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_SENDER= diff --git a/CHANGELOG.md b/CHANGELOG.md index 182aa42..7b36e82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,6 @@ # CHANGE LOG ## Unreleased -- [ ] Auth: add token refresh -- [ ] Register: add email validations -- [ ] Resources: add paginations -- [ ] Resources: index routes return `error not found` when needs to return `{ data: [] }` ### Add - [x] Users. @@ -15,9 +11,9 @@ - [x] Login with email and password. - [x] JWT authorization. - [ ] Token Refresh -- [ ] Register - - When register send an email confirmation. -- [ ] resources: add pagination. +- [x] Register + - [x] When register send an email confirmation. +- [ ] Resources: add pagination. - [x] Added public routes - [x] GET /ongs - [x] GET /ongs/:id diff --git a/Dockerfile b/Dockerfile index b7c4882..8b35d2a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ COPY ./server.js /api/server.js COPY ./app /api/app COPY ./bootstrap /api/bootstrap COPY ./config /api/config +COPY ./services /api/services WORKDIR /api diff --git a/app/Authentication/controller.js b/app/Authentication/controller.js index 3ab60a2..d85b477 100644 --- a/app/Authentication/controller.js +++ b/app/Authentication/controller.js @@ -1,5 +1,8 @@ const passport = require('passport') -const { handleUnauthorized } = require('../Responses') +const Token = require('./token') +const trasnformUser = require('../Transformers/user') +const UserRepository = require('../Users/repository') +const { handleUnauthorized, handleResponse } = require('../Responses') const login = (request, response) => ( passport.authenticate('local', (error, user) => { @@ -13,6 +16,16 @@ const login = (request, response) => ( })(request, response) ) +const confirmation = (request, response) => { + const { user } = Token.decode(request.query.token, ['user']) + + UserRepository + .save(user, { activated: true }) + .then(trasnformUser.withToken) + .then(handleResponse(response)) +} + module.exports = { - login + login, + confirmation } diff --git a/app/Authentication/routes.js b/app/Authentication/routes.js index 280bfb6..3069a65 100644 --- a/app/Authentication/routes.js +++ b/app/Authentication/routes.js @@ -2,5 +2,6 @@ const Router = require('express').Router() const controller = require('./controller') Router.post('/login', controller.login) +Router.get('/confirmation', controller.confirmation) module.exports = Router diff --git a/app/Authentication/token.js b/app/Authentication/token.js new file mode 100644 index 0000000..2bb747d --- /dev/null +++ b/app/Authentication/token.js @@ -0,0 +1,14 @@ +const jwt = require('jsonwebtoken') +const { key } = require('../../config/app') + +const create = (data, expiresIn) => jwt.sign(data, key, { + expiresIn, + algorithm: 'HS256' +}) + +const decode = token => jwt.decode(token) + +module.exports = { + create: create, + decode: decode +} diff --git a/app/Organizations/routes.js b/app/Organizations/routes.js index 4823211..fa27e69 100644 --- a/app/Organizations/routes.js +++ b/app/Organizations/routes.js @@ -4,7 +4,7 @@ const { authorizeWithJwt } = require('../Authentication/middleware') const controller = require('./controller') const request = require('./request') -Router.get('/', authorizeWithJwt, controller.index) +Router.get('/', controller.index) Router.get('/:id', controller.show) Router.put('/:id', authorizeWithJwt, controller.save) Router.post('/', authorizeWithJwt, request, controller.store) diff --git a/app/Responses.js b/app/Responses.js index 9c2327b..6c47aed 100644 --- a/app/Responses.js +++ b/app/Responses.js @@ -23,7 +23,7 @@ const handleResponse = response => result => const handleError = response => error => error.kind === 'ObjectId' ? notFound(response) - : responseWithInternalServerError(response) + : responseWithInternalServerError(response)(error) const handleUnauthorized = response => ( response.status(401).json({ diff --git a/app/Transformers/user.js b/app/Transformers/user.js new file mode 100644 index 0000000..b9a5cfb --- /dev/null +++ b/app/Transformers/user.js @@ -0,0 +1,30 @@ +const { applySpec, prop } = require('ramda') +const Token = require('../Authentication/token') + +const createUsertToken = ({ id }) => Token.create({ user: id }, '15min') + +const withToken = applySpec({ + data: { + id: prop('id'), + name: prop('name'), + email: prop('email'), + token: createUsertToken, + created_at: prop('created_at'), + updated_at: prop('updated_at') + } +}) + +const transform = applySpec({ + data: { + id: prop('id'), + name: prop('name'), + email: prop('email'), + created_at: prop('created_at'), + updated_at: prop('updated_at') + } +}) + +module.exports = { + withToken, + transform +} diff --git a/app/Users/controller.js b/app/Users/controller.js index fbd0e1f..05cb69b 100644 --- a/app/Users/controller.js +++ b/app/Users/controller.js @@ -1,10 +1,9 @@ -const { - Controller, - Transform -} = require('@anarklab/expressive-controller') +const { Controller, Transform } = require('@anarklab/expressive-controller') +const Repository = require('./repository') +const SendEmail = require('../../services/mail/send') +const { transform: transformUser } = require('../Transformers/user') const { handleError, handleResponse } = require('../Responses') -const Repository = require('./repository') const controller = Controller(Repository) const show = (request, response) => { @@ -17,7 +16,20 @@ const show = (request, response) => { .catch(handleError(response)) } +const store = (request, response) => { + Repository + .create(request.body) + .then(user => { + SendEmail.confirmation(user) + return user + }) + .then(transformUser) + .then(handleResponse(response)) + .catch(error => handleError(response)([error.message])) +} + module.exports = { ...controller, - show + show, + store } diff --git a/app/Users/model.js b/app/Users/model.js index ad72213..1843fc0 100644 --- a/app/Users/model.js +++ b/app/Users/model.js @@ -16,6 +16,10 @@ const UserScheme = new Schema({ type: String, required: true, select: false + }, + activated: { + type: Boolean, + default: false } }, { timestamps: { diff --git a/app/Users/repository.js b/app/Users/repository.js index ee0517e..f5b5bf4 100644 --- a/app/Users/repository.js +++ b/app/Users/repository.js @@ -11,7 +11,7 @@ const handleSaveErrors = error => { const find = () => User.find() const findOne = id => User.findById(id) const create = user => new User(user).save().catch(handleSaveErrors) -const save = (id, data) => User.findByIdAndUpdate(id, data).catch(handleSaveErrors) +const save = (id, data) => User.findByIdAndUpdate(id, data, {new: true}).catch(handleSaveErrors) const remove = id => User.findByIdAndRemove(id) module.exports = { diff --git a/app/Users/routes.js b/app/Users/routes.js index 0f15bf6..daf04d3 100644 --- a/app/Users/routes.js +++ b/app/Users/routes.js @@ -6,5 +6,6 @@ const request = require('./request') Router.get('/:id', authorizeWithJwt, users.show) Router.post('/', request, users.store) +Router.get('/', authorizeWithJwt, users.index) module.exports = Router diff --git a/bootstrap/strategies/local.js b/bootstrap/strategies/local.js index 5f69d52..94b71f4 100644 --- a/bootstrap/strategies/local.js +++ b/bootstrap/strategies/local.js @@ -6,7 +6,7 @@ const { key } = require('../../config/app') const auth = (email, password, done) => ( User - .findOne({ email }) + .findOne({ email, activated: true }) .select('+password') .then(validatePasswordAndGetUser(password)) .then(transformUser) diff --git a/config/mail.js b/config/mail.js new file mode 100644 index 0000000..c9ee883 --- /dev/null +++ b/config/mail.js @@ -0,0 +1,7 @@ +module.exports = { + host: process.env.MAIL_HOST, + port: process.env.MAIL_PORT, + username: process.env.MAIL_USERNAME, + password: process.env.MAIL_PASSWORD, + sender: process.env.MAIL_SENDER +} diff --git a/docker-compose.yml b/docker-compose.yml index 99e0da7..c9531c7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -24,6 +24,7 @@ services: - ./app:/api/app - ./bootstrap:/api/bootstrap - ./config:/api/config + - ./services:/api/services base-tests: extends: base diff --git a/package.json b/package.json index c298198..4be5311 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "jsonwebtoken": "^8.2.1", "mongoose-paginate": "^5.0.3", "morgan": "^1.9.0", + "nodemailer": "^4.6.5", "passport": "^0.4.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", diff --git a/services/mail/index.js b/services/mail/index.js new file mode 100644 index 0000000..edf1c93 --- /dev/null +++ b/services/mail/index.js @@ -0,0 +1,34 @@ +const nodemailer = require('nodemailer') +const { + host, + username, + password, + sender +} = require('../../config/mail') + +const transporter = nodemailer.createTransport({ + service: host, + auth: { + user: username, + pass: password + } +}) + +const defaultMailOptions = { + from: sender +} + +const dispatch = mail => transporter.sendMail({ + ...defaultMailOptions, + ...mail +}, error => { + if (error) { + throw error + } + + return true +}) + +module.exports = { + dispatch +} diff --git a/services/mail/send.js b/services/mail/send.js new file mode 100644 index 0000000..5152833 --- /dev/null +++ b/services/mail/send.js @@ -0,0 +1,20 @@ +const Token = require('../../app/Authentication/token') +const Mail = require('./index') + +const confirmation = user => { + const token = Token.create({ user: user.id }, '1day') + const mail = { + to: user.email, + subject: 'Confirme seu cadastro', + html: ` +

Olá ${user.name}.

+

Você pode confirmar seu registro accesando este link.

+ ` + } + + return Mail.dispatch(mail) +} + +module.exports = { + confirmation +} diff --git a/yarn.lock b/yarn.lock index cdd35ac..327da23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3255,6 +3255,10 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" +nodemailer@^4.6.5: + version "4.6.5" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-4.6.5.tgz#306ce18c4543f3f06e5f76665d3cb6277f872f53" + nodemon@^1.14.12: version "1.17.3" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.17.3.tgz#3b0bbc2ee05ccb43b1aef15ba05c63c7bc9b8530"