npm package: @glitch_protocol/auth-server
Description: glitch_protocol engine and Express adapter for Node.js backends
Author: drcount-root
Latest version: 0.2.0

# @glitch_protocol/auth-server

Core authentication engine (`glitch_protocolEngine`) with session management, JWT token handling, and support for pluggable database/cache/socket adapters.

## Install

```bash
npm install @glitch_protocol/auth-server @glitch_protocol/auth-adapter-drizzle @glitch_protocol/auth-adapter-redis
```

## API

### glitch_protocolEngine Constructor

```ts
new glitch_protocolEngine(
  db: DatabaseAdapter,
  cache: CacheAdapter,
  socket: SocketBroadcaster,
  config: glitch_protocolConfig,
)
```

**Config:**
```ts
interface glitch_protocolConfig {
  jwtSecret: string;                // Min 32 chars. Signs access tokens.
  accessTokenExpiryMinutes?: number; // Default: 15
  refreshTokenExpiryDays?: number;   // Default: 30
  bcryptRounds?: number;             // Default: 10 (for password hashing)
}
```

**Example:**
```ts
import { glitch_protocolEngine } from "@glitch_protocol/auth-server";
import { DrizzleAdapter } from "@glitch_protocol/auth-adapter-drizzle";
import { RedisCacheAdapter } from "@glitch_protocol/auth-adapter-redis";

const engine = new glitch_protocolEngine(
  new DrizzleAdapter(db, { users, sessions, refreshTokens }),
  new RedisCacheAdapter(redis),
  socketBroadcaster,
  { jwtSecret: process.env.JWT_SECRET! },
);
```

---

## Token Methods

### generateTokens
```ts
async generateTokens(userId: string, sessionId: string): Promise<TokenPair>
```
Generates a new access/refresh token pair. Called after login, registration, and token rotation.

### rotateRefreshToken
```ts
async rotateRefreshToken(rawRefreshToken: string): Promise<{
  tokens: TokenPair;
  userId: string;
  sessionId: string;
}>
```
Rotates a refresh token: validates, checks reuse, revokes old token, generates new pair. **Security:** if a revoked token is reused, all user sessions are revoked immediately.

### verifyAccessToken
```ts
verifyAccessToken(token: string): { sub: string; sessionId: string }
```
Verifies a JWT access token. Throws if expired or invalid.

### hashPassword
```ts
async hashPassword(password: string): Promise<string>
```
Hashes a password using bcryptjs. Use before storing in DB.

### comparePassword
```ts
async comparePassword(password: string, hash: string): Promise<boolean>
```
Compares a plaintext password against a hash.

---

## Session Methods

### createSession
```ts
async createSession(
  userId: string,
  ipAddress: string,
  userAgent: string | undefined,
): Promise<{ session: SessionInfo; tokens: TokenPair }>
```
Creates a new session after login. Generates tokens, broadcasts `SESSION_NEW` event to all user devices, and invalidates the sessions cache.

### getActiveSessions
```ts
async getActiveSessions(userId: string): Promise<SessionInfo[]>
```
Gets all active sessions for a user (cached). Used to display "your devices" list.

### terminateSession
```ts
async terminateSession(
  sessionId: string,
  userId: string,
  reason?: string,
): Promise<void>
```
Terminates a specific session. Deactivates in DB, caches the deactivation, revokes tokens, broadcasts termination event to the terminated device, and updates session list for all devices.

### updateSessionActivity
```ts
async updateSessionActivity(sessionId: string): Promise<void>
```
Updates a session's `lastActivity` timestamp (debounced via cache). Called on every request.

### updateSessionSocketId
```ts
async updateSessionSocketId(sessionId: string, socketId: string | null): Promise<void>
```
Associates a session with a Socket.IO socket ID for real-time events.

### terminateAllOtherSessions
```ts
async terminateAllOtherSessions(
  userId: string,
  currentSessionId: string,
): Promise<void>
```
Terminates all other sessions for a user (except current). Enables "log out all other devices".

---

## Cleanup & Lifecycle

### cleanupExpiredSessions
```ts
async cleanupExpiredSessions(): Promise<void>
```
Revokes expired sessions and tokens. Runs under distributed lock (only one instance per cluster runs this). Called by `startCleanupJob()` every 15 minutes.

### startCleanupJob
```ts
startCleanupJob(intervalMs?: number): void
```
Starts the cleanup job. Default interval: 900000ms (15 minutes). Should be called once at server startup.

### stopCleanupJob
```ts
stopCleanupJob(): void
```
Stops the cleanup job. Should be called on graceful shutdown (SIGTERM/SIGINT).

---

## Database Access

### getDatabaseAdapter
```ts
get getDatabaseAdapter(): DatabaseAdapter
```
Provides direct access to the database adapter for operations not exposed through the engine's public API.

---

## Error Classes

### TokenError
```ts
class TokenError extends Error {
  code: string;
}
```
Thrown by token operations. Check `error.code` against `ERROR_CODES.TOKEN_*` constants.

### SessionError
```ts
class SessionError extends Error {
  code: string;
}
```
Thrown by session operations. Check `error.code` against `ERROR_CODES.SESSION_*` constants.

**Error factory functions:**
```ts
createTokenExpiredError(): TokenError
createTokenInvalidError(): TokenError
createTokenReuseError(): TokenError // Token compromise detected
createSessionNotFoundError(): SessionError
createSessionExpiredError(): SessionError
createUserNotFoundError(): SessionError
```

---

## Utilities

### noopCache
```ts
const noopCache: CacheAdapter = {
  get: async () => null,
  set: async () => {},
  delete: async () => {},
  deleteMany: async () => {},
  acquireLock: async () => true,
  releaseLock: async () => {},
  isAvailable: async () => false,
};
```
In-memory fallback cache. **Use only for single-instance dev.** For production, use [`@glitch_protocol/auth-adapter-redis`](../adapter-redis/README.md).

---

## Express Integration

Add the engine to your app:

```ts
import express from "express";
import { engine } from "./engine";

const app = express();

app.post("/api/auth/login", async (req, res) => {
  const { email, password } = req.body;
  const user = await findUser(email);

  if (!user || !(await engine.comparePassword(password, user.passwordHash))) {
    return res.status(401).json({ success: false, error: "Invalid credentials" });
  }

  const { session, tokens } = await engine.createSession(
    user.id,
    req.ip ?? "unknown",
    req.headers["user-agent"],
  );

  // Set httpOnly refresh token cookie
  res.cookie("refreshToken", tokens.refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: "strict",
    maxAge: 30 * 24 * 60 * 60 * 1000,
  });

  res.json({
    success: true,
    data: {
      accessToken: tokens.accessToken,
      user: { id: user.id, email: user.email, name: user.name },
      sessions: [session],
    },
  });
});

app.post("/api/auth/refresh", async (req, res) => {
  try {
    const { tokens } = await engine.rotateRefreshToken(req.cookies.refreshToken);
    res.cookie("refreshToken", tokens.refreshToken, { httpOnly: true, secure: true, sameSite: "strict" });
    res.json({ success: true, data: { accessToken: tokens.accessToken } });
  } catch (error) {
    res.status(401).json({ success: false, error: "Refresh failed", code: error.code });
  }
});

app.post("/api/sessions/terminate", async (req, res) => {
  const { sessionId } = req.body;
  const userId = extractUserIdFromJWT(req.headers.authorization);
  await engine.terminateSession(sessionId, userId);
  res.json({ success: true });
});

// Startup
engine.startCleanupJob();
process.on("SIGTERM", () => engine.stopCleanupJob());
```

---

## Peer Dependencies

- `@glitch_protocol/auth-core` — interface definitions
- `@glitch_protocol/auth-adapter-drizzle` or similar — database adapter
- `@glitch_protocol/auth-adapter-redis` or `noopCache` — cache adapter

## Node Version

**ESM-only.** Requires Node.js >=18.