@withnen/server
v0.4.0
Published
MIT licensed server middleware for end-to-end encrypted API payloads. withNen, withNenStream, session stores, HMAC auth.
Downloads
512
Readme
Nen Server SDK (@withnen/server)
The Next.js / serverless middleware for Nen. It runs the handshake, manages session keys, verifies the per-request HMAC, and decrypts/encrypts payloads.
Install
npm install @withnen/serverSetup
Mount the session routes (src/app/api/nen/[action]/route.ts):
import {
handleHandshake, handleRotate, handleTerminate, handleStatus,
setSessionStore, InMemorySessionStore,
} from '@withnen/server';
setSessionStore(new InMemorySessionStore()); // see "Session stores" below
export async function POST(req: Request, { params }: { params: Promise<{ action: string }> }) {
const { action } = await params;
if (action === 'handshake') return handleHandshake(req);
if (action === 'rotate') return handleRotate(req);
if (action === 'terminate') return handleTerminate(req);
return new Response('Not Found', { status: 404 });
}
export async function GET(req: Request, { params }: { params: Promise<{ action: string }> }) {
const { action } = await params;
return action === 'status' ? handleStatus(req) : new Response('Not Found', { status: 404 });
}Protect any endpoint:
import { withNen } from '@withnen/server';
export const POST = withNen(async (req, body) => {
// body is already decrypted AND the request is already authenticated
return { ok: true, body };
});Stream (SSE): withNenStream(async (req, body) => asyncGeneratorOfChunks).
How it works
store.ts— theSessionStoreinterface plusInMemorySessionStore(bound toglobalThisso Next.js HMR doesn't wipe keys in dev). Stores{ sharedSecret, hmacKey }persessionIdand tracks nonces.middleware.ts—handleHandshake(ML-KEM encapsulate + issue a random HMAC key + optional ML-DSA identity check),decryptPayload,encryptPayload, and the lifecycle handlers.wrapper.ts/stream-wrapper.ts— thewithNen/withNenStreamDX wrappers.
Mandatory per-request HMAC
HMAC is required by default. decryptPayload/withNen reject any request
that lacks a valid X-Nen-Signature + in-window timestamp with
ISO-3001. Pass withNen(handler, { strict: false })
only for explicitly opted-in legacy clients that cannot sign.
Session stores
import { RedisSessionStore, UpstashSessionStore } from '@withnen/server';
// Any node/serverless runtime (ioredis, node-redis, or @upstash/redis client):
setSessionStore(new RedisSessionStore(redisClient));
// Edge runtimes (Workers, Vercel Edge) — Upstash REST over fetch, no TCP:
setSessionStore(new UpstashSessionStore(
process.env.UPSTASH_REDIS_REST_URL!,
process.env.UPSTASH_REDIS_REST_TOKEN!,
));Coded errors
Every failure is an NenError with a stable ISO-xxxx code. The wire body is
{ error: { code, message } } (safe message only); the precise diagnosis is logged
server-side. Resolve a code with describeNenCode(...). Catalog:
../../ERROR_CODES.md.
Build & test
npm run build # tsup → dist/ (CJS + ESM + .d.ts)
npm test # 19 tests: handshake, HMAC-mandatory, replay, AEAD tamper, identity, UpstashThe wire format is base64-only ({ ct, n }) as of v0.2.0. See
../../PROTOCOL.md.
