lockvault
v1.0.2
Published
Production-grade authentication toolkit for Node.js — JWT, sessions, TOTP/2FA, OAuth, multi-database support
Downloads
92
Maintainers
Readme
LockVault
Authentication toolkit for Node.js. One package for JWT tokens, sessions, two-factor auth, OAuth logins, and database storage — with zero runtime dependencies.
npm install lockvaultWhy LockVault?
Most auth setups need you to wire together 5+ packages (jsonwebtoken, express-session, speakeasy, passport, etc.) and get the security details right yourself. LockVault gives you all of it in one import, with safe defaults already configured.
import { createLockVault, createMemoryAdapter } from 'lockvault';
const auth = createLockVault({
jwt: { accessTokenSecret: process.env.JWT_SECRET! },
adapter: createMemoryAdapter(), // swap for Postgres/MongoDB/Redis in production
});
await auth.initialize();
// Log a user in — returns JWT tokens + a session
const { tokens, session } = await auth.login('user-123');
// Verify a token on any request
const payload = await auth.jwt.verifyAccessToken(tokens.accessToken);
console.log(payload.sub); // 'user-123'
// Refresh when the access token expires
const newTokens = await auth.refresh(tokens.refreshToken);
// Log out (revokes the token and session)
await auth.logout(tokens.accessToken);That's the basic flow. Everything below goes deeper.
Table of Contents
- Installation
- Core Concepts
- Configuration Reference
- JWT Tokens
- Sessions
- TOTP / Two-Factor Auth
- OAuth / Social Login
- Database Adapters
- Express Middleware
- Fastify Middleware
- Rate Limiting
- Key-Value Store
- Plugin System
- Email / SMTP (Optional)
- Utility Functions
- Error Handling
- Security Checklist
- API Reference
- License
Installation
npm install lockvaultLockVault has zero runtime dependencies. It uses only Node.js built-in crypto. Database drivers and email are optional — install only what you need:
npm install pg # if using PostgreSQL
npm install mongodb # if using MongoDB
npm install ioredis # if using Redis
npm install nodemailer # if using the email module (optional)Requires Node.js 18+.
Core Concepts
LockVault has four main pieces. You can use them all together through the createLockVault() factory, or import any piece individually.
| Module | What it does | |--------|-------------| | JWTManager | Signs, verifies, refreshes, and revokes JWT tokens | | SessionManager | Tracks login sessions per user and device | | TOTPManager | Generates and verifies 2FA codes (Google Authenticator, etc.) | | OAuthManager | Handles Google, GitHub, Facebook, Apple, and Microsoft login flows | | EmailManager | Optional SMTP email with themed templates and general-purpose mailing |
All four are connected through a DatabaseAdapter — an interface that tells LockVault where to store sessions, token families, TOTP secrets, and OAuth links. You pick the database; LockVault handles the logic. The EmailManager is a standalone module that works independently — it doesn't require a database adapter and can be used even without the core createLockVault() setup.
Configuration Reference
Here's every option available. Only jwt.accessTokenSecret and adapter are required for HS256 setups.
import { createLockVault, createMemoryAdapter } from 'lockvault';
import type { LockVaultConfig } from 'lockvault';
const config: LockVaultConfig = {
// ── JWT Settings (required) ──────────────────────────────────────────
jwt: {
algorithm: 'HS256', // 'HS256' | 'RS256' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA'
accessTokenSecret: '...', // min 32 chars for HS256 (not needed for asymmetric)
refreshTokenSecret: '...', // optional — defaults to accessTokenSecret
accessTokenTTL: 900, // seconds (default: 15 minutes)
refreshTokenTTL: 604800, // seconds (default: 7 days)
issuer: 'my-app', // optional — validated on verify if set
audience: 'my-api', // optional — validated on verify if set
privateKey: '...', // PEM string — required for RS256/ES256/ES384/ES512/EdDSA
publicKey: '...', // PEM string — required for RS256/ES256/ES384/ES512/EdDSA
},
// ── Session Settings ─────────────────────────────────────────────────
session: {
enabled: true, // default: true
maxPerUser: 10, // max concurrent sessions per user
inactivityTimeout: 7200, // seconds — revoke after 2h of no activity
},
// ── Refresh Token Security ───────────────────────────────────────────
refreshToken: {
rotation: true, // new refresh token on every refresh (default: true)
reuseDetection: true, // detect stolen tokens (default: true)
familyRevocationOnReuse: true, // revoke ALL tokens if theft detected (default: true)
encryption: { // optional — encrypt refresh tokens with AES-256-GCM
enabled: true,
key: '...', // 64-char hex string (= 32 bytes)
},
},
// ── TOTP / 2FA ───────────────────────────────────────────────────────
totp: {
issuer: 'MyApp', // shows in authenticator apps
algorithm: 'SHA1', // 'SHA1' | 'SHA256' | 'SHA512'
digits: 6, // code length
period: 30, // seconds per code
window: 1, // accept ±1 time step
},
// ── OAuth ────────────────────────────────────────────────────────────
oauth: {
providers: {}, // configured via registerPreset/registerProvider
stateStore: myRedisKvStore, // optional — for multi-instance deployments
},
// ── Key-Value Store ──────────────────────────────────────────────────
kvStore: myRedisKvStore, // optional — for TOTP replay protection in multi-instance
// ── Database Adapter (required) ──────────────────────────────────────
adapter: createMemoryAdapter(),
// ── Plugins ──────────────────────────────────────────────────────────
plugins: [],
};
const auth = createLockVault(config);
await auth.initialize();JWT Tokens
Supported Algorithms
| Algorithm | Type | Use Case |
|-----------|------|----------|
| HS256 | Symmetric (shared secret) | Simple setups, single-service apps |
| RS256 | RSA (key pair) | Microservices where verifiers don't need the signing key |
| ES256 | ECDSA P-256 (key pair) | Smaller signatures, modern standard |
| ES384 | ECDSA P-384 (key pair) | Higher security ECDSA |
| ES512 | ECDSA P-521 (key pair) | Maximum ECDSA security |
| EdDSA | Ed25519 (key pair) | Fastest, smallest signatures, recommended for new projects |
Using EdDSA (Ed25519) — Recommended
import { generateKeyPairSync } from 'node:crypto';
// Generate a key pair (do this once, store the PEM strings in env vars)
const { privateKey, publicKey } = generateKeyPairSync('ed25519', {
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' },
});
const auth = createLockVault({
jwt: {
algorithm: 'EdDSA',
accessTokenSecret: '', // not used for asymmetric algorithms
privateKey, // signs tokens
publicKey, // verifies tokens (safe to share)
},
adapter: createMemoryAdapter(),
});Using ES256 (ECDSA)
const { privateKey, publicKey } = generateKeyPairSync('ec', {
namedCurve: 'prime256v1',
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
publicKeyEncoding: { type: 'spki', format: 'pem' },
});
const auth = createLockVault({
jwt: { algorithm: 'ES256', accessTokenSecret: '', privateKey, publicKey },
adapter: createMemoryAdapter(),
});Token Claims
Every token includes these standard claims automatically:
| Claim | Meaning |
|-------|---------|
| sub | User ID |
| iat | Issued at (unix timestamp) |
| nbf | Not valid before (unix timestamp) |
| exp | Expires at (unix timestamp) |
| jti | Unique token ID (used for revocation) |
| type | 'access' or 'refresh' |
| iss | Issuer (if configured) |
| aud | Audience (if configured) |
You can add your own custom claims during login:
const { tokens } = await auth.login('user-123', {
customClaims: { roles: ['admin'], orgId: 'org-456' },
});
// Later, when verifying:
const payload = await auth.jwt.verifyAccessToken(tokens.accessToken);
console.log(payload.roles); // ['admin']Refresh Token Rotation
When a client refreshes, the old refresh token is invalidated and a new one is issued. If an attacker tries to reuse a stolen refresh token, LockVault detects it and revokes the entire token family — logging out all sessions for that user.
// Normal flow:
const newTokens = await auth.refresh(tokens.refreshToken);
// ✓ Returns new access + refresh tokens
// Attacker replays the old refresh token:
await auth.refresh(tokens.refreshToken);
// ✗ Throws RefreshTokenReuseError — entire family revokedKey Rotation
Rotate your signing keys without invalidating existing tokens:
auth.rotateJWTKeys('new-secret-at-least-32-characters-long!');
// New tokens use the new key
// Old tokens still verify against the previous key (up to 3 old keys kept)Sessions
Sessions track where a user is logged in and from which device. They work alongside JWT tokens — the session ID is embedded in the token's sid claim.
// Get all active sessions for a user
const sessions = await auth.sessions.getUserSessions('user-123');
// Each session contains:
// { id, userId, deviceInfo, ipAddress, createdAt, expiresAt, lastActiveAt }
// Log in with device info
const { tokens, session } = await auth.login('user-123', {
deviceInfo: { userAgent: req.headers['user-agent'], deviceType: 'mobile' },
ipAddress: req.ip,
});
// Revoke a specific session (e.g., "log out my phone")
await auth.sessions.revokeSession(session.id);
// Log out everywhere
await auth.logoutAll('user-123');Session Limits
If a user exceeds maxPerUser sessions (default: 10), the oldest session is automatically revoked to make room for the new one.
Inactivity Timeout
If inactivityTimeout is set, sessions are automatically revoked when they haven't been used for that many seconds. The middleware updates lastActiveAt on every authenticated request.
TOTP / Two-Factor Auth
LockVault implements RFC 6238 (TOTP) and RFC 4226 (HOTP). Compatible with Google Authenticator, Authy, 1Password, and any TOTP app.
Setup Flow
// Step 1: Generate a secret and otpauth URI
const setup = await auth.setupTOTP('user-123', '[email protected]');
// Returns: { secret, uri, backupCodes }
// Step 2: Show the user a QR code (use any QR library)
import QRCode from 'qrcode'; // npm install qrcode
const qrDataUrl = await QRCode.toDataURL(setup.uri);
// Display qrDataUrl as an <img> for the user to scan
// Step 3: User scans QR, types the 6-digit code to confirm
await auth.confirmTOTP('user-123', setup.secret, userEnteredCode, setup.backupCodes);
// 2FA is now active for this user
// Step 4: On future logins, verify the code
await auth.verifyTOTP('user-123', code);
// Disable 2FA
await auth.disableTOTP('user-123');Backup Codes
When TOTP is set up, 10 backup codes are generated (format: XXXX-XXXX-XXXX, 48 bits of entropy each). Users can enter a backup code instead of a TOTP code if they lose access to their authenticator app. Each backup code can only be used once.
const remaining = await auth.totp.getBackupCodesCount('user-123');
const newCodes = await auth.totp.regenerateBackupCodes('user-123');Built-in Protections
LockVault's TOTP implementation includes three layers of protection that most libraries leave for you to build yourself:
- Rate limiting — 5 attempts per minute per user. Prevents brute-forcing 6-digit codes.
- Replay protection — Each code can only be used once within its validity window. Prevents intercepted codes from being reused.
- Timing-safe comparison — Code verification runs in constant time regardless of which digits match. Prevents timing side-channel attacks.
OAuth / Social Login
Built-in presets for Google, GitHub, Facebook, Apple, and Microsoft. You can also register any custom OAuth 2.0 provider.
Register a Provider
auth.registerOAuthPreset('google', {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
redirectUri: 'https://myapp.com/auth/google/callback',
});
auth.registerOAuthPreset('github', {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
redirectUri: 'https://myapp.com/auth/github/callback',
});OAuth Flow
// 1. Redirect the user to the provider's login page
app.get('/auth/google', async (req, res) => {
const url = await auth.getOAuthAuthorizationUrl('google');
res.redirect(url);
});
// 2. Handle the callback after the user logs in
app.get('/auth/google/callback', async (req, res) => {
const { profile, tokens } = await auth.handleOAuthCallback(
'google',
req.query.code as string,
req.query.state as string,
);
// profile = { id, email, name, avatar, raw }
let userId = await auth.oauth.findUserByOAuth('google', profile.id);
if (!userId) {
userId = createUserInYourDB(profile);
await auth.oauth.linkAccount(userId, 'google', profile, tokens);
}
const { tokens: authTokens } = await auth.login(userId);
res.json(authTokens);
});Custom OAuth Provider
auth.registerOAuthProvider('gitlab', {
clientId: '...',
clientSecret: '...',
redirectUri: 'https://myapp.com/auth/gitlab/callback',
authorizationUrl: 'https://gitlab.com/oauth/authorize',
tokenUrl: 'https://gitlab.com/oauth/token',
userInfoUrl: 'https://gitlab.com/api/v4/user',
scopes: ['read_user'],
mapProfile: (data) => ({
id: String(data.id),
email: String(data.email ?? ''),
name: String(data.name ?? ''),
avatar: String(data.avatar_url ?? ''),
raw: data,
}),
});Database Adapters
PostgreSQL
import { Pool } from 'pg';
import { createPostgresAdapter } from 'lockvault/adapters/postgres';
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = createPostgresAdapter(pool, { tablePrefix: 'auth_' });
const auth = createLockVault({ jwt: { ... }, adapter });
await auth.initialize(); // creates tables and indexes automaticallyMongoDB
import { MongoClient } from 'mongodb';
import { createMongoDBAdapter } from 'lockvault/adapters/mongodb';
const client = new MongoClient(process.env.MONGO_URL!);
const db = client.db('myapp');
const adapter = createMongoDBAdapter(db, { collectionPrefix: 'auth_' });Redis
import Redis from 'ioredis';
import { createRedisAdapter } from 'lockvault/adapters/redis';
const redis = new Redis(process.env.REDIS_URL);
const adapter = createRedisAdapter(redis, { prefix: 'auth:' });
// Sessions and revoked tokens auto-expire via Redis TTLIn-Memory (for development and testing)
import { createMemoryAdapter } from 'lockvault';
const adapter = createMemoryAdapter();Custom Adapter
Implement the DatabaseAdapter interface. The MemoryAdapter source at src/adapters/memory/index.ts is a complete reference implementation.
import type { DatabaseAdapter } from 'lockvault';
function createMyAdapter(): DatabaseAdapter {
return {
async createSession(session) { /* ... */ return session; },
async getSession(id) { /* ... */ return null; },
async getSessionsByUser(userId) { /* ... */ return []; },
// ... see DatabaseAdapter interface for all required methods
};
}Express Middleware
import { authenticate, authorize, csrfProtection, setAuthCookies, clearAuthCookies } from 'lockvault/middleware/express';
// Create the middleware
const authMiddleware = authenticate({
jwtManager: auth.jwt,
sessionManager: auth.sessions, // optional — validates session too
});
// Protect a route
app.get('/api/me', authMiddleware, (req, res) => {
res.json({ user: req.lockvault!.user });
});
// Role-based access
app.delete('/api/users/:id', authMiddleware, authorize('admin'), (req, res) => {
// Only users with { roles: ['admin'] } in their token reach here
});
// Set auth cookies after login (httpOnly, secure, sameSite: lax)
app.post('/auth/login', async (req, res) => {
const { tokens } = await auth.login(userId);
setAuthCookies(res, tokens);
res.json({ success: true });
});Fastify Middleware
import { lockVaultPlugin, fastifyAuthorize } from 'lockvault/middleware/fastify';
await app.register(lockVaultPlugin, {
jwtManager: auth.jwt,
sessionManager: auth.sessions,
publicRoutes: ['/auth/login', '/auth/register', '/health'],
});
// Every non-public route is now protected automatically
app.get('/api/admin', {
preHandler: [fastifyAuthorize('admin')],
}, async (req) => {
return { user: req.lockvault!.user };
});Rate Limiting
LockVault includes a sliding-window rate limiter. It's already used internally for TOTP, but you can use it for login endpoints, API routes, or anything else:
import { createRateLimiter, RateLimitError } from 'lockvault';
const loginLimiter = createRateLimiter({
windowMs: 60_000, // 1 minute window
maxAttempts: 5, // max 5 attempts per window
});
app.post('/auth/login', async (req, res) => {
try {
await loginLimiter.consume(req.ip); // throws if over limit
// ... do login ...
loginLimiter.reset(req.ip); // reset on success
} catch (err) {
if (err instanceof RateLimitError) {
return res.status(429).json({
error: err.message,
retryAfterMs: err.retryAfterMs,
});
}
throw err;
}
});Key-Value Store
LockVault uses a KeyValueStore interface for ephemeral data (OAuth state, TOTP replay tracking). An in-memory store works out of the box. For multi-instance deployments, provide a shared store:
import type { KeyValueStore } from 'lockvault';
// Example: Redis-backed store
function createRedisKeyValueStore(redis: Redis): KeyValueStore {
return {
async get(key) { return redis.get(key); },
async set(key, value, ttlMs?) {
if (ttlMs) await redis.set(key, value, 'PX', ttlMs);
else await redis.set(key, value);
},
async delete(key) { return (await redis.del(key)) > 0; },
};
}
const kvStore = createRedisKeyValueStore(redis);
const auth = createLockVault({
jwt: { ... },
adapter: postgresAdapter,
kvStore, // TOTP replay protection across instances
oauth: { providers: {}, stateStore: kvStore }, // OAuth state across instances
});Plugin System
Plugins let you hook into every step of the auth lifecycle:
import type { LockVaultPlugin } from 'lockvault';
const auditPlugin: LockVaultPlugin = {
name: 'audit-log',
hooks: {
afterTokenCreate: async (tokenPair) => {
console.log('Token created:', tokenPair.accessTokenExpiresAt);
},
onReuseDetected: async (family, userId) => {
await alertSecurityTeam(`Possible token theft for user ${userId}`);
},
onError: async (error, context) => {
await logToSentry(error, { context });
},
},
};
const auth = createLockVault({ ...config, plugins: [auditPlugin] });Available Hooks
| Hook | When it fires |
|------|--------------|
| beforeTokenCreate | Before the JWT payload is signed |
| afterTokenCreate | After both tokens are created |
| beforeTokenVerify | Before signature verification |
| afterTokenVerify | After successful verification |
| beforeSessionCreate | Before session is saved |
| afterSessionCreate | After session is saved |
| onTokenRevoked | When a token is revoked |
| onReuseDetected | When refresh token reuse is detected |
| onError | On any auth error |
Email / SMTP (Optional)
LockVault includes a full-featured email module with themed templates for auth flows and general-purpose mailing. Email is entirely optional — LockVault works perfectly without it. If you never import from lockvault/email, nodemailer is never loaded and there's zero impact on your bundle.
Install nodemailer only if you need email:
npm install nodemailer
npm install -D @types/nodemailer # TypeScript usersQuick Setup
import { createEmailManager } from 'lockvault/email';
const mailer = createEmailManager({
smtp: {
host: 'smtp.gmail.com',
port: 587,
auth: { user: '[email protected]', pass: 'app-password' },
from: '"My App" <[email protected]>',
},
defaults: { appName: 'My App', supportUrl: 'https://myapp.com/support' },
});General-Purpose Mailing
Use LockVault as a normal mailer — plain text, HTML, attachments, CC/BCC, priority headers:
// Plain text
await mailer.sendMail({ to: '[email protected]', subject: 'Hello', text: 'Just checking in.' });
// HTML with attachments
await mailer.sendMail({
to: ['[email protected]', '[email protected]'],
subject: 'Monthly Report',
html: '<h1>Report</h1><p>See attachment.</p>',
attachments: [{ filename: 'report.pdf', path: './report.pdf' }],
priority: 'high',
});
// CC, BCC, custom from, headers
await mailer.sendMail({
to: '[email protected]',
cc: ['[email protected]'],
from: '"Finance" <[email protected]>', // override default from
subject: 'Payment Confirmation',
html: '<p>Your payment of $299 has been received.</p>',
headers: { 'X-Transaction-ID': 'txn_abc' },
});Inline Templates
Pass HTML with {{variable}} interpolation — no registration needed:
await mailer.sendCustom({
to: '[email protected]',
subject: 'Order #{{orderId}} Confirmed',
html: `
<p>Hi {{name}}, your order #{{orderId}} is confirmed.</p>
{{#if trackingUrl}}<a href="{{trackingUrl}}">Track shipment</a>{{/if}}
{{#each items}}<p>• {{name}} — {{price}}</p>{{/each}}
`,
variables: {
name: 'Ayush',
orderId: 'ORD-123',
trackingUrl: 'https://track.example.com/ORD-123',
items: [{ name: 'Widget', price: '$29.99' }],
},
});The template engine supports {{var}}, {{{rawVar}}}, {{#if var}}...{{/if}}, {{#unless var}}...{{/unless}}, {{#each items}}...{{/each}}, and {{nested.path}} dot-notation.
Built-In Auth Themes
9 production-ready email templates across 3 categories, each with 3 visual themes:
| Category | Themes | Convenience Method |
|---|---|---|
| Login Notification | minimal, corporate, vibrant | sendLoginNotification() |
| Forgot Password | clean, secure, friendly | sendForgotPassword() |
| Security Alert | standard, urgent, subtle | sendAlert() |
await mailer.sendForgotPassword('[email protected]', {
resetUrl: 'https://app.com/reset?token=abc',
expiresIn: '15 minutes',
theme: 'friendly', // or 'clean', 'secure'
});
await mailer.sendLoginNotification('[email protected]', {
loginTime: new Date().toLocaleString(),
ipAddress: '192.168.1.42',
deviceInfo: 'Chrome on macOS',
theme: 'vibrant',
});
await mailer.sendAlert('[email protected]', {
alertTitle: 'Failed Login Attempts',
alertMessage: '15 failed attempts from IP 10.0.0.1.',
severity: 'critical',
theme: 'urgent',
});Named Templates
Register templates once, send by name. Three source types — inline HTML, file-based, or a custom render function:
// Inline HTML
mailer.registerNamedTemplate('order-shipped', {
source: { type: 'html', content: '<p>Hi {{name}}, order #{{orderId}} shipped!</p>' },
defaultSubject: 'Order #{{orderId}} Shipped',
});
// From a file on disk
mailer.registerNamedTemplate('welcome', {
source: { type: 'file', path: './templates/welcome.html' },
defaultSubject: 'Welcome to {{appName}}!',
});
// Custom render function (Handlebars, EJS, MJML, React Email, etc.)
mailer.registerNamedTemplate('newsletter', {
source: { type: 'render', fn: (vars) => ejs.render(myTemplate, vars) },
defaultSubject: '{{appName}} Newsletter',
});
// Send by name
await mailer.sendWithTemplate({
to: '[email protected]',
template: 'order-shipped',
variables: { name: 'Ayush', orderId: 'ORD-789' },
});Custom Categories and Themes
Create your own template categories with multiple themes, or override built-in ones:
mailer.registerCategory('invoice', 'simple');
mailer.registerTemplate('invoice', 'simple', simpleInvoiceHtml);
mailer.registerTemplate('invoice', 'detailed', detailedInvoiceHtml);
mailer.setDefaultTheme('invoice', 'simple');
// Override built-in templates if you want
mailer.registerTemplate('login', 'minimal', myCustomLoginHtml);
// Remove templates/categories you don't need
mailer.removeTemplate('alert', 'subtle');
mailer.removeCategory('invoice');Custom Rendering Engine
Plug in any template engine globally — Handlebars, EJS, MJML, React Email, or anything else:
import Handlebars from 'handlebars';
const mailer = createEmailManager({
smtp: { /* ... */ },
customRenderer: (html, vars) => Handlebars.compile(html)(vars),
});Bulk Sending
Send the same template to many recipients with per-recipient variables and optional rate limiting:
const result = await mailer.sendBulk({
subject: 'Your monthly summary',
html: '<p>Hi {{name}}, you had {{count}} logins.</p>',
recipients: [
{ to: '[email protected]', variables: { name: 'Alice', count: 42 } },
{ to: '[email protected]', variables: { name: 'Bob', count: 7 } },
],
delayMs: 100, // 100ms between sends
});
// result = { total: 2, sent: 2, failed: 0, results: [...] }Preview and Development
Render templates without sending — useful for dev servers and testing:
// Preview a category+theme template
const html = mailer.preview('login', 'vibrant', { userName: 'Test', loginTime: 'now' });
// Preview a named template
const html2 = await mailer.previewNamedTemplate('order-shipped', { name: 'Test', orderId: 'DEMO' });
// Render inline template (no SMTP needed)
const html3 = mailer.renderInline('<p>Hi {{name}}</p>', { name: 'Ayush' });
// Verify SMTP connection
const ok = await mailer.verify(); // true or false
// List everything available
mailer.listCategories(); // ['login', 'forgot-password', 'alert', ...]
mailer.listThemes('login'); // ['minimal', 'corporate', 'vibrant']
mailer.listNamedTemplates(); // ['order-shipped', 'welcome', ...]createEmailManager() API Reference
| Method | Description |
|--------|-------------|
| sendMail(options) | Send raw email (plain text, HTML, attachments) |
| send(options) | Alias for sendMail() |
| sendCustom(options) | Send with inline HTML template + variables |
| sendWithTemplate(options) | Send using a registered named template |
| sendTemplate(options) | Send using a category + theme template |
| sendLoginNotification(to, vars) | Login alert with themed template |
| sendForgotPassword(to, vars) | Password reset with themed template |
| sendAlert(to, vars) | Security alert with themed template |
| sendBulk(options) | Batch send with per-recipient variables |
| registerNamedTemplate(name, def) | Register a named template |
| registerTemplate(category, theme, html) | Register/override a category template |
| registerCategory(name, defaultTheme?) | Create a new category |
| preview(category, theme, vars) | Render category template without sending |
| previewNamedTemplate(name, vars) | Render named template without sending |
| renderInline(html, vars) | Render inline template string |
| verify() | Test SMTP connection |
| close() | Close SMTP connection pool |
Utility Functions
Standalone functions — no LockVault instance needed:
import { hashPassword, verifyPassword, generateId, generateUUID, generateBackupCodes } from 'lockvault';
// Password hashing (scrypt, N=16384, r=8, p=1)
const hash = await hashPassword('my-password');
const isValid = await verifyPassword('my-password', hash); // true
// Random IDs
const id = generateId(32); // 64-char hex string
const uuid = generateUUID(); // UUID v4
// Backup codes (48-bit entropy each)
const codes = generateBackupCodes(10); // ['A1B2-C3D4-E5F6', ...]Error Handling
Every error thrown by LockVault extends LockVaultError with a machine-readable code and HTTP statusCode:
import { LockVaultError, TokenExpiredError, RateLimitError } from 'lockvault';
try {
await auth.jwt.verifyAccessToken(token);
} catch (err) {
if (err instanceof TokenExpiredError) {
// err.code === 'TOKEN_EXPIRED', err.statusCode === 401
}
if (err instanceof RateLimitError) {
// err.code === 'RATE_LIMITED', err.statusCode === 429
// err.retryAfterMs → milliseconds until retry is allowed
}
if (err instanceof LockVaultError) {
res.status(err.statusCode).json({ error: err.message, code: err.code });
}
}Error Codes
| Code | Status | Meaning |
|------|--------|---------|
| TOKEN_EXPIRED | 401 | Access token is past its exp time |
| TOKEN_INVALID | 401 | Bad signature, wrong algorithm, malformed, or failed iss/aud check |
| TOKEN_REVOKED | 401 | Token was explicitly revoked |
| REFRESH_TOKEN_REUSE | 401 | A used refresh token was replayed (possible theft) |
| SESSION_EXPIRED | 401 | Session past expiry or inactivity timeout |
| SESSION_NOT_FOUND | 401 | Session ID doesn't exist |
| SESSION_REVOKED | 401 | Session was revoked |
| TOTP_INVALID | 400 | Wrong code, wrong backup code, or code already used |
| TOTP_NOT_ENABLED | 400 | TOTP hasn't been set up for this user |
| TOTP_ALREADY_ENABLED | 400 | TOTP already active — call disable first |
| RATE_LIMITED | 429 | Too many attempts — retry after the specified delay |
| OAUTH_ERROR | 400 | OAuth flow failed |
| CONFIGURATION_ERROR | 500 | Invalid config at startup |
| EMAIL_ERROR | 500 | SMTP connection or email send failure |
Security Checklist
These are the defaults, but verify your setup covers them:
- [ ] JWT secrets are 32+ characters — LockVault rejects shorter secrets at startup
- [ ] Refresh token rotation is on — enabled by default, detects token theft
- [ ] Access token TTL is short — 15 minutes by default
- [ ] HTTPS only — cookie defaults are
secure: true - [ ] Algorithm is enforced — tokens with a different
algheader are rejected - [ ] Issuer/audience validated — set
issuerandaudiencein config - [ ] Encrypted refresh tokens for sensitive apps — set
refreshToken.encryption - [ ] Cleanup is running — call
auth.startCleanup()to purge expired data - [ ] Key rotation plan — use
auth.rotateJWTKeys()periodically - [ ] Graceful shutdown — call
auth.close()when your server stops
API Reference
createLockVault(config)
| Method | Returns | Description |
|--------|---------|-------------|
| initialize() | Promise<void> | Create database tables / indexes |
| login(userId, options?) | Promise<{ tokens, session }> | Log in — creates tokens + session |
| refresh(refreshToken, claims?) | Promise<TokenPair> | Refresh with automatic rotation |
| logout(accessToken) | Promise<void> | Revoke token + session |
| logoutAll(userId) | Promise<number> | Revoke all sessions for a user |
| setupTOTP(userId, email?) | Promise<TOTPSetupResult> | Generate TOTP setup |
| confirmTOTP(userId, secret, code, backupCodes) | Promise<boolean> | Confirm 2FA setup |
| verifyTOTP(userId, code) | Promise<boolean> | Verify a TOTP or backup code |
| disableTOTP(userId) | Promise<void> | Remove TOTP for a user |
| registerOAuthPreset(preset, config) | void | Register Google/GitHub/etc. |
| registerOAuthProvider(name, config) | void | Register a custom OAuth provider |
| getOAuthAuthorizationUrl(provider) | Promise<string> | Get the redirect URL |
| handleOAuthCallback(provider, code, state) | Promise<{ profile, tokens }> | Exchange code for profile |
| rotateJWTKeys(newSecret) | void | Rotate signing keys |
| startCleanup(intervalMs?) | void | Start automatic cleanup |
| close() | Promise<void> | Stop timers and close adapter |
Sub-Modules
| Property | Type | Access |
|----------|------|--------|
| auth.jwt | JWTManager | Direct token operations |
| auth.sessions | SessionManager | Direct session operations |
| auth.totp | TOTPManager | Direct TOTP operations |
| auth.oauth | OAuthManager | Direct OAuth operations |
| auth.adapter | DatabaseAdapter | Direct database access |
License
MIT
