webssh2-server
Version: 
A Websocket to SSH2 gateway using xterm.js, socket.io, ssh2
103 lines (102 loc) • 2.96 kB
JavaScript
// app/middleware/csrf.middleware.ts
// CSRF protection middleware
import { HTTP, DEFAULTS } from '../constants.js';
/**
 * Create CSRF protection middleware
 * @param config - Application configuration
 * @returns Express middleware handler
 */
export function createCSRFMiddleware(config) {
    return (req, res, next) => {
        if (!config.sso.csrfProtection) {
            next();
            return;
        }
        if (isTrustedProxy(req, config.sso.trustedProxies)) {
            next();
            return;
        }
        if (hasSsoHeaders(req)) {
            next();
            return;
        }
        if (req.method === 'POST' && !isValidCsrfToken(req)) {
            res.status(HTTP.FORBIDDEN).send('CSRF token validation failed');
            return;
        }
        next();
    };
}
const isTrustedProxy = (req, trustedProxies) => {
    if (trustedProxies.length === 0) {
        return false;
    }
    const candidateIp = getClientIp(req);
    if (candidateIp === undefined) {
        return false;
    }
    return trustedProxies.includes(candidateIp);
};
const getClientIp = (req) => {
    if (typeof req.ip === 'string' && req.ip !== '') {
        return req.ip;
    }
    const connection = req.connection;
    return connection?.remoteAddress;
};
const hasSsoHeaders = (req) => {
    const usernameHeader = req.headers[DEFAULTS.SSO_HEADERS.USERNAME];
    const sessionHeader = req.headers[DEFAULTS.SSO_HEADERS.SESSION];
    return usernameHeader != null || sessionHeader != null;
};
const isValidCsrfToken = (req) => {
    const enrichedRequest = req;
    const sessionToken = extractSessionToken(enrichedRequest);
    if (sessionToken === undefined) {
        return false;
    }
    const requestToken = extractRequestToken(enrichedRequest);
    return requestToken === sessionToken;
};
const extractSessionToken = (req) => {
    const session = req.session;
    if (!isObjectLike(session)) {
        return undefined;
    }
    return session['csrfToken'];
};
const extractRequestToken = (req) => {
    const bodyToken = extractBodyToken(normalizeBody(req.body));
    if (bodyToken !== undefined) {
        return bodyToken;
    }
    return toHeaderString(req.headers['x-csrf-token']);
};
const normalizeBody = (body) => {
    if (!isPlainRecord(body)) {
        return undefined;
    }
    return body;
};
const extractBodyToken = (body) => {
    if (body == null) {
        return undefined;
    }
    const csrfCandidate = body['_csrf'];
    return typeof csrfCandidate === 'string' && csrfCandidate !== '' ? csrfCandidate : undefined;
};
const toHeaderString = (value) => {
    if (typeof value === 'string') {
        return value;
    }
    if (Array.isArray(value)) {
        return value[0];
    }
    return undefined;
};
const isObjectLike = (value) => {
    return typeof value === 'object' && value !== null;
};
const isPlainRecord = (value) => {
    return isObjectLike(value) && !Array.isArray(value);
};