Security

Security

JWT, AES-256-GCM sessions, scrypt vault, rate limiting, XSS sanitizer, CSRF, CORS, CSP — all built in, no plugins needed.

Street includes a complete security layer built on node:crypto. No third-party auth libraries — every primitive is implemented directly.


JWT authentication

1
2
3
4
5
6
7
8
9
10
import { JwtService, container } from '@streetjs/core';

const jwt = new JwtService(process.env['JWT_SECRET']!);
container.register(JwtService, jwt);

// Sign a token (default 7 days)
const token = jwt.sign({ userId: '123', roles: ['admin'] }, '7d');

// Verify — throws if invalid or expired
const payload = jwt.verify(token) as { userId: string; roles: string[] };

Auth middleware:

1
2
3
4
5
6
7
import { authMiddleware, requireRoles } from '@streetjs/core';

// Require valid JWT on all routes
app.use(authMiddleware);

// Require specific role
app.use(requireRoles('admin'));

Or per-controller:

1
2
3
4
5
6
7
8
9
10
11
import { UnauthorizedException, container, JwtService } from '@streetjs/core';
import type { StreetContext } from '@streetjs/core';

async function authenticate(ctx: StreetContext, next: () => Promise<void>) {
  const auth = ctx.headers['authorization'];
  if (!auth?.startsWith('Bearer ')) throw new UnauthorizedException();
  const jwt = container.resolve(JwtService);
  const payload = jwt.verify(auth.slice(7));
  ctx.user = payload as StreetContext['user'];
  await next();
}

Sessions (AES-256-GCM)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { SessionManager, container } from '@streetjs/core';

const sessions = new SessionManager(process.env['SESSION_KEY']!);
container.register(SessionManager, sessions);

// Encrypt session data
const blob = sessions.encrypt({ userId: '123', roles: ['user'] });

// Set as cookie
ctx.setCookie('session', blob, {
  httpOnly: true,
  secure:   true,
  sameSite: 'Lax',
  maxAge:   86400,
});

// Decrypt on subsequent requests
const session = sessions.decrypt(ctx.cookie('session') ?? '');

Generate a session key:

1
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Rate limiting

Sliding-window rate limiter with BigInt nanosecond precision:

1
2
3
4
5
6
7
8
9
10
11
import { RateLimiter } from '@streetjs/core';

const limiter = new RateLimiter({
  windowMs:    60_000,   // 1-minute window
  maxRequests: 100,      // max 100 requests per IP per window
});

app.use(limiter.middleware());

// Clean up on shutdown
process.once('SIGTERM', () => limiter.destroy());

The limiter tracks up to 100K IPs and 1K timestamps per IP. Stale entries are swept periodically to prevent unbounded growth.


XSS sanitization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { xssMiddleware, sanitizeDeep, sanitizeString } from '@streetjs/core';

// Global middleware — sanitizes all request bodies
app.use(xssMiddleware);

// Manual sanitization
const clean = sanitizeString('<script>alert(1)</script>Hello');
// → 'Hello'

const cleanObj = sanitizeDeep({
  name: '<b>Alice</b>',
  bio:  'Hello <script>evil()</script> world',
});
// → { name: 'Alice', bio: 'Hello  world' }

Security headers

1
2
3
import { securityHeaders } from '@streetjs/core';

app.use(securityHeaders);

Sets:


CORS

1
2
3
4
5
6
7
import { corsMiddleware } from '@streetjs/core';

// Allow specific origins
app.use(corsMiddleware(['https://app.example.com', 'https://admin.example.com']));

// Allow all origins (development only)
app.use(corsMiddleware(['*']));

Vault mode (KEK-based secret decryption)

For encrypting secrets at rest (e.g. database passwords in config files):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { encryptSecret, decryptSecret, loadConfig } from '@streetjs/core';

// Encrypt a secret with a Key Encryption Key
const encrypted = await encryptSecret('my-db-password', process.env['KEK']!);
// → 'enc:v1:...' (store this in your config file)

// Decrypt at runtime
const dbPassword = await decryptSecret(encrypted, process.env['KEK']!);

// Or load an entire config object, decrypting all 'enc:v1:...' values
const config = await loadConfig({
  pgPassword: 'enc:v1:...',
  jwtSecret:  'enc:v1:...',
}, process.env['KEK']!);

Generate a KEK:

1
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Security checklist

Before going to production: