Commit c1f6e9ad authored by Pavel Mishakov's avatar Pavel Mishakov

webinar is done with email reset password BACK

parent 2e137c79
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"mongoose": "^7.0.3", "mongoose": "^7.0.3",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",
"nodemailer": "^6.9.1",
"pg": "^8.9.0", "pg": "^8.9.0",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.29.0", "sequelize": "^6.29.0",
...@@ -35,6 +36,7 @@ ...@@ -35,6 +36,7 @@
"@types/mongoose": "^5.11.97", "@types/mongoose": "^5.11.97",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/nanoid": "^3.0.0", "@types/nanoid": "^3.0.0",
"@types/nodemailer": "^6.4.7",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"@types/uuidv4": "^5.0.0", "@types/uuidv4": "^5.0.0",
"@types/validator": "^13.7.12", "@types/validator": "^13.7.12",
...@@ -277,6 +279,15 @@ ...@@ -277,6 +279,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
"integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
}, },
"node_modules/@types/nodemailer": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz",
"integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
...@@ -1654,6 +1665,14 @@ ...@@ -1654,6 +1665,14 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/nodemailer": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz",
"integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nopt": { "node_modules/nopt": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
...@@ -2959,6 +2978,15 @@ ...@@ -2959,6 +2978,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.1.tgz",
"integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==" "integrity": "sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ=="
}, },
"@types/nodemailer": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz",
"integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/qs": { "@types/qs": {
"version": "6.9.7", "version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
...@@ -4014,6 +4042,11 @@ ...@@ -4014,6 +4042,11 @@
} }
} }
}, },
"nodemailer": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz",
"integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA=="
},
"nopt": { "nopt": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
"mongoose": "^7.0.3", "mongoose": "^7.0.3",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"nanoid": "^4.0.1", "nanoid": "^4.0.1",
"nodemailer": "^6.9.1",
"pg": "^8.9.0", "pg": "^8.9.0",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.29.0", "sequelize": "^6.29.0",
...@@ -36,6 +37,7 @@ ...@@ -36,6 +37,7 @@
"@types/mongoose": "^5.11.97", "@types/mongoose": "^5.11.97",
"@types/multer": "^1.4.7", "@types/multer": "^1.4.7",
"@types/nanoid": "^3.0.0", "@types/nanoid": "^3.0.0",
"@types/nodemailer": "^6.4.7",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"@types/uuidv4": "^5.0.0", "@types/uuidv4": "^5.0.0",
"@types/validator": "^13.7.12", "@types/validator": "^13.7.12",
......
...@@ -6,6 +6,7 @@ import { EStatuses } from "../enums/EStatuses" ...@@ -6,6 +6,7 @@ import { EStatuses } from "../enums/EStatuses"
import IUserGetDto from "../interfaces/IUserGetDto" import IUserGetDto from "../interfaces/IUserGetDto"
import { auth } from "../middlewares/auth" import { auth } from "../middlewares/auth"
import IRequestWithTokenData from "../interfaces/IRequestWithTokenData" import IRequestWithTokenData from "../interfaces/IRequestWithTokenData"
import jwt from 'jsonwebtoken'
...@@ -20,6 +21,8 @@ export class UserController { ...@@ -20,6 +21,8 @@ export class UserController {
this.router.post('/login', this.login) this.router.post('/login', this.login)
this.router.get('/token', auth, this.checkToken) this.router.get('/token', auth, this.checkToken)
this.router.put('/', auth, this.editUser) this.router.put('/', auth, this.editUser)
this.router.post('/forgot-password', this.sendEmailPassword)
this.router.put('/reset-password', this.resetPassword)
} }
public getRouter = (): Router => { public getRouter = (): Router => {
return this.router return this.router
...@@ -55,6 +58,18 @@ export class UserController { ...@@ -55,6 +58,18 @@ export class UserController {
} }
res.status(200).send(response) res.status(200).send(response)
} }
private sendEmailPassword = async (req: Request, res: Response): Promise<void> => {
const response: IResponse<undefined> = await this.service.sendEmailPassword(req.body)
res.status(200).send(response)
}
private resetPassword = async (req: Request, res: Response): Promise<void> => {
const user = jwt.verify(req.body.token || '', process.env.SECRET_KEY || '') as {isResetPassword: boolean, _id: string}
if (!user || !user.isResetPassword) return
const response: IResponse<undefined> = await this.service.resetPassword(req.body.password, user._id)
res.status(200).send(response)
}
} }
export const userController = new UserController() export const userController = new UserController()
\ No newline at end of file
import nodemailer, { Transporter } from "nodemailer"
class EmailService {
private transporter: Transporter | null = null
public init = (): void => {
try {
this.transporter = nodemailer.createTransport({
host: 'smtp.gmail.com',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
})
console.log('Email service transporter is ready')
} catch (err: unknown) {
console.log(err)
}
}
public sendEmail = async (to: string, subject: string, html: string): Promise<void> => {
try {
if (!this.transporter) return
await this.transporter.sendMail({
to,
subject,
html
})
} catch (err: unknown) {
console.log(err)
}
}
}
export const emailService = new EmailService()
\ No newline at end of file
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
export const generateJWT = (payload: {[key: string]: string | number | boolean}) => { export const generateJWT = (payload: {[key: string]: string | number | boolean}, lifeTime: string | number) => {
return jwt.sign(payload, process.env.SECRET_KEY || '', {expiresIn: '2h'}) return jwt.sign(payload, process.env.SECRET_KEY || '', {expiresIn: lifeTime})
} }
\ No newline at end of file
...@@ -10,6 +10,7 @@ import IDataBase from './interfaces/IDataBase' ...@@ -10,6 +10,7 @@ import IDataBase from './interfaces/IDataBase'
import { SuppliersController } from './controllers/suppliers' import { SuppliersController } from './controllers/suppliers'
import { UserController } from './controllers/user' import { UserController } from './controllers/user'
import { mongo } from './repository/mongo' import { mongo } from './repository/mongo'
import { emailService } from './email/emailService'
dotenv.config() dotenv.config()
class App { class App {
...@@ -42,6 +43,7 @@ class App { ...@@ -42,6 +43,7 @@ class App {
// process.on('exit', () => { // process.on('exit', () => {
// currentDb.close() // currentDb.close()
// }) // })
emailService.init()
mongo.init() mongo.init()
process.on('exit', () => { process.on('exit', () => {
mongo.close() mongo.close()
......
import IUser from "./IUser";
export default interface IUserResetPasswordDto {
password: IUser['password']
token: string
}
\ No newline at end of file
...@@ -15,6 +15,8 @@ import IUserCreateDto from '../interfaces/IUserCreateDto' ...@@ -15,6 +15,8 @@ import IUserCreateDto from '../interfaces/IUserCreateDto'
import IUser from '../interfaces/IUser' import IUser from '../interfaces/IUser'
import IUserGetDto from '../interfaces/IUserGetDto' import IUserGetDto from '../interfaces/IUserGetDto'
import { generateJWT } from '../helpers/generateJWT' import { generateJWT } from '../helpers/generateJWT'
import { emailService } from '../email/emailService'
import bcrypt from 'bcrypt'
dotenv.config() dotenv.config()
export class Mongo implements IDataBase { export class Mongo implements IDataBase {
...@@ -259,7 +261,7 @@ export class Mongo implements IDataBase { ...@@ -259,7 +261,7 @@ export class Mongo implements IDataBase {
const data = { const data = {
_id: user._id, _id: user._id,
username: user.username, username: user.username,
token: generateJWT({_id: user._id, username: user.username}) token: generateJWT({_id: user._id, username: user.username}, '2h')
} }
return { return {
status: EStatuses.OK, status: EStatuses.OK,
...@@ -281,15 +283,16 @@ export class Mongo implements IDataBase { ...@@ -281,15 +283,16 @@ export class Mongo implements IDataBase {
if (!user) { if (!user) {
throw new Error('Not found') throw new Error('Not found')
} }
const isMatch: boolean = user.checkPassword(userDto.password) const isMatch: boolean = await user.checkPassword(userDto.password)
if (!isMatch) { if (!isMatch) {
throw new Error('Wrong password') throw new Error('Wrong password')
} }
console.log('IS MATHC!!!!!!!! ', isMatch)
const data = { const data = {
_id: user._id, _id: user._id,
username: user.username, username: user.username,
token: generateJWT({_id: user._id, username: user.username}) token: generateJWT({_id: user._id, username: user.username}, '2h')
} }
await user.save() await user.save()
return { return {
...@@ -329,7 +332,9 @@ export class Mongo implements IDataBase { ...@@ -329,7 +332,9 @@ export class Mongo implements IDataBase {
username: userDto.username username: userDto.username
} }
if (userDto.password) { if (userDto.password) {
update.password = userDto.password const salt = await bcrypt.genSalt(parseInt(process.env.BCRYPT_SALT || '') || 10)
const hash = await bcrypt.hash(userDto.password, salt)
update.password = hash
} }
const user: IUserGetDto | null = await User.findOneAndUpdate({_id: id}, update, { const user: IUserGetDto | null = await User.findOneAndUpdate({_id: id}, update, {
new: true new: true
...@@ -348,6 +353,56 @@ export class Mongo implements IDataBase { ...@@ -348,6 +353,56 @@ export class Mongo implements IDataBase {
} }
} }
public sendEmailPassword = async (email: string): Promise<IResponse<undefined>> => {
try {
const user = await User.findOne({username: email})
if (user) {
const token = generateJWT({_id: user._id, isResetPassword: true}, '2m')
emailService.sendEmail(
'pashamishakov@gmail.com',
'Webinar 29 March',
`<h1>Hello ${user.username}</h1>
<p>Please click the link:</p>
<div>
<a href='http://localhost:3001/reset-password?token=${token}'>CLICK ME TO RESET YOUR PASSWORD</a>
</div>
`
)
}
return {
status: EStatuses.OK,
result: undefined,
message: `Email was sent to ${email}`
}
} catch(err: unknown) {
return {
status: EStatuses.NOT_OK,
result: undefined,
message: `Email was sent to ${email}`
}
}
}
public resetPassword = async (password: string, _id: string): Promise<IResponse<undefined>> => {
try {
const salt = await bcrypt.genSalt(parseInt(process.env.BCRYPT_SALT || '') || 10)
const hash = await bcrypt.hash(password, salt)
await User.findOneAndUpdate({_id: _id}, {password: hash})
return {
status: EStatuses.OK,
result: undefined,
message: 'Password successfully was changed'
}
} catch(err: unknown) {
return {
status: EStatuses.NOT_OK,
result: undefined,
message: '[ERROR] Cannot change password'
}
}
}
} }
export const mongo = new Mongo() export const mongo = new Mongo()
\ No newline at end of file
...@@ -26,6 +26,14 @@ export class UserService { ...@@ -26,6 +26,14 @@ export class UserService {
public getUsers = async (): Promise<IResponse<IUser[] | undefined>> => { public getUsers = async (): Promise<IResponse<IUser[] | undefined>> => {
return await this.repository.getUsers() return await this.repository.getUsers()
} }
public sendEmailPassword = async (req: {email: string}): Promise<IResponse<undefined>> => {
return await this.repository.sendEmailPassword(req.email)
}
public resetPassword = async (password: string, _id: string): Promise<IResponse<undefined>> => {
return await this.repository.resetPassword(password, _id)
}
} }
export const userService = new UserService() export const userService = new UserService()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment