homelab-personal-cloud/core/src/http/mvc/login/access-attempt.service.ts

121 lines
3.8 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { Request } from 'express';
import { ConfigService } from '../../../config/config.service';
import { SystemSettings } from '../../../domain/system-settings.types';
import { AuthAccessAttemptDao } from '../../../persistence/auth-access-attempt.dao';
import { CacheService } from '../../../cache/cache.service';
export enum AccessAttemptError {
}
interface RequestData {
userAgent: string;
requestPath: string;
ip: string;
ipAddressKey: string;
usernameKey: string;
}
@Injectable()
export class AccessAttemptService {
private readonly logger: Logger = new Logger(AccessAttemptService.name);
private readonly maxAttemptsAllowed: number;
private readonly lockTimeout: number;
constructor(
private readonly cacheService: CacheService,
private readonly accessAttemptService: AuthAccessAttemptDao,
configService: ConfigService,
) {
this.maxAttemptsAllowed = configService.get(SystemSettings.Auth.AccessAttempts.MaxAttempts);
this.lockTimeout = configService.get(SystemSettings.Auth.AccessAttempts.Timeout);
}
async checkIfLocked(request: Request, username: string): Promise<boolean> {
const { ipAddressKey, usernameKey, ip } = this.parseRequestData(request, username);
const ipAttempts = +(await this.cacheService.get(ipAddressKey) ?? 0);
const usernameAttempts = +(await this.cacheService.get(usernameKey) ?? 0);
if (ipAttempts >= this.maxAttemptsAllowed || usernameAttempts >= this.maxAttemptsAllowed) {
const newIpAttempts = (ipAttempts + 1).toString();
await this.cacheService.set(ipAddressKey, newIpAttempts, this.lockTimeout);
this.logger.debug(`${ip} now has ${newIpAttempts} failed attempts tracked`);
const newUsernameAttempts = (usernameAttempts + 1).toString();
await this.cacheService.set(usernameKey, newUsernameAttempts, this.lockTimeout);
this.logger.debug(`${username} now has ${newUsernameAttempts} failed attempts tracked`);
return true;
}
return false;
}
async recordAttempt(request: Request, username: string, loginSucceeded: boolean): Promise<{ isLocked: boolean }> {
const isLocked = await this.checkIfLocked(request, username);
if (isLocked) {
return { isLocked: true };
}
const {
userAgent,
requestPath,
ip,
ipAddressKey,
usernameKey,
} = this.parseRequestData(request, username);
await this.accessAttemptService.createAttempt({
userAgent,
username,
ip,
requestPath,
valid: loginSucceeded,
});
if (loginSucceeded) {
await this.cacheService.del(ipAddressKey);
await this.cacheService.del(usernameKey);
return { isLocked: false }
}
const ipAttempts = +(await this.cacheService.get(ipAddressKey) ?? 0);
const usernameAttempts = +(await this.cacheService.get(usernameKey) ?? 0);
const newIpAttempts = (ipAttempts + 1).toString();
await this.cacheService.set(ipAddressKey, newIpAttempts, this.lockTimeout);
this.logger.debug(`${ip} now has ${newIpAttempts} failed attempts tracked`);
const newUsernameAttempts = (usernameAttempts + 1).toString();
await this.cacheService.set(usernameKey, newUsernameAttempts, this.lockTimeout);
this.logger.debug(`${username} now has ${newUsernameAttempts} failed attempts tracked`);
return { isLocked: false };
}
private parseRequestData(request: Request, username: string): RequestData {
const userAgent = request.headers['user-agent'] ?? '';
const requestPath = request.path;
const ip = request.ip ?? '';
const ipAddressKey = `access-attempt:ip:${ip}`;
const usernameKey = `access-attempt:username:${username}`;
return {
userAgent,
requestPath,
ip,
ipAddressKey,
usernameKey,
}
}
}