This commit is contained in:
2026-03-10 12:21:25 +03:30
parent ef2f4bf796
commit 0c0774bdca
19 changed files with 26 additions and 657 deletions
-2
View File
@@ -9,7 +9,6 @@ import { TokensService } from './services/tokens.service';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { AdminAuthGuard } from './guards/adminAuth.guard';
import { RefreshToken } from './entities/refresh-token.entity';
import { NotificationsModule } from '../notifications/notifications.module';
import { BusinessModule } from '../business/business.module';
@Module({
@@ -32,7 +31,6 @@ import { BusinessModule } from '../business/business.module';
forwardRef(() => AdminModule),
forwardRef(() => BusinessModule),
MikroOrmModule.forFeature([RefreshToken]),
forwardRef(() => NotificationsModule),
BusinessModule
],
controllers: [AuthController],
@@ -1,12 +1,7 @@
import { Controller, Post, Body, UseGuards } from '@nestjs/common';
import { AuthService } from '../services/auth.service';
import { RequestOtpDto } from '../dto/request-otp.dto';
import { DirectLoginDto } from '../dto/direct-login.dto';
import { ApiTags, ApiOperation, ApiBody } from '@nestjs/swagger';
import { VerifyOtpDto } from '../dto/verify-otp.dto';
import { Throttle } from '@nestjs/throttler';
import { RefreshTokenRateLimit } from 'src/common/decorators/rate-limit.decorator';
import { RefreshTokenDto } from '../dto/refresh-token.dto';
import { SuperAdminAuthGuard } from '../guards/superAdminAuth.guard';
@ApiTags('auth')
@@ -2,13 +2,9 @@ import { Entity, Enum, Index, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
export enum RefreshTokenType {
USER = 'user',
ADMIN = 'admin',
}
@Entity({ tableName: 'refreshtokens' })
@Index({ properties: ['ownerId', 'restId', 'type'] })
@Index({ properties: ['adminId', 'businessId'] })
@Index({ properties: ['hashedToken'] })
@Index({ properties: ['expiresAt'] })
export class RefreshToken extends BaseEntity {
@@ -16,13 +12,10 @@ export class RefreshToken extends BaseEntity {
hashedToken!: string;
@Property()
ownerId!: string;
adminId!: string;
@Property()
restId!: string;
@Enum(() => RefreshTokenType)
type!: RefreshTokenType;
businessId!: string;
@Property({ type: 'timestamptz' })
expiresAt!: Date;
+7 -18
View File
@@ -1,28 +1,14 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { RequestOtpDto } from '../dto/request-otp.dto';
import { CacheService } from '../../utils/cache.service';
import { SmsService } from '../../notifications/services/sms.service';
import { randomInt } from 'crypto';
import { Injectable } from '@nestjs/common';
import { TokensService } from './tokens.service';
import { AuthMessage, RestMessage } from 'src/common/enums/message.enum';
import { AdminRepository } from 'src/modules/admin/repositories/admin.repository';
import { ConfigService } from '@nestjs/config';
import { normalizePhone } from '../../utils/phone.util';
import { BusinessService } from 'src/modules/business/business.service';
@Injectable()
export class AuthService {
readonly ADMIN_PERMISSIONS_KEY = 'admin-perms';
private OTP_EXPIRATION_TIME: number;
constructor(
private readonly cacheService: CacheService,
private readonly smsService: SmsService,
private readonly tokensService: TokensService,
private readonly adminRepository: AdminRepository,
private readonly configService: ConfigService,
private readonly businessService: BusinessService,
) {
this.OTP_EXPIRATION_TIME = this.configService.get<number>('OTP_EXPIRATION_TIME') ?? 240;
}
// private userOtpKey(restaurantSlug: string, phone: string) {
@@ -33,8 +19,11 @@ export class AuthService {
// return `otp-admin:${restaurantSlug}:${phone}`;
// }
directLogin(danakSubId: string) {
const business=this.businessService.findBySubIdOrFail(danakSubId)
async directLogin(danakSubId: string) {
const business = await this.businessService.findBySubIdOrFail(danakSubId)
const admin = business.admin
const tokens = await this.tokensService.generateAccessAndRefreshToken(admin.id, business.id);
return tokens
}
// async requestOtp(dto: RequestOtpDto, isAdmin: boolean) {
+13 -28
View File
@@ -3,13 +3,12 @@ import { EntityManager } from '@mikro-orm/postgresql';
import { LockMode } from '@mikro-orm/core';
import { Injectable, Logger, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthMessage } from '../../../common/enums/message.enum';
import { RefreshToken } from '../entities/refresh-token.entity';
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
import { JwtService } from '@nestjs/jwt';
import dayjs from 'dayjs';
import { AuthMessage } from '../../../common/enums/message.enum';
import { RefreshToken, RefreshTokenType } from '../entities/refresh-token.entity';
import { IAdminTokenPayload, ITokenPayload } from '../interfaces/IToken-payload';
@Injectable()
export class TokensService {
private readonly logger = new Logger(TokensService.name);
@@ -17,28 +16,23 @@ export class TokensService {
private readonly configService: ConfigService,
private readonly jwtService: JwtService,
private readonly em: EntityManager,
) {}
) { }
async generateAccessAndRefreshToken(
ownerId: string,
restaurantId: string,
isAdmin: boolean,
slug: string,
adminId: string,
businessId: string,
em?: EntityManager,
) {
const refreshExpire = this.configService.getOrThrow<number>('REFRESH_TOKEN_EXPIRE');
const accessExpire = this.configService.getOrThrow<number>('JWT_EXPIRATION_TIME');
const payload: ITokenPayload | IAdminTokenPayload = isAdmin
? { adminId: ownerId, restId: restaurantId }
: { userId: ownerId, restId: restaurantId, slug };
const payload: IAdminTokenPayload = { adminId: adminId, restId: businessId }
const accessToken = await this.generateAccessToken(payload, accessExpire);
const refreshToken = this.generateRefreshToken();
const type = isAdmin ? RefreshTokenType.ADMIN : RefreshTokenType.USER;
// Only pass em if it's a transaction manager (not this.em), otherwise pass undefined to trigger flush
await this.storeRefreshToken(ownerId, restaurantId, refreshToken, type, em);
await this.storeRefreshToken(adminId, businessId, refreshToken, em);
return {
accessToken: { token: accessToken, expire: dayjs().add(accessExpire, 'seconds').valueOf() },
@@ -57,7 +51,6 @@ export class TokensService {
ownerId: string,
restId: string,
refreshToken: string,
type: RefreshTokenType,
em?: EntityManager,
) {
const entityManager = em || this.em;
@@ -68,9 +61,8 @@ export class TokensService {
const token = entityManager.create(RefreshToken, {
hashedToken,
ownerId,
restId,
type,
adminId: ownerId,
businessId: restId,
expiresAt,
});
@@ -104,20 +96,13 @@ export class TokensService {
}
// Store token data before removal
const ownerId = token.ownerId;
const restId = token.restId;
const isAdmin = token.type === RefreshTokenType.ADMIN;
// Verify restaurant still exists
// const restaurant = await em.findOne(Restaurant, { id: restId });
// if (!restaurant) {
// throw new UnauthorizedException(AuthMessage.INVALID_REFRESH_TOKEN);
// }
const adminId = token.adminId;
const businessId = token.businessId;
try {
// Generate new tokens first (before removing old token)
// Pass the transaction EntityManager to ensure operations are within the transaction
const tokens = await this.generateAccessAndRefreshToken(ownerId, "restaurant.id", isAdmin, "restaurant.slug", em);
const tokens = await this.generateAccessAndRefreshToken(adminId, businessId, em);
// Remove old token only after new token is created
em.remove(token);