npm package: @revas-hq/kit-session
Description: The `@revas-hq/kit-session` package provides flexible session management for TypeScript applications within the Revas Kit ecosystem. It allows storing session data associated with users across requests, integrating seamlessly with `@revas-hq/kit-context`,
Latest version: 0.0.16

# Revas Kit: Session (`@revas-hq/kit-session`)

The `@revas-hq/kit-session` package provides flexible session management for TypeScript applications within the Revas Kit ecosystem. It allows storing session data associated with users across requests, integrating seamlessly with `@revas-hq/kit-context`, `@revas-hq/kit-http`, and offering optional encryption.

## Features

- **Session Management API (`SessionManager`):** Provides functions to load, save, read, modify, and destroy session data.
- **Context Integration:** Stores the current request's session state within the `@revas-hq/kit-context`, making it available throughout the request lifecycle.
- **Storage Abstraction (`SessionStore`):** Decouples session logic from persistence via an interface, allowing different storage backends (DB, Redis, etc.).
- **Stateless Cookie Store (`createCookieSessionStore`):** Includes a built-in store that encodes session data directly into the session token (cookie value). **Requires signing/encryption for security.**
- **HTTP Middleware:** Provides `RequestFunction` (`createSessionRequestFunction`) and `ResponseFunction` (`createSessionResponseFunction`) for easy integration with `@revas-hq/kit-http`.
- **Optional Encryption (`createEncryptSessionStoreMiddleware`):** Offers robust AES-GCM encryption for both the session token and optionally the stored session data, using the Web Crypto API.
- **Secure Cookie Configuration:** Detailed options (`SessionCookieOptions`) for setting secure cookie attributes (`HttpOnly`, `Secure`, `SameSite`, etc.).

## Installation

```bash
# Install core package and essential peer dependencies
npm install @revas-hq/kit-session @revas-hq/kit-context @revas-hq/kit-http cookie
# or
yarn add @revas-hq/kit-session @revas-hq/kit-context @revas-hq/kit-http cookie
```

> **Optional:** If using encryption middleware with raw keys  
> No external crypto dependencies needed if environment supports Web Crypto API (Node >=15, Deno, CF Workers, Browsers)

---

## Core Concepts

### Session State (`Session<T>`)

An object stored in the context representing the current request's session data (`values`), unique identifier (`token`), expiry (`expireTime`), and modification status (`state`).

### Session Manager (`SessionManager`)

The main API for interacting with sessions. It orchestrates loading state from storage via cookies (`extractSession`), saving state back (`injectSession`), and modifying the state (`setValue`, `unsetValue`, `destroyValues`). It operates on the `Session<T>` object found within the Context.

### Session Store (`SessionStore<T>`)

An interface defining how session data is persisted (`getSession`, `setSession`, `deleteSession`). You provide an implementation (like `createCookieSessionStore` or a custom one for Redis/DB).

### HTTP Middleware

Functions (`createSessionRequestFunction`, `createSessionResponseFunction`) hook into the kit-http lifecycle to call the manager's `extractSession` and `injectSession` methods automatically.

### Encryption Middleware

Optional `SessionStoreMiddleware` (`createEncryptSessionStoreMiddleware`) wraps a `SessionStore` to add a layer of encryption using AES-GCM.

---

## Important: Session State Mutability

This library uses `@revas-hq/kit-context` to store the session state object (`Session<T>`). However, unlike the immutable pattern typically encouraged by kit-context's `withValue`, the `SessionManager` methods like `setValue`, `unsetValue`, and `destroyValues` directly mutate the `Session<T>` object retrieved from the context.

**How it works:**  
JavaScript objects are passed by reference. When the manager gets the session state from context, it modifies that specific object instance. Later, the `injectSession` method reads the same object instance from context, sees the mutated state (`state` and `values`), and saves accordingly.

**Implication:**  
You do **not** need to call `withContextValue` after using `manager.setValue`, `manager.unsetValue`, or `manager.destroyValues` to make the changes effective for saving. The mutation handles this.

**Be Aware:**  
While convenient, this deviates from pure functional immutability. Ensure you understand that session modifications within the lifecycle affect the state seen by subsequent operations in the same request.

---

## Configuration

### 1. Session Store

Choose or implement a `SessionStore`.

**Using the Stateless Cookie Store:**  
_Note: This store is insecure by itself. Use `createEncryptSessionStoreMiddleware` or another signing/encryption method._

```typescript
import { createCookieSessionStore } from '@revas-hq/kit-session/storage/cookie'; // Adjust path if needed

const cookieStore = createCookieSessionStore<{ userId?: string; theme?: string }>();
// or with default Record<string, any>
// const cookieStore = createCookieSessionStore();
```

### 2. Encryption (Optional but Recommended for Cookie Store)

```typescript
import { createEncryptSessionStoreMiddleware, importRawKey } from '@revas-hq/kit-session/encrypt'; // Adjust path

// WARNING: Load key securely (e.g., from environment variables/secrets manager). NEVER hardcode keys.
// Key must be Base64 encoded corresponding to 16, 24, or 32 bytes for AES-128/192/256.
const base64Key = process.env.SESSION_SECRET_KEY_BASE64;
if (!base64Key) throw new Error("SESSION_SECRET_KEY_BASE64 is not set!");

const cryptoKey = await importRawKey(base64Key);

// Create the encryption middleware (encrypts token AND values)
const encryptMiddleware = createEncryptSessionStoreMiddleware(cryptoKey, { encryptValues: true });

// Wrap the base store
const secureCookieStore = encryptMiddleware(cookieStore);
```

### 3. Session Manager

Create the manager, providing a context key name, cookie options, and the (potentially wrapped) store.

```typescript
import { createSessionManager, SessionManagerOptions, SessionCookieOptions } from '@revas-hq/kit-session';

const cookieOptions: SessionCookieOptions = {
    name: '__Host-Session', // Recommended secure prefix
    path: '/',
    httpOnly: true,
    secure: true, // Ensure your site uses HTTPS
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7, // 1 week in seconds (optional)
};

const managerOptions: SessionManagerOptions = {
    name: 'myAppSession', // Unique key for storing Session state in context
    cookie: cookieOptions,
    storage: secureCookieStore, // Use the encrypted store
    // storage: cookieStore, // Or use the unencrypted store (NOT RECOMMENDED without signing)
};

const sessionManager = createSessionManager(managerOptions);
```

### 4. HTTP Middleware

Create the request/response functions using the manager.

```typescript
import { createSessionRequestFunction, createSessionResponseFunction } from '@revas-hq/kit-session/http'; // Adjust path

const loadSession = createSessionRequestFunction(sessionManager);
const saveSession = createSessionResponseFunction(sessionManager);
```

---

## Usage

### Integration with kit-http-react-router

Add the `loadSession` and `saveSession` functions to the `beforeFunctions` and `afterFunctions` arrays respectively when using `createLoader` or `createAction`.

```typescript
// ~/main.ts (Example)
import { createLoader } from '@revas-hq/kit-http-react-router';
// ... import loadSession, saveSession, endpoint, decoder etc.

const myLoader = createLoader({
    endpoint: myEndpoint,
    decodeRequest: myDecoder,
    beforeFunctions: [
        // captureAuthorizationHeader, // Example: Run auth first if needed
        loadSession, // Load session state into context
    ],
    afterFunctions: [
        saveSession, // Save session state (if modified) to cookie
    ],
    // ... other options
});

// Register myLoader in the service container...
```

### Accessing and Modifying Session Data (e.g., in Endpoints)

Use the `SessionManager` instance (or helper functions if you create them) to interact with the session state stored in the context.

```typescript
import type { Endpoint, EndpointArgs } from '@revas-hq/kit-endpoint';
import type { Context } from '@revas-hq/kit-context';
// Assuming sessionManager is accessible (e.g., via container or closure)

const myEndpoint: Endpoint</* TRequest, TResponse */> = async ({ context, request }) => {
    // Get session data using the manager and current context
    const userId = sessionManager.getValue<string>(context, 'userId');
    const counter = sessionManager.getValue<number>(context, 'counter') ?? 0;

    if (!userId) {
        // Handle unauthenticated user...
        // Maybe set a flash message?
        sessionManager.setValue(context, 'flash', { type: 'error', message: 'Please log in'});
        // Note: setValue mutated the session state in the current context
        // No need for withContextValue here for the session change itself.

        // If returning early, make sure saveSession middleware still runs if needed!
        // EndpointResult doesn't carry context modifications intrinsically here,
        // relying on middleware order and the shared context object.
        return { success: false, context, error: { code: 401, message: 'Unauthorized' } };
    }

    // Modify session data
    sessionManager.setValue(context, 'counter', counter + 1);
    sessionManager.setValue(context, 'lastVisit', new Date().toISOString());

    // To destroy the session on response:
    // sessionManager.destroyValues(context);

    // Check modification status (optional)
    // console.log('Session Modified?', sessionManager.isModified(context));

    // Continue with business logic...
    const response = { /* ... */ };

    // The mutated session state in `context` will be picked up by `saveSession` middleware
    return { success: true, context, response };
};
```

---

## Security Considerations

- **Secrets:** Keep your `SessionMiddlewareOptions.secret` (if using signing directly) or the raw key for `importRawKey` (if using encryption) secure and confidential. Load them from environment variables or a secrets management system. Never hardcode secrets.
- **Cookie Options:** Use secure cookie settings:
  - `name`: Use `__Host-` or `__Secure-` prefixes.
  - `httpOnly: true` (Default): Prevents client-side script access.
  - `secure: true` (Default in prod): Ensures cookie is only sent over HTTPS. Requires your site to use HTTPS.
  - `sameSite: 'lax'` (Default) or `'strict'`: Mitigates CSRF attacks. Avoid `'none'` unless strictly necessary and `secure: true` is set.
  - `path`: Use the most restrictive path possible.
  - `domain`: Omit unless cross-subdomain access is needed (incompatible with `__Host-`).
- **Cookie Store:** The default `CookieSessionStore` puts all session data in the cookie.
  - **Size Limit:** Cookies are limited to ~4KB. Do not store large amounts of data.
  - **Sensitivity:** Do not store sensitive data in the cookie unless using strong encryption (like the provided `createEncryptSessionStoreMiddleware`). Even with encryption, prefer storing only essential identifiers (like `userId`) and fetching sensitive data from your backend when needed. Signing prevents tampering but not reading.
- **Encryption:** AES-GCM (`encrypt.ts`) provides strong authenticated encryption. Ensure your key management is secure.
- **Web Crypto API:** The encryption middleware relies on the Web Crypto API. Ensure your target environment (Node.js 15+, Deno, Cloudflare Workers, modern browsers) supports `crypto.subtle`.

---

## License

This project is licensed under the MIT License. See the LICENSE file for details.