UNPKG

@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
'use strict'; 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