@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
277 lines (274 loc) • 8.05 kB
JavaScript
'use strict';
var crypto$1 = require('crypto');
// src/auth/defaults/session/memory.ts
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
var MemorySessionProvider = class {
sessions = /* @__PURE__ */ new Map();
ttl;
cookieName;
cookiePath;
cleanupTimer = null;
constructor(options = {}) {
this.ttl = options.ttl ?? 7 * 24 * 60 * 60 * 1e3;
this.cookieName = options.cookieName ?? "mastra_session";
this.cookiePath = options.cookiePath ?? "/";
const cleanupInterval = options.cleanupInterval ?? 6e4;
this.cleanupTimer = setInterval(() => this.cleanup(), cleanupInterval);
console.warn(
"[MemorySessionProvider] Using in-memory sessions. Sessions will be lost on server restart. Use a persistent session provider in production."
);
}
async createSession(userId, metadata) {
const session = {
id: crypto.randomUUID(),
userId,
expiresAt: new Date(Date.now() + this.ttl),
createdAt: /* @__PURE__ */ new Date(),
metadata
};
this.sessions.set(session.id, session);
return session;
}
async validateSession(sessionId) {
const session = this.sessions.get(sessionId);
if (!session) {
return null;
}
if (session.expiresAt < /* @__PURE__ */ new Date()) {
this.sessions.delete(sessionId);
return null;
}
return session;
}
async destroySession(sessionId) {
this.sessions.delete(sessionId);
}
async refreshSession(sessionId) {
const session = await this.validateSession(sessionId);
if (!session) {
return null;
}
session.expiresAt = new Date(Date.now() + this.ttl);
this.sessions.set(sessionId, session);
return session;
}
getSessionIdFromRequest(request) {
const cookieHeader = request.headers.get("cookie");
if (!cookieHeader) return null;
const escapedName = escapeRegExp(this.cookieName);
const match = cookieHeader.match(new RegExp(`${escapedName}=([^;]+)`));
return match?.[1] ?? null;
}
getSessionHeaders(session) {
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
return {
"Set-Cookie": `${this.cookieName}=${session.id}; HttpOnly; SameSite=Lax; Path=${this.cookiePath}; Max-Age=${maxAge}`
};
}
getClearSessionHeaders() {
return {
"Set-Cookie": `${this.cookieName}=; HttpOnly; SameSite=Lax; Path=${this.cookiePath}; Max-Age=0`
};
}
/**
* Clean up expired sessions.
*/
cleanup() {
const now = /* @__PURE__ */ new Date();
for (const [id, session] of this.sessions) {
if (session.expiresAt < now) {
this.sessions.delete(id);
}
}
}
/**
* Stop the cleanup timer.
*/
dispose() {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
}
/**
* Get the number of active sessions (for debugging).
*/
getSessionCount() {
return this.sessions.size;
}
};
function escapeRegExp2(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
var CookieSessionProvider = class {
secret;
ttl;
cookieName;
cookiePath;
cookieDomain;
secure;
constructor(options) {
if (!options.secret || options.secret.length < 32) {
throw new Error("CookieSessionProvider requires a secret of at least 32 characters");
}
this.secret = options.secret;
this.ttl = options.ttl ?? 7 * 24 * 60 * 60 * 1e3;
this.cookieName = options.cookieName ?? "mastra_session";
this.cookiePath = options.cookiePath ?? "/";
this.cookieDomain = options.cookieDomain;
this.secure = options.secure ?? process.env["NODE_ENV"] === "production";
}
async createSession(userId, metadata) {
const now = Date.now();
const session = {
id: crypto.randomUUID(),
userId,
expiresAt: new Date(now + this.ttl),
createdAt: new Date(now),
metadata
};
return session;
}
async validateSession(_sessionId) {
return null;
}
async destroySession(_sessionId) {
}
async refreshSession(_sessionId) {
return null;
}
getSessionIdFromRequest(request) {
const session = this.getSessionFromCookie(request);
return session?.id ?? null;
}
/**
* Get full session from cookie.
*/
getSessionFromCookie(request) {
const cookieHeader = request.headers.get("cookie");
if (!cookieHeader) return null;
const escapedName = escapeRegExp2(this.cookieName);
const match = cookieHeader.match(new RegExp(`${escapedName}=([^;]+)`));
if (!match?.[1]) return null;
try {
const decoded = this.decodeAndVerify(match[1]);
if (!decoded) return null;
if (decoded.expiresAt < Date.now()) {
return null;
}
return {
id: decoded.id,
userId: decoded.userId,
expiresAt: new Date(decoded.expiresAt),
createdAt: new Date(decoded.createdAt),
metadata: decoded.metadata
};
} catch {
return null;
}
}
getSessionHeaders(session) {
const data = {
id: session.id,
userId: session.userId,
expiresAt: session.expiresAt.getTime(),
createdAt: session.createdAt.getTime(),
metadata: session.metadata
};
const encoded = this.signAndEncode(data);
const maxAge = Math.floor((session.expiresAt.getTime() - Date.now()) / 1e3);
let cookie = `${this.cookieName}=${encoded}; HttpOnly; SameSite=Lax; Path=${this.cookiePath}; Max-Age=${maxAge}`;
if (this.cookieDomain) {
cookie += `; Domain=${this.cookieDomain}`;
}
if (this.secure) {
cookie += "; Secure";
}
return { "Set-Cookie": cookie };
}
getClearSessionHeaders() {
let cookie = `${this.cookieName}=; HttpOnly; SameSite=Lax; Path=${this.cookiePath}; Max-Age=0`;
if (this.cookieDomain) {
cookie += `; Domain=${this.cookieDomain}`;
}
return { "Set-Cookie": cookie };
}
/**
* Sign and encode session data.
*/
signAndEncode(data) {
const json = JSON.stringify(data);
const signature = this.sign(json);
const payload = `${this.base64Encode(json)}.${signature}`;
return encodeURIComponent(payload);
}
/**
* Decode and verify session cookie.
*/
decodeAndVerify(cookie) {
try {
const decoded = decodeURIComponent(cookie);
const [data, signature] = decoded.split(".");
if (!data || !signature) return null;
const json = this.base64Decode(data);
const expectedSignature = this.sign(json);
if (!this.secureCompare(signature, expectedSignature)) {
return null;
}
return JSON.parse(json);
} catch {
return null;
}
}
/**
* Create HMAC-SHA256 signature.
*/
sign(data) {
return crypto$1.createHmac("sha256", this.secret).update(data).digest("base64url");
}
/**
* Base64 encode (consistent across Node.js and browser runtimes).
*/
base64Encode(str) {
const bytes = new TextEncoder().encode(str);
if (typeof Buffer !== "undefined") {
return Buffer.from(bytes).toString("base64");
}
let binary = "";
for (const byte of bytes) {
binary += String.fromCharCode(byte);
}
return btoa(binary);
}
/**
* Base64 decode (consistent across Node.js and browser runtimes).
*/
base64Decode(str) {
if (typeof Buffer !== "undefined") {
return Buffer.from(str, "base64").toString("utf-8");
}
const binary = atob(str);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return new TextDecoder().decode(bytes);
}
/**
* Constant-time string comparison.
*/
secureCompare(a, b) {
if (a.length !== b.length) return false;
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
};
exports.CookieSessionProvider = CookieSessionProvider;
exports.MemorySessionProvider = MemorySessionProvider;
//# sourceMappingURL=index.cjs.map
//# sourceMappingURL=index.cjs.map