Migrar backend a PostgreSQL + Node.js/Express con nuevas funcionalidades
Backend (water-api/): - Crear API REST completa con Express + TypeScript - Implementar autenticación JWT con refresh tokens - CRUD completo para: projects, concentrators, meters, gateways, devices, users, roles - Agregar validación con Zod para todas las entidades - Implementar webhooks para The Things Stack (LoRaWAN) - Agregar endpoint de lecturas con filtros y resumen de consumo - Implementar carga masiva de medidores via Excel (.xlsx) Frontend: - Crear cliente HTTP con manejo automático de JWT y refresh - Actualizar todas las APIs para usar nuevo backend - Agregar sistema de autenticación real (login, logout, me) - Agregar selector de tipo (LORA, LoRaWAN, Grandes) en concentradores y medidores - Agregar campo Meter ID en medidores - Crear modal de carga masiva para medidores - Agregar página de consumo con gráficas y filtros - Corregir carga de proyectos independiente de datos existentes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
137
water-api/src/utils/jwt.ts
Normal file
137
water-api/src/utils/jwt.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
||||
import config from '../config';
|
||||
import logger from './logger';
|
||||
|
||||
interface TokenPayload {
|
||||
id: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an access token
|
||||
* @param payload - Data to encode in the token
|
||||
* @returns Signed JWT access token
|
||||
*/
|
||||
export const generateAccessToken = (payload: TokenPayload): string => {
|
||||
const options: SignOptions = {
|
||||
expiresIn: config.jwt.accessTokenExpiresIn as SignOptions['expiresIn'],
|
||||
algorithm: 'HS256',
|
||||
};
|
||||
|
||||
try {
|
||||
const token = jwt.sign(payload, config.jwt.accessTokenSecret, options);
|
||||
return token;
|
||||
} catch (error) {
|
||||
logger.error('Error generating access token', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw new Error('Failed to generate access token');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a refresh token
|
||||
* @param payload - Data to encode in the token
|
||||
* @returns Signed JWT refresh token
|
||||
*/
|
||||
export const generateRefreshToken = (payload: TokenPayload): string => {
|
||||
const options: SignOptions = {
|
||||
expiresIn: config.jwt.refreshTokenExpiresIn as SignOptions['expiresIn'],
|
||||
algorithm: 'HS256',
|
||||
};
|
||||
|
||||
try {
|
||||
const token = jwt.sign(payload, config.jwt.refreshTokenSecret, options);
|
||||
return token;
|
||||
} catch (error) {
|
||||
logger.error('Error generating refresh token', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw new Error('Failed to generate refresh token');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify an access token
|
||||
* @param token - JWT access token to verify
|
||||
* @returns Decoded payload if valid, null if invalid or expired
|
||||
*/
|
||||
export const verifyAccessToken = (token: string): JwtPayload | null => {
|
||||
const options: VerifyOptions = {
|
||||
algorithms: ['HS256'],
|
||||
};
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(
|
||||
token,
|
||||
config.jwt.accessTokenSecret,
|
||||
options
|
||||
) as JwtPayload;
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
logger.debug('Access token expired');
|
||||
} else if (error instanceof jwt.JsonWebTokenError) {
|
||||
logger.debug('Invalid access token', {
|
||||
error: error.message,
|
||||
});
|
||||
} else {
|
||||
logger.error('Error verifying access token', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify a refresh token
|
||||
* @param token - JWT refresh token to verify
|
||||
* @returns Decoded payload if valid, null if invalid or expired
|
||||
*/
|
||||
export const verifyRefreshToken = (token: string): JwtPayload | null => {
|
||||
const options: VerifyOptions = {
|
||||
algorithms: ['HS256'],
|
||||
};
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(
|
||||
token,
|
||||
config.jwt.refreshTokenSecret,
|
||||
options
|
||||
) as JwtPayload;
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
logger.debug('Refresh token expired');
|
||||
} else if (error instanceof jwt.JsonWebTokenError) {
|
||||
logger.debug('Invalid refresh token', {
|
||||
error: error.message,
|
||||
});
|
||||
} else {
|
||||
logger.error('Error verifying refresh token', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a token without verification (for debugging)
|
||||
* @param token - JWT token to decode
|
||||
* @returns Decoded payload or null
|
||||
*/
|
||||
export const decodeToken = (token: string): JwtPayload | null => {
|
||||
try {
|
||||
const decoded = jwt.decode(token) as JwtPayload | null;
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
logger.error('Error decoding token', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user