@shopify/shopify-api
Version:
Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks
137 lines (133 loc) • 4.65 kB
JavaScript
;
var utils$1 = require('../crypto/utils.js');
var utils = require('./utils.js');
var headers = require('./headers.js');
// import type {Headers} from "./headers";
class Cookies {
response;
static parseCookies(hdrs) {
const entries = hdrs
.filter((hdr) => hdr.trim().length > 0)
.map((cookieDef) => {
const [keyval, ...opts] = cookieDef.split(';');
const [name, value] = utils.splitN(keyval, '=', 2).map((value) => value.trim());
return [
name,
{
name,
value,
...Object.fromEntries(opts.map((opt) => utils.splitN(opt, '=', 2).map((value) => value.trim()))),
},
];
});
const jar = Object.fromEntries(entries);
for (const cookie of Object.values(jar)) {
if (typeof cookie.expires === 'string') {
cookie.expires = new Date(cookie.expires);
}
}
return jar;
}
static encodeCookie(data) {
let result = '';
result += `${data.name}=${data.value};`;
result += Object.entries(data)
.filter(([key]) => !['name', 'value', 'expires'].includes(key))
.map(([key, value]) => `${key}=${value}`)
.join('; ');
if (data.expires) {
result += ';';
result += `expires=${data.expires.toUTCString()}`;
}
return result;
}
receivedCookieJar = {};
outgoingCookieJar = {};
keys = [];
constructor(request, response, { keys = [] } = {}) {
this.response = response;
if (keys)
this.keys = keys;
const cookieReqHdr = headers.getHeader(request.headers, 'Cookie') ?? '';
this.receivedCookieJar = Cookies.parseCookies(cookieReqHdr.split(';'));
const cookieResHdr = headers.getHeaders(response.headers, 'Set-Cookie') ?? [];
this.outgoingCookieJar = Cookies.parseCookies(cookieResHdr);
}
toHeaders() {
return Object.values(this.outgoingCookieJar).map((cookie) => Cookies.encodeCookie(cookie));
}
updateHeader() {
if (!this.response.headers) {
this.response.headers = {};
}
headers.removeHeader(this.response.headers, 'Set-Cookie');
this.toHeaders().map((hdr) => headers.addHeader(this.response.headers, 'Set-Cookie', hdr));
}
get(name) {
return this.receivedCookieJar[name]?.value;
}
deleteCookie(name) {
this.set(name, '', {
path: '/',
expires: new Date(0),
});
}
async getAndVerify(name) {
const value = this.get(name);
if (!value)
return undefined;
if (!(await this.isSignedCookieValid(name))) {
return undefined;
}
return value;
}
get canSign() {
return this.keys?.length > 0;
}
set(name, value, opts = {}) {
this.outgoingCookieJar[name] = {
...opts,
name,
value,
};
this.updateHeader();
}
async setAndSign(name, value, opts = {}) {
if (!this.canSign) {
throw Error('No keys provided for signing.');
}
this.set(name, value, opts);
const sigName = `${name}.sig`;
const signature = await utils$1.createSHA256HMAC(this.keys[0], value);
this.set(sigName, signature, opts);
this.updateHeader();
}
async isSignedCookieValid(cookieName) {
const signedCookieName = `${cookieName}.sig`;
if (!this.cookieExists(cookieName) ||
!this.cookieExists(signedCookieName)) {
this.deleteInvalidCookies(cookieName, signedCookieName);
return false;
}
const cookieValue = this.get(cookieName);
const signature = this.get(signedCookieName);
if (!cookieValue || !signature) {
this.deleteInvalidCookies(cookieName, signedCookieName);
return false;
}
const allCheckSignatures = await Promise.all(this.keys.map((key) => utils$1.createSHA256HMAC(key, cookieValue)));
if (!allCheckSignatures.includes(signature)) {
this.deleteInvalidCookies(cookieName, signedCookieName);
return false;
}
return true;
}
cookieExists(cookieName) {
return Boolean(this.get(cookieName));
}
deleteInvalidCookies(...cookieNames) {
cookieNames.forEach((cookieName) => this.deleteCookie(cookieName));
}
}
exports.Cookies = Cookies;
//# sourceMappingURL=cookies.js.map