Skip to content

Commit f4756d4

Browse files
committed
first commit
1 parent ffa9b96 commit f4756d4

30 files changed

+813
-0
lines changed

.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# dependencies
2+
/node_modules
3+
4+
# IDE
5+
/.idea
6+
/.awcache
7+
/.vscode
8+
9+
# misc
10+
npm-debug.log
11+
12+
# example
13+
/quick-start
14+
15+
# tests
16+
/test
17+
/coverage
18+
/.nyc_output
19+
20+
# dist
21+
/dist

index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require('ts-node/register');
2+
require('./src/main');

nodemon.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"watch": ["src"],
3+
"ext": "ts",
4+
"ignore": ["src/**/*.spec.ts"],
5+
"exec": "node ./index"
6+
}

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "nest-typescript-starter",
3+
"version": "1.0.0",
4+
"description": "Nest TypeScript starter repository",
5+
"license": "MIT",
6+
"scripts": {
7+
"start": "node index.js",
8+
"start:watch": "nodemon",
9+
"prestart:prod": "tsc",
10+
"start:prod": "node dist/main.js"
11+
},
12+
"dependencies": {
13+
"@nestjs/common": "^4.5.9",
14+
"@nestjs/core": "^4.5.10",
15+
"@nestjs/microservices": "^4.5.8",
16+
"@nestjs/mongoose": "^3.0.1",
17+
"@nestjs/testing": "^4.5.5",
18+
"@nestjs/websockets": "^4.5.8",
19+
"bcrypt": "^1.0.3",
20+
"jsonwebtoken": "^8.2.0",
21+
"mongoose": "^5.0.10",
22+
"nodemailer": "^4.6.3",
23+
"passport": "^0.4.0",
24+
"passport-jwt": "^3.0.1",
25+
"redis": "^2.7.1",
26+
"reflect-metadata": "^0.1.12",
27+
"rxjs": "^5.5.6",
28+
"typescript": "^2.6.2"
29+
},
30+
"devDependencies": {
31+
"@types/mongoose": "^5.0.7",
32+
"@types/node": "^9.3.0",
33+
"nodemon": "^1.14.1",
34+
"ts-node": "^4.1.0"
35+
}
36+
}

src/app.module.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MongooseModule } from '@nestjs/mongoose';
2+
import { Module, NestModule, MiddlewaresConsumer } from '@nestjs/common';
3+
import { LoggerMiddleware } from './common/middlewares/logger.middleware';
4+
import { UsersModule } from './users/users.module';
5+
import { AuthModule } from './auth/auth.module';
6+
import { UsersController } from './users/users.controller';
7+
8+
@Module({
9+
imports: [MongooseModule.forRoot('mongodb://localhost/nest'), UsersModule, AuthModule],
10+
controllers: [],
11+
components: [],
12+
})
13+
export class ApplicationModule {}

src/auth/auth.controller.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { Controller, Post, HttpStatus, HttpCode, Get, Body, Param } from '@nestjs/common';
2+
import { AuthService } from './auth.service';
3+
import { Login } from './interfaces/login.interface';
4+
import { User } from '../users/interfaces/user.interface';
5+
import { ResponseSuccess, ResponseError } from '../common/dto/response.dto';
6+
import { IResponse } from '../common/interfaces/response.interface';
7+
import { CreateUserDto } from '../users/dto/create-user.dto';
8+
import { UserDto } from '../users/dto/user.dto';
9+
import { UsersService } from '../users/users.service';
10+
import { ResetPasswordDto } from './dto/reset-password.dto';
11+
12+
13+
@Controller('auth')
14+
export class AuthController {
15+
constructor(private readonly authService: AuthService, private readonly userService: UsersService ) {}
16+
17+
@Post('email/login')
18+
@HttpCode(HttpStatus.OK)
19+
public async login(@Body() login: Login): Promise<IResponse> {
20+
try {
21+
var response = await this.authService.validateLogin(login.email, login.password);
22+
return new ResponseSuccess("LOGIN.SUCCESS", response);
23+
} catch(error) {
24+
return new ResponseError("LOGIN.ERROR", error);
25+
}
26+
}
27+
28+
@Post('email/register')
29+
@HttpCode(HttpStatus.OK)
30+
async register(@Body() createUserDto: CreateUserDto): Promise<IResponse> {
31+
try {
32+
var newUser = new UserDto(await this.userService.createNewUser(createUserDto));
33+
await this.authService.createEmailToken(newUser.email);
34+
var sended = await this.authService.sendEmailVerification(newUser.email);
35+
if(sended){
36+
return new ResponseSuccess("REGISTER.USER_REGISTERED_SUCCESSFULLY");
37+
} else {
38+
return new ResponseError("REGISTER.ERROR.MAIL_NOT_SENDED");
39+
}
40+
} catch(error){
41+
return new ResponseError("REGISTER.ERROR.GENERIC_ERROR", error);
42+
}
43+
}
44+
45+
@Get('email/verify/:token')
46+
public async verifyEmail(@Param() params): Promise<IResponse> {
47+
try {
48+
var isEmailVerified = await this.authService.verifyEmail(params.token);
49+
return new ResponseSuccess("LOGIN.EMAIL_VERIFIED", isEmailVerified);
50+
} catch(error) {
51+
return new ResponseError("LOGIN.ERROR", error);
52+
}
53+
}
54+
55+
@Get('email/resend-verification/:email')
56+
public async sendEmailVerification(@Param() params): Promise<IResponse> {
57+
try {
58+
await this.authService.createEmailToken(params.email);
59+
var isEmailSended = await this.authService.sendEmailVerification(params.email);
60+
if(isEmailSended){
61+
return new ResponseSuccess("LOGIN.EMAIL_RESENDED");
62+
} else {
63+
return new ResponseError("REGISTER.ERROR.MAIL_NOT_SENDED");
64+
}
65+
} catch(error) {
66+
return new ResponseError("LOGIN.ERROR.SEND_EMAIL", error);
67+
}
68+
}
69+
70+
@Get('email/forgot-password/:email')
71+
public async sendEmailForgotPassword(@Param() params): Promise<IResponse> {
72+
try {
73+
var isEmailSended = await this.authService.sendEmailForgotPassword(params.email);
74+
if(isEmailSended){
75+
return new ResponseSuccess("LOGIN.EMAIL_RESENDED");
76+
} else {
77+
return new ResponseError("REGISTER.ERROR.MAIL_NOT_SENDED");
78+
}
79+
} catch(error) {
80+
return new ResponseError("LOGIN.ERROR.SEND_EMAIL", error);
81+
}
82+
}
83+
84+
@Post('email/reset-password')
85+
@HttpCode(HttpStatus.OK)
86+
public async setNewPassord(@Body() resetPassword: ResetPasswordDto): Promise<IResponse> {
87+
try {
88+
var forgottenPasswordModel = await this.authService.getForgottenPasswordModel(resetPassword.newPasswordToken, resetPassword.newPassword);
89+
var isNewPasswordChanged = await this.userService.setPassword(forgottenPasswordModel.email, resetPassword.newPassword);
90+
if(isNewPasswordChanged) await forgottenPasswordModel.remove();
91+
return new ResponseSuccess("LOGIN.PASSWORD_CHANGED", isNewPasswordChanged);
92+
} catch(error) {
93+
return new ResponseError("LOGIN.ERROR.CHANGE_PASSWORD", error);
94+
}
95+
}
96+
97+
}

src/auth/auth.module.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as passport from 'passport';
2+
import { MongooseModule } from '@nestjs/mongoose';
3+
import {
4+
Module,
5+
NestModule,
6+
MiddlewaresConsumer,
7+
RequestMethod,
8+
} from '@nestjs/common';
9+
import { AuthService } from './auth.service';
10+
import { JwtStrategy } from './passport/jwt.strategy';
11+
import { AuthController } from './auth.controller';
12+
import { UserSchema } from '../users/schemas/user.schema';
13+
import { EmailVerificationSchema } from '../auth/schemas/emailverification.schema';
14+
import { ForgottenPasswordSchema } from './schemas/forgottenpassword.schema';
15+
import { UsersService } from '../users/users.service';
16+
import { JWTService } from './jwt.service';
17+
18+
@Module({
19+
imports: [MongooseModule.forFeature([
20+
{ name: 'User', schema: UserSchema },
21+
{ name: 'EmailVerification', schema: EmailVerificationSchema },
22+
{ name: 'ForgottenPassword', schema: ForgottenPasswordSchema }
23+
])],
24+
components: [JWTService, JwtStrategy, AuthService, UsersService],
25+
controllers: [AuthController],
26+
})
27+
export class AuthModule implements NestModule {
28+
public configure(consumer: MiddlewaresConsumer) {}
29+
}

src/auth/auth.service.ts

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import * as jwt from 'jsonwebtoken';
2+
import * as bcrypt from 'bcrypt';
3+
import * as nodemailer from 'nodemailer';
4+
import {default as config} from '../config';
5+
import { Component, HttpException, HttpStatus } from '@nestjs/common';
6+
import { JWTService } from './jwt.service';
7+
import { Model } from 'mongoose';
8+
import { InjectModel } from '@nestjs/mongoose';
9+
import { User } from '../users/interfaces/user.interface';
10+
import { UserDto } from '../users/dto/user.dto';
11+
import { UserSchema } from '../users/schemas/user.schema';
12+
import { EmailVerification } from './interfaces/emailverification.interface';
13+
import { EmailVerificationSchema } from './schemas/emailverification.schema';
14+
import { ForgottenPassword } from './interfaces/forgottenpassword.interface';
15+
import { ForgottenPasswordSchema } from './schemas/forgottenpassword.schema';
16+
17+
18+
const saltRounds = 10;
19+
20+
@Component()
21+
export class AuthService {
22+
constructor(@InjectModel(UserSchema) private readonly userModel: Model<User>,
23+
@InjectModel(EmailVerificationSchema) private readonly emailVerificationModel: Model<EmailVerification>,
24+
@InjectModel(ForgottenPasswordSchema) private readonly forgottenPasswordModel: Model<ForgottenPassword>,
25+
private readonly jwtService: JWTService) {}
26+
27+
28+
async validateLogin(email, password) {
29+
var userFromDb = await this.userModel.findOne({ email: email});
30+
if(!userFromDb) throw new HttpException('LOGIN.USER_NOT_FOUND', HttpStatus.NOT_FOUND);
31+
if(!userFromDb.auth.email.valid) throw new HttpException('LOGIN.EMAIL_NOT_VERIFIED', HttpStatus.FORBIDDEN);
32+
33+
var isValidPass = await bcrypt.compare(password, userFromDb.password);
34+
35+
if(isValidPass){
36+
var accessToken = await this.jwtService.createToken(email);
37+
return { token: accessToken, user: new UserDto(userFromDb)}
38+
} else {
39+
throw new HttpException('LOGIN.PASSWORD_INCORRECT', HttpStatus.UNAUTHORIZED);
40+
}
41+
42+
}
43+
44+
async createEmailToken(email: string): Promise<boolean> {
45+
var emailVerificationModel = await this.emailVerificationModel.findOneAndUpdate(
46+
{email: email},
47+
{
48+
email: email,
49+
emailToken: Math.floor(Math.random() * (900000000)) + 100000000, //Generate 9 digits number
50+
timestamp: new Date()
51+
},
52+
{upsert: true}
53+
);
54+
if(emailVerificationModel){
55+
return true;
56+
} else {
57+
throw new HttpException('LOGIN.ERROR.GENERIC_ERROR', HttpStatus.INTERNAL_SERVER_ERROR);
58+
}
59+
}
60+
61+
async createForgottenPasswordToken(email: string): Promise<ForgottenPassword> {
62+
var forgottenPasswordModel = await this.forgottenPasswordModel.findOneAndUpdate(
63+
{email: email},
64+
{
65+
email: email,
66+
newPasswordToken: Math.floor(Math.random() * (900000000)) + 100000000, //Generate 9 digits number,
67+
timestamp: new Date()
68+
},
69+
{upsert: true}
70+
);
71+
if(forgottenPasswordModel){
72+
return forgottenPasswordModel;
73+
} else {
74+
throw new HttpException('LOGIN.ERROR.GENERIC_ERROR', HttpStatus.INTERNAL_SERVER_ERROR);
75+
}
76+
}
77+
78+
async verifyEmail(token: string): Promise<boolean> {
79+
var emailVerif = await this.emailVerificationModel.findOne({ emailToken: token});
80+
if(emailVerif && emailVerif.email){
81+
var userFromDb = await this.userModel.findOne({ email: emailVerif.email});
82+
if (userFromDb) {
83+
userFromDb.auth.email.valid = true;
84+
var savedUser = await userFromDb.save();
85+
await emailVerif.remove();
86+
return !!savedUser;
87+
}
88+
} else {
89+
throw new HttpException('LOGIN.EMAIL_CODE_NOT_VALID', HttpStatus.FORBIDDEN);
90+
}
91+
}
92+
93+
async getForgottenPasswordModel(newPasswordToken: string, newPassword: string): Promise<ForgottenPassword> {
94+
return await this.forgottenPasswordModel.findOne({newPasswordToken: newPasswordToken});
95+
}
96+
97+
async sendEmailVerification(email: string): Promise<boolean> {
98+
var model = await this.emailVerificationModel.findOne({ email: email});
99+
100+
if(model && model.emailToken){
101+
let transporter = nodemailer.createTransport({
102+
host: config.mail.host,
103+
port: config.mail.port,
104+
secure: config.mail.secure,
105+
auth: {
106+
user: config.mail.user,
107+
pass: config.mail.pass
108+
}
109+
});
110+
111+
let mailOptions = {
112+
from: '"NestJs Auth"',
113+
to: email,
114+
subject: 'Verify Email',
115+
text: 'Verify Email',
116+
html: 'Hi! <br><br> Thanks for your registration<br><br>'+
117+
'<a href='+ config.host.url + ':' + config.host.port +'/auth/email/verify/'+ model.emailToken + '>Click here to activate your account</a>' // html body
118+
};
119+
120+
var sended = await new Promise<boolean>(async function(resolve, reject) {
121+
return await transporter.sendMail(mailOptions, async (error, info) => {
122+
if (error) {
123+
console.log('Message sent: %s', error);
124+
return reject(false);
125+
}
126+
console.log('Message sent: %s', info.messageId);
127+
resolve(true);
128+
});
129+
})
130+
131+
return sended;
132+
} else {
133+
throw new HttpException('REGISTER.USER_NOT_REGISTERED', HttpStatus.FORBIDDEN);
134+
}
135+
}
136+
137+
async sendEmailForgotPassword(email: string): Promise<boolean> {
138+
var userFromDb = await this.userModel.findOne({ email: email});
139+
if(!userFromDb) throw new HttpException('LOGIN.USER_NOT_FOUND', HttpStatus.NOT_FOUND);
140+
141+
var tokenModel = await this.createForgottenPasswordToken(email);
142+
143+
if(tokenModel && tokenModel.newPasswordToken){
144+
let transporter = nodemailer.createTransport({
145+
host: config.mail.host,
146+
port: config.mail.port,
147+
secure: config.mail.secure,
148+
auth: {
149+
user: config.mail.user,
150+
pass: config.mail.pass
151+
}
152+
});
153+
154+
let mailOptions = {
155+
from: '"NestJs Auth"',
156+
to: email,
157+
subject: 'Forgot Password',
158+
text: 'Forgot Password',
159+
html: 'Hi! <br><br> If you requested to reset your password<br><br>'+
160+
'<a href='+ config.host.url + ':' + config.host.port +'/auth/email/reset-password/'+ tokenModel.newPasswordToken + '>Click here</a>' // html body
161+
};
162+
163+
var sended = await new Promise<boolean>(async function(resolve, reject) {
164+
return await transporter.sendMail(mailOptions, async (error, info) => {
165+
if (error) {
166+
console.log('Message sent: %s', error);
167+
return reject(false);
168+
}
169+
console.log('Message sent: %s', info.messageId);
170+
resolve(true);
171+
});
172+
})
173+
174+
return sended;
175+
} else {
176+
throw new HttpException('REGISTER.USER_NOT_REGISTERED', HttpStatus.FORBIDDEN);
177+
}
178+
}
179+
180+
181+
182+
}

0 commit comments

Comments
 (0)