feat: session lock stores IP + timestamp for debugging

- SessionService stores JSON { memberId, ip, lockedAt } instead of plain memberId
- Auth controller extracts client IP from x-forwarded-for header
- Lockout error message includes IP of blocking device

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 13:21:13 +05:30
parent 77c5335955
commit a35a7d70bf
2 changed files with 25 additions and 9 deletions

View File

@@ -1,4 +1,5 @@
import { Controller, Post, Body, Headers, Logger, HttpException } from '@nestjs/common';
import { Controller, Post, Body, Headers, Req, Logger, HttpException } from '@nestjs/common';
import type { Request } from 'express';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { OzonetelAgentService } from '../ozonetel/ozonetel-agent.service';
@@ -24,7 +25,7 @@ export class AuthController {
}
@Post('login')
async login(@Body() body: { email: string; password: string }) {
async login(@Body() body: { email: string; password: string }, @Req() req: Request) {
this.logger.log(`Login attempt for ${body.email}`);
try {
@@ -128,13 +129,15 @@ export class AuthController {
}
// Check for duplicate login — strict: one device only
const existingSession = await this.sessionService.isSessionLocked(agentConfig.ozonetelAgentId);
const clientIp = (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ?? req.ip ?? 'unknown';
const existingSession = await this.sessionService.getSession(agentConfig.ozonetelAgentId);
if (existingSession) {
throw new HttpException('You are already logged in on another device. Please log out there first.', 409);
this.logger.warn(`Duplicate login blocked for ${body.email} — session held by IP ${existingSession.ip} since ${existingSession.lockedAt}`);
throw new HttpException(`You are already logged in from another device (${existingSession.ip}). Please log out there first.`, 409);
}
// Lock session in Redis
await this.sessionService.lockSession(agentConfig.ozonetelAgentId, memberId);
// Lock session in Redis with IP
await this.sessionService.lockSession(agentConfig.ozonetelAgentId, memberId, clientIp);
// Login to Ozonetel with agent-specific credentials
const ozAgentPassword = process.env.OZONETEL_AGENT_PASSWORD ?? 'Test123$';