@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
JavaScript
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 };