fortify2-js
Version:
MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.
590 lines (587 loc) • 20.7 kB
JavaScript
import { SecurityPlugin } from '../core/SecurityPlugin.js';
import { PluginPriority } from '../types/PluginTypes.js';
/**
* JWT Authentication Plugin
*
* High-performance JWT authentication plugin leveraging FortifyJS security utilities
* with <2ms execution time for token validation and user authentication.
*/
/**
* JWT Authentication Plugin for ultra-fast token validation
*/
class JWTAuthPlugin extends SecurityPlugin {
constructor() {
super(...arguments);
this.id = "fortify.auth.jwt";
this.name = "JWT Authentication Plugin";
this.version = "1.0.0";
this.priority = PluginPriority.HIGH; // Authentication is high priority
this.jwtAlgorithm = "HS256";
this.tokenExpiry = 3600; // 1 hour
this.issuer = "fortifyjs";
this.audience = "fortifyjs-app";
// Performance optimization: Token cache for validated tokens
this.tokenCache = new Map();
// Rate limiting for authentication attempts
this.authAttempts = new Map();
}
/**
* Initialize JWT authentication plugin
*/
async initializeSecurityPlugin(context) {
// Get JWT secret from environment or configuration
this.jwtSecret =
process.env.JWT_SECRET || context.config.customSettings.jwtSecret;
if (!this.jwtSecret) {
throw new Error("JWT secret is required for JWT authentication plugin");
}
// Configure JWT settings from context
if (context.config.customSettings.jwtAlgorithm) {
this.jwtAlgorithm = context.config.customSettings.jwtAlgorithm;
}
if (context.config.customSettings.tokenExpiry) {
this.tokenExpiry = context.config.customSettings.tokenExpiry;
}
if (context.config.customSettings.issuer) {
this.issuer = context.config.customSettings.issuer;
}
if (context.config.customSettings.audience) {
this.audience = context.config.customSettings.audience;
}
// Setup token cache cleanup
this.setupTokenCacheCleanup();
// Setup rate limiting cleanup
this.setupRateLimitingCleanup();
context.logger.info(`JWT Authentication Plugin initialized with algorithm: ${this.jwtAlgorithm}`);
}
/**
* Execute JWT authentication logic
*/
async executeSecurityLogic(context) {
const { req } = context;
// Check if authentication is required for this route
if (!this.requiresAuthentication(req.path)) {
return { authenticated: false, required: false };
}
// Check rate limiting
const clientIp = req.ip || req.connection?.remoteAddress || "unknown";
if (this.isRateLimited(clientIp)) {
throw new Error("Too many authentication attempts. Please try again later.");
}
// Extract JWT token
const token = this.extractJWTToken(req);
if (!token) {
this.recordAuthAttempt(clientIp, false);
throw new Error("Authentication token is required");
}
// Check token cache first for performance
const cachedAuth = this.getCachedAuthentication(token);
if (cachedAuth) {
// Update security context with cached data
context.security.isAuthenticated = true;
context.security.userId = cachedAuth.userId;
context.security.roles = cachedAuth.roles;
context.security.permissions = cachedAuth.permissions;
return {
authenticated: true,
userId: cachedAuth.userId,
roles: cachedAuth.roles,
permissions: cachedAuth.permissions,
source: "cache",
};
}
// Validate JWT token
const authResult = await this.validateJWTToken(token);
if (!authResult.valid) {
this.recordAuthAttempt(clientIp, false);
throw new Error(authResult.error || "Invalid authentication token");
}
// Cache the authentication result
this.cacheAuthentication(token, authResult.payload);
// Update security context
context.security.isAuthenticated = true;
context.security.userId = authResult.payload.userId;
context.security.roles = authResult.payload.roles || [];
context.security.permissions = authResult.payload.permissions || [];
// Record successful authentication
this.recordAuthAttempt(clientIp, true);
return {
authenticated: true,
userId: authResult.payload.userId,
roles: authResult.payload.roles || [],
permissions: authResult.payload.permissions || [],
source: "token",
};
}
/**
* Perform authentication logic
*/
async performAuthentication(authData, context) {
try {
const result = await this.executeSecurityLogic(context);
return result.authenticated;
}
catch (error) {
return false;
}
}
/**
* Perform authorization logic
*/
async performAuthorization(context, resource) {
if (!context.security.isAuthenticated) {
return false;
}
// Check if user has required permissions for the resource
return await this.checkResourcePermissions(context, resource);
}
// ===== JWT-SPECIFIC METHODS =====
/**
* Extract JWT token from request
*/
extractJWTToken(req) {
// Check Authorization header
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith("Bearer ")) {
return authHeader.substring(7);
}
// Check cookies
if (req.cookies && req.cookies.token) {
return req.cookies.token;
}
// Check query parameter (less secure, but sometimes needed)
if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
/**
* Validate JWT token using FortifyJS Hash utilities
*/
async validateJWTToken(token) {
try {
// Split token into parts
const parts = token.split(".");
if (parts.length !== 3) {
return { valid: false, error: "Invalid token format" };
}
const [headerB64, payloadB64, signatureB64] = parts;
// Decode header and payload
const header = JSON.parse(this.base64UrlDecode(headerB64));
const payload = JSON.parse(this.base64UrlDecode(payloadB64));
// Verify algorithm
if (header.alg !== this.jwtAlgorithm) {
return { valid: false, error: "Invalid algorithm" };
}
// Verify signature using FortifyJS Hash
const signatureData = `${headerB64}.${payloadB64}`;
const expectedSignature = this.hashUtil.create(signatureData + this.jwtSecret, {
algorithm: "sha256",
outputFormat: "base64",
});
const expectedSignatureB64 = this.base64UrlEncode(expectedSignature);
if (signatureB64 !== expectedSignatureB64) {
return { valid: false, error: "Invalid signature" };
}
// Verify expiration
if (payload.exp && Date.now() / 1000 > payload.exp) {
return { valid: false, error: "Token expired" };
}
// Verify issuer
if (payload.iss && payload.iss !== this.issuer) {
return { valid: false, error: "Invalid issuer" };
}
// Verify audience
if (payload.aud && payload.aud !== this.audience) {
return { valid: false, error: "Invalid audience" };
}
return { valid: true, payload };
}
catch (error) {
return { valid: false, error: error.message };
}
}
/**
* Check if route requires authentication
*/
requiresAuthentication(path) {
// Public routes that don't require authentication
const publicRoutes = [
"/",
"/health",
"/login",
"/register",
"/public",
"/fortify/health",
];
// Check if path starts with any public route
return !publicRoutes.some((route) => path.startsWith(route));
}
/**
* Check resource permissions (override from SecurityPlugin)
*/
async checkResourcePermissions(context, resource) {
// Simple permission checking - can be enhanced based on requirements
const userPermissions = context.security.permissions;
const requiredPermissions = this.getRequiredPermissions(resource);
return requiredPermissions.every((permission) => userPermissions.includes(permission) ||
userPermissions.includes("admin"));
}
/**
* Get required permissions for resource
*/
getRequiredPermissions(resource) {
// Map resources to required permissions
const permissionMap = {
"/api/users": ["user.read"],
"/api/users/create": ["user.write"],
"/api/users/delete": ["user.delete"],
"/api/admin": ["admin"],
"/api/reports": ["report.read"],
};
return permissionMap[resource] || ["authenticated"];
}
// ===== CACHING METHODS =====
/**
* Get cached authentication result
*/
getCachedAuthentication(token) {
const cached = this.tokenCache.get(token);
if (!cached)
return null;
// Check if cached token is expired
if (Date.now() > cached.expiry) {
this.tokenCache.delete(token);
return null;
}
return cached;
}
/**
* Cache authentication result
*/
cacheAuthentication(token, payload) {
const expiry = Date.now() + this.tokenExpiry * 1000;
this.tokenCache.set(token, {
userId: payload.userId || payload.sub,
roles: payload.roles || [],
permissions: payload.permissions || [],
expiry,
});
}
/**
* Setup token cache cleanup
*/
setupTokenCacheCleanup() {
// Clean up expired tokens every 5 minutes
setInterval(() => {
const now = Date.now();
for (const [token, cached] of this.tokenCache.entries()) {
if (now > cached.expiry) {
this.tokenCache.delete(token);
}
}
}, 300000); // 5 minutes
}
// ===== RATE LIMITING METHODS =====
/**
* Check if IP is rate limited
*/
isRateLimited(ip) {
const attempt = this.authAttempts.get(ip);
if (!attempt)
return false;
// Reset after 15 minutes
if (Date.now() - attempt.lastAttempt > 900000) {
this.authAttempts.delete(ip);
return false;
}
return attempt.blocked;
}
/**
* Record authentication attempt
*/
recordAuthAttempt(ip, success) {
const now = Date.now();
const attempt = this.authAttempts.get(ip) || {
count: 0,
lastAttempt: now,
blocked: false,
};
if (success) {
// Reset on successful authentication
this.authAttempts.delete(ip);
}
else {
attempt.count++;
attempt.lastAttempt = now;
// Block after 5 failed attempts
if (attempt.count >= 5) {
attempt.blocked = true;
}
this.authAttempts.set(ip, attempt);
}
}
/**
* Setup rate limiting cleanup
*/
setupRateLimitingCleanup() {
// Clean up old rate limiting records every 10 minutes
setInterval(() => {
const now = Date.now();
for (const [ip, attempt] of this.authAttempts.entries()) {
if (now - attempt.lastAttempt > 900000) {
// 15 minutes
this.authAttempts.delete(ip);
}
}
}, 600000); // 10 minutes
}
// ===== UTILITY METHODS =====
/**
* Base64 URL decode
*/
base64UrlDecode(str) {
// Add padding if needed
str += "=".repeat((4 - (str.length % 4)) % 4);
// Replace URL-safe characters
str = str.replace(/-/g, "+").replace(/_/g, "/");
return Buffer.from(str, "base64").toString("utf8");
}
/**
* Base64 URL encode
*/
base64UrlEncode(str) {
return Buffer.from(str)
.toString("base64")
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
// ===== SECURITY VALIDATION OVERRIDES =====
/**
* Validate request body for security threats
*/
validateRequestBody(body) {
if (!body || typeof body !== "object") {
return true; // No body to validate
}
try {
// Check for suspicious patterns in body
const bodyString = JSON.stringify(body);
// Check for SQL injection patterns
const sqlPatterns = [
/(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)/i,
/(--|\/\*|\*\/|;)/,
/(\b(OR|AND)\s+\d+\s*=\s*\d+)/i,
];
for (const pattern of sqlPatterns) {
if (pattern.test(bodyString)) {
console.warn(`SQL injection pattern detected in request body: ${this.id}`);
return false;
}
}
// Check for XSS patterns
const xssPatterns = [
/<script[^>]*>.*?<\/script>/gi,
/javascript:/gi,
/on\w+\s*=/gi,
/<iframe[^>]*>.*?<\/iframe>/gi,
];
for (const pattern of xssPatterns) {
if (pattern.test(bodyString)) {
console.warn(`XSS pattern detected in request body: ${this.id}`);
return false;
}
}
return true;
}
catch (error) {
console.error(`Error validating request body: ${error}`);
return false;
}
}
/**
* Validate query parameters for security threats
*/
validateQueryParameters(query) {
if (!query || typeof query !== "object") {
return true; // No query params to validate
}
try {
for (const [key, value] of Object.entries(query)) {
if (typeof value === "string") {
// Check for path traversal
if (value.includes("../") || value.includes("..\\")) {
console.warn(`Path traversal detected in query param ${key}: ${this.id}`);
return false;
}
// Check for command injection
const cmdPatterns = [
/[;&|`$()]/,
/\b(cat|ls|pwd|whoami|id|uname|ps|netstat|ifconfig)\b/i,
];
for (const pattern of cmdPatterns) {
if (pattern.test(value)) {
console.warn(`Command injection pattern detected in query param ${key}: ${this.id}`);
return false;
}
}
}
}
return true;
}
catch (error) {
console.error(`Error validating query parameters: ${error}`);
return false;
}
}
/**
* Validate headers for security threats
*/
validateHeaders(headers) {
if (!headers || typeof headers !== "object") {
return true; // No headers to validate
}
try {
// Check for suspicious user agents
const userAgent = headers["user-agent"];
if (userAgent) {
const suspiciousPatterns = [
/sqlmap/i,
/nikto/i,
/nessus/i,
/burp/i,
/scanner/i,
];
for (const pattern of suspiciousPatterns) {
if (pattern.test(userAgent)) {
console.warn(`Suspicious user agent detected: ${this.id}`);
return false;
}
}
}
// Check for header injection
for (const [key, value] of Object.entries(headers)) {
if (typeof value === "string") {
if (value.includes("\r") || value.includes("\n")) {
console.warn(`Header injection detected in ${key}: ${this.id}`);
return false;
}
}
}
return true;
}
catch (error) {
console.error(`Error validating headers: ${error}`);
return false;
}
}
/**
* Apply data sanitization rules
*/
applySanitizationRules(data) {
if (!data || typeof data !== "object") {
return data;
}
try {
// Use fObject for secure data handling
const secureData = this.secureObjectUtil(data);
// Get sanitized data
const sanitized = secureData.getAll();
// Additional sanitization for JWT-specific data
if (sanitized.token) {
// Remove any non-JWT token patterns
sanitized.token = sanitized.token.replace(/[^A-Za-z0-9._-]/g, "");
}
if (sanitized.userId) {
// Sanitize user ID to alphanumeric only
sanitized.userId = sanitized.userId
.toString()
.replace(/[^A-Za-z0-9-_]/g, "");
}
return sanitized;
}
catch (error) {
console.error(`Error applying sanitization rules: ${error}`);
return data; // Return original data if sanitization fails
}
}
/**
* Parse authorization header
*/
parseAuthorizationHeader(header) {
try {
if (header.startsWith("Bearer ")) {
const token = header.substring(7);
return {
type: "bearer",
token: token,
userId: null, // Will be extracted from token
};
}
if (header.startsWith("Basic ")) {
const credentials = Buffer.from(header.substring(6), "base64").toString("utf8");
const [username, password] = credentials.split(":");
return {
type: "basic",
username,
password,
userId: username,
};
}
return null;
}
catch (error) {
console.error(`Error parsing authorization header: ${error}`);
return null;
}
}
/**
* Parse authentication cookies
*/
parseAuthenticationCookies(cookies) {
try {
if (cookies.token) {
return {
type: "cookie",
token: cookies.token,
userId: null, // Will be extracted from token
};
}
if (cookies.sessionId) {
return {
type: "session",
sessionId: cookies.sessionId,
userId: null, // Will be extracted from session
};
}
return null;
}
catch (error) {
console.error(`Error parsing authentication cookies: ${error}`);
return null;
}
}
/**
* Parse session data
*/
parseSessionData(session) {
try {
if (session.userId) {
return {
type: "session",
userId: session.userId,
roles: session.roles || [],
permissions: session.permissions || [],
};
}
return null;
}
catch (error) {
console.error(`Error parsing session data: ${error}`);
return null;
}
}
}
export { JWTAuthPlugin };
//# sourceMappingURL=JWTAuthPlugin.js.map