UNPKG

@analog-tools/session

Version:

Session management for AnalogJS server-side applications

259 lines (258 loc) 7.5 kB
import { getCookie as x, setCookie as _ } from "h3"; import { createStorage as S } from "unstorage"; import g from "ioredis"; let f = (e = 21) => crypto.getRandomValues(new Uint8Array(e)).reduce((t, r) => (r &= 63, r < 36 ? t += r.toString(36) : r < 62 ? t += (r - 26).toString(36).toUpperCase() : r > 62 ? t += "-" : t += "_", t), ""); async function I(e, t) { if (typeof globalThis < "u" && globalThis.crypto && globalThis.crypto.subtle) { const r = new TextEncoder(), s = await globalThis.crypto.subtle.importKey( "raw", r.encode(t), { name: "HMAC", hash: "SHA-256" }, !1, ["sign"] ), i = await globalThis.crypto.subtle.sign("HMAC", s, r.encode(e)), l = R(i).replace(/=+$/, ""); return `s:${e}.${l}`; } throw new Error("Crypto API not available - ensure you are running in a compatible environment with Web Crypto API support"); } async function A(e, t) { if (!e.startsWith("s:")) return null; const [, r] = e.split("s:", 2), s = r.lastIndexOf("."); if (s === -1) return null; const i = r.slice(0, s), l = r.slice(s + 1); for (const u of t) try { const n = await I(i, u), [, a] = n.split("s:", 2), o = a.slice(a.lastIndexOf(".") + 1); if (C(l, o)) return i; } catch { continue; } return null; } function R(e) { const t = new Uint8Array(e); let r = ""; for (let s = 0; s < t.byteLength; s++) r += String.fromCharCode(t[s]); return btoa(r); } function C(e, t) { if (e.length !== t.length) return !1; let r = 0; for (let s = 0; s < e.length; s++) r |= e.charCodeAt(s) ^ t.charCodeAt(s); return r === 0; } const w = "__session_data__", h = "__session_id__"; async function $(e, t) { var i, l, u, n, a; const r = t.name || "connect.sid", s = Array.isArray(t.secret) ? t.secret : [t.secret]; e.context.__session_config__ = t; try { const o = x(e, r); let c = null; o && (c = await A(o, s)); let m; if (c) { const d = await t.store.getItem(c); d ? m = d : (c = f(), m = t.generate ? t.generate() : {}); } else c = f(), m = t.generate ? t.generate() : {}; e.context[w] = m, e.context[h] = c; try { const d = await I(c, s[0]); _(e, r, d, { maxAge: t.maxAge || 86400, httpOnly: ((i = t.cookie) == null ? void 0 : i.httpOnly) ?? !0, secure: ((l = t.cookie) == null ? void 0 : l.secure) ?? !1, sameSite: ((u = t.cookie) == null ? void 0 : u.sameSite) ?? "lax", domain: (n = t.cookie) == null ? void 0 : n.domain, path: ((a = t.cookie) == null ? void 0 : a.path) || "/" }); } catch (d) { throw y("COOKIE_ERROR", "Failed to sign or set session cookie", { error: d }); } try { await t.store.setItem(c, m); } catch (d) { throw y("STORAGE_ERROR", "Failed to save session data to store", { error: d }); } } catch (o) { throw y("CRYPTO_ERROR", "An unexpected error occurred during session initialization", { error: o }); } } function p(e) { return e.context[w] || null; } async function H(e, t) { const r = p(e), s = e.context[h]; if (!r || !s) throw y("INVALID_SESSION", "No active session found"); const i = t(r), l = { ...r, ...i }; e.context[w] = l; const u = e.context.__session_config__; u != null && u.store && await u.store.setItem(s, l); } async function V(e) { const t = e.context[h], r = e.context.__session_config__; if (t) try { r != null && r.store && await r.store.removeItem(t), delete e.context[w], delete e.context[h]; const s = (r == null ? void 0 : r.name) || "connect.sid"; _(e, s, "", { maxAge: 0, httpOnly: !0, path: "/" }); } catch (s) { throw y("STORAGE_ERROR", "Failed to destroy session", { error: s }); } } async function B(e) { var i, l, u, n, a; const t = p(e), r = e.context[h], s = e.context.__session_config__; if (!t || !r || !s) throw y("INVALID_SESSION", "No active session to regenerate"); try { const o = f(); e.context[h] = o, await s.store.setItem(o, t), await s.store.removeItem(r); const c = Array.isArray(s.secret) ? s.secret : [s.secret], m = await I(o, c[0]), d = s.name || "connect.sid"; _(e, d, m, { maxAge: s.maxAge || 86400, httpOnly: ((i = s.cookie) == null ? void 0 : i.httpOnly) ?? !0, secure: ((l = s.cookie) == null ? void 0 : l.secure) ?? !1, sameSite: ((u = s.cookie) == null ? void 0 : u.sameSite) ?? "lax", domain: (n = s.cookie) == null ? void 0 : n.domain, path: ((a = s.cookie) == null ? void 0 : a.path) || "/" }); } catch (o) { throw y("STORAGE_ERROR", "Failed to regenerate session", { error: o }); } } function y(e, t, r) { const s = new Error(t); return s.code = e, s.details = r, s; } function E(e, t = ":") { return e ? e.replace(/[:/\\]/g, t).replace(/^[:/\\]|[:/\\]$/g, "") : ""; } function k(...e) { return e.map((t) => E(t)).filter(Boolean).join(":"); } const O = "memory", T = () => { const e = /* @__PURE__ */ new Map(); return { name: O, getInstance: () => e, hasItem(t) { return e.has(t); }, getItem(t) { return e.get(t) ?? null; }, getItemRaw(t) { return e.get(t) ?? null; }, setItem(t, r) { e.set(t, r); }, setItemRaw(t, r) { e.set(t, r); }, removeItem(t) { e.delete(t); }, getKeys() { return [...e.keys()]; }, clear() { e.clear(); }, dispose() { e.clear(); } }; }, N = "redis", b = (e) => { let t; const r = () => t || (e.cluster ? t = new g.Cluster(e.cluster, e.clusterOptions) : e.url ? t = new g(e.url, e) : t = new g(e), t), s = (e.base || "").replace(/:$/, ""), i = (...n) => k(s, ...n), l = (n) => s ? n.replace(`${s}:`, "") : n; if (e.preConnect) try { r(); } catch (n) { console.error(n); } const u = async (n) => { const a = r(), o = []; let c = "0"; do { const [m, d] = e.scanCount ? await a.scan(c, "MATCH", n, "COUNT", e.scanCount) : await a.scan(c, "MATCH", n); c = m, o.push(...d); } while (c !== "0"); return o; }; return { name: N, options: e, getInstance: r, async hasItem(n) { return !!await r().exists(i(n)); }, async getItem(n) { return await r().get(i(n)) ?? null; }, async getItems(n) { const a = n.map((c) => i(c.key)), o = await r().mget(...a); return a.map((c, m) => ({ key: l(c), value: o[m] ?? null })); }, async setItem(n, a, o) { const c = (o == null ? void 0 : o.ttl) ?? e.ttl; c ? await r().set(i(n), a, "EX", c) : await r().set(i(n), a); }, async removeItem(n) { await r().unlink(i(n)); }, async getKeys(n) { return (await u(i(n, "*"))).map((o) => l(o)); }, async clear(n) { const a = await u(i(n, "*")); a.length !== 0 && await r().unlink(a); }, dispose() { return r().disconnect(); } }; }; function F() { return S({ driver: T() }); } function U(e) { return S({ driver: b({ url: e.url, host: e.host, port: e.port, username: e.username, password: e.password, db: e.db, ttl: e.ttl }) }); } export { F as createMemoryStore, U as createRedisStore, V as destroySession, p as getSession, B as regenerateSession, I as signCookie, A as unsignCookie, H as updateSession, $ as useSession };