feat: add user createOne and refactor code
This commit is contained in:
@@ -1,6 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { HashingProvider } from './hashing.provider';
|
import { HashingProvider } from './hashing.provider';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
@@ -8,9 +5,9 @@ import * as bcrypt from 'bcrypt';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class BcryptProvider implements HashingProvider {
|
export class BcryptProvider implements HashingProvider {
|
||||||
public async hash(data: string | Buffer): Promise<string> {
|
public async hash(data: string | Buffer): Promise<string> {
|
||||||
const salt = (await bcrypt.genSalt()) as string;
|
const salt = await bcrypt.genSalt();
|
||||||
|
|
||||||
return (await bcrypt.hash(data, salt)) as string;
|
return await bcrypt.hash(data, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async compare(
|
public async compare(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"password": "Password"
|
"password": "Password",
|
||||||
|
"otp": "Otp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"fields": {
|
"fields": {
|
||||||
"phone": "شماره موبایل",
|
"phone": "شماره موبایل",
|
||||||
"password": "رمز عبور"
|
"password": "رمز عبور",
|
||||||
|
"otp": "کد تایید"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { OtpModule } from '../otp/otp.module';
|
|||||||
import { HashingModule } from '@/common/modules/hashing/hashing.module';
|
import { HashingModule } from '@/common/modules/hashing/hashing.module';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { LoginProvider } from './providers/login.provider';
|
||||||
|
import { GenerateTokenProvider } from './providers/generate-token.provider';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -25,6 +27,6 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
providers: [AuthService],
|
providers: [AuthService, GenerateTokenProvider, LoginProvider],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Transform } from 'class-transformer';
|
|||||||
import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator';
|
import { IsNotEmpty, IsOptional, IsString, Matches } from 'class-validator';
|
||||||
import { i18nValidationMessage as t } from 'nestjs-i18n';
|
import { i18nValidationMessage as t } from 'nestjs-i18n';
|
||||||
|
|
||||||
export class LoginDTO {
|
export class PhoneDTO {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: `User's Phone Number`,
|
description: `User's Phone Number`,
|
||||||
@@ -24,7 +24,9 @@ export class LoginDTO {
|
|||||||
})
|
})
|
||||||
@Transform(({ value }) => Utils.StringUtils.normalizePhone(value as string))
|
@Transform(({ value }) => Utils.StringUtils.normalizePhone(value as string))
|
||||||
phone: string;
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LoginDTO extends PhoneDTO {
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'Password for User Account',
|
description: 'Password for User Account',
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
import { PhoneDTO } from './login.dto';
|
||||||
|
import { i18nValidationMessage as t } from 'nestjs-i18n';
|
||||||
|
|
||||||
|
export class VerifyOtpDTO extends PhoneDTO {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty({
|
||||||
|
message: t('validation.requiredField', {
|
||||||
|
field: '$t(auth.fields.otp)',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
otp: string;
|
||||||
|
}
|
||||||
@@ -2,6 +2,6 @@ POST http://localhost:3000/users/login
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"phone": "09333026363",
|
"phone": "09126093033",
|
||||||
"password": "rad1.3.5"
|
"password": "rad1.3.5"
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface AccessTokenPayload {
|
||||||
|
sub: number;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RefreshTokenPayload {
|
||||||
|
sub: number;
|
||||||
|
jti: string;
|
||||||
|
}
|
||||||
@@ -1,88 +1,17 @@
|
|||||||
import { UsersService } from '@/modules/users/providers/users.service';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
|
||||||
import { LoginDTO } from '../dtos/login.dto';
|
import { LoginDTO } from '../dtos/login.dto';
|
||||||
import { OtpService } from '@/modules/otp/providers/otp.service';
|
import { LoginProvider } from './login.provider';
|
||||||
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import type { ConfigType } from '@nestjs/config';
|
|
||||||
import jwtConfig from '@/config/jwt.config';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(
|
constructor(
|
||||||
/**
|
/**
|
||||||
* Inject Users Service
|
* Inject Login Provider
|
||||||
*/
|
*/
|
||||||
private readonly usersService: UsersService,
|
private readonly loginProvider: LoginProvider,
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject OTP Service
|
|
||||||
*/
|
|
||||||
private readonly otpService: OtpService,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject Hashing Provider
|
|
||||||
*/
|
|
||||||
private readonly hashingProvider: HashingProvider,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject Jwt Service
|
|
||||||
*/
|
|
||||||
private readonly jwtService: JwtService,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inject Jwt Config
|
|
||||||
*/
|
|
||||||
@Inject(jwtConfig.KEY)
|
|
||||||
private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async logIn(loginDto: LoginDTO) {
|
public async logIn(loginDto: LoginDTO) {
|
||||||
const { phone, password } = loginDto;
|
return await this.loginProvider.logIn(loginDto);
|
||||||
const user = await this.usersService.findOneByPhone(phone);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
// TODO: Send OTP
|
|
||||||
const otpCode = await this.otpService.createOTP(phone);
|
|
||||||
|
|
||||||
return {
|
|
||||||
newUser: true,
|
|
||||||
message: `Otp sent to ${phone}`,
|
|
||||||
// Just for development
|
|
||||||
code: otpCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!password) {
|
|
||||||
return {
|
|
||||||
newUser: false,
|
|
||||||
message: 'Please send password',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPasswordCorrect = await this.hashingProvider.compare(
|
|
||||||
password,
|
|
||||||
user.password || '',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isPasswordCorrect) {
|
|
||||||
const tokenPayload = {
|
|
||||||
sub: user.id,
|
|
||||||
phone: user.phone,
|
|
||||||
};
|
|
||||||
|
|
||||||
const accessToken = await this.jwtService.signAsync(tokenPayload);
|
|
||||||
|
|
||||||
const refreshToken = await this.jwtService.signAsync(tokenPayload, {
|
|
||||||
secret: this.jwtConfiguration.refresh.secret,
|
|
||||||
expiresIn: this.jwtConfiguration.refresh.expiresIn,
|
|
||||||
});
|
|
||||||
|
|
||||||
return { access: accessToken, refresh: refreshToken };
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new UnauthorizedException(
|
|
||||||
'Entered phone number or password is wrong.',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import jwtConfig from '@/config/jwt.config';
|
||||||
|
import { User } from '@/modules/users/user.entity';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import type { ConfigType } from '@nestjs/config';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import {
|
||||||
|
AccessTokenPayload,
|
||||||
|
RefreshTokenPayload,
|
||||||
|
} from '../interfaces/jwt.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GenerateTokenProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Jwt Service
|
||||||
|
*/
|
||||||
|
private readonly jwtService: JwtService,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Jwt Config
|
||||||
|
*/
|
||||||
|
@Inject(jwtConfig.KEY)
|
||||||
|
private readonly jwtConfiguration: ConfigType<typeof jwtConfig>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async signToken<T>(
|
||||||
|
userId: number,
|
||||||
|
expiresIn: number,
|
||||||
|
secret?: string,
|
||||||
|
payload?: T,
|
||||||
|
) {
|
||||||
|
return await this.jwtService.signAsync(
|
||||||
|
{
|
||||||
|
sub: userId,
|
||||||
|
...payload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
audience: this.jwtConfiguration.audience,
|
||||||
|
issuer: this.jwtConfiguration.issuer,
|
||||||
|
secret,
|
||||||
|
expiresIn,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async generateTokens(user: User, jti: string) {
|
||||||
|
const [access, refresh] = await Promise.all([
|
||||||
|
// Sign Access Token
|
||||||
|
this.signToken<Partial<AccessTokenPayload>>(
|
||||||
|
user.id,
|
||||||
|
this.jwtConfiguration.access.expiresIn,
|
||||||
|
this.jwtConfiguration.access.secret,
|
||||||
|
{ phone: user.phone },
|
||||||
|
),
|
||||||
|
|
||||||
|
// Sign Refresh Token
|
||||||
|
this.signToken<Partial<RefreshTokenPayload>>(
|
||||||
|
user.id,
|
||||||
|
this.jwtConfiguration.refresh.expiresIn,
|
||||||
|
this.jwtConfiguration.refresh.secret,
|
||||||
|
{ jti },
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { access, refresh };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { LoginDTO } from '../dtos/login.dto';
|
||||||
|
import { UsersService } from '@/modules/users/providers/users.service';
|
||||||
|
import { OtpService } from '@/modules/otp/providers/otp.service';
|
||||||
|
import { HashingProvider } from '@/common/modules/hashing/providers/hashing.provider';
|
||||||
|
import { GenerateTokenProvider } from './generate-token.provider';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoginProvider {
|
||||||
|
constructor(
|
||||||
|
/**
|
||||||
|
* Inject Users Service
|
||||||
|
*/
|
||||||
|
private readonly usersService: UsersService,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject OTP Service
|
||||||
|
*/
|
||||||
|
private readonly otpService: OtpService,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject Hashing Provider
|
||||||
|
*/
|
||||||
|
private readonly hashingProvider: HashingProvider,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject GenerateToken Provider
|
||||||
|
*/
|
||||||
|
private readonly generateTokenProvider: GenerateTokenProvider,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async logIn(loginDto: LoginDTO) {
|
||||||
|
const { phone, password } = loginDto;
|
||||||
|
const user = await this.usersService.findOneByPhone(phone);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
// TODO: Send OTP
|
||||||
|
const otpCode = await this.otpService.createOTP(phone);
|
||||||
|
|
||||||
|
return {
|
||||||
|
newUser: true,
|
||||||
|
message: `Otp sent to ${phone}`,
|
||||||
|
// Just for development
|
||||||
|
code: otpCode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
return {
|
||||||
|
newUser: false,
|
||||||
|
message: 'Please send password',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPasswordCorrect = await this.hashingProvider.compare(
|
||||||
|
password,
|
||||||
|
user.password || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isPasswordCorrect) {
|
||||||
|
const jti = randomUUID();
|
||||||
|
return await this.generateTokenProvider.generateTokens(user, jti);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
'Entered phone number or password is wrong.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import { Injectable, RequestTimeoutException } from '@nestjs/common';
|
|||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { User } from '../user.entity';
|
import { User } from '../user.entity';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { CreateUserDTO } from '../dto/create-user.dto';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
@@ -25,8 +24,8 @@ export class UsersService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createOne(createUserDto: CreateUserDTO) {
|
public async createOne(phone: string) {
|
||||||
const newUser = this.userRepository.create(createUserDto);
|
const newUser = this.userRepository.create({ phone });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.userRepository.save(newUser);
|
await this.userRepository.save(newUser);
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import {
|
import { Controller } from '@nestjs/common';
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
ParseIntPipe,
|
|
||||||
Post,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { CreateUserDTO } from './dto/create-user.dto';
|
|
||||||
import { UsersService } from './providers/users.service';
|
import { UsersService } from './providers/users.service';
|
||||||
|
|
||||||
@Controller('users')
|
@Controller('users')
|
||||||
@@ -18,18 +10,18 @@ export class UsersController {
|
|||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
// @Get()
|
||||||
public getAllUsers() {
|
// public getAllUsers() {
|
||||||
return ['user1', 'user2'];
|
// return ['user1', 'user2'];
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Get(':id')
|
// @Get(':id')
|
||||||
public getUserById(@Param('id', ParseIntPipe) id: number) {
|
// public getUserById(@Param('id', ParseIntPipe) id: number) {
|
||||||
return `user ${id}`;
|
// return `user ${id}`;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@Post()
|
// @Post()
|
||||||
public async createUser(@Body() createUserDto: CreateUserDTO) {
|
// public async createUser(@Body() createUserDto: CreateUserDTO) {
|
||||||
return await this.usersService.createOne(createUserDto);
|
// return await this.usersService.createOne(createUserDto);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user