fix logs
This commit is contained in:
parent
5aed7d81ce
commit
6b9ad95811
@ -22,6 +22,7 @@ import { InvitationService } from '../services/invitation.service';
|
||||
import { InvitationsController } from '../controllers/invitations.controller';
|
||||
import { EmailModule } from '../../infrastructure/email/email.module';
|
||||
import { SubscriptionsModule } from '../subscriptions/subscriptions.module';
|
||||
import { AuditModule } from '../audit/audit.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -53,6 +54,9 @@ import { SubscriptionsModule } from '../subscriptions/subscriptions.module';
|
||||
|
||||
// Subscriptions module for license checks
|
||||
SubscriptionsModule,
|
||||
|
||||
// Audit module for login/logout tracking
|
||||
AuditModule,
|
||||
],
|
||||
controllers: [AuthController, InvitationsController],
|
||||
providers: [
|
||||
|
||||
@ -235,9 +235,11 @@ export class AuthService {
|
||||
* The revocation list lives in Redis with a TTL matching the token's
|
||||
* remaining lifetime, so entries clean themselves up.
|
||||
*/
|
||||
async logout(refreshToken?: string): Promise<void> {
|
||||
async logout(
|
||||
refreshToken?: string
|
||||
): Promise<{ userId: string; email: string; organizationId: string } | null> {
|
||||
if (!refreshToken) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
@ -248,9 +250,19 @@ export class AuthService {
|
||||
|
||||
await this.cache.set(this.revokedTokenKey(refreshToken), true, remainingSeconds);
|
||||
this.logger.log(`Refresh token revoked for user: ${payload?.email ?? 'unknown'}`);
|
||||
|
||||
if (payload?.sub) {
|
||||
return {
|
||||
userId: payload.sub,
|
||||
email: payload.email,
|
||||
organizationId: payload.organizationId,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
// Never block logout on revocation failures — log and continue
|
||||
this.logger.error(`Failed to revoke refresh token: ${error}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,6 +34,8 @@ import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||||
import { UserRepository, USER_REPOSITORY } from '@domain/ports/out/user.repository';
|
||||
import { UserMapper } from '../mappers/user.mapper';
|
||||
import { InvitationService } from '../services/invitation.service';
|
||||
import { AuditService } from '../services/audit.service';
|
||||
import { AuditAction } from '@domain/entities/audit-log.entity';
|
||||
import {
|
||||
AUTH_COOKIE_NAMES,
|
||||
authCookieOptions,
|
||||
@ -72,9 +74,26 @@ export class AuthController {
|
||||
private readonly authService: AuthService,
|
||||
@Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
|
||||
private readonly invitationService: InvitationService,
|
||||
private readonly auditService: AuditService,
|
||||
@Inject(EMAIL_PORT) private readonly emailService: EmailPort
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Extract the client IP and user agent from the request so we can record
|
||||
* *who* connected and *from where* in the audit trail.
|
||||
*/
|
||||
private getClientInfo(req: Request): { ipAddress?: string; userAgent?: string } {
|
||||
const forwardedFor = req.headers['x-forwarded-for'];
|
||||
const ipAddress =
|
||||
(Array.isArray(forwardedFor) ? forwardedFor[0] : forwardedFor?.split(',')[0]?.trim()) ||
|
||||
req.ip ||
|
||||
req.socket?.remoteAddress;
|
||||
return {
|
||||
ipAddress,
|
||||
userAgent: req.headers['user-agent'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deliver tokens as httpOnly cookies so they are out of reach of XSS.
|
||||
* When rememberMe is false the cookies are session-scoped (cleared when
|
||||
@ -208,18 +227,59 @@ export class AuthController {
|
||||
})
|
||||
async login(
|
||||
@Body() dto: LoginDto,
|
||||
@Req() req: Request,
|
||||
@Res({ passthrough: true }) res: Response
|
||||
): Promise<AuthResponseDto> {
|
||||
const rememberMe = dto.rememberMe === true;
|
||||
const { ipAddress, userAgent } = this.getClientInfo(req);
|
||||
|
||||
try {
|
||||
const result = await this.authService.login(dto.email, dto.password, rememberMe);
|
||||
|
||||
this.setAuthCookies(res, result, rememberMe);
|
||||
|
||||
// Audit log: record who logged in, when and from where
|
||||
await this.auditService.logSuccess(
|
||||
AuditAction.USER_LOGIN,
|
||||
result.user.id,
|
||||
result.user.email,
|
||||
result.user.organizationId,
|
||||
{
|
||||
resourceType: 'user',
|
||||
resourceId: result.user.id,
|
||||
ipAddress,
|
||||
userAgent,
|
||||
metadata: { rememberMe },
|
||||
}
|
||||
);
|
||||
|
||||
this.logger.log(`Login success: ${result.user.email} from ${ipAddress ?? 'unknown IP'}`);
|
||||
|
||||
return {
|
||||
accessToken: result.accessToken,
|
||||
refreshToken: result.refreshToken,
|
||||
user: result.user,
|
||||
};
|
||||
} catch (error: any) {
|
||||
// Audit log: record failed login attempts (the attempted email is the
|
||||
// only identity we have — the credentials did not match a valid user)
|
||||
await this.auditService.logFailure(
|
||||
AuditAction.USER_LOGIN,
|
||||
'unknown',
|
||||
dto.email,
|
||||
'unknown',
|
||||
error?.message || 'Invalid credentials',
|
||||
{
|
||||
resourceType: 'user',
|
||||
ipAddress,
|
||||
userAgent,
|
||||
}
|
||||
);
|
||||
|
||||
this.logger.warn(`Login failed for ${dto.email} from ${ipAddress ?? 'unknown IP'}`);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,9 +362,22 @@ export class AuthController {
|
||||
): Promise<{ message: string }> {
|
||||
const refreshToken = req.cookies?.[AUTH_COOKIE_NAMES.refreshToken];
|
||||
|
||||
await this.authService.logout(refreshToken);
|
||||
const loggedOutUser = await this.authService.logout(refreshToken);
|
||||
this.clearAuthCookies(res);
|
||||
|
||||
// Audit log: record who logged out and when
|
||||
if (loggedOutUser) {
|
||||
const { ipAddress, userAgent } = this.getClientInfo(req);
|
||||
await this.auditService.logSuccess(
|
||||
AuditAction.USER_LOGOUT,
|
||||
loggedOutUser.userId,
|
||||
loggedOutUser.email,
|
||||
loggedOutUser.organizationId,
|
||||
{ resourceType: 'user', resourceId: loggedOutUser.userId, ipAddress, userAgent }
|
||||
);
|
||||
this.logger.log(`Logout: ${loggedOutUser.email} from ${ipAddress ?? 'unknown IP'}`);
|
||||
}
|
||||
|
||||
return { message: 'Logout successful' };
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user