hono
Version:
Web framework built on Web Standards
138 lines (137 loc) • 4.73 kB
JavaScript
// src/utils/cookie.ts
import { decodeURIComponent_ } from "./url.js";
var algorithm = { name: "HMAC", hash: "SHA-256" };
var getCryptoKey = async (secret) => {
const secretBuf = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
return await crypto.subtle.importKey("raw", secretBuf, algorithm, false, ["sign", "verify"]);
};
var makeSignature = async (value, secret) => {
const key = await getCryptoKey(secret);
const signature = await crypto.subtle.sign(algorithm.name, key, new TextEncoder().encode(value));
return btoa(String.fromCharCode(...new Uint8Array(signature)));
};
var verifySignature = async (base64Signature, value, secret) => {
try {
const signatureBinStr = atob(base64Signature);
const signature = new Uint8Array(signatureBinStr.length);
for (let i = 0, len = signatureBinStr.length; i < len; i++) {
signature[i] = signatureBinStr.charCodeAt(i);
}
return await crypto.subtle.verify(algorithm, secret, signature, new TextEncoder().encode(value));
} catch (e) {
return false;
}
};
var validCookieNameRegEx = /^[\w!#$%&'*.^`|~+-]+$/;
var validCookieValueRegEx = /^[ !#-:<-[\]-~]*$/;
var parse = (cookie, name) => {
const pairs = cookie.trim().split(";");
return pairs.reduce((parsedCookie, pairStr) => {
pairStr = pairStr.trim();
const valueStartPos = pairStr.indexOf("=");
if (valueStartPos === -1) {
return parsedCookie;
}
const cookieName = pairStr.substring(0, valueStartPos).trim();
if (name && name !== cookieName || !validCookieNameRegEx.test(cookieName)) {
return parsedCookie;
}
let cookieValue = pairStr.substring(valueStartPos + 1).trim();
if (cookieValue.startsWith('"') && cookieValue.endsWith('"')) {
cookieValue = cookieValue.slice(1, -1);
}
if (validCookieValueRegEx.test(cookieValue)) {
parsedCookie[cookieName] = decodeURIComponent_(cookieValue);
}
return parsedCookie;
}, {});
};
var parseSigned = async (cookie, secret, name) => {
const parsedCookie = {};
const secretKey = await getCryptoKey(secret);
for (const [key, value] of Object.entries(parse(cookie, name))) {
const signatureStartPos = value.lastIndexOf(".");
if (signatureStartPos < 1) {
continue;
}
const signedValue = value.substring(0, signatureStartPos);
const signature = value.substring(signatureStartPos + 1);
if (signature.length !== 44 || !signature.endsWith("=")) {
continue;
}
const isVerified = await verifySignature(signature, signedValue, secretKey);
parsedCookie[key] = isVerified ? signedValue : false;
}
return parsedCookie;
};
var _serialize = (name, value, opt = {}) => {
let cookie = `${name}=${value}`;
if (name.startsWith("__Secure-") && !opt.secure) {
throw new Error("__Secure- Cookie must have Secure attributes");
}
if (name.startsWith("__Host-")) {
if (!opt.secure) {
throw new Error("__Host- Cookie must have Secure attributes");
}
if (opt.path !== "/") {
throw new Error('__Host- Cookie must have Path attributes with "/"');
}
if (opt.domain) {
throw new Error("__Host- Cookie must not have Domain attributes");
}
}
if (opt && typeof opt.maxAge === "number" && opt.maxAge >= 0) {
if (opt.maxAge > 3456e4) {
throw new Error(
"Cookies Max-Age SHOULD NOT be greater than 400 days (34560000 seconds) in duration."
);
}
cookie += `; Max-Age=${Math.floor(opt.maxAge)}`;
}
if (opt.domain && opt.prefix !== "host") {
cookie += `; Domain=${opt.domain}`;
}
if (opt.path) {
cookie += `; Path=${opt.path}`;
}
if (opt.expires) {
if (opt.expires.getTime() - Date.now() > 3456e7) {
throw new Error(
"Cookies Expires SHOULD NOT be greater than 400 days (34560000 seconds) in the future."
);
}
cookie += `; Expires=${opt.expires.toUTCString()}`;
}
if (opt.httpOnly) {
cookie += "; HttpOnly";
}
if (opt.secure) {
cookie += "; Secure";
}
if (opt.sameSite) {
cookie += `; SameSite=${opt.sameSite.charAt(0).toUpperCase() + opt.sameSite.slice(1)}`;
}
if (opt.partitioned) {
if (!opt.secure) {
throw new Error("Partitioned Cookie must have Secure attributes");
}
cookie += "; Partitioned";
}
return cookie;
};
var serialize = (name, value, opt) => {
value = encodeURIComponent(value);
return _serialize(name, value, opt);
};
var serializeSigned = async (name, value, secret, opt = {}) => {
const signature = await makeSignature(value, secret);
value = `${value}.${signature}`;
value = encodeURIComponent(value);
return _serialize(name, value, opt);
};
export {
parse,
parseSigned,
serialize,
serializeSigned
};