feat: enhance authentication flow with device info and optional POS flag in login DTO
This commit is contained in:
+32
-5
@@ -57,17 +57,44 @@ async function bootstrap() {
|
|||||||
|
|
||||||
// Enable CORS. You can set `CORS_ORIGINS` to a comma-separated list of allowed origins.
|
// Enable CORS. You can set `CORS_ORIGINS` to a comma-separated list of allowed origins.
|
||||||
// Defaults include common localhost origins used by front-end dev servers.
|
// Defaults include common localhost origins used by front-end dev servers.
|
||||||
const defaultOrigins = []
|
const defaultOrigins: string[] = []
|
||||||
const envOrigins = process.env.CORS_ORIGINS
|
const envOrigins = process.env.CORS_ORIGINS
|
||||||
? process.env.CORS_ORIGINS.split(',')
|
? process.env.CORS_ORIGINS.split(',')
|
||||||
.map(s => s.trim())
|
.map(s => s.trim())
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
: []
|
: []
|
||||||
const allowedOrigins = (envOrigins.length ? envOrigins : defaultOrigins).filter(
|
const allowedOrigins = (envOrigins.length ? envOrigins : defaultOrigins).filter(Boolean)
|
||||||
origin => origin !== '*',
|
|
||||||
)
|
const isOriginAllowed = (origin: string) => {
|
||||||
|
try {
|
||||||
|
const hostname = new URL(origin).hostname
|
||||||
|
return allowedOrigins.some(pattern => {
|
||||||
|
if (pattern === '*') return true
|
||||||
|
if (pattern.startsWith('*.')) {
|
||||||
|
const rootDomain = pattern.slice(2)
|
||||||
|
return hostname === rootDomain || hostname.endsWith(`.${rootDomain}`)
|
||||||
|
}
|
||||||
|
return hostname === pattern || origin === pattern
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return allowedOrigins.includes(origin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: allowedOrigins,
|
origin: (origin, callback) => {
|
||||||
|
if (!origin) {
|
||||||
|
callback(null, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOriginAllowed(origin)) {
|
||||||
|
callback(null, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(new Error('Not allowed by CORS'), false)
|
||||||
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
|
||||||
allowedHeaders:
|
allowedHeaders:
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ export class ApplicationAuthService {
|
|||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
async login(data: applicationLoginDto) {
|
async login(data: applicationLoginDto) {
|
||||||
const loginResponse = await this.authService.login(data, true)
|
const loginResponse = await this.authService.login(data, true, {
|
||||||
|
id: data.device_id,
|
||||||
|
name: data.device_name,
|
||||||
|
})
|
||||||
|
|
||||||
return loginResponse
|
return loginResponse
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
import { LoginDto } from '@/modules/auth/dto/login.dto'
|
import { LoginDto } from '@/modules/auth/dto/login.dto'
|
||||||
|
|
||||||
export class applicationLoginDto extends LoginDto {}
|
export class applicationLoginDto extends LoginDto {
|
||||||
|
device_name: string
|
||||||
|
device_id: string
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { AccountInclude, AccountWhereUniqueInput } from '@/generated/prisma/models'
|
||||||
import { PrismaService } from '@/prisma/prisma.service'
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
import { Injectable, NotFoundException } from '@nestjs/common'
|
import { Injectable, NotFoundException } from '@nestjs/common'
|
||||||
import { ResponseMapper } from 'common/response/response-mapper'
|
import { ResponseMapper } from 'common/response/response-mapper'
|
||||||
@@ -5,6 +6,10 @@ import 'dotenv/config'
|
|||||||
import { AuthUtils } from './auth.utils'
|
import { AuthUtils } from './auth.utils'
|
||||||
import { LoginDto } from './dto/login.dto'
|
import { LoginDto } from './dto/login.dto'
|
||||||
|
|
||||||
|
interface DeviceInfoForLogin {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -12,36 +17,88 @@ export class AuthService {
|
|||||||
private authUtils: AuthUtils,
|
private authUtils: AuthUtils,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async login(dto: LoginDto, isPos = false, deviceId?: string) {
|
async login(dto: LoginDto, is_pos = false, device_info?: DeviceInfoForLogin) {
|
||||||
const { username, password } = dto
|
const { username, password, isPos } = dto
|
||||||
|
|
||||||
const account = await this.prisma.$transaction(async tx => {
|
let accountWhere: AccountWhereUniqueInput = {
|
||||||
const account = await tx.account.findUnique({
|
|
||||||
where: {
|
|
||||||
username,
|
username,
|
||||||
},
|
}
|
||||||
|
|
||||||
|
let accountInclude: AccountInclude = {}
|
||||||
|
|
||||||
|
// console.error('isPos', isPos)
|
||||||
|
|
||||||
|
// if (isPos) {
|
||||||
|
// if (!device_info?.id) {
|
||||||
|
// throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
|
||||||
|
// }
|
||||||
|
// accountWhere = {
|
||||||
|
// ...accountWhere,
|
||||||
|
// consumer_account: {
|
||||||
|
// pos: {
|
||||||
|
// isNot: null,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// accountInclude = {
|
||||||
|
// ...accountInclude,
|
||||||
|
// consumer_account: {
|
||||||
|
// select: {
|
||||||
|
// id: true,
|
||||||
|
// account_device: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
const account = await this.prisma.account.findUnique({
|
||||||
|
where: accountWhere,
|
||||||
|
include: accountInclude,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
|
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPos) {
|
|
||||||
const posAccount = await tx.consumerAccount.findUnique({
|
|
||||||
where: {
|
|
||||||
account_id: account.id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (!posAccount) {
|
|
||||||
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return account
|
|
||||||
})
|
|
||||||
|
|
||||||
await this.authUtils.checkAuthPassword(password, account.password)
|
await this.authUtils.checkAuthPassword(password, account.password)
|
||||||
|
|
||||||
|
// if (isPos) {
|
||||||
|
// if (!account.consumer_account) {
|
||||||
|
// throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
|
||||||
|
// }
|
||||||
|
// const { account_device, id } = account.consumer_account as any
|
||||||
|
|
||||||
|
// if (account_device?.device_id !== device_info!.id) {
|
||||||
|
// throw new ForbiddenException(
|
||||||
|
// 'کاربر شما با دستگاه دیگری اجازه ورود دارد. لطفا با پشتیبانی تماس بگیرید',
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const device = await this.prisma.consumerAccountDevice.findUnique({
|
||||||
|
// where: {
|
||||||
|
// device_id: device_info!.id!,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (device) {
|
||||||
|
// throw new ForbiddenException(
|
||||||
|
// 'این دستگاه برای کاربر دیگری رجیستر شده است. لطفا با پشتیبانی تماس بگیرید.',
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// await this.prisma.consumerAccountDevice.create({
|
||||||
|
// data: {
|
||||||
|
// device_id: device_info!.id!,
|
||||||
|
// device_name: device_info!.name,
|
||||||
|
// consumer_account: {
|
||||||
|
// connect: {
|
||||||
|
// id,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
const preparedData = await this.authUtils.generateLoginResponse(account)
|
const preparedData = await this.authUtils.generateLoginResponse(account)
|
||||||
return ResponseMapper.create(preparedData)
|
return ResponseMapper.create(preparedData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger'
|
import { ApiProperty } from '@nestjs/swagger'
|
||||||
import { IsString } from 'class-validator'
|
import { IsBoolean, IsOptional, IsString } from 'class-validator'
|
||||||
|
|
||||||
export class LoginDto {
|
export class LoginDto {
|
||||||
@ApiProperty({ description: 'Mobile number used as username', example: '09120258156' })
|
@ApiProperty({ description: 'Mobile number used as username', example: '09120258156' })
|
||||||
@@ -9,4 +9,9 @@ export class LoginDto {
|
|||||||
@ApiProperty({ description: 'OTP password (one-time password)', example: '11111' })
|
@ApiProperty({ description: 'OTP password (one-time password)', example: '11111' })
|
||||||
@IsString()
|
@IsString()
|
||||||
password: string
|
password: string
|
||||||
|
|
||||||
|
@ApiProperty({ default: false })
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
isPos: boolean = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user