@ordinatio/auth
v1.1.3
Published
Authentication security primitives for any Node.js application — account lockout with exponential backoff, password strength validation, session security with suspicious activity detection, and CSRF protection with HMAC-SHA256 signed tokens.
Downloads
233
Readme
@ordinatio/auth
Authentication security primitives for any Node.js application — account lockout with exponential backoff, password strength validation, session security with suspicious activity detection, and CSRF protection with HMAC-SHA256 signed tokens.
Zero framework dependencies. Pure Node.js crypto. Works with any auth library (better-auth, Passport, NextAuth, custom).
369 tests. 29 error codes. 7 source files.
Installation
npm install @ordinatio/authOr via the Ordinatio Domus orchestrator:
npx ordinatio add authQuick Start
import {
recordLoginAttempt,
checkAccountLockout,
validatePasswordStrength,
checkSessionValidity,
detectSuspiciousActivity,
generateCsrfToken,
validateCsrfTokens,
} from '@ordinatio/auth';
// 1. Check lockout before authentication
const lockout = checkAccountLockout('[email protected]');
if (lockout.locked) {
return res.status(403).json({ error: lockout.reason });
}
// 2. Validate password strength on registration
const strength = validatePasswordStrength('MyP@ssw0rd!xyz');
if (!strength.valid) {
return res.status(400).json({ errors: strength.errors });
}
// 3. Record login attempts (success or failure)
recordLoginAttempt({
email: '[email protected]',
ip: request.ip,
timestamp: new Date(),
success: false,
});
// 4. Check session validity
const session = { id: 'sess-1', userId: 'u-1', createdAt, lastActiveAt, ip: '1.2.3.4' };
const validity = checkSessionValidity(session);
if (!validity.valid) {
return redirect('/login');
}
// 5. Detect suspicious activity
const suspicious = detectSuspiciousActivity(session, request.ip, {
country: geolocate(request.ip),
});
if (suspicious.recommendation === 'block') {
return res.status(403).json({ error: 'Suspicious activity detected' });
}
// 6. CSRF protection
const secret = process.env.AUTH_SECRET;
const token = generateCsrfToken(secret);
// ... set as cookie, include in forms/headers ...
const result = validateCsrfTokens(requestToken, cookieToken, secret);
if (!result.valid) {
return res.status(403).json({ error: result.error, code: result.code });
}Architecture
@ordinatio/auth
├── lockout.ts Login attempt tracking, exponential backoff lockout
├── password.ts Password strength validation (zero deps)
├── session.ts Session validity, suspicious activity detection
├── csrf.ts CSRF token generation, parsing, validation (HMAC-SHA256)
├── errors.ts 29 error codes (AUTH_100-404) with diagnostic metadata
├── types.ts All type definitions
└── index.ts Barrel exportsAll functions are pure utilities — no database, no framework, no env vars. Functions that need secrets (CSRF) take them as parameters. Functions that need logging accept an optional AuthCallbacks object.
Modules
Account Lockout
Brute-force protection with exponential backoff. After 5 failed attempts in 15 minutes, the account locks with escalating durations.
import {
recordLoginAttempt,
checkAccountLockout,
unlockAccount,
AUTH_LOCKOUT_CONFIG,
} from '@ordinatio/auth';Lockout escalation:
| Level | Duration | Trigger | |-------|----------|---------| | 1 | 5 minutes | First lockout (5 failed in 15 min) | | 2 | 15 minutes | Second lockout | | 3 | 1 hour | Third lockout | | 4 | 24 hours | Fourth+ lockout |
Lockout level resets to 0 after 7 days of no lockouts. Successful login clears failed attempts.
// Record a failed login
recordLoginAttempt({
email: '[email protected]',
ip: '192.168.1.1',
timestamp: new Date(),
success: false,
userAgent: request.headers['user-agent'],
});
// Check before allowing login
const status = checkAccountLockout('[email protected]');
// { locked: false, failedAttempts: 3, lockoutLevel: 0 }
// OR
// { locked: true, unlockAt: Date, reason: '...', failedAttempts: 5, lockoutLevel: 1 }
// Admin unlock
unlockAccount('[email protected]', true); // true = also reset lockout levelStorage: In-memory Map. Replace with Redis in production for multi-instance deployments. Automatic cleanup runs every 5 minutes (removes expired records).
Password Validation
Comprehensive password strength checking with 11 validation rules. Zero dependencies.
import { validatePasswordStrength, AUTH_PASSWORD_CONFIG } from '@ordinatio/auth';
const result = validatePasswordStrength('MyP@ssw0rd!xyz', {
username: 'jdoe',
email: '[email protected]',
});
// { valid: true, score: 85, errors: [], suggestions: [] }
const weak = validatePasswordStrength('password123');
// { valid: false, score: 15, errors: ['Password must contain...', ...], suggestions: ['Use a passphrase...'] }Validation checks:
| Check | Requirement | |-------|-------------| | Minimum length | 12 characters | | Character classes | Upper + lower + number + special | | Common passwords | 200+ passwords with leet-speak reversal (p@ssw0rd -> password) | | Keyboard patterns | qwerty, asdfgh, 123456, and reversed | | Consecutive chars | Max 3 identical in a row | | Unique chars | Minimum 8 unique characters | | Personal info | Username and email not in password |
Returns a score (0-100) and actionable suggestions for weak passwords.
Session Security
Session validity checking (inactivity timeout + absolute lifetime) and suspicious activity detection with 5 real-time checks.
import {
checkSessionValidity,
detectSuspiciousActivity,
invalidateUserSessions,
AUTH_SESSION_CONFIG,
AUTH_SUSPICIOUS_CONFIG,
} from '@ordinatio/auth';Session timeouts:
| Config | Default | Purpose |
|--------|---------|---------|
| inactivityTimeoutMs | 30 minutes | Idle timeout |
| absoluteLifetimeMs | 24 hours | Maximum session age |
| timeoutWarningMs | 5 minutes | Warning before expiry |
| activityRefreshThresholdMs | 5 minutes | Refresh debounce |
const result = checkSessionValidity(session);
// { valid: true, shouldRefresh: true, remainingTime: 1500000 }
// { valid: false, reason: 'SESSION_INACTIVE' }
// { valid: false, reason: 'SESSION_EXPIRED' }Suspicious activity detection:
| Check | Flag Type | Severity |
|-------|-----------|----------|
| Multiple IPs (>2 per session) | MULTIPLE_IPS | medium/high |
| Country change within minutes | IMPOSSIBLE_TRAVEL | high |
| Login at 2-5 AM UTC | UNUSUAL_TIME | low |
| >60 requests/minute | RAPID_REQUESTS | medium/high |
| Internal IP in production | KNOWN_BAD_IP | medium |
const result = detectSuspiciousActivity(session, '203.0.113.1', {
country: 'US',
userAgent: 'Mozilla/5.0...',
});
// Result includes risk level and recommendation
// { suspicious: false, riskLevel: 'low', flags: [], recommendation: 'allow' }
// { suspicious: true, riskLevel: 'high', flags: [...], recommendation: 'challenge' }
// { suspicious: true, riskLevel: 'critical', flags: [...], recommendation: 'block' }Risk escalation:
| Condition | Risk Level | Recommendation | |-----------|-----------|----------------| | No flags | low | allow | | 1+ low/medium flags | low | notify | | 2+ medium flags OR 1 high | medium-high | challenge | | 2+ high flags | critical | block |
CSRF Protection
Double-submit cookie pattern with HMAC-SHA256 signed tokens. Uses Node.js crypto only — works anywhere.
import {
generateCsrfToken,
parseToken,
verifySignature,
isTokenExpired,
extractCsrfToken,
validateCsrfTokens,
csrfErrorResponse,
CSRF_COOKIE_NAME,
CSRF_HEADER_NAME,
CSRF_FORM_FIELD,
TOKEN_VALIDITY_MS,
} from '@ordinatio/auth';Token format: base64url(timestamp:random:hmac)
timestamp— Unix ms (for expiration checks)random— 32 bytes ofcrypto.randomBytes(for uniqueness)hmac— HMAC-SHA256 oftimestamp:random(for integrity)
Constants:
| Constant | Value | Purpose |
|----------|-------|---------|
| CSRF_COOKIE_NAME | __Host-csrf | Cookie name (with __Host- prefix for security) |
| CSRF_HEADER_NAME | x-csrf-token | Header for AJAX requests |
| CSRF_FORM_FIELD | _csrf | Hidden form field name |
| TOKEN_VALIDITY_MS | 3,600,000 (1 hour) | Token expiration |
const secret = process.env.AUTH_SECRET;
// Generate a token
const token = generateCsrfToken(secret);
// Parse and inspect
const parsed = parseToken(token);
// { timestamp: 1709827200000, random: 'a1b2c3...', signature: 'f4e5d6...' }
// Verify signature integrity
verifySignature(parsed, secret); // true
// Check expiration
isTokenExpired(parsed); // false (within 1 hour)
isTokenExpired(parsed, 5 * 60 * 1000); // custom 5-minute validity
// Extract from incoming request (checks header, then JSON body, then form body)
const requestToken = await extractCsrfToken(request);
// Full validation (6 checks: both exist, both parse, both verify, both fresh, both match)
const result = validateCsrfTokens(requestToken, cookieToken, secret);
// { valid: true }
// { valid: false, error: 'CSRF cookie not found', code: 'MISSING_COOKIE' }
// { valid: false, error: 'Invalid CSRF token signature', code: 'SIGNATURE_MISMATCH' }
// Pre-built 403 response for failures
if (!result.valid) {
return csrfErrorResponse(result); // Response with JSON body + 403 status
}Validation codes:
| Code | Meaning |
|------|---------|
| MISSING_COOKIE | No __Host-csrf cookie in request |
| MISSING_TOKEN | No x-csrf-token header or _csrf body field |
| INVALID_FORMAT | Token doesn't parse (not base64url or wrong structure) |
| SIGNATURE_MISMATCH | HMAC verification failed (tampered or wrong secret) |
| EXPIRED | Token older than 1 hour |
| TOKEN_MISMATCH | Cookie and request tokens don't match |
Callback Injection
All functions that produce log output accept an optional AuthCallbacks parameter. This replaces direct logger dependencies so the package stays framework-agnostic.
import type { AuthCallbacks } from '@ordinatio/auth';
const callbacks: AuthCallbacks = {
log: (level, message, data) => {
// Route to your logger: winston, pino, console, etc.
myLogger[level](message, data);
},
};
// Pass callbacks to any function that supports them
recordLoginAttempt(attempt, callbacks);
checkAccountLockout(email, callbacks);
checkSessionValidity(session, callbacks);
detectSuspiciousActivity(session, ip, options, callbacks);
unlockAccount(email, resetLevel, callbacks);Functions that don't need logging (password validation, CSRF crypto) don't accept callbacks.
Error Registry
29 error codes organized into 4 categories, each with diagnostic metadata for debugging.
import { authError, AUTH_ERRORS } from '@ordinatio/auth';
// Generate a timestamped reference
const { code, ref } = authError('AUTH_101');
// { code: 'AUTH_101', ref: 'AUTH_101-20260306T143000' }
// Look up diagnostic info
const info = AUTH_ERRORS.AUTH_101;
// {
// code: 'AUTH_101',
// httpStatus: 403,
// severity: 'warn',
// recoverable: true,
// description: 'Login blocked — account is locked out.',
// diagnosis: [
// 'Too many failed login attempts triggered lockout',
// 'Check checkAccountLockout() result for lockoutLevel and unlockAt',
// 'Admin can call unlockAccount() to manually unlock',
// 'Lockout levels: 5min -> 15min -> 1hr -> 24hr (exponential backoff)',
// ],
// }Error code ranges:
| Range | Category | Count | |-------|----------|-------| | AUTH_100-106 | Login / Logout | 7 | | AUTH_200-206 | Session Management | 7 | | AUTH_300-306 | Password / Lockout | 7 | | AUTH_400-404 | CSRF Protection | 5 |
Every error includes file, function, httpStatus, severity, recoverable, description, and diagnosis[] (ordered troubleshooting steps).
Integration with Domus
When used through @ordinatio/domus, the auth module is automatically wrapped with the domus callback system:
import { createDomus } from '@ordinatio/domus';
const app = await createDomus({
modules: ['auth', 'email', 'tasks'],
});
// Auth API is pre-wired with logging callbacks
const lockout = app.auth.checkLockout('[email protected]');
app.auth.recordAttempt({ email, ip, timestamp: new Date(), success: false });
const strength = app.auth.validatePassword('MyP@ssw0rd!xyz');
const csrf = app.auth.validateCsrf(requestToken, cookieToken, secret);Auth has no cross-module wiring — it's foundational. Other modules may depend on it, but it doesn't depend on them.
Prisma schema fragment (auth.prisma): 4 models — User, Session, Account, Verification. Compatible with better-auth. Models are managed by the auth framework, not by Ordinatio seeds.
Feature flags: CSRF_PROTECTION, ACCOUNT_LOCKOUT (both enabled by default when auth module is active).
Framework Integration Example
Here's how System 1701 integrates @ordinatio/auth with Next.js:
// services/auth/index.ts — App-layer bridge
import { createLogger } from '@/lib/logger';
import {
recordLoginAttempt as _recordLoginAttempt,
checkAccountLockout as _checkAccountLockout,
type AuthCallbacks,
type LoginAttempt,
} from '@ordinatio/auth';
const authLogger = createLogger({ module: 'auth-security' });
const callbacks: AuthCallbacks = {
log: (level, message, data) => authLogger[level](message, data),
};
// Wrap with pre-injected callbacks
export function recordLoginAttempt(attempt: LoginAttempt) {
return _recordLoginAttempt(attempt, callbacks);
}
export function checkAccountLockout(email: string) {
return _checkAccountLockout(email, callbacks);
}
// Re-export pure functions directly (no wrapping needed)
export { validatePasswordStrength, generateCsrfToken, ... } from '@ordinatio/auth';// lib/csrf-token.ts — CSRF with Next.js cookies
import { cookies } from 'next/headers';
import { generateCsrfToken as _generateCsrfToken } from '@ordinatio/auth';
const CSRF_SECRET = process.env.CSRF_SECRET || process.env.AUTH_SECRET || '';
export function generateCsrfToken(): string {
return _generateCsrfToken(CSRF_SECRET);
}
export async function setCsrfCookie(token: string): Promise<void> {
const cookieStore = await cookies();
cookieStore.set('__Host-csrf', token, {
httpOnly: false,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
maxAge: 3600,
});
}Testing Utilities
import {
_resetLoginAttemptStore,
_resetSessionActivityStore,
_getLoginAttempts,
_getSessionActivity,
_generateTestToken,
} from '@ordinatio/auth';
beforeEach(() => {
_resetLoginAttemptStore();
_resetSessionActivityStore();
});
// Generate a token with a specific timestamp (for expiration tests)
const expiredToken = _generateTestToken(Date.now() - 2 * 60 * 60 * 1000, secret);API Reference
Functions
| Function | Module | Description |
|----------|--------|-------------|
| recordLoginAttempt(attempt, callbacks?) | lockout | Record a login attempt |
| checkAccountLockout(email, callbacks?) | lockout | Check if account is locked |
| unlockAccount(email, resetLevel?, callbacks?) | lockout | Admin unlock |
| validatePasswordStrength(password, context?) | password | Validate password strength |
| checkSessionValidity(session, callbacks?) | session | Check session timeout/lifetime |
| invalidateUserSessions(userId, reason, callbacks?) | session | Force logout all sessions |
| detectSuspiciousActivity(session, ip, options?, callbacks?) | session | Detect suspicious patterns |
| generateCsrfToken(secret) | csrf | Generate signed CSRF token |
| parseToken(token) | csrf | Parse token into components |
| verifySignature(parsed, secret) | csrf | Verify HMAC signature |
| isTokenExpired(parsed, validityMs?) | csrf | Check token expiration |
| extractCsrfToken(request) | csrf | Extract token from Request |
| validateCsrfTokens(requestToken, cookieToken, secret) | csrf | Full 6-check validation |
| csrfErrorResponse(result) | csrf | Pre-built 403 Response |
| authError(code) | errors | Generate timestamped error ref |
Types
| Type | Description |
|------|-------------|
| AuthCallbacks | { log?: (level, message, data?) => void } |
| LoginAttempt | { email, ip, timestamp, success, userAgent? } |
| AccountLockoutStatus | { locked, unlockAt?, reason?, failedAttempts, lockoutLevel } |
| PasswordStrengthResult | { valid, score, errors[], suggestions[] } |
| Session | { id, userId, createdAt, lastActiveAt, ip, userAgent?, country? } |
| SessionValidityResult | { valid, reason?, shouldRefresh?, remainingTime? } |
| SuspiciousActivityResult | { suspicious, riskLevel, flags[], recommendation } |
| SuspiciousFlag | { type, description, severity, metadata? } |
| ParsedToken | { timestamp, random, signature } |
| CsrfValidationResult | { valid, error?, code? } |
Pugil Integration
This package includes a Pugil reporter that generates Council-consumable trial_report artifacts from test results.
# Normal test run (no Pugil overhead)
pnpm --filter @ordinatio/auth test:run
# With Pugil trial report generation
PUGIL_ENABLED=true pnpm --filter @ordinatio/auth test:run
# With Council cycle integration
PUGIL_ENABLED=true PUGIL_CYCLE_ID=cycle-auth-v1 pnpm --filter @ordinatio/auth test:run- Config:
src/pugil.config.ts— maps test files to categories (unit, integration, adversarial, chaos, concurrency) - Reporter:
src/pugil-reporter.ts— Vitest custom reporter, writes topugil-reports/ - Types:
PugilTestResult,PugilTestCategoryfrom@ordinatio/core
License
MIT
