-
Notifications
You must be signed in to change notification settings - Fork 315
Solution #253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Solution #253
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| /* eslint-disable no-console */ | ||
| require('dotenv').config(); | ||
|
|
||
| const { Sequelize } = require('sequelize'); | ||
|
|
||
| const sequelize = new Sequelize( | ||
| process.env.DATABASE, | ||
| process.env.USERNAME_DATABASE, | ||
| process.env.PASSWORD_DATABASE, | ||
| { | ||
| host: process.env.HOST_DATABASE, | ||
| dialect: 'postgres', | ||
| }, | ||
| ); | ||
|
|
||
| module.exports = { sequelize }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| /* eslint-disable no-console */ | ||
| const User = require('../models/user.model'); | ||
| const bcrypt = require('bcrypt'); | ||
| const { | ||
| validateEmail, | ||
| validatePassword, | ||
| send, | ||
| } = require('../services/auth.services'); | ||
| const { v4: uuidv4 } = require('uuid'); | ||
| const jwt = require('jsonwebtoken'); | ||
|
|
||
| const registration = async (req, res) => { | ||
| const { name, email, password } = req.body; | ||
| const errorEmail = validateEmail(email); | ||
| const errorPassword = validatePassword(password); | ||
|
|
||
| if (errorEmail) { | ||
| return res.status(400).send(errorEmail); | ||
| } | ||
|
|
||
| if (errorPassword) { | ||
| return res.status(400).send(errorPassword); | ||
| } | ||
|
|
||
| const exist = await User.findOne({ where: { email } }); | ||
|
|
||
| if (exist !== null) { | ||
| return res.status(401).send('User already exist'); | ||
| } | ||
|
|
||
| const cashedPassword = await bcrypt.hash(password, 10); | ||
|
|
||
| const uuid = uuidv4(); | ||
|
|
||
| await User.create({ | ||
| name: name, | ||
| email: email, | ||
| password: cashedPassword, | ||
| activationToken: uuid, | ||
| }); | ||
|
|
||
| await send( | ||
| email, | ||
| 'Activate email', | ||
| `go to link http://localhost:3000/activation/${uuid}`, | ||
| ); | ||
| res.status(201).send('User created'); | ||
| }; | ||
|
|
||
| const activation = async (req, res) => { | ||
| const { activationToken } = req.params; | ||
|
|
||
| const user = await User.findOne({ where: { activationToken } }); | ||
|
|
||
| if (!user) { | ||
| return res.status(404).send('Invalid activation token'); | ||
| } | ||
|
|
||
| user.isActivated = true; | ||
| user.activationToken = null; | ||
| await user.save(); | ||
| res.redirect('http://localhost:3000/profile'); | ||
| }; | ||
|
|
||
| const login = async (req, res) => { | ||
| const { email, password } = req.body; | ||
|
|
||
| const existUser = await User.findOne({ where: { email } }); | ||
|
|
||
| if (!existUser) { | ||
| return res.status(404).send('User not found'); | ||
| } | ||
|
|
||
| const isValid = await bcrypt.compare(password, existUser.password); | ||
|
|
||
| if (!isValid) { | ||
| return res.status(401).send('Invalid credentials'); | ||
| } | ||
|
|
||
| if (!existUser.isActivated) { | ||
| await send( | ||
| email, | ||
| 'Activate email', | ||
| `go to link http://localhost:3000/activation/${existUser.activationToken}`, | ||
| ); | ||
|
|
||
| return res.status(403).send('Activate email first'); | ||
| } | ||
|
|
||
| const token = jwt.sign( | ||
| { id: existUser.id, email: existUser.email }, | ||
| process.env.JWT_SECRET, | ||
| { expiresIn: '30 days' }, | ||
| ); | ||
|
|
||
| res.cookie('token', token, { httpOnly: true }); | ||
| res.redirect('http://localhost:3000/profile'); | ||
| }; | ||
|
|
||
| module.exports = { | ||
| authController: { | ||
| registration, | ||
| activation, | ||
| login, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| /* eslint-disable no-console */ | ||
| const User = require('../models/user.model'); | ||
| const bcrypt = require('bcrypt'); | ||
| const { v4: uuidv4 } = require('uuid'); | ||
|
|
||
| const { | ||
| validateEmail, | ||
| validatePassword, | ||
| send, | ||
| } = require('../services/auth.services'); | ||
|
|
||
| const profile = async (req, res) => { | ||
| const userId = req.user.id; | ||
|
|
||
| const user = await User.findByPk(userId, { | ||
| attributes: ['id', 'name', 'email'], | ||
| }); | ||
|
|
||
| res.send(user); | ||
| }; | ||
|
|
||
| const changeName = async (req, res) => { | ||
| const userId = req.user.id; | ||
| const { name } = req.body; | ||
|
|
||
| const user = await User.findByPk(userId); | ||
|
|
||
| user.name = name; | ||
| await user.save(); | ||
| res.send('Username changed'); | ||
| }; | ||
|
|
||
| const changePassword = async (req, res) => { | ||
| const userId = req.user.id; | ||
| const { password, newPassword, confirm } = req.body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the task requirements, this function should expect fields named |
||
|
|
||
| if (newPassword !== confirm) { | ||
| return res.send('Passwords do not match'); | ||
| } | ||
|
|
||
| const user = await User.findByPk(userId); | ||
|
|
||
| const isValid = await bcrypt.compare(password, user.password); | ||
|
|
||
| if (!isValid) { | ||
| return res.status(401).send('Invalid credentials'); | ||
| } | ||
|
|
||
| user.password = await bcrypt.hash(newPassword, 10); | ||
| await user.save(); | ||
| res.send('password changed'); | ||
| }; | ||
|
|
||
| const resetPassword = async (req, res) => { | ||
| const { email } = req.body; | ||
|
|
||
| const user = await User.findOne({ where: { email } }); | ||
|
|
||
| if (!user) { | ||
| return res.status(401).send('Invalid credentials'); | ||
| } | ||
|
|
||
| const token = uuidv4(); | ||
|
|
||
| user.resetToken = token; | ||
|
|
||
| await send( | ||
| user.email, | ||
| 'password reset', | ||
| `go to link http://localhost:3000/reset/${token}`, | ||
| ); | ||
|
|
||
| await user.save(); | ||
| res.send('email sent'); | ||
| }; | ||
|
|
||
| const resetPasswordConfirm = async (req, res) => { | ||
| const { resetToken } = req.params; | ||
| const { password, repeatPassword } = req.body; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The task requirements specify that the password reset confirmation should expect |
||
|
|
||
| const errorPassword = validatePassword(password); | ||
|
|
||
| if (errorPassword) { | ||
| return res.status(400).send(errorPassword); | ||
| } | ||
|
|
||
| if (password !== repeatPassword) { | ||
| return res.send('passwords not equil'); | ||
| } | ||
|
|
||
| const user = await User.findOne({ where: { resetToken } }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should check if a user was found with the provided |
||
|
|
||
| if (!user) { | ||
| return res.status(401).send('Invalid credentials'); | ||
| } | ||
|
|
||
| user.password = await bcrypt.hash(password, 10); | ||
| user.resetToken = null; | ||
| await user.save(); | ||
|
|
||
| return res.send(` | ||
| <h1>Password changed successfully</h1> | ||
| <a href="/login">Go to login</a> | ||
| `); | ||
| }; | ||
|
|
||
| const changeEmail = async (req, res) => { | ||
| const userId = req.user.id; | ||
| const { password, email, confirmEmail } = req.body; | ||
|
|
||
| const user = await User.findByPk(userId); | ||
|
|
||
| const isValid = await bcrypt.compare(password, user.password); | ||
|
|
||
| if (!isValid) { | ||
| return res.status(401).send('Invalid credentials'); | ||
| } | ||
|
|
||
| if (email !== confirmEmail) { | ||
| return res.status(400).send('Emails do not match'); | ||
| } | ||
|
|
||
| const errorEmail = validateEmail(email); | ||
|
|
||
| if (errorEmail) { | ||
| return res.status(400).send(errorEmail); | ||
| } | ||
|
|
||
| const uuid = uuidv4(); | ||
|
|
||
| await send( | ||
| user.email, | ||
| 'Email change notification', | ||
| 'Your email is being changed.', | ||
| ); | ||
|
|
||
| await send( | ||
| email, | ||
| 'Activate new email', | ||
| `Go to link http://localhost:3000/activation/${uuid}`, | ||
| ); | ||
|
Comment on lines
+131
to
+141
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The implementation for changing an email doesn't fully meet the requirements:
|
||
|
|
||
| user.email = email; | ||
| user.isActivated = false; | ||
| user.activationToken = uuid; | ||
|
|
||
| await user.save(); | ||
|
|
||
| return res.redirect('/login'); | ||
| }; | ||
|
|
||
| const logout = async (req, res) => { | ||
| res.clearCookie('token'); | ||
| res.redirect('/login'); | ||
| }; | ||
|
|
||
| module.exports = { | ||
| userController: { | ||
| profile, | ||
| changeName, | ||
| changePassword, | ||
| changeEmail, | ||
| logout, | ||
| resetPassword, | ||
| resetPasswordConfirm, | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,38 @@ | ||
| /* eslint-disable max-len */ | ||
| /* eslint-disable no-console */ | ||
| 'use strict'; | ||
|
|
||
| const express = require('express'); | ||
| const cookieParser = require('cookie-parser'); | ||
| const { sequelize } = require('../src/config/database'); // import connect to database | ||
| const { router: authRouter } = require('../src/routes/auth.routes'); | ||
| const { router: userRouter } = require('../src/routes/user.routes'); // import routes | ||
|
|
||
| async function start() { | ||
| try { | ||
| await sequelize.authenticate(); // connect to database | ||
| console.log('database connected'); | ||
|
|
||
| await sequelize.sync({ alter: true }); // create tables | ||
| console.log('tables created'); | ||
|
|
||
| const app = express(); // create express app | ||
|
|
||
| app.use(cookieParser()); | ||
| app.use(express.json()); | ||
|
|
||
| app.use(authRouter); | ||
| app.use(userRouter); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The task requires you to return a 404 error for any pages that are not defined by your routers. You should add a catch-all middleware here, after all other routes, to handle requests to non-existent pages. |
||
|
|
||
| // 404 handler | ||
| app.use((req, res) => { | ||
| res.status(404).send('Page not found'); | ||
| }); | ||
|
|
||
| app.listen(3000, () => console.log('server running on 3000 port')); // start server | ||
| } catch (err) { | ||
| console.error('unable connect to database', err); | ||
| } | ||
| } | ||
|
|
||
| start(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| const jwt = require('jsonwebtoken'); | ||
|
|
||
| require('dotenv').config(); | ||
|
|
||
| function authMiddleware(req, res, next) { | ||
| const token = req.cookies.token; | ||
|
|
||
| if (!token) { | ||
| return res.status(401).send('unauthorized'); | ||
| } | ||
|
|
||
| try { | ||
| const userData = jwt.verify(token, process.env.JWT_SECRET); | ||
|
|
||
| req.user = userData; | ||
| next(); | ||
| } catch { | ||
| return res.status(401).send('Invalid token'); | ||
| } | ||
| } | ||
|
|
||
| function guestMiddleware(req, res, next) { | ||
| const token = req.cookies.token; | ||
|
|
||
| if (token) { | ||
| return res.redirect('/profile'); | ||
| } | ||
|
|
||
| next(); | ||
| } | ||
|
|
||
| module.exports = { authMiddleware, guestMiddleware }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| const { DataTypes } = require('sequelize'); | ||
| const { sequelize } = require('../config/database'); | ||
|
|
||
| const Users = sequelize.define('Users', { | ||
| name: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| email: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| unique: true, | ||
| }, | ||
| password: { | ||
| type: DataTypes.STRING, | ||
| allowNull: false, | ||
| }, | ||
| isActivated: { | ||
| type: DataTypes.BOOLEAN, | ||
| allowNull: true, | ||
| defaultValue: false, | ||
| }, | ||
| activationToken: { | ||
| type: DataTypes.STRING, | ||
| allowNull: true, | ||
| }, | ||
| resetToken: { | ||
| type: DataTypes.STRING, | ||
| allowNull: true, | ||
| }, | ||
| }); | ||
|
|
||
| module.exports = Users; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The task requires redirecting to the profile page after activation, which you've done. However, the profile page is protected and requires the user to be authenticated. Currently, after activation, the user isn't logged in, so they'll be blocked from accessing the profile.
To fix this, you should log the user in by creating a JWT and setting it as a cookie, just like you do in the
loginfunction, before redirecting them to their profile.