Skip to content

Commit bcb5190

Browse files
Adicionando autenticação ao projeto MyMoneyApp
1 parent 0f375fb commit bcb5190

File tree

19 files changed

+410
-12
lines changed

19 files changed

+410
-12
lines changed

my-money-app/backend/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
2-
*.log
2+
*.log
3+
.env

my-money-app/backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
"author": "",
1212
"license": "ISC",
1313
"dependencies": {
14+
"bcrypt": "^1.0.2",
1415
"body-parser": "^1.15.2",
1516
"express": "^4.14.0",
1617
"express-query-int": "^1.0.1",
18+
"jsonwebtoken": "^7.3.0",
1719
"lodash": "^4.17.4",
1820
"mongoose": "^4.7.0",
1921
"mongoose-paginate": "^5.0.3",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const _ = require('lodash')
2+
const jwt = require('jsonwebtoken')
3+
const bcrypt = require('bcrypt')
4+
const User = require('./user')
5+
const env = require('../../.env')
6+
7+
const emailRegex = /\S+@\S+\.\S+/
8+
const passwordRegex = /((?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%]).{6,20})/
9+
10+
const sendErrorsFromDB = (res, dbErrors) => {
11+
const errors = []
12+
_.forIn(dbErrors.errors, error => errors.push(error.message))
13+
return res.status(400).json({ errors })
14+
}
15+
16+
const login = (req, res, next) => {
17+
const email = req.body.email || ''
18+
const password = req.body.password || ''
19+
20+
User.findOne({ email }, (err, user) => {
21+
if (err) {
22+
return sendErrorsFromDB(res, err)
23+
} else if (user && bcrypt.compareSync(password, user.password)) {
24+
const token = jwt.sign(user, env.authSecret, {
25+
expiresIn: "1 day"
26+
})
27+
const { name, email } = user
28+
res.json({ name, email, token })
29+
} else {
30+
return res.status(400).send({ errors: ['Usuário/Senha inválidos'] })
31+
}
32+
})
33+
}
34+
35+
const validateToken = (req, res, next) => {
36+
const token = req.body.token || ''
37+
38+
jwt.verify(token, env.authSecret, function (err, decoded) {
39+
return res.status(200).send({ valid: !err })
40+
})
41+
}
42+
43+
const signup = (req, res, next) => {
44+
const name = req.body.name || ''
45+
const email = req.body.email || ''
46+
const password = req.body.password || ''
47+
const confirmPassword = req.body.confirm_password || ''
48+
49+
if (!email.match(emailRegex)) {
50+
return res.status(400).send({ errors: ['O e-mail informado está inválido'] })
51+
}
52+
53+
if (!password.match(passwordRegex)) {
54+
return res.status(400).send({
55+
errors: [
56+
"Senha precisar ter: uma letra maiúscula, uma letra minúscula, um número, uma caractere especial(@#$ %) e tamanho entre 6-20."
57+
]
58+
})
59+
}
60+
61+
const salt = bcrypt.genSaltSync()
62+
const passwordHash = bcrypt.hashSync(password, salt)
63+
if (!bcrypt.compareSync(confirmPassword, passwordHash)) {
64+
return res.status(400).send({ errors: ['Senhas não conferem.'] })
65+
}
66+
67+
User.findOne({ email }, (err, user) => {
68+
if (err) {
69+
return sendErrorsFromDB(res, err)
70+
} else if (user) {
71+
return res.status(400).send({ errors: ['Usuário já cadastrado.'] })
72+
} else {
73+
const newUser = new User({ name, email, password: passwordHash })
74+
newUser.save(err => {
75+
if (err) {
76+
return sendErrorsFromDB(res, err)
77+
} else {
78+
login(req, res, next)
79+
}
80+
})
81+
}
82+
})
83+
}
84+
85+
module.exports = { login, signup, validateToken }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const restful = require('node-restful')
2+
const mongoose = restful.mongoose
3+
4+
const userSchema = new mongoose.Schema({
5+
name: { type: String, required: true },
6+
email: { type: String, required: true },
7+
password: { type: String, min: 6, max: 12, required: true }
8+
})
9+
10+
module.exports = restful.model('User', userSchema)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const jwt = require('jsonwebtoken')
2+
const env = require('../.env')
3+
4+
module.exports = (req, res, next) => {
5+
// CORS preflight request
6+
if (req.method === 'OPTIONS') {
7+
next()
8+
} else {
9+
const token = req.body.token || req.query.token || req.headers['authorization']
10+
11+
if (!token) {
12+
return res.status(403).send({ errors: ['No token provided.'] })
13+
}
14+
15+
jwt.verify(token, env.authSecret, function (err, decoded) {
16+
if (err) {
17+
return res.status(403).send({
18+
errors: ['Failed to authenticate token.']
19+
})
20+
} else {
21+
// req.decoded = decoded
22+
next()
23+
}
24+
})
25+
}
26+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = (req, res, next) => {
22
res.header('Access-Control-Allow-Origin', '*')
33
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE')
4-
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
4+
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization')
55
next()
66
}
Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
const express = require('express')
2+
const auth = require('./auth')
23

3-
module.exports = function(server) {
4+
module.exports = function (server) {
45

5-
// Definir URL base para todas as rotas
6-
const router = express.Router()
7-
server.use('/api', router)
6+
/*
7+
* Rotas protegidas por Token JWT
8+
*/
9+
const protectedApi = express.Router()
10+
server.use('/api', protectedApi)
11+
12+
protectedApi.use(auth)
813

9-
// Rotas de Ciclo de Pagamento
1014
const BillingCycle = require('../api/billingCycle/billingCycleService')
11-
BillingCycle.register(router, '/billingCycles')
15+
BillingCycle.register(protectedApi, '/billingCycles')
16+
17+
/*
18+
* Rotas abertas
19+
*/
20+
const openApi = express.Router()
21+
server.use('/oapi', openApi)
22+
23+
const AuthService = require('../api/user/AuthService')
24+
openApi.post('/login', AuthService.login)
25+
openApi.post('/signup', AuthService.signup)
26+
openApi.post('/validateToken', AuthService.validateToken)
1227
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.wrapper {
2+
background-color: #fff!important;
3+
}
4+
5+
.login-box-body {
6+
background-color: #eee;
7+
}
8+
9+
.login-box button {
10+
margin-left: 0px;
11+
}
12+
13+
.login-box a:hover {
14+
cursor: pointer;
15+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import './auth.css'
2+
import React, { Component } from 'react'
3+
import { reduxForm, Field } from 'redux-form'
4+
import { connect } from 'react-redux'
5+
import { bindActionCreators } from 'redux'
6+
7+
import { login, signup } from './authActions'
8+
import Row from '../common/layout/row'
9+
import Grid from '../common/layout/grid'
10+
import If from '../common/operator/if'
11+
import Messages from '../common/msg/messages'
12+
import Input from '../common/form/inputAuth'
13+
14+
class Auth extends Component {
15+
constructor(props) {
16+
super(props)
17+
this.state = { loginMode: true }
18+
}
19+
20+
changeMode() {
21+
this.setState({ loginMode: !this.state.loginMode })
22+
}
23+
24+
onSubmit(values) {
25+
const { login, signup } = this.props
26+
this.state.loginMode ? login(values) : signup(values)
27+
}
28+
29+
render() {
30+
const { loginMode } = this.state
31+
const { handleSubmit } = this.props
32+
return (
33+
<div className="login-box">
34+
<div className="login-logo"><b> My</b> Money</div>
35+
<div className="login-box-body">
36+
<p className="login-box-msg">Bem vindo!</p>
37+
<form onSubmit={handleSubmit(v => this.onSubmit(v))}>
38+
<Field component={Input} type="input" name="name"
39+
placeholder="Nome" icon='user' hide={loginMode} />
40+
<Field component={Input} type="email" name="email"
41+
placeholder="E-mail" icon='envelope' />
42+
<Field component={Input} type="password" name="password"
43+
placeholder="Senha" icon='lock' />
44+
<Field component={Input} type="password" name="confirm_password"
45+
placeholder="Confirmar Senha" icon='lock' hide={loginMode} />
46+
<Row>
47+
<Grid cols="4">
48+
<button type="submit"
49+
className="btn btn-primary btn-block btn-flat">
50+
{loginMode ? 'Entrar' : 'Registrar'}
51+
</button>
52+
</Grid>
53+
</Row>
54+
</form>
55+
<br />
56+
<a onClick={() => this.changeMode()}>
57+
{loginMode ? 'Novo usuário? Registrar aqui!' :
58+
'Já é cadastrado? Entrar aqui!'}
59+
</a>
60+
</div>
61+
<Messages />
62+
</div>
63+
)
64+
}
65+
}
66+
67+
Auth = reduxForm({ form: 'authForm' })(Auth)
68+
const mapDispatchToProps = dispatch => bindActionCreators({ login, signup }, dispatch)
69+
export default connect(null, mapDispatchToProps)(Auth)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { toastr } from 'react-redux-toastr'
2+
import axios from 'axios'
3+
import consts from '../consts'
4+
5+
export function login(values) {
6+
return submit(values, `${consts.OAPI_URL}/login`)
7+
}
8+
9+
export function signup(values) {
10+
return submit(values, `${consts.OAPI_URL}/signup`)
11+
}
12+
13+
function submit(values, url) {
14+
return dispatch => {
15+
axios.post(url, values)
16+
.then(resp => {
17+
dispatch([
18+
{ type: 'USER_FETCHED', payload: resp.data }
19+
])
20+
})
21+
.catch(e => {
22+
e.response.data.errors.forEach(
23+
error => toastr.error('Erro', error))
24+
})
25+
}
26+
}
27+
28+
export function logout() {
29+
return { type: 'TOKEN_VALIDATED', payload: false }
30+
}
31+
32+
export function validateToken(token) {
33+
return dispatch => {
34+
if (token) {
35+
axios.post(`${consts.OAPI_URL}/validateToken`, { token })
36+
.then(resp => {
37+
dispatch({ type: 'TOKEN_VALIDATED', payload: resp.data.valid })
38+
})
39+
.catch(e => dispatch({ type: 'TOKEN_VALIDATED', payload: false }))
40+
} else {
41+
dispatch({ type: 'TOKEN_VALIDATED', payload: false })
42+
}
43+
}
44+
}

0 commit comments

Comments
 (0)