initial commit

This commit is contained in:
2026-06-09 14:07:37 +03:30
parent 30ac533800
commit 996a4fcda7
121 changed files with 20557 additions and 3 deletions

158
src/auth/auth.service.ts Normal file
View File

@@ -0,0 +1,158 @@
import {
ForbiddenException,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { AuditEvent } from '../common/enums/audit-event.enum';
import { AuditService } from '../audit/audit.service';
import { UsersService, RequestContext } from '../users/users.service';
import { PasswordService } from './services/password.service';
import { TokenService } from './services/token.service';
import { LoginDto } from './dto/login.dto';
import { AuthTokensDto } from './dto/auth-tokens.dto';
import { ProfileDto } from './dto/profile.dto';
import { UserMapper } from '../users/mappers/user.mapper';
import { JwtPayload } from './interfaces/jwt-payload.interface';
import { AuthenticatedUser } from './interfaces/authenticated-user.interface';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly passwordService: PasswordService,
private readonly tokenService: TokenService,
private readonly auditService: AuditService,
) {}
async login(dto: LoginDto, context: RequestContext = {}): Promise<AuthTokensDto> {
const user = await this.usersService.findByUsername(dto.username, true);
if (!user) {
await this.auditService.log(AuditEvent.LOGIN_FAILED, {
username: dto.username,
ip: context.ip,
userAgent: context.userAgent,
metadata: { reason: 'invalid_credentials' },
});
throw new UnauthorizedException('Invalid credentials');
}
if (!user.isActive) {
await this.auditFailedLogin(user._id.toString(), user.username, 'inactive', context);
throw new ForbiddenException('Account is inactive');
}
if (user.isBlocked) {
await this.auditFailedLogin(user._id.toString(), user.username, 'blocked', context);
throw new ForbiddenException('Account is blocked');
}
const valid = await this.passwordService.compare(dto.password, user.password);
if (!valid) {
await this.auditFailedLogin(user._id.toString(), user.username, 'invalid_credentials', context);
throw new UnauthorizedException('Invalid credentials');
}
const tokens = await this.issueTokens(user);
await this.usersService.saveRefreshToken(user._id.toString(), tokens.refreshToken);
await this.usersService.recordLogin(user._id.toString());
await this.auditService.log(AuditEvent.LOGIN, {
userId: user._id.toString(),
username: user.username,
ip: context.ip,
userAgent: context.userAgent,
});
return tokens;
}
async refresh(refreshToken: string, context: RequestContext = {}): Promise<AuthTokensDto> {
let payload: { sub: string };
try {
payload = this.tokenService.verifyRefreshToken(refreshToken);
} catch {
throw new UnauthorizedException('Invalid refresh token');
}
const valid = await this.usersService.validateRefreshToken(payload.sub, refreshToken);
if (!valid) {
throw new UnauthorizedException('Invalid refresh token');
}
const user = await this.usersService.findById(payload.sub);
if (!user.isActive) {
throw new ForbiddenException('Account is inactive');
}
if (user.isBlocked) {
throw new ForbiddenException('Account is blocked');
}
const tokens = await this.issueTokens(user);
await this.usersService.saveRefreshToken(user._id.toString(), tokens.refreshToken);
await this.auditService.log(AuditEvent.LOGIN, {
userId: user._id.toString(),
username: user.username,
ip: context.ip,
userAgent: context.userAgent,
metadata: { type: 'refresh' },
});
return tokens;
}
async logout(userId: string, context: RequestContext = {}): Promise<void> {
await this.usersService.clearRefreshToken(userId);
const user = await this.usersService.findById(userId);
await this.auditService.log(AuditEvent.LOGOUT, {
userId,
username: user.username,
ip: context.ip,
userAgent: context.userAgent,
});
}
async getProfile(userId: string): Promise<ProfileDto> {
const user = await this.usersService.findById(userId);
return UserMapper.toProfileDto(user);
}
private async issueTokens(user: {
_id: { toString(): string };
role: JwtPayload['role'];
clientType: JwtPayload['clientType'];
appName: string;
}): Promise<AuthTokensDto> {
const payload: JwtPayload = {
sub: user._id.toString(),
role: user.role,
clientType: user.clientType,
appName: user.appName,
};
const pair = this.tokenService.generateTokenPair(payload);
return {
accessToken: pair.accessToken,
refreshToken: pair.refreshToken,
tokenType: 'Bearer',
expiresIn: 900,
};
}
private async auditFailedLogin(
userId: string,
username: string,
reason: string,
context: RequestContext,
): Promise<void> {
await this.auditService.log(AuditEvent.LOGIN_FAILED, {
userId,
username,
ip: context.ip,
userAgent: context.userAgent,
metadata: { reason },
});
}
}