UNPKG

@azure/static-web-apps-cli

Version:
181 lines 6.53 kB
import chalk from "chalk"; import cookie from "cookie"; import { SWA_AUTH_CONTEXT_COOKIE, SWA_AUTH_COOKIE } from "../constants.js"; import { isValueEncryptedAndSigned, validateSignatureAndDecrypt } from "./auth.js"; import { logger } from "./logger.js"; /** * Serialize a cookie name-value pair into a string that can be used in Set-Cookie header. * @param cookieName The name for the cookie. * @param cookieValue The value to set the cookie to. * @param options An object containing serialization options * @throws {TypeError} when maxAge options is invalid. * @returns The serialized value. */ export function serializeCookie(cookieName, cookieValue, options) { return cookie.serialize(cookieName, cookieValue, options); } /** * Check if the StaticWebAppsAuthCookie is available. * @param cookieValue The cookie value. * @returns True if StaticWebAppsAuthCookie is found. False otherwise. */ export function validateCookie(cookieValue) { return validateCookieByName(SWA_AUTH_COOKIE, cookieValue); } /** * * @param cookieValue * @returns A ClientPrincipal object. */ export function decodeCookie(cookieValue) { const stringValue = decodeCookieByName(SWA_AUTH_COOKIE, cookieValue); return stringValue ? JSON.parse(stringValue) : null; } /** * Check if the StaticWebAppsAuthContextCookie is available. * @param cookieValue The cookie value. * @returns True if StaticWebAppsAuthContextCookie is found. False otherwise. */ export function validateAuthContextCookie(cookieValue) { return validateCookieByName(SWA_AUTH_CONTEXT_COOKIE, cookieValue); } /** * * @param cookieValue * @returns StaticWebAppsAuthContextCookie string. */ export function decodeAuthContextCookie(cookieValue) { const stringValue = decodeCookieByName(SWA_AUTH_CONTEXT_COOKIE, cookieValue); return stringValue ? JSON.parse(stringValue) : null; } // local functions function getCookie(cookieName, cookies) { const nonChunkedCookie = cookies[cookieName]; if (nonChunkedCookie) { // prefer the non-chunked cookie if it exists return nonChunkedCookie; } let chunkedCookie = ""; let chunk = ""; let index = 0; do { chunkedCookie = `${chunkedCookie}${chunk}`; chunk = cookies[`${cookieName}_${index}`]; index += 1; } while (chunk); return chunkedCookie; } function validateCookieByName(cookieName, cookieValue) { if (typeof cookieValue !== "string") { throw Error(`TypeError: cookie value must be a string`); } const cookies = cookie.parse(cookieValue); return !!getCookie(cookieName, cookies); } function decodeCookieByName(cookieName, cookieValue) { logger.silly(`decoding ${cookieName} cookie`); const cookies = cookie.parse(cookieValue); const value = getCookie(cookieName, cookies); if (value) { const decodedValue = Buffer.from(value, "base64").toString(); logger.silly(` - ${cookieName} decoded: ${chalk.yellow(decodedValue)}`); if (!decodedValue) { logger.silly(` - failed to decode '${cookieName}'`); return null; } if (isValueEncryptedAndSigned(decodedValue)) { const decryptedValue = validateSignatureAndDecrypt(decodedValue); logger.silly(` - ${cookieName} decrypted: ${chalk.yellow(decryptedValue)}`); if (!decryptedValue) { logger.silly(` - failed to validate and decrypt '${cookieName}'`); return null; } return decryptedValue; } return decodedValue; } logger.silly(` - no cookie '${cookieName}' found`); return null; } export class CookiesManager { _chunkSize = 2000; _existingCookies; _cookiesToSet = {}; _cookiesToDelete = {}; constructor(requestCookie) { this._existingCookies = requestCookie ? cookie.parse(requestCookie) : {}; } _generateDeleteChunks(name, force /* add the delete cookie even if the corresponding cookie doesn't exist */) { const cookies = {}; // check for unchunked cookie if (force || this._existingCookies[name]) { cookies[name] = { name: name, value: "deleted", path: "/", httpOnly: false, expires: new Date(1).toUTCString(), }; } // check for chunked cookie let found = true; let index = 0; while (found) { const chunkName = `${name}_${index}`; found = !!this._existingCookies[chunkName]; if (found) { cookies[chunkName] = { name: chunkName, value: "deleted", path: "/", httpOnly: false, expires: new Date(1).toUTCString(), }; } index += 1; } return cookies; } _generateChunks(options) { const { name, value } = options; // pre-populate with cookies for deleting existing chunks const cookies = this._generateDeleteChunks(options.name, false); // generate chunks if (value !== "deleted") { const chunkCount = Math.ceil(value.length / this._chunkSize); let index = 0; let chunkName = ""; while (index < chunkCount) { const position = index * this._chunkSize; const chunk = value.substring(position, position + this._chunkSize); chunkName = `${name}_${index}`; cookies[chunkName] = { ...options, name: chunkName, value: chunk, }; index += 1; } } return Object.values(cookies); } addCookieToSet(options) { this._cookiesToSet[options.name.toLowerCase()] = options; } addCookieToDelete(name) { this._cookiesToDelete[name.toLowerCase()] = name; } getCookies() { const allCookies = []; Object.values(this._cookiesToDelete).forEach((cookieName) => { const chunks = this._generateDeleteChunks(cookieName, true); allCookies.push(...Object.values(chunks)); }); Object.values(this._cookiesToSet).forEach((cookie) => { const chunks = this._generateChunks(cookie); allCookies.push(...chunks); }); return allCookies; } } //# sourceMappingURL=cookie.js.map