feat: enhance authentication flow with device info and optional POS flag in login DTO

This commit is contained in:
2026-05-11 19:09:04 +03:30
parent 5e6bd33cdd
commit 2a4e778c31
5 changed files with 125 additions and 30 deletions
+32 -5
View File
@@ -57,17 +57,44 @@ async function bootstrap() {
// 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.
const defaultOrigins = []
const defaultOrigins: string[] = []
const envOrigins = process.env.CORS_ORIGINS
? process.env.CORS_ORIGINS.split(',')
.map(s => s.trim())
.filter(Boolean)
: []
const allowedOrigins = (envOrigins.length ? envOrigins : defaultOrigins).filter(
origin => origin !== '*',
)
const allowedOrigins = (envOrigins.length ? envOrigins : defaultOrigins).filter(Boolean)
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({
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,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
allowedHeaders:
+4 -1
View File
@@ -7,7 +7,10 @@ export class ApplicationAuthService {
constructor(private readonly authService: AuthService) {}
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
}
+4 -1
View File
@@ -1,3 +1,6 @@
import { LoginDto } from '@/modules/auth/dto/login.dto'
export class applicationLoginDto extends LoginDto {}
export class applicationLoginDto extends LoginDto {
device_name: string
device_id: string
}
+79 -22
View File
@@ -1,3 +1,4 @@
import { AccountInclude, AccountWhereUniqueInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable, NotFoundException } from '@nestjs/common'
import { ResponseMapper } from 'common/response/response-mapper'
@@ -5,6 +6,10 @@ import 'dotenv/config'
import { AuthUtils } from './auth.utils'
import { LoginDto } from './dto/login.dto'
interface DeviceInfoForLogin {
id: string
name: string
}
@Injectable()
export class AuthService {
constructor(
@@ -12,36 +17,88 @@ export class AuthService {
private authUtils: AuthUtils,
) {}
async login(dto: LoginDto, isPos = false, deviceId?: string) {
const { username, password } = dto
async login(dto: LoginDto, is_pos = false, device_info?: DeviceInfoForLogin) {
const { username, password, isPos } = dto
const account = await this.prisma.$transaction(async tx => {
const account = await tx.account.findUnique({
where: {
username,
},
})
let accountWhere: AccountWhereUniqueInput = {
username,
}
if (!account) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
let accountInclude: AccountInclude = {}
if (isPos) {
const posAccount = await tx.consumerAccount.findUnique({
where: {
account_id: account.id,
},
})
if (!posAccount) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
}
// console.error('isPos', isPos)
return account
// 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) {
throw new NotFoundException('اطلاعات ورودی شما اشتباه است')
}
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)
return ResponseMapper.create(preparedData)
}
+6 -1
View File
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsString } from 'class-validator'
import { IsBoolean, IsOptional, IsString } from 'class-validator'
export class LoginDto {
@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' })
@IsString()
password: string
@ApiProperty({ default: false })
@IsBoolean()
@IsOptional()
isPos: boolean = false
}