Audit table with better data
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthenticatedRequest } from '../middleware/auth.middleware';
|
||||
import type { AuthenticatedRequest } from '../types';
|
||||
import * as authService from '../services/auth.service';
|
||||
import { LoginInput, RefreshInput } from '../validators/auth.validator';
|
||||
import { createAuditLog, getIpAddress, getUserAgent } from '../services/audit.service';
|
||||
@@ -106,7 +106,7 @@ export async function refresh(req: Request, res: Response): Promise<void> {
|
||||
*/
|
||||
export async function logout(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
const userId = req.user?.userId;
|
||||
|
||||
if (!userId) {
|
||||
res.status(401).json({ success: false, error: 'Authentication required' });
|
||||
@@ -121,12 +121,12 @@ export async function logout(req: AuthenticatedRequest, res: Response): Promise<
|
||||
|
||||
if (req.user) {
|
||||
createAuditLog({
|
||||
userId: req.user.id,
|
||||
userId: req.user.userId,
|
||||
userEmail: req.user.email,
|
||||
userName: req.user.role || req.user.email,
|
||||
userName: req.user.roleName || req.user.email,
|
||||
action: 'LOGOUT',
|
||||
tableName: 'users',
|
||||
recordId: req.user.id,
|
||||
recordId: req.user.userId,
|
||||
description: `User logged out`,
|
||||
ipAddress: getIpAddress(req),
|
||||
userAgent: getUserAgent(req),
|
||||
@@ -150,7 +150,7 @@ export async function logout(req: AuthenticatedRequest, res: Response): Promise<
|
||||
*/
|
||||
export async function getMe(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
const userId = req.user?.userId;
|
||||
|
||||
if (!userId) {
|
||||
res.status(401).json({ success: false, error: 'Authentication required' });
|
||||
|
||||
@@ -18,18 +18,141 @@ function methodToAction(method: string): AuditAction {
|
||||
}
|
||||
}
|
||||
|
||||
function extractTableName(path: string): string {
|
||||
const segments = path.replace(/^\/api\//, '').split('/');
|
||||
let tableName = segments[0] || 'unknown';
|
||||
function extractSection(path: string): string {
|
||||
const cleanPath = path.replace(/^\/api\//, '').replace(/^\//, '');
|
||||
const segments = cleanPath.split('/').filter(s => s.length > 0);
|
||||
|
||||
const tableMapping: Record<string, string> = {
|
||||
if (segments.length === 0) return 'general';
|
||||
|
||||
const sectionMapping: Record<string, string> = {
|
||||
'auth': 'authentication',
|
||||
'me': 'profile',
|
||||
'users': 'user-management',
|
||||
'roles': 'role-management',
|
||||
'meters': 'meters',
|
||||
'concentrators': 'concentrators',
|
||||
'gateways': 'gateways',
|
||||
'devices': 'devices',
|
||||
'projects': 'projects',
|
||||
'readings': 'readings',
|
||||
'bulk-upload': 'bulk-operations',
|
||||
'audit-logs': 'audit',
|
||||
'webhooks': 'webhooks',
|
||||
};
|
||||
|
||||
return sectionMapping[segments[0]] || segments[0];
|
||||
}
|
||||
|
||||
function extractTableName(path: string): string {
|
||||
const cleanPath = path.replace(/^\/api\//, '').replace(/^\//, '');
|
||||
const segments = cleanPath.split('/').filter(s => s.length > 0);
|
||||
|
||||
if (segments.length === 0) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
const baseTableMapping: Record<string, string> = {
|
||||
'auth': 'users',
|
||||
'me': 'users',
|
||||
'users': 'users',
|
||||
'roles': 'roles',
|
||||
'meters': 'meters',
|
||||
'concentrators': 'concentrators',
|
||||
'gateways': 'gateways',
|
||||
'devices': 'devices',
|
||||
'projects': 'projects',
|
||||
'readings': 'readings',
|
||||
'webhooks': 'webhooks',
|
||||
'bulk-upload': 'bulk_operations',
|
||||
'audit-logs': 'audit_logs',
|
||||
};
|
||||
|
||||
return tableMapping[tableName] || tableName;
|
||||
const operations = new Set([
|
||||
'password',
|
||||
'stats',
|
||||
'health',
|
||||
'template',
|
||||
'summary',
|
||||
'statistics',
|
||||
'my-activity',
|
||||
'record',
|
||||
'refresh',
|
||||
'logout',
|
||||
'uplink',
|
||||
'join',
|
||||
'ack',
|
||||
'dev-eui',
|
||||
]);
|
||||
|
||||
const nestedResourceMapping: Record<string, string> = {
|
||||
'meters/readings': 'readings',
|
||||
'concentrators/meters': 'meters',
|
||||
'gateways/devices': 'devices',
|
||||
'projects/meters': 'meters',
|
||||
'projects/concentrators': 'concentrators',
|
||||
};
|
||||
|
||||
const firstSegment = segments[0];
|
||||
const baseTable = baseTableMapping[firstSegment] || firstSegment;
|
||||
|
||||
if (segments.length === 1) {
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
const secondSegment = segments[1];
|
||||
const thirdSegment = segments[2];
|
||||
|
||||
if (segments.length >= 3 && isUUID(secondSegment)) {
|
||||
const nestedKey = `${firstSegment}/${thirdSegment}`;
|
||||
|
||||
if (nestedResourceMapping[nestedKey]) {
|
||||
return nestedResourceMapping[nestedKey];
|
||||
}
|
||||
|
||||
if (operations.has(thirdSegment)) {
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
if (baseTableMapping[thirdSegment] || isPluralResourceName(thirdSegment)) {
|
||||
return baseTableMapping[thirdSegment] || thirdSegment;
|
||||
}
|
||||
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
if (firstSegment === 'bulk-upload' && secondSegment) {
|
||||
return baseTableMapping[secondSegment] || secondSegment;
|
||||
}
|
||||
|
||||
if (firstSegment === 'audit-logs') {
|
||||
if (secondSegment === 'record' && segments.length >= 4) {
|
||||
return segments[3];
|
||||
}
|
||||
return 'audit_logs';
|
||||
}
|
||||
|
||||
if (firstSegment === 'auth') {
|
||||
return 'users';
|
||||
}
|
||||
|
||||
if (firstSegment === 'devices' && secondSegment === 'dev-eui') {
|
||||
return 'devices';
|
||||
}
|
||||
|
||||
if (segments.length === 2 && isUUID(secondSegment)) {
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
return baseTable;
|
||||
}
|
||||
|
||||
function isUUID(str: string): boolean {
|
||||
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(str) || /^\d+$/.test(str);
|
||||
}
|
||||
|
||||
function isPluralResourceName(str: string): boolean {
|
||||
return str.endsWith('s') || str.endsWith('ies') || str.includes('-');
|
||||
}
|
||||
|
||||
function extractRecordId(req: Request): string | undefined {
|
||||
@@ -54,74 +177,157 @@ function shouldExclude(path: string): boolean {
|
||||
return EXCLUDED_PATHS.some(excluded => path.startsWith(excluded));
|
||||
}
|
||||
|
||||
function generateDescription(
|
||||
fullPath: string,
|
||||
action: AuditAction,
|
||||
tableName: string,
|
||||
recordId?: string,
|
||||
section?: string
|
||||
): string {
|
||||
const actionDescriptions: Record<AuditAction, string> = {
|
||||
'CREATE': 'Created',
|
||||
'UPDATE': 'Updated',
|
||||
'DELETE': 'Deleted',
|
||||
'READ': 'Viewed',
|
||||
'LOGIN': 'Logged in',
|
||||
'LOGOUT': 'Logged out',
|
||||
'EXPORT': 'Exported',
|
||||
'BULK_UPLOAD': 'Bulk uploaded',
|
||||
'STATUS_CHANGE': 'Changed status of',
|
||||
'PERMISSION_CHANGE': 'Changed permissions for',
|
||||
};
|
||||
|
||||
const tableLabels: Record<string, string> = {
|
||||
'users': 'user',
|
||||
'roles': 'role',
|
||||
'meters': 'meter',
|
||||
'concentrators': 'concentrator',
|
||||
'gateways': 'gateway',
|
||||
'devices': 'device',
|
||||
'projects': 'project',
|
||||
'readings': 'reading',
|
||||
'bulk_operations': 'bulk operation',
|
||||
};
|
||||
|
||||
const actionLabel = actionDescriptions[action] || action;
|
||||
const tableLabel = tableLabels[tableName] || tableName;
|
||||
|
||||
if (fullPath === '/api/me' || fullPath === '/me' || fullPath.endsWith('/auth/me')) {
|
||||
return 'Viewed own profile';
|
||||
}
|
||||
|
||||
if (fullPath.includes('/users') && action === 'READ' && !recordId) {
|
||||
return 'Viewed users list';
|
||||
}
|
||||
|
||||
if (fullPath.includes('/meters') && action === 'READ' && !recordId) {
|
||||
return 'Viewed meters list';
|
||||
}
|
||||
|
||||
if (fullPath.includes('/concentrators') && action === 'READ' && !recordId) {
|
||||
return 'Viewed concentrators list';
|
||||
}
|
||||
|
||||
if (fullPath.includes('/projects') && action === 'READ' && !recordId) {
|
||||
return 'Viewed projects list';
|
||||
}
|
||||
|
||||
if (fullPath.includes('/readings') && action === 'READ' && !recordId) {
|
||||
return 'Viewed readings list';
|
||||
}
|
||||
|
||||
if (recordId) {
|
||||
if (tableName === 'unknown' && section) {
|
||||
return `${actionLabel} record in ${section} (ID: ${recordId.substring(0, 8)}...)`;
|
||||
}
|
||||
return `${actionLabel} ${tableLabel} (ID: ${recordId.substring(0, 8)}...)`;
|
||||
}
|
||||
|
||||
if (tableName === 'unknown' && section) {
|
||||
return `${actionLabel} in ${section} section`;
|
||||
}
|
||||
|
||||
return `${actionLabel} ${tableLabel}`;
|
||||
}
|
||||
|
||||
export function auditMiddleware(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
console.log(`[AUDIT-MIDDLEWARE] Request received: ${req.method} ${req.path}`);
|
||||
|
||||
if (shouldExclude(req.path)) {
|
||||
console.log(`[AUDIT-MIDDLEWARE] Path excluded: ${req.path}`);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.path.startsWith('/api')) {
|
||||
console.log(`[AUDIT-MIDDLEWARE] Not an API path: ${req.path}`);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[AUDIT-MIDDLEWARE] Setting up audit tracking for: ${req.method} ${req.path}`);
|
||||
|
||||
const originalSend = res.send;
|
||||
const originalJson = res.json;
|
||||
let responseBody: any;
|
||||
let responseBody: unknown;
|
||||
|
||||
res.send = function (body: any): Response {
|
||||
res.send = function (body: unknown): Response {
|
||||
responseBody = body;
|
||||
return originalSend.call(this, body);
|
||||
};
|
||||
|
||||
res.json = function (body: any): Response {
|
||||
res.json = function (body: unknown): Response {
|
||||
responseBody = body;
|
||||
return originalJson.call(this, body);
|
||||
};
|
||||
|
||||
res.on('finish', async () => {
|
||||
try {
|
||||
console.log('🔍 [Audit] Response finished:', {
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
user: req.user ? req.user.email : 'NO USER',
|
||||
statusCode: res.statusCode
|
||||
});
|
||||
|
||||
if (!req.user) {
|
||||
console.log('⚠️ [Audit] Skipping - no authenticated user');
|
||||
if (!req.user || !req.user.userId || !req.user.email) {
|
||||
return;
|
||||
}
|
||||
|
||||
const action = methodToAction(req.method);
|
||||
const tableName = extractTableName(req.path);
|
||||
const fullPath = (req.originalUrl || req.url).split('?')[0];
|
||||
const section = extractSection(fullPath);
|
||||
const tableName = extractTableName(fullPath);
|
||||
const recordId = extractRecordId(req);
|
||||
const success = res.statusCode >= 200 && res.statusCode < 400;
|
||||
|
||||
let description = `${req.method} ${req.path}`;
|
||||
let description = generateDescription(fullPath, action, tableName, recordId, section);
|
||||
|
||||
if (req.path.includes('/login')) {
|
||||
description = 'User logged in';
|
||||
} else if (req.path.includes('/logout')) {
|
||||
if (fullPath.includes('/login')) {
|
||||
description = 'User logged in successfully';
|
||||
} else if (fullPath.includes('/logout')) {
|
||||
description = 'User logged out';
|
||||
} else if (req.path.includes('/password')) {
|
||||
} else if (fullPath.includes('/password')) {
|
||||
description = 'Password changed';
|
||||
} else if (fullPath.includes('/bulk-upload')) {
|
||||
description = `Bulk upload for ${tableName}`;
|
||||
}
|
||||
|
||||
let newValues: unknown = undefined;
|
||||
let oldValues: unknown = undefined;
|
||||
|
||||
if (['POST', 'PUT', 'PATCH'].includes(req.method) && req.body && Object.keys(req.body).length > 0) {
|
||||
newValues = sanitizeData(req.body);
|
||||
}
|
||||
|
||||
const bodyData = (responseBody as Record<string, unknown>)?.data;
|
||||
if (req.method === 'GET' && recordId && bodyData && !Array.isArray(bodyData)) {
|
||||
newValues = sanitizeData(bodyData);
|
||||
}
|
||||
|
||||
if (req.method === 'DELETE' && bodyData) {
|
||||
oldValues = sanitizeData(bodyData);
|
||||
}
|
||||
|
||||
const userWithName = req.user as { name?: string; userName?: string; roleName: string; email: string };
|
||||
const userName = userWithName.name || userWithName.userName || userWithName.roleName || userWithName.email;
|
||||
|
||||
const auditData = {
|
||||
userId: req.user.userId,
|
||||
userEmail: req.user.email,
|
||||
userName: req.user.roleName || req.user.email,
|
||||
userName,
|
||||
action,
|
||||
tableName,
|
||||
recordId,
|
||||
@@ -129,21 +335,13 @@ export function auditMiddleware(
|
||||
ipAddress: getIpAddress(req),
|
||||
userAgent: getUserAgent(req),
|
||||
success,
|
||||
newValues: ['POST', 'PUT', 'PATCH'].includes(req.method)
|
||||
? sanitizeData(req.body)
|
||||
: undefined,
|
||||
errorMessage: !success && responseBody?.error
|
||||
? responseBody.error
|
||||
newValues: newValues as Record<string, unknown> | undefined,
|
||||
oldValues: oldValues as Record<string, unknown> | undefined,
|
||||
errorMessage: !success && typeof (responseBody as Record<string, unknown>)?.error === 'string'
|
||||
? (responseBody as Record<string, unknown>).error as string
|
||||
: undefined,
|
||||
};
|
||||
|
||||
console.log('✅ [Audit] Creating audit log...', {
|
||||
action,
|
||||
tableName,
|
||||
recordId,
|
||||
userEmail: req.user.email
|
||||
});
|
||||
|
||||
createAuditLog(auditData)
|
||||
.then(logId => {
|
||||
console.log('✅ [Audit] Log created successfully:', logId);
|
||||
@@ -161,16 +359,25 @@ export function auditMiddleware(
|
||||
next();
|
||||
}
|
||||
|
||||
function sanitizeData(data: any): any {
|
||||
if (!data || typeof data !== 'object') {
|
||||
function sanitizeData(data: unknown): unknown {
|
||||
if (!data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const sanitized = { ...data };
|
||||
if (typeof data !== 'object') {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map(item => sanitizeData(item));
|
||||
}
|
||||
|
||||
const sanitized: Record<string, unknown> = {};
|
||||
|
||||
const sensitiveFields = [
|
||||
'password',
|
||||
'password_hash',
|
||||
'passwordHash',
|
||||
'currentPassword',
|
||||
'newPassword',
|
||||
'token',
|
||||
@@ -179,13 +386,21 @@ function sanitizeData(data: any): any {
|
||||
'secret',
|
||||
'api_key',
|
||||
'apiKey',
|
||||
'credit_card',
|
||||
'creditCard',
|
||||
'ssn',
|
||||
'cvv',
|
||||
];
|
||||
|
||||
sensitiveFields.forEach(field => {
|
||||
if (field in sanitized) {
|
||||
sanitized[field] = '[REDACTED]';
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
if (sensitiveFields.includes(key)) {
|
||||
sanitized[key] = '[REDACTED]';
|
||||
} else if (value && typeof value === 'object') {
|
||||
sanitized[key] = sanitizeData(value);
|
||||
} else {
|
||||
sanitized[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { Response, NextFunction } from 'express';
|
||||
import { verifyAccessToken } from '../utils/jwt';
|
||||
|
||||
/**
|
||||
* Extended Request interface with authenticated user
|
||||
*/
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
};
|
||||
}
|
||||
import { AuthenticatedRequest } from '../types';
|
||||
|
||||
/**
|
||||
* Middleware to authenticate JWT access tokens
|
||||
@@ -47,9 +37,10 @@ export function authenticateToken(
|
||||
}
|
||||
|
||||
req.user = {
|
||||
id: decoded.id,
|
||||
email: decoded.email,
|
||||
role: decoded.role,
|
||||
userId: (decoded as any).userId || (decoded as any).id,
|
||||
email: (decoded as any).email,
|
||||
roleId: (decoded as any).roleId || (decoded as any).role,
|
||||
roleName: (decoded as any).roleName || (decoded as any).role,
|
||||
};
|
||||
|
||||
next();
|
||||
@@ -74,7 +65,7 @@ export function requireRole(...roles: string[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
if (!roles.includes(req.user.roleId) && !roles.includes(req.user.roleName)) {
|
||||
res.status(403).json({ error: 'Insufficient permissions' });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,15 +78,15 @@ export async function login(
|
||||
throw new Error('Invalid email or password');
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
const accessToken = generateAccessToken({
|
||||
id: user.id,
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role_name,
|
||||
roleId: user.id,
|
||||
roleName: user.role_name,
|
||||
});
|
||||
|
||||
const refreshToken = generateRefreshToken({
|
||||
id: user.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
// Hash and store refresh token
|
||||
@@ -136,7 +136,8 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
||||
// Hash token to check against database
|
||||
const hashedToken = hashToken(refreshToken);
|
||||
|
||||
// Find token in database
|
||||
const userId = (decoded as any).userId || (decoded as any).id;
|
||||
|
||||
const tokenResult = await query<{
|
||||
id: string;
|
||||
expires_at: Date;
|
||||
@@ -144,7 +145,7 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
||||
`SELECT id, expires_at FROM refresh_tokens
|
||||
WHERE token_hash = $1 AND user_id = $2 AND revoked_at IS NULL
|
||||
LIMIT 1`,
|
||||
[hashedToken, decoded.id]
|
||||
[hashedToken, userId]
|
||||
);
|
||||
|
||||
const storedToken = tokenResult.rows[0];
|
||||
@@ -175,7 +176,7 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
||||
JOIN roles r ON u.role_id = r.id
|
||||
WHERE u.id = $1 AND u.is_active = true
|
||||
LIMIT 1`,
|
||||
[decoded.id]
|
||||
[userId]
|
||||
);
|
||||
|
||||
const user = userResult.rows[0];
|
||||
@@ -184,11 +185,11 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
// Generate new access token
|
||||
const accessToken = generateAccessToken({
|
||||
id: user.id,
|
||||
userId: user.id,
|
||||
email: user.email,
|
||||
role: user.role_name,
|
||||
roleId: user.id,
|
||||
roleName: user.role_name,
|
||||
});
|
||||
|
||||
return { accessToken };
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import jwt, { JwtPayload, SignOptions, VerifyOptions } from 'jsonwebtoken';
|
||||
import jwt, { SignOptions, VerifyOptions } from 'jsonwebtoken';
|
||||
import config from '../config';
|
||||
import logger from './logger';
|
||||
import type { JwtPayload } from '../types';
|
||||
|
||||
interface TokenPayload {
|
||||
id: string;
|
||||
userId?: string;
|
||||
email?: string;
|
||||
roleId?: string;
|
||||
roleName?: string;
|
||||
id?: string;
|
||||
role?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
@@ -58,7 +62,7 @@ export const generateRefreshToken = (payload: TokenPayload): string => {
|
||||
* @param token - JWT access token to verify
|
||||
* @returns Decoded payload if valid, null if invalid or expired
|
||||
*/
|
||||
export const verifyAccessToken = (token: string): JwtPayload | null => {
|
||||
export const verifyAccessToken = (token: string): any => {
|
||||
const options: VerifyOptions = {
|
||||
algorithms: ['HS256'],
|
||||
};
|
||||
@@ -68,7 +72,7 @@ export const verifyAccessToken = (token: string): JwtPayload | null => {
|
||||
token,
|
||||
config.jwt.accessTokenSecret,
|
||||
options
|
||||
) as JwtPayload;
|
||||
);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
@@ -91,7 +95,7 @@ export const verifyAccessToken = (token: string): JwtPayload | null => {
|
||||
* @param token - JWT refresh token to verify
|
||||
* @returns Decoded payload if valid, null if invalid or expired
|
||||
*/
|
||||
export const verifyRefreshToken = (token: string): JwtPayload | null => {
|
||||
export const verifyRefreshToken = (token: string): any => {
|
||||
const options: VerifyOptions = {
|
||||
algorithms: ['HS256'],
|
||||
};
|
||||
@@ -101,7 +105,7 @@ export const verifyRefreshToken = (token: string): JwtPayload | null => {
|
||||
token,
|
||||
config.jwt.refreshTokenSecret,
|
||||
options
|
||||
) as JwtPayload;
|
||||
);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
if (error instanceof jwt.TokenExpiredError) {
|
||||
@@ -124,9 +128,9 @@ export const verifyRefreshToken = (token: string): JwtPayload | null => {
|
||||
* @param token - JWT token to decode
|
||||
* @returns Decoded payload or null
|
||||
*/
|
||||
export const decodeToken = (token: string): JwtPayload | null => {
|
||||
export const decodeToken = (token: string): any => {
|
||||
try {
|
||||
const decoded = jwt.decode(token) as JwtPayload | null;
|
||||
const decoded = jwt.decode(token);
|
||||
return decoded;
|
||||
} catch (error) {
|
||||
logger.error('Error decoding token', {
|
||||
|
||||
Reference in New Issue
Block a user