@edge-csrf/node-http
Version:
Edge-CSRF integration library for node's http module
303 lines (302 loc) • 9.45 kB
JavaScript
/*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
var E = _, A = D, L = Object.prototype.toString, O = Object.prototype.hasOwnProperty, T = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/, P = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/, j = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i, H = /^[\u0020-\u003A\u003D-\u007E]*$/;
function _(e, r) {
if (typeof e != "string")
throw new TypeError("argument str must be a string");
var t = {}, n = e.length;
if (n < 2) return t;
var i = r && r.decode || R, o = 0, a = 0, s = 0;
do {
if (a = e.indexOf("=", o), a === -1) break;
if (s = e.indexOf(";", o), s === -1)
s = n;
else if (a > s) {
o = e.lastIndexOf(";", a - 1) + 1;
continue;
}
var c = w(e, o, a), f = p(e, a, c), d = e.slice(c, f);
if (!O.call(t, d)) {
var l = w(e, a + 1, s), h = p(e, s, l);
e.charCodeAt(l) === 34 && e.charCodeAt(h - 1) === 34 && (l++, h--);
var u = e.slice(l, h);
t[d] = N(u, i);
}
o = s + 1;
} while (o < n);
return t;
}
function w(e, r, t) {
do {
var n = e.charCodeAt(r);
if (n !== 32 && n !== 9) return r;
} while (++r < t);
return t;
}
function p(e, r, t) {
for (; r > t; ) {
var n = e.charCodeAt(--r);
if (n !== 32 && n !== 9) return r + 1;
}
return t;
}
function D(e, r, t) {
var n = t && t.encode || encodeURIComponent;
if (typeof n != "function")
throw new TypeError("option encode is invalid");
if (!T.test(e))
throw new TypeError("argument name is invalid");
var i = n(r);
if (!P.test(i))
throw new TypeError("argument val is invalid");
var o = e + "=" + i;
if (!t) return o;
if (t.maxAge != null) {
var a = Math.floor(t.maxAge);
if (!isFinite(a))
throw new TypeError("option maxAge is invalid");
o += "; Max-Age=" + a;
}
if (t.domain) {
if (!j.test(t.domain))
throw new TypeError("option domain is invalid");
o += "; Domain=" + t.domain;
}
if (t.path) {
if (!H.test(t.path))
throw new TypeError("option path is invalid");
o += "; Path=" + t.path;
}
if (t.expires) {
var s = t.expires;
if (!B(s) || isNaN(s.valueOf()))
throw new TypeError("option expires is invalid");
o += "; Expires=" + s.toUTCString();
}
if (t.httpOnly && (o += "; HttpOnly"), t.secure && (o += "; Secure"), t.partitioned && (o += "; Partitioned"), t.priority) {
var c = typeof t.priority == "string" ? t.priority.toLowerCase() : t.priority;
switch (c) {
case "low":
o += "; Priority=Low";
break;
case "medium":
o += "; Priority=Medium";
break;
case "high":
o += "; Priority=High";
break;
default:
throw new TypeError("option priority is invalid");
}
}
if (t.sameSite) {
var f = typeof t.sameSite == "string" ? t.sameSite.toLowerCase() : t.sameSite;
switch (f) {
case !0:
o += "; SameSite=Strict";
break;
case "lax":
o += "; SameSite=Lax";
break;
case "strict":
o += "; SameSite=Strict";
break;
case "none":
o += "; SameSite=None";
break;
default:
throw new TypeError("option sameSite is invalid");
}
}
return o;
}
function R(e) {
return e.indexOf("%") !== -1 ? decodeURIComponent(e) : e;
}
function B(e) {
return L.call(e) === "[object Date]";
}
function N(e, r) {
try {
return r(e);
} catch {
return e;
}
}
class m {
constructor(r) {
this.domain = "", this.httpOnly = !0, this.maxAge = void 0, this.name = "_csrfSecret", this.partitioned = void 0, this.path = "/", this.sameSite = "strict", this.secure = !0, Object.assign(this, r);
}
}
class g {
constructor(r) {
this.fieldName = "csrf_token", this.value = void 0, Object.assign(this, r), this._fieldNameRegex = new RegExp(`^(\\d+_)*${this.fieldName}$`);
}
}
class x {
constructor(r) {
this.excludePathPrefixes = [], this.ignoreMethods = ["GET", "HEAD", "OPTIONS"], this.saltByteLength = 8, this.secretByteLength = 18, this.cookie = new m(), this.token = new g();
const t = r || {};
if (t.cookie && (t.cookie = new m(t.cookie)), t.token && (t.token = new g(t.token)), Object.assign(this, t), this.saltByteLength < 1 || this.saltByteLength > 255)
throw Error("saltBytLength must be greater than 0 and less than 256");
if (this.secretByteLength < 1 || this.secretByteLength > 255)
throw Error("secretBytLength must be greater than 0 and less than 256");
}
}
const C = new g();
function U(e) {
const r = new Uint8Array(e);
return crypto.getRandomValues(r), r;
}
function v(e) {
let r = "";
for (let t = 0; t < e.byteLength; t += 1)
r += String.fromCharCode(e[t]);
return btoa(r);
}
function k(e) {
let r;
try {
r = atob(e);
} catch (n) {
if (n instanceof Error && n.name === "InvalidCharacterError")
return new Uint8Array();
throw n;
}
const t = new Uint8Array(r.length);
for (let n = 0; n < r.length; n += 1) t[n] = r.charCodeAt(n);
return t;
}
function $(e, r = C) {
for (const [t, n] of e.entries())
if (r._fieldNameRegex.test(t)) return n;
}
async function z(e, r = C) {
if (r.value !== void 0) return r.value(e);
const t = e.headers.get("x-csrf-token");
if (t !== null) return t;
const { fieldName: n } = r, i = e.headers.get("content-type") || "text/plain";
if (i === "application/x-www-form-urlencoded" || i.startsWith("multipart/form-data")) {
const a = await e.formData(), s = $(a, r);
return typeof s == "string" ? s : "";
}
if (i === "application/json" || i === "application/ld+json") {
const s = (await e.json())[n];
return typeof s == "string" ? s : "";
}
const o = await e.text();
if (i.startsWith("text/plain"))
try {
const a = JSON.parse(o);
if (!Array.isArray(a) || a.length === 0) return o;
const s = a[0], c = typeof s;
return c === "string" ? s : c === "object" ? s[n] || "" : s;
} catch {
return o;
}
return o;
}
function V(e) {
const r = new Uint8Array(e);
for (let t = 0; t < e; t += 1)
r[t] = Math.floor(Math.random() * 255);
return r;
}
async function S(e, r) {
const t = new Uint8Array(e.byteLength + r.byteLength);
t.set(e), t.set(r, e.byteLength);
const n = await crypto.subtle.digest("SHA-1", t);
return new Uint8Array(n);
}
async function M(e, r) {
const t = V(r), n = await S(e, t), i = new Uint8Array(2 + r + n.byteLength);
return i[0] = 0, i[1] = r, i.set(t, 2), i.set(n, r + 2), i;
}
async function F(e, r) {
if (e.byteLength < 22) return !1;
const t = e[1], n = e.subarray(2, 2 + t), i = e.subarray(2 + t), o = await S(r, n);
if (i.byteLength !== o.byteLength) return !1;
for (let a = 0; a < i.byteLength; a += 1)
if (i[a] !== o[a]) return !1;
return !0;
}
class I extends Error {
}
function W(e) {
const r = new x(e || {});
return async (t) => {
const { request: n, url: i, getCookie: o, setCookie: a } = t;
for (const d of r.excludePathPrefixes)
if (i.pathname.startsWith(d)) return;
const s = o(r.cookie.name);
let c;
if (s === void 0 ? (c = U(r.secretByteLength), a({ ...r.cookie, value: v(c) })) : c = k(s), !r.ignoreMethods.includes(n.method)) {
const d = await z(n, r.token);
if (!await F(k(d), c))
throw new I("csrf validation error");
}
const f = await M(c, r.saltByteLength);
return v(f);
};
}
function G(e) {
return new Promise((r, t) => {
const n = [], i = () => {
t(new Error("request aborted"));
}, o = (f) => {
n.push(f);
}, a = () => {
e.body = Buffer.concat(n), r(e.body.toString());
}, s = (f) => {
t(f);
}, c = () => {
e.removeListener("data", o), e.removeListener("end", a), e.removeListener("err", s), e.removeListener("aborted", i), e.removeListener("close", c);
};
e.on("aborted", i), e.on("data", o), e.on("end", a), e.on("err", s), e.on("close", c);
});
}
class b extends g {
constructor(r) {
super(r), this.responseHeader = "X-CSRF-Token", Object.assign(this, r);
}
}
class J extends x {
constructor(r) {
super(r), this.excludePathPrefixes = [], this.token = new b();
const t = r || {};
t.token && (t.token = new b(t.token)), Object.assign(this, t);
}
}
function X(e) {
const r = new J(e), t = W(r);
return async (n, i) => {
const o = E(n.headers.cookie || ""), { url: a, headers: { host: s } } = n, c = new URL(`http://${s}${a || ""}`), f = new Headers();
Object.entries(n.headers).forEach(([h, u]) => {
Array.isArray(u) ? u.forEach((y) => f.append(h, y)) : u !== void 0 && f.append(h, u);
});
const d = new Request(c, {
method: n.method,
headers: f,
body: ["GET", "HEAD"].includes(n.method || "") ? void 0 : await G(n)
}), l = await t({
request: d,
url: c,
getCookie: (h) => o[h],
setCookie: (h) => {
const u = A(h.name, h.value, h), y = i.getHeader("Set-Cookie");
Array.isArray(y) ? i.setHeader("Set-Cookie", [...y, u]) : typeof y == "string" ? i.setHeader("Set-Cookie", [y, u]) : i.setHeader("Set-Cookie", u);
}
});
l && i.setHeader(r.token.responseHeader, l);
};
}
export {
I as CsrfError,
J as NodeHttpConfig,
b as NodeHttpTokenOptions,
X as createCsrfProtect
};