workspace-v1/shared/catalog/session-management/README.md
rckrdmrd cb4c0681d3 feat(workspace): Add new projects and update architecture
New projects created:
- michangarrito (marketplace mobile)
- template-saas (SaaS template)
- clinica-dental (dental ERP)
- clinica-veterinaria (veterinary ERP)

Architecture updates:
- Move catalog from core/ to shared/
- Add MCP servers structure and templates
- Add git management scripts
- Update SUBREPOSITORIOS.md with 15 new repos
- Update .gitignore for new projects

Repository infrastructure:
- 4 main repositories
- 11 subrepositorios
- Gitea remotes configured

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 04:43:28 -06:00

7.2 KiB

Gestión de Sesiones

Versión: 1.0.0 Origen: projects/gamilit Estado: Producción Última actualización: 2025-12-08


Descripción

Sistema de gestión de sesiones de usuario con:

  • Múltiples sesiones concurrentes (máx 5)
  • Tracking de dispositivo, IP, ubicación
  • Renovación automática con refresh tokens
  • Revocación individual y masiva
  • Limpieza automática de sesiones expiradas

Características

Característica Descripción
Multi-sesión Hasta 5 sesiones concurrentes por usuario
Auto-limpieza Sesiones más antiguas eliminadas automáticamente
Device Tracking IP, User-Agent, dispositivo, navegador, OS
Geo-location País y ciudad (si disponible)
Refresh Tokens Hasheados con SHA256
Revocación Individual o masiva
Multi-tenant Soporte para múltiples tenants

Stack Tecnológico

backend:
  framework: NestJS
  orm: TypeORM
  crypto: Node.js crypto (SHA256)

database:
  engine: PostgreSQL
  schemas:
    - auth_management (sesiones)

Dependencias NPM

{
  "typeorm": "^0.3.x",
  "@nestjs/typeorm": "^10.x"
}

Nota: crypto es nativo de Node.js, no requiere instalación.


Tabla Requerida

CREATE TABLE auth_management.user_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
    tenant_id UUID,
    session_token TEXT NOT NULL UNIQUE,
    refresh_token TEXT, -- Hasheado con SHA256
    user_agent TEXT,
    ip_address INET,
    device_type VARCHAR(50), -- desktop, mobile, tablet, unknown
    browser VARCHAR(100),
    os VARCHAR(100),
    country VARCHAR(100),
    city VARCHAR(100),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    last_activity_at TIMESTAMPTZ DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    revoked_at TIMESTAMPTZ,
    metadata JSONB DEFAULT '{}'
);

-- Índices
CREATE INDEX idx_user_sessions_user_id ON auth_management.user_sessions(user_id);
CREATE INDEX idx_user_sessions_tenant_id ON auth_management.user_sessions(tenant_id);
CREATE INDEX idx_user_sessions_session_token ON auth_management.user_sessions(session_token);
CREATE INDEX idx_user_sessions_expires_at ON auth_management.user_sessions(expires_at);

Estructura del Módulo

session-management/
├── services/
│   └── session-management.service.ts
├── entities/
│   └── user-session.entity.ts
├── dto/
│   ├── create-user-session.dto.ts
│   ├── update-user-session.dto.ts
│   └── user-session-response.dto.ts
└── __tests__/
    └── session-management.service.spec.ts

API del Servicio

class SessionManagementService {
  // Crear nueva sesión
  async createSession(dto: CreateUserSessionDto): Promise<UserSession>;

  // Validar sesión y actualizar actividad
  async validateSession(sessionId: string): Promise<UserSession | null>;

  // Renovar sesión
  async refreshSession(sessionId: string, newExpiresAt: Date): Promise<UserSession>;

  // Revocar sesión específica
  async revokeSession(sessionId: string, userId: string): Promise<{ message: string }>;

  // Revocar todas excepto la actual
  async revokeAllSessions(userId: string, currentSessionId: string): Promise<{ message: string; count: number }>;

  // Limpiar sesiones expiradas (cron)
  async cleanExpiredSessions(): Promise<number>;

  // Obtener sesiones activas del usuario
  async getSessions(userId: string): Promise<UserSession[]>;
}

Uso Rápido

1. Crear sesión al login

import { SessionManagementService } from '@/modules/auth/services';

// En AuthService.login()
const session = await this.sessionManagementService.createSession({
  user_id: user.id,
  tenant_id: profile.tenant_id,
  session_token: crypto.randomBytes(32).toString('hex'),
  refresh_token: refreshToken, // Será hasheado internamente
  ip_address: req.ip,
  user_agent: req.headers['user-agent'],
  device_type: this.detectDeviceType(userAgent),
  browser: this.detectBrowser(userAgent),
  os: this.detectOS(userAgent),
  expires_at: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 7 días
});

2. Obtener sesiones activas

// En UsersController
@Get('sessions')
@UseGuards(JwtAuthGuard)
async getSessions(@Request() req) {
  return this.sessionManagementService.getSessions(req.user.id);
}

3. Revocar sesión

// En UsersController
@Delete('sessions/:id')
@UseGuards(JwtAuthGuard)
async revokeSession(@Param('id') sessionId: string, @Request() req) {
  return this.sessionManagementService.revokeSession(sessionId, req.user.id);
}

4. Cerrar todas las sesiones

// En UsersController
@Post('sessions/revoke-all')
@UseGuards(JwtAuthGuard)
async revokeAllSessions(@Request() req, @Body() body: { currentSessionId: string }) {
  return this.sessionManagementService.revokeAllSessions(
    req.user.id,
    body.currentSessionId
  );
}

5. Cron job para limpieza

import { Cron, CronExpression } from '@nestjs/schedule';

@Cron(CronExpression.EVERY_HOUR)
async cleanExpiredSessions() {
  const count = await this.sessionManagementService.cleanExpiredSessions();
  this.logger.log(`Limpiadas ${count} sesiones expiradas`);
}

Comportamientos Clave

Límite de Sesiones

MAX_SESSIONS_PER_USER = 5;

// Al crear la 6ta sesión:
// 1. Se eliminan sesiones expiradas
// 2. Si aún hay 5+, se elimina la más antigua
// 3. Se crea la nueva sesión

Hasheo de Refresh Tokens

// El refresh token se hashea antes de guardar en BD
const hashedToken = crypto.createHash('sha256')
  .update(refreshToken)
  .digest('hex');

Validación de Propiedad

// Solo el dueño puede revocar sus sesiones
await this.sessionRepository.findOne({
  where: { id: sessionId, user_id: userId }, // Validación de ownership
});

Flujo de Sesiones

LOGIN
  │
  ├─► Crear sesión con tokens
  │   ├─► Limpiar expiradas
  │   ├─► Si >5, eliminar antigua
  │   └─► Guardar nueva sesión
  │
REFRESH
  │
  ├─► Validar refresh token
  ├─► Buscar sesión por hash
  └─► Actualizar expiración
  │
LOGOUT
  │
  └─► Marcar sesión como inactiva
      └─► Establecer revoked_at

Seguridad

  • Refresh tokens nunca se almacenan en texto plano
  • Validación de ownership en revocación
  • Soft delete con is_active = false y revoked_at
  • Limpieza periódica de datos obsoletos
  • No se serializa refresh_token en respuestas

Adaptaciones Necesarias

  1. Límite de sesiones: Ajustar MAX_SESSIONS_PER_USER según necesidades
  2. Tiempo de expiración: Configurar según política de seguridad
  3. Geo-location: Implementar si se requiere país/ciudad
  4. Multi-tenant: Omitir tenant_id si no aplica
  5. Cron schedule: Ajustar frecuencia de limpieza

Referencias

  • Código completo: projects/gamilit/apps/backend/src/modules/auth/services/session-management.service.ts
  • Entity: projects/gamilit/apps/backend/src/modules/auth/entities/user-session.entity.ts

Mantenido por: Sistema NEXUS Proyecto origen: Gamilit Platform