npm package: @periodic/tungsten-session
Description: Production-grade session orchestration for Node.js with sliding expiry, idle timeout, and storage-agnostic adapters
Author: Uday Thakur
Latest version: 1.0.0

# 🔑 Periodic Tungsten Session

[![npm version](https://img.shields.io/npm/v/@periodic/tungsten-session.svg)](https://www.npmjs.com/package/@periodic/tungsten-session)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.3-blue)](https://www.typescriptlang.org/)

**Production-grade session orchestration for Node.js with sliding expiry, idle timeout, and storage-agnostic adapters**

Part of the **Periodic** series of Node.js packages by Uday Thakur.

---

## 💡 Why Tungsten Session?

**`@periodic/tungsten-session`** is the session management layer of the `@periodic/tungsten` authentication family. While `@periodic/tungsten` handles cryptographic primitives — signing tokens, hashing passwords, verifying HMAC signatures — this library handles the server-side session lifecycle: creating sessions, validating them on every request, rotating IDs after privilege escalation, enforcing idle timeouts, and revoking sessions on logout or security events.

Sessions are deceptively simple until they aren't. A session that never expires is a permanent backdoor. A session that doesn't rotate on login is vulnerable to fixation attacks. A session store without support for bulk revocation leaves you powerless when a user's account is compromised. **Tungsten Session handles all of it with a clean, storage-agnostic interface that works identically across Redis, PostgreSQL, DynamoDB, and any backend you choose.**

The name represents:
- **Durability**: Sessions persist correctly across requests, processes, and server restarts
- **Precision**: Absolute expiry, sliding expiry, and idle timeout are independently configurable
- **Security**: Rotation, revocation, and timing-safe lookups are built in — not bolted on
- **Flexibility**: The storage interface is minimal — adapt it to any backend in minutes

Just as `@periodic/tungsten` handles the cryptographic layer without shortcuts, **@periodic/tungsten-session** handles the session layer without assumptions about your infrastructure.

---

## 🎯 Why Choose Tungsten Session?

Session management seems simple until you look closely — and most implementations miss at least one critical detail:

- **No session rotation** leaves users vulnerable to session fixation attacks on login
- **No idle timeout** means an inactive session stays valid until absolute expiry — a long window for hijacking
- **Absolute expiry only** logs users out mid-task on long operations instead of after true inactivity
- **No bulk revocation** means there's no way to log a user out of all devices after a password change
- **No storage abstraction** means the session logic is tightly coupled to Redis or a specific ORM
- **Timing-unsafe lookups** leak information about which session IDs exist via response time differences

**Periodic Tungsten Session** provides the perfect solution:

✅ **Storage-agnostic** — Redis, PostgreSQL, DynamoDB, or any custom adapter  
✅ **Session creation** — cryptographically secure IDs, stored with full metadata  
✅ **Session validation** — checks absolute and idle expiry in a single call  
✅ **Sliding expiration** — idle timeout resets on each validated access  
✅ **Absolute expiration** — hard maximum lifetime, never extended  
✅ **Idle timeout** — separate inactivity window independent of absolute expiry  
✅ **Session rotation** — new ID on every sensitive operation, prevents fixation  
✅ **Single revocation** — invalidate one session by ID on explicit logout  
✅ **Bulk revocation** — invalidate all sessions for a user on security events  
✅ **Timing-safe operations** — no information leakage via response timing  
✅ **Type-safe** — strict TypeScript throughout, zero `any`  
✅ **No global state** — no side effects on import  
✅ **Production-ready** — non-blocking, never crashes your app

---

## 📦 Installation

```bash
npm install @periodic/tungsten-session @periodic/tungsten
```

Or with yarn:

```bash
yarn add @periodic/tungsten-session @periodic/tungsten
```

---

## 🚀 Quick Start

```typescript
import { createSession, validateSession } from '@periodic/tungsten-session';

// 1. Define a storage adapter for your backend
const storage: SessionStorage = {
  async get(id: string) {
    const raw = await redis.get(`session:${id}`);
    return raw ? JSON.parse(raw) : null;
  },
  async set(session: Session) {
    await redis.set(`session:${session.id}`, JSON.stringify(session), 'EX', 604800);
  },
  async delete(id: string) {
    await redis.del(`session:${id}`);
  },
  async deleteByUserId(userId: string) {
    const keys = await redis.keys(`session:user:${userId}:*`);
    if (keys.length) await redis.del(...keys);
    return keys.length;
  },
};

// 2. Create a session on login
const { sessionId, session } = await createSession('user_123', {
  storage,
  ttl: '7d',
  idleTimeout: '24h',
});

// Set as a cookie
res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });

// 3. Validate on each request
const result = await validateSession(req.cookies.sid, {
  storage,
  extendOnAccess: true,
});

if (result.valid) {
  req.userId = result.session.userId;
}
```

**Example session object:**

```json
{
  "id": "sess_01HQ4K2N8XVPQ3Z...",
  "userId": "user_123",
  "createdAt": 1708000000000,
  "lastAccessedAt": 1708003600000,
  "expiresAt": 1708604800000,
  "idleExpiresAt": 1708090000000
}
```

---

## 🧠 Core Concepts

### The Session Lifecycle

Five functions cover the complete lifecycle — each does exactly one thing:

- **`createSession`** — generates a secure session ID, builds the session record, stores it, returns the ID
- **`validateSession`** — looks up the session, checks absolute and idle expiry, optionally extends idle TTL
- **`rotateSession`** — atomically replaces the session ID — use after privilege escalation (login, sudo)
- **`revokeSession`** — deletes a session by ID — use on explicit logout
- **`revokeAllUserSessions`** — deletes all sessions for a user — use on password change or security event

**Design principle:**
> The session functions are pure orchestration — they delegate all storage to your adapter. The same call works identically with Redis, Postgres, or an in-memory store.

### Expiry Model

Three independent expiry mechanisms work together:

```
Session created
       │
       ├── Absolute expiry (ttl)        — hard maximum, never extended
       │
       └── Idle timeout (idleTimeout)   — reset on each validated access

Session valid while: now < expiresAt  AND  now < idleExpiresAt
```

A session expires when **either** limit is crossed — whichever comes first. The idle timeout resets on every call to `validateSession` with `extendOnAccess: true`. The absolute expiry never changes.

### The `SessionStorage` Interface

The only contract between tungsten-session and your infrastructure — four methods, nothing more:

```typescript
interface SessionStorage {
  get(id: string): Promise<Session | null>;
  set(session: Session): Promise<void>;
  delete(id: string): Promise<void>;
  deleteByUserId(userId: string): Promise<number>; // returns count deleted
}
```

---

## ✨ Features

### 🆕 Session Creation

```typescript
const { sessionId, session } = await createSession('user_123', {
  storage,
  ttl: '7d',          // absolute expiry — '7d', '24h', '30m', or milliseconds
  idleTimeout: '24h', // idle expiry — reset on each access
  metadata: {         // optional — attach any extra data to the session
    ipAddress: req.ip,
    userAgent: req.headers['user-agent'],
  },
});
```

### ✅ Session Validation

Validate and optionally extend the idle timeout in a single call:

```typescript
const result = await validateSession(sessionId, {
  storage,
  extendOnAccess: true,
});

if (result.valid) {
  console.log(result.session.userId);
} else {
  // result.reason: 'not_found' | 'expired' | 'idle_expired'
  console.log('Invalid:', result.reason);
}
```

### 🔄 Session Rotation

Replace the session ID atomically after sensitive operations — prevents fixation:

```typescript
const { newSessionId } = await rotateSession(currentSessionId, { storage });
res.cookie('sid', newSessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
```

### 🚪 Session Revocation

```typescript
// Single session — explicit logout
await revokeSession(req.cookies.sid, storage);
res.clearCookie('sid');

// All sessions — password change, account compromise
const count = await revokeAllUserSessions('user_123', storage);
console.log(`Revoked ${count} sessions`);
```

---

## 📚 Common Patterns

### 1. Express Session Middleware

```typescript
import { validateSession } from '@periodic/tungsten-session';

export async function sessionMiddleware(req, res, next) {
  const sessionId = req.cookies?.sid;
  if (!sessionId) return next();

  const result = await validateSession(sessionId, { storage, extendOnAccess: true });

  if (result.valid) {
    req.session = result.session;
    req.userId = result.session.userId;
  } else {
    res.clearCookie('sid');
  }

  next();
}
```

### 2. Login with Session Creation

```typescript
import { verifyPassword } from '@periodic/tungsten';
import { createSession } from '@periodic/tungsten-session';

app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await db.users.findOne({ email });
  const isValid = await verifyPassword(password, user.passwordHash);
  if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });

  const { sessionId } = await createSession(user.id, {
    storage,
    ttl: '7d',
    idleTimeout: '24h',
    metadata: { ipAddress: req.ip, userAgent: req.headers['user-agent'] },
  });

  res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ userId: user.id });
});
```

### 3. Logout

```typescript
import { revokeSession } from '@periodic/tungsten-session';

app.post('/auth/logout', async (req, res) => {
  if (req.cookies.sid) {
    await revokeSession(req.cookies.sid, storage);
    res.clearCookie('sid');
  }
  res.sendStatus(204);
});
```

### 4. Logout All Devices

```typescript
import { revokeAllUserSessions } from '@periodic/tungsten-session';

app.post('/auth/logout-all', requireAuth, async (req, res) => {
  const count = await revokeAllUserSessions(req.userId, storage);
  res.clearCookie('sid');
  res.json({ revokedSessions: count });
});
```

### 5. Password Change with Full Revocation

```typescript
import { hashPassword } from '@periodic/tungsten';
import { revokeAllUserSessions, createSession } from '@periodic/tungsten-session';

app.post('/auth/change-password', requireAuth, async (req, res) => {
  const hash = await hashPassword(req.body.newPassword);
  await db.users.update({ id: req.userId }, { passwordHash: hash });

  // Revoke all existing sessions — force re-login everywhere
  await revokeAllUserSessions(req.userId, storage);

  // Create a fresh session for the current device
  const { sessionId } = await createSession(req.userId, { storage, ttl: '7d' });
  res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ message: 'Password changed — other sessions revoked' });
});
```

### 6. Privilege Escalation with Rotation

```typescript
import { rotateSession } from '@periodic/tungsten-session';

app.post('/auth/sudo', requireAuth, async (req, res) => {
  const isValid = await verifyPassword(req.body.password, req.user.passwordHash);
  if (!isValid) return res.status(401).json({ error: 'Invalid password' });

  // Rotate session ID to prevent fixation after privilege change
  const { newSessionId } = await rotateSession(req.cookies.sid, { storage });
  res.cookie('sid', newSessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ elevated: true });
});
```

### 7. Structured Logging Integration

```typescript
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { validateSession } from '@periodic/tungsten-session';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

async function validate(sessionId: string) {
  const result = await validateSession(sessionId, { storage, extendOnAccess: true });
  if (!result.valid) {
    logger.warn('session.invalid', { reason: result.reason });
  } else {
    logger.info('session.valid', { userId: result.session.userId });
  }
  return result;
}
```

### 8. Production Configuration

```typescript
import { createSession, validateSession, revokeAllUserSessions } from '@periodic/tungsten-session';

const isDevelopment = process.env.NODE_ENV === 'development';

export const sessionConfig = {
  storage,
  ttl: isDevelopment ? '1h' : '7d',
  idleTimeout: isDevelopment ? '15m' : '24h',
};

export const session = {
  create: (userId: string) => createSession(userId, sessionConfig),
  validate: (id: string) => validateSession(id, { ...sessionConfig, extendOnAccess: true }),
  revokeAll: (userId: string) => revokeAllUserSessions(userId, storage),
};

export default session;
```

---

## 🎛️ Configuration Options

### `createSession` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storage` | `SessionStorage` | required | Storage adapter |
| `ttl` | `string \| number` | required | Absolute expiry (`'7d'`, `'24h'`, `'30m'`, or ms) |
| `idleTimeout` | `string \| number` | — | Idle timeout — reset on each valid access |
| `metadata` | `Record<string, unknown>` | — | Optional data stored with the session |

### `validateSession` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storage` | `SessionStorage` | required | Storage adapter |
| `extendOnAccess` | `boolean` | `false` | Reset idle timeout on each valid access |

### `rotateSession` Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `storage` | `SessionStorage` | required | Storage adapter |
| `ttl` | `string \| number` | — | New absolute TTL for the rotated session |

### `SessionValidationResult`

| Field | Type | Description |
|-------|------|-------------|
| `valid` | `boolean` | Whether the session is valid |
| `session` | `Session \| null` | The session object (when `valid: true`) |
| `reason` | `'not_found' \| 'expired' \| 'idle_expired'` | Why invalid (when `valid: false`) |

---

## 📋 API Reference

### Session Functions

```typescript
createSession(userId: string, options: CreateSessionOptions): Promise<{ sessionId: string; session: Session }>
validateSession(sessionId: string, options: ValidateSessionOptions): Promise<SessionValidationResult>
rotateSession(sessionId: string, options: RotateSessionOptions): Promise<{ newSessionId: string }>
revokeSession(sessionId: string, storage: SessionStorage): Promise<void>
revokeAllUserSessions(userId: string, storage: SessionStorage): Promise<number>
```

### Types

```typescript
import type {
  Session,
  SessionStorage,
  CreateSessionOptions,
  ValidateSessionOptions,
  SessionValidationResult,
} from '@periodic/tungsten-session';
```

---

## 🧩 Architecture

```
@periodic/tungsten-session/
├── src/
│   ├── core/
│   │   ├── session.ts         # createSession, validateSession, rotateSession, revokeSession, revokeAllUserSessions
│   │   └── types.ts           # Session, SessionStorage, all options and result types
│   ├── utils/
│   │   ├── id.ts              # Cryptographically secure session ID generation
│   │   ├── ttl.ts             # TTL string parsing ('7d', '24h', '30m') → ms
│   │   └── time.ts            # Clock abstraction for deterministic testing
│   └── index.ts               # Public API
```

**Design Philosophy:**
- **Functions, not classes** — each operation is an explicit function call, not a method on a stateful object
- **Storage is injected** — no global adapter, no singleton, safe for multi-tenant apps
- **No framework coupling** — works with Express, Fastify, Koa, or no framework at all
- **No database opinions** — the storage interface is four methods, nothing more
- **Clock is injectable** — pass a custom `clock` function for deterministic testing

---

## 📈 Performance

- **Single storage read per validation** — no extra round trips for expiry checks
- **Atomic rotation** — `rotateSession` creates and deletes in the correct order, no window of inconsistency
- **Timing-safe** — session ID lookups use constant-time comparison, no timing side channels
- **No global state** — multiple configurations in the same process are fully isolated
- **No monkey-patching** — pure functions only, no prototype mutation

---

## 🚫 Explicit Non-Goals

This package **intentionally does not** include:

❌ JWT or token signing (use `@periodic/tungsten`)  
❌ Browser-side session state (use `@periodic/tungsten-client`)  
❌ Built-in Redis, Postgres, or DynamoDB adapters — bring your own  
❌ Cookie management — set cookies yourself with your framework  
❌ Rate limiting (use `@periodic/titanium`)  
❌ Magic or implicit behavior on import  
❌ Configuration files (configure in code)

Focus on doing one thing well: **correct, storage-agnostic server-side session orchestration**.

---

## 🎨 TypeScript Support

Full TypeScript support with complete type safety:

```typescript
import type {
  Session,
  SessionStorage,
  CreateSessionOptions,
  ValidateSessionOptions,
  SessionValidationResult,
} from '@periodic/tungsten-session';

// SessionValidationResult is discriminated — narrow by valid
const result = await validateSession(id, { storage });
if (result.valid) {
  result.session.userId; // string — only present when valid
} else {
  result.reason; // 'not_found' | 'expired' | 'idle_expired' — only when invalid
}

// Storage adapter is fully typed
const storage: SessionStorage = {
  get: async (id) => { /* return Session | null */ },
  set: async (session) => { /* store session */ },
  delete: async (id) => { /* delete by id */ },
  deleteByUserId: async (userId) => { /* return count deleted */ },
};
```

---

## 🧪 Testing

```bash
# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch
```

**Note:** All tests achieve >80% code coverage.

---

## 🤝 Related Packages

Part of the **Periodic** series by Uday Thakur:

- [**@periodic/tungsten**](https://www.npmjs.com/package/@periodic/tungsten) - Authentication primitives (JWT, Argon2, TOTP, HMAC)
- [**@periodic/tungsten-client**](https://www.npmjs.com/package/@periodic/tungsten-client) - Browser token state management
- [**@periodic/iridium**](https://www.npmjs.com/package/@periodic/iridium) - Structured logging
- [**@periodic/arsenic**](https://www.npmjs.com/package/@periodic/arsenic) - Semantic runtime monitoring
- [**@periodic/zirconium**](https://www.npmjs.com/package/@periodic/zirconium) - Environment configuration
- [**@periodic/vanadium**](https://www.npmjs.com/package/@periodic/vanadium) - Idempotency and distributed locks
- [**@periodic/strontium**](https://www.npmjs.com/package/@periodic/strontium) - Resilient HTTP client
- [**@periodic/titanium**](https://www.npmjs.com/package/@periodic/titanium) - Rate limiting
- [**@periodic/osmium**](https://www.npmjs.com/package/@periodic/osmium) - Redis caching

Build complete, production-ready APIs with the Periodic series!

---

## 📖 Documentation

- [Quick Start Guide](QUICKSTART.md)
- [Installation Guide](INSTALLATION.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)

---

## 🛠️ Production Recommendations

### Environment Variables

```bash
SESSION_TTL=7d
SESSION_IDLE_TIMEOUT=24h
NODE_ENV=production
REDIS_URL=redis://localhost:6379
```

### Log Aggregation

Pair with `@periodic/iridium` for structured JSON output:

```typescript
import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

async function sessionMiddleware(req, res, next) {
  const result = await validateSession(req.cookies.sid, { storage, extendOnAccess: true });
  if (!result.valid) {
    logger.warn('session.invalid', { reason: result.reason, ip: req.ip });
  }
  next();
}

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.
```

### Security Monitoring

```typescript
async function onPasswordChange(userId: string) {
  const count = await revokeAllUserSessions(userId, storage);
  Sentry.captureMessage('sessions.bulk_revocation', {
    level: 'info',
    extra: { userId, count },
  });
}
```

---

## 📝 License

MIT © [Uday Thakur](LICENSE)

---

## 🙏 Contributing

Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on:

- Code of conduct
- Development setup
- Pull request process
- Coding standards
- Architecture principles

---

## 📞 Support

- 📧 **Email:** udaythakurwork@gmail.com
- 🐛 **Issues:** [GitHub Issues](https://github.com/udaythakur7469/periodic-tungsten-session/issues)
- 💬 **Discussions:** [GitHub Discussions](https://github.com/udaythakur7469/periodic-tungsten-session/discussions)

---

## 🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!

---

**Built with ❤️ by Uday Thakur for production-grade Node.js applications**