mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-29 15:44:13 +00:00
Merge pull request #4038 from Chriss4123/feature/localhost-secure-context
feat: Add RFC 6761–compliant localhost loopback checks so `secure` cookies work on localhost (fixes: #1676)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const { Cookie, CookieJar } = require('tough-cookie');
|
||||
const each = require('lodash/each');
|
||||
const { isPotentiallyTrustworthyOrigin } = require('@usebruno/requests').utils;
|
||||
|
||||
const cookieJar = new CookieJar();
|
||||
|
||||
@@ -11,7 +12,9 @@ const addCookieToJar = (setCookieHeader, requestUrl) => {
|
||||
};
|
||||
|
||||
const getCookiesForUrl = (url) => {
|
||||
return cookieJar.getCookiesSync(url);
|
||||
return cookieJar.getCookiesSync(url, {
|
||||
secure: isPotentiallyTrustworthyOrigin(url)
|
||||
});
|
||||
};
|
||||
|
||||
const getCookieStringForUrl = (url) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { Cookie, CookieJar } = require('tough-cookie');
|
||||
const each = require('lodash/each');
|
||||
const moment = require('moment');
|
||||
const { isPotentiallyTrustworthyOrigin } = require('@usebruno/requests').utils;
|
||||
|
||||
const cookieJar = new CookieJar();
|
||||
|
||||
@@ -12,7 +13,9 @@ const addCookieToJar = (setCookieHeader, requestUrl) => {
|
||||
};
|
||||
|
||||
const getCookiesForUrl = (url) => {
|
||||
return cookieJar.getCookiesSync(url);
|
||||
return cookieJar.getCookiesSync(url, {
|
||||
secure: isPotentiallyTrustworthyOrigin(url)
|
||||
});
|
||||
};
|
||||
|
||||
const getCookieStringForUrl = (url) => {
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
export { addDigestInterceptor, getOAuth2Token } from './auth';
|
||||
|
||||
export * as utils from './utils';
|
||||
|
||||
105
packages/bruno-requests/src/utils/cookie-utils.js
Normal file
105
packages/bruno-requests/src/utils/cookie-utils.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const { URL } = require('node:url');
|
||||
const net = require('node:net');
|
||||
|
||||
const isLoopbackV4 = (address) => {
|
||||
// 127.0.0.0/8: first octet = 127
|
||||
const octets = address.split('.');
|
||||
return (
|
||||
octets.length === 4
|
||||
) && parseInt(octets[0], 10) === 127;
|
||||
}
|
||||
|
||||
const isLoopbackV6 = (address) => {
|
||||
// new URL(...) follows the WHATWG URL Standard
|
||||
// which compresses IPv6 addresses, therefore the IPv6
|
||||
// loopback address will always be compressed to '[::1]':
|
||||
// https://url.spec.whatwg.org/#concept-ipv6-serializer
|
||||
return (address === '::1');
|
||||
}
|
||||
|
||||
const isIpLoopback = (address) => {
|
||||
if (net.isIPv4(address)) {
|
||||
return isLoopbackV4(address);
|
||||
}
|
||||
|
||||
if (net.isIPv6(address)) {
|
||||
return isLoopbackV6(address);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const isNormalizedLocalhostTLD = (host) => {
|
||||
return host.toLowerCase().endsWith('.localhost');
|
||||
}
|
||||
|
||||
const isLocalHostname = (host) => {
|
||||
return host.toLowerCase() === 'localhost' ||
|
||||
isNormalizedLocalhostTLD(host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading and trailing square brackets if present.
|
||||
* Adapted from https://github.com/chromium/chromium/blob/main/url/gurl.cc#L440-L448
|
||||
*
|
||||
* @param {string} host
|
||||
* @returns {string}
|
||||
*/
|
||||
const hostNoBrackets = (host) => {
|
||||
if (host.length >= 2 && host.startsWith('[') && host.endsWith(']')) {
|
||||
return host.substring(1, host.length - 1);
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a URL string represents a potentially trustworthy origin.
|
||||
*
|
||||
* A URL is considered potentially trustworthy if it:
|
||||
* - Uses HTTPS, WSS or file schemes
|
||||
* - Points to a loopback address (IPv4 127.0.0.0/8 or IPv6 ::1)
|
||||
* - Uses localhost or *.localhost hostnames
|
||||
*
|
||||
* @param {string} urlString - The URL to check
|
||||
* @returns {boolean}
|
||||
* @see {@link https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin W3C Spec}
|
||||
*/
|
||||
const isPotentiallyTrustworthyOrigin = (urlString) => {
|
||||
let url;
|
||||
|
||||
// try ... catch doubles as an opaque origin check
|
||||
try {
|
||||
url = new URL(urlString);
|
||||
} catch (e) {
|
||||
if (e instanceof TypeError && e.code === 'ERR_INVALID_URL') {
|
||||
return false;
|
||||
} else throw e;
|
||||
}
|
||||
|
||||
const scheme = url.protocol.replace(':', '').toLowerCase();
|
||||
const hostname = hostNoBrackets(
|
||||
url.hostname
|
||||
).replace(/\.+$/, '');
|
||||
|
||||
if (
|
||||
scheme === 'https' ||
|
||||
scheme === 'wss' ||
|
||||
scheme === 'file' // https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's already an IP literal, check if it's a loopback address
|
||||
if (net.isIP(hostname)) {
|
||||
return isIpLoopback(hostname);
|
||||
}
|
||||
|
||||
// RFC 6761 states that localhost names will always resolve
|
||||
// to the respective IP loopback address:
|
||||
// https://datatracker.ietf.org/doc/html/rfc6761#section-6.3
|
||||
return isLocalHostname(hostname);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
isPotentiallyTrustworthyOrigin
|
||||
};
|
||||
1
packages/bruno-requests/src/utils/index.ts
Normal file
1
packages/bruno-requests/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './cookie-utils';
|
||||
Reference in New Issue
Block a user