UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

274 lines (272 loc) • 7.98 kB
import { createHmac } from '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 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; } }; export { CookieSessionProvider, MemorySessionProvider }; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map