Initial commit
This commit is contained in:
222
src/integration/api-gateway/middleware/auth.middleware.ts
Normal file
222
src/integration/api-gateway/middleware/auth.middleware.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
// Zero-Trust Authentication Middleware
|
||||
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { sovereignIdentityFabric } from '@/sovereign/identity/sovereign-identity-fabric.service';
|
||||
import { hsmService } from '@/integration/hsm/hsm.service';
|
||||
import { DbisError, ErrorCode, JwtPayload } from '@/shared/types';
|
||||
import { logger } from '@/infrastructure/monitoring/logger';
|
||||
import { getEnv } from '@/shared/config/env-validator';
|
||||
|
||||
export interface AuthenticatedRequest extends Request {
|
||||
sovereignBankId?: string;
|
||||
identityType?: string;
|
||||
apiRole?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract Sovereign Identity Token (SIT) from Authorization header
|
||||
*/
|
||||
export function extractSitToken(req: Request): string | null {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('SOV-TOKEN ')) {
|
||||
return null;
|
||||
}
|
||||
return authHeader.substring(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify request signature (X-SOV-SIGNATURE)
|
||||
*/
|
||||
export async function verifyRequestSignature(
|
||||
req: Request,
|
||||
sovereignBankId: string,
|
||||
identityType: string
|
||||
): Promise<boolean> {
|
||||
const signature = req.headers['x-sov-signature'] as string;
|
||||
const timestamp = req.headers['x-sov-timestamp'] as string;
|
||||
const nonce = req.headers['x-sov-nonce'] as string;
|
||||
|
||||
if (!signature || !timestamp || !nonce) {
|
||||
logger.warn('Missing request signature headers', {
|
||||
sovereignBankId,
|
||||
identityType,
|
||||
hasSignature: !!signature,
|
||||
hasTimestamp: !!timestamp,
|
||||
hasNonce: !!nonce,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate timestamp (prevent replay attacks)
|
||||
const requestTime = parseInt(timestamp, 10);
|
||||
const currentTime = Date.now();
|
||||
const timeDiff = Math.abs(currentTime - requestTime);
|
||||
const maxTimeDiff = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
if (isNaN(requestTime) || timeDiff > maxTimeDiff) {
|
||||
logger.warn('Invalid or expired request timestamp', {
|
||||
sovereignBankId,
|
||||
timestamp,
|
||||
timeDiff: `${timeDiff}ms`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create payload for signing (must match client-side signing)
|
||||
const payload = JSON.stringify({
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
body: req.body,
|
||||
timestamp,
|
||||
nonce,
|
||||
});
|
||||
|
||||
try {
|
||||
// Get sovereign identity to retrieve HSM key ID
|
||||
const identity = await sovereignIdentityFabric.getIdentity(sovereignBankId, identityType);
|
||||
if (!identity || !identity.hsmKeyId) {
|
||||
logger.error('Sovereign identity not found or missing HSM key', {
|
||||
sovereignBankId,
|
||||
identityType,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify signature using HSM
|
||||
const isValid = await hsmService.verify(payload, signature, identity.hsmKeyId);
|
||||
|
||||
if (!isValid) {
|
||||
logger.warn('Invalid request signature', {
|
||||
sovereignBankId,
|
||||
identityType,
|
||||
keyId: identity.hsmKeyId,
|
||||
});
|
||||
}
|
||||
|
||||
return isValid;
|
||||
} catch (error) {
|
||||
logger.error('Error verifying request signature', {
|
||||
sovereignBankId,
|
||||
identityType,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zero-Trust Authentication Middleware
|
||||
* Enforces authentication and authorization for all requests
|
||||
*/
|
||||
export async function zeroTrustAuthMiddleware(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Extract SIT token
|
||||
const token = extractSitToken(req);
|
||||
if (!token) {
|
||||
throw new DbisError(
|
||||
ErrorCode.UNAUTHORIZED,
|
||||
'Missing or invalid Sovereign Identity Token'
|
||||
);
|
||||
}
|
||||
|
||||
// Verify JWT token
|
||||
const jwtSecret = getEnv('JWT_SECRET');
|
||||
if (!jwtSecret) {
|
||||
logger.error('JWT_SECRET environment variable is not set');
|
||||
throw new DbisError(
|
||||
ErrorCode.INTERNAL_ERROR,
|
||||
'Server configuration error: JWT secret not configured'
|
||||
);
|
||||
}
|
||||
|
||||
let decoded: JwtPayload;
|
||||
try {
|
||||
decoded = jwt.verify(token, jwtSecret) as JwtPayload;
|
||||
} catch (error) {
|
||||
logger.warn('JWT verification failed', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid or expired token');
|
||||
}
|
||||
|
||||
// Extract sovereign bank ID and identity type
|
||||
req.sovereignBankId = decoded.sovereignBankId;
|
||||
req.identityType = decoded.identityType;
|
||||
req.apiRole = decoded.apiRole;
|
||||
|
||||
if (!req.sovereignBankId) {
|
||||
throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid token payload');
|
||||
}
|
||||
|
||||
// Verify request signature
|
||||
const signatureValid = await verifyRequestSignature(
|
||||
req,
|
||||
req.sovereignBankId,
|
||||
req.identityType || ''
|
||||
);
|
||||
if (!signatureValid) {
|
||||
throw new DbisError(ErrorCode.UNAUTHORIZED, 'Invalid request signature');
|
||||
}
|
||||
|
||||
// Check token expiration
|
||||
if (decoded.exp && decoded.exp < Date.now() / 1000) {
|
||||
throw new DbisError(ErrorCode.UNAUTHORIZED, 'Token expired');
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error instanceof DbisError) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
},
|
||||
timestamp: new Date(),
|
||||
});
|
||||
} else {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: ErrorCode.INTERNAL_ERROR,
|
||||
message: 'Authentication error',
|
||||
},
|
||||
timestamp: new Date(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional authentication middleware (for public endpoints)
|
||||
*/
|
||||
export function optionalAuthMiddleware(
|
||||
req: AuthenticatedRequest,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
const token = extractSitToken(req);
|
||||
if (token) {
|
||||
try {
|
||||
const jwtSecret = getEnv('JWT_SECRET');
|
||||
if (jwtSecret) {
|
||||
const decoded = jwt.verify(token, jwtSecret) as JwtPayload;
|
||||
req.sovereignBankId = decoded.sovereignBankId;
|
||||
req.identityType = decoded.identityType;
|
||||
req.apiRole = decoded.apiRole;
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore auth errors for optional auth
|
||||
logger.debug('Optional auth failed (ignored)', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user