# @slices/session Session management for Slice applications with OAuth integration. ## Features - **Multiple Storage Adapters**: Memory, SQLite, PostgreSQL - **OAuth Integration**: Works seamlessly with `@slices/oauth` - **Automatic Token Refresh**: Keeps OAuth tokens valid - **Secure Cookie Handling**: HttpOnly, Secure, SameSite defaults - **Automatic Cleanup**: Expired session cleanup - **Framework Agnostic**: Works with any Deno web framework ## Installation ```bash deno add @slices/session ``` ## Quick Start ### Basic Usage ```typescript import { SessionStore, SQLiteAdapter } from "@slices/session"; const sessionStore = new SessionStore({ adapter: new SQLiteAdapter("./sessions.db"), cookieOptions: { httpOnly: true, secure: true, sameSite: "lax" } }); // Create a session const sessionId = await sessionStore.createSession("user123", "alice.bsky.social"); // Get session from request const session = await sessionStore.getSessionFromRequest(request); if (session) { console.log("User:", session.handle); } ``` ### With OAuth Integration ```typescript import { SessionStore, SQLiteAdapter, withOAuthSession } from "@slices/session"; import { OAuthClient, SQLiteOAuthStorage } from "@slices/oauth"; const sessionStore = new SessionStore({ adapter: new SQLiteAdapter("./sessions.db") }); const oauthStorage = new SQLiteOAuthStorage("./oauth.db"); const oauthConfig = { clientId: "your-client-id", clientSecret: "your-client-secret", authBaseUrl: "https://auth.example.com", redirectUri: "http://localhost:8000/oauth/callback", scopes: ["atproto"], }; const oauthSessions = withOAuthSession( sessionStore, oauthConfig, oauthStorage, { autoRefresh: true } ); // OAuth callback flow const tempClient = new OAuthClient(oauthConfig, oauthStorage, "temp"); const tokens = await tempClient.handleCallback({ code, state }); // Create OAuth session (handles user info fetch and token storage) const sessionId = await oauthSessions.createOAuthSession(tokens); // Get session with auto token refresh const session = await oauthSessions.getOAuthSession(sessionId); // Create session-scoped OAuth client const sessionClient = new OAuthClient(oauthConfig, oauthStorage, sessionId); const userInfo = await sessionClient.getUserInfo(); ``` ### Storage Adapters #### Memory Adapter (Development) ```typescript import { MemoryAdapter } from "@slices/session"; const adapter = new MemoryAdapter(); ``` #### SQLite Adapter (Production Single Instance) ```typescript import { SQLiteAdapter } from "@slices/session"; const adapter = new SQLiteAdapter("./sessions.db"); // or with URL const adapter = new SQLiteAdapter("sqlite://./sessions.db"); ``` #### PostgreSQL Adapter (Production Distributed) ```typescript import { PostgresAdapter } from "@slices/session"; const adapter = new PostgresAdapter("postgresql://user:pass@localhost/db"); // Initialize the database table await adapter.initialize(); ``` ## API Reference ### SessionStore Main session management class. ```typescript const store = new SessionStore({ adapter: SessionAdapter, cookieName?: string, // Default: "slice-session" cookieOptions?: CookieOptions, sessionTTL?: number, // Default: 30 days (ms) cleanupInterval?: number, // Default: 1 hour (ms) generateId?: () => string // Default: crypto.randomUUID() }); ``` #### Methods - `createSession(userId, handle?, data?)` - Create new session - `getSession(sessionId)` - Get session by ID - `updateSession(sessionId, updates)` - Update session data - `deleteSession(sessionId)` - Delete session - `getSessionFromRequest(request)` - Extract session from HTTP request - `getCurrentUser(request)` - Get user info from request - `createSessionCookie(sessionId)` - Create Set-Cookie header - `createLogoutCookie()` - Create logout cookie header - `cleanup()` - Remove expired sessions ### OAuthSessionManager OAuth-enabled session management with session-scoped tokens. ```typescript const manager = withOAuthSession( sessionStore, oauthConfig, oauthStorage, { autoRefresh: true, // Auto-refresh expired tokens onTokenRefresh: async (sessionId, tokens) => { // Handle token refresh }, onLogout: async (sessionId) => { // Handle logout } } ); ``` #### Methods - `createOAuthSession(tokens)` - Create session with OAuth tokens (fetches user info, stores tokens by sessionId) - `getOAuthSession(sessionId)` - Get session with token refresh - `logout(sessionId)` - OAuth logout and session cleanup - `hasValidOAuthTokens(sessionId)` - Check token validity - `getAccessToken(sessionId)` - Get access token for API calls #### How it works 1. **OAuth callback** returns tokens without `sub` 2. `createOAuthSession(tokens)`: - Creates temp OAuth client - Fetches user info to get `sub` - Creates session with userId - Stores tokens by sessionId (not userId!) 3. All subsequent operations use sessionId for token lookup ### Session Data Structure ```typescript interface SessionData { sessionId: string; userId: string; // User's DID or ID handle?: string; // User's handle (e.g., alice.bsky.social) isAuthenticated: boolean; data?: Record; // Custom session data createdAt: number; // Timestamp expiresAt: number; // Timestamp lastAccessedAt: number; // Timestamp } ``` ## Framework Integration ### Deno Fresh ```typescript // routes/_middleware.ts import { SessionStore, SQLiteAdapter } from "@slices/session"; const sessionStore = new SessionStore({ adapter: new SQLiteAdapter("./sessions.db") }); export async function handler(req: Request, ctx: FreshContext) { const user = await sessionStore.getCurrentUser(req); ctx.state.user = user; return ctx.next(); } ``` ### Hono ```typescript import { Hono } from "hono"; import { SessionStore, SQLiteAdapter } from "@slices/session"; const app = new Hono(); const sessionStore = new SessionStore({ adapter: new SQLiteAdapter("./sessions.db") }); app.use("*", async (c, next) => { const user = await sessionStore.getCurrentUser(c.req.raw); c.set("user", user); await next(); }); ``` ## Multi-Device Support The session-scoped OAuth pattern enables proper multi-device support: ```typescript // User logs in on laptop const laptopSessionId = await oauthSessions.createOAuthSession(laptopTokens); const laptopClient = new OAuthClient(config, storage, laptopSessionId); // Same user logs in on phone const phoneSessionId = await oauthSessions.createOAuthSession(phoneTokens); const phoneClient = new OAuthClient(config, storage, phoneSessionId); // Each device has independent tokens // Logging out laptop doesn't affect phone session await oauthSessions.logout(laptopSessionId); // Only laptop session cleared ``` ## Security - Sessions are stored with secure, httpOnly cookies by default - Automatic cleanup of expired sessions - CSRF protection through SameSite cookies - Secure session ID generation using crypto.randomUUID() - Optional token refresh to keep OAuth sessions valid - Session-scoped tokens prevent multi-device conflicts ## License MIT