@analog-tools/session
Version:
Session management for AnalogJS server-side applications
259 lines (258 loc) • 7.5 kB
JavaScript
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
};