File size: 2,093 Bytes
94ad3aa
 
 
 
4ec305b
 
 
 
 
 
 
 
 
 
 
94ad3aa
 
4ec305b
 
94ad3aa
 
 
 
 
 
4ec305b
b6ee67e
 
 
4ec305b
 
 
b6ee67e
4ec305b
b6ee67e
 
 
 
 
 
4ec305b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * Helpers para firmar y verificar tokens JWT.
 *
 * Responsabilidades:
 *   - signToken(payload)        β†’ firma un token HS256 con expiracion configurable.
 *                                 Incluye claim `jti` (UUID v4) para poder invalidarlo.
 *   - verifyToken(token)        β†’ verifica firma, expiracion y algoritmo (solo HS256).
 *   - addToDenylist(jti, exp)   β†’ anota el jti como invalidado hasta su expiracion.
 *   - isBlocked(jti)            β†’ true si el jti esta en la denylist.
 *   - pruneExpiredDenylist()    β†’ elimina entradas ya expiradas (llamado en cada logout).
 *
 * Denylist:
 *   - En memoria (Map<jti, expEpochMs>). Sin persistencia entre reinicios.
 *   - Aceptable porque el TTL del token es 1h; al reiniciar el servidor
 *     cualquier token antiguo expirara por si solo antes de que importe.
 *
 * Consumido por:
 *   - auth.service.js   β†’ signToken al hacer login; addToDenylist al hacer logout.
 *   - requireAuth.js    β†’ verifyToken + isBlocked en cada peticion protegida.
 *
 * Configuracion:
 *   - JWT_SECRET    : minimo 32 chars (validado en config.js).
 *   - JWT_EXPIRES_IN: default '1h'.
 */

import { randomUUID } from 'node:crypto';
import jwt from 'jsonwebtoken';
import { config } from '../config.js';

/** @type {Map<string, number>} jti β†’ expiry timestamp (ms) */
const denylist = new Map();

export const signToken = (payload) =>
  jwt.sign({ ...payload, jti: randomUUID() }, config.JWT_SECRET, {
    algorithm: 'HS256',
    expiresIn: config.JWT_EXPIRES_IN,
  });

export const verifyToken = (token) =>
  jwt.verify(token, config.JWT_SECRET, { algorithms: ['HS256'] });

export const addToDenylist = (jti, exp) => {
  pruneExpiredDenylist();
  denylist.set(jti, exp * 1000);
};

export const isBlocked = (jti) => {
  const exp = denylist.get(jti);
  if (exp === undefined) return false;
  if (Date.now() > exp) { denylist.delete(jti); return false; }
  return true;
};

export const pruneExpiredDenylist = () => {
  const now = Date.now();
  for (const [jti, exp] of denylist) {
    if (now > exp) denylist.delete(jti);
  }
};