UNPKG

@orpc/server

Version:

<div align="center"> <image align="center" src="https://orpc.unnoq.com/logo.webp" width=280 alt="oRPC logo" /> </div>

199 lines (193 loc) • 5.31 kB
import { serialize, parse } from 'cookie'; function encodeBase64url(data) { const chunkSize = 8192; let binaryString = ""; for (let i = 0; i < data.length; i += chunkSize) { const chunk = data.subarray(i, i + chunkSize); binaryString += String.fromCharCode(...chunk); } const base64 = btoa(binaryString); return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function decodeBase64url(base64url) { try { if (typeof base64url !== "string") { return void 0; } let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); while (base64.length % 4) { base64 += "="; } const binaryString = atob(base64); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } return bytes; } catch { return void 0; } } function setCookie(headers, name, value, options = {}) { if (headers === void 0) { return; } const cookieString = serialize(name, value, { path: "/", ...options }); headers.append("Set-Cookie", cookieString); } function getCookie(headers, name, options = {}) { if (headers === void 0) { return void 0; } const cookieHeader = headers.get("cookie"); if (cookieHeader === null) { return void 0; } return parse(cookieHeader, options)[name]; } function deleteCookie(headers, name, options = {}) { return setCookie(headers, name, "", { ...options, maxAge: 0 }); } const PBKDF2_CONFIG = { name: "PBKDF2", iterations: 6e4, // Recommended minimum iterations per current OWASP guidelines hash: "SHA-256" }; const AES_GCM_CONFIG = { name: "AES-GCM", length: 256 }; const CRYPTO_CONSTANTS = { SALT_LENGTH: 16, IV_LENGTH: 12 }; async function encrypt(value, secret) { const encoder = new TextEncoder(); const data = encoder.encode(value); const salt = crypto.getRandomValues(new Uint8Array(CRYPTO_CONSTANTS.SALT_LENGTH)); const keyMaterial = await crypto.subtle.importKey( "raw", encoder.encode(secret), PBKDF2_CONFIG.name, false, ["deriveKey"] ); const key = await crypto.subtle.deriveKey( { ...PBKDF2_CONFIG, salt }, keyMaterial, AES_GCM_CONFIG, false, ["encrypt"] ); const iv = crypto.getRandomValues(new Uint8Array(CRYPTO_CONSTANTS.IV_LENGTH)); const encrypted = await crypto.subtle.encrypt( { name: AES_GCM_CONFIG.name, iv }, key, data ); const result = new Uint8Array(salt.length + iv.length + encrypted.byteLength); result.set(salt, 0); result.set(iv, salt.length); result.set(new Uint8Array(encrypted), salt.length + iv.length); return encodeBase64url(result); } async function decrypt(encrypted, secret) { try { const data = decodeBase64url(encrypted); if (data === void 0) { return void 0; } const encoder = new TextEncoder(); const decoder = new TextDecoder(); const salt = data.slice(0, CRYPTO_CONSTANTS.SALT_LENGTH); const iv = data.slice(CRYPTO_CONSTANTS.SALT_LENGTH, CRYPTO_CONSTANTS.SALT_LENGTH + CRYPTO_CONSTANTS.IV_LENGTH); const encryptedData = data.slice(CRYPTO_CONSTANTS.SALT_LENGTH + CRYPTO_CONSTANTS.IV_LENGTH); const keyMaterial = await crypto.subtle.importKey( "raw", encoder.encode(secret), PBKDF2_CONFIG.name, false, ["deriveKey"] ); const key = await crypto.subtle.deriveKey( { ...PBKDF2_CONFIG, salt }, keyMaterial, AES_GCM_CONFIG, false, ["decrypt"] ); const decrypted = await crypto.subtle.decrypt( { name: AES_GCM_CONFIG.name, iv }, key, encryptedData ); return decoder.decode(decrypted); } catch { return void 0; } } const ALGORITHM = { name: "HMAC", hash: "SHA-256" }; async function sign(value, secret) { const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), ALGORITHM, false, ["sign"] ); const signature = await crypto.subtle.sign( ALGORITHM, key, encoder.encode(value) ); return `${value}.${encodeBase64url(new Uint8Array(signature))}`; } async function unsign(signedValue, secret) { if (typeof signedValue !== "string") { return void 0; } const lastDotIndex = signedValue.lastIndexOf("."); if (lastDotIndex === -1) { return void 0; } const value = signedValue.slice(0, lastDotIndex); const signatureBase64url = signedValue.slice(lastDotIndex + 1); const signature = decodeBase64url(signatureBase64url); if (signature === void 0) { return void 0; } const encoder = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", encoder.encode(secret), ALGORITHM, false, ["verify"] ); const isValid = await crypto.subtle.verify( ALGORITHM, key, signature, encoder.encode(value) ); return isValid ? value : void 0; } function getSignedValue(signedValue) { if (typeof signedValue !== "string") { return void 0; } const lastDotIndex = signedValue.lastIndexOf("."); if (lastDotIndex === -1) { return void 0; } return signedValue.slice(0, lastDotIndex); } export { decodeBase64url, decrypt, deleteCookie, encodeBase64url, encrypt, getCookie, getSignedValue, setCookie, sign, unsign };