@focura/auth-core 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @focura/auth-core
2
2
 
3
- Production-ready authentication core for Express.js backends.
3
+ Production-ready authentication engine for Node.js backends.
4
4
 
5
5
  ## Features
6
6
 
@@ -19,19 +19,19 @@ Production-ready authentication core for Express.js backends.
19
19
  ## Installation
20
20
 
21
21
  ```bash
22
- npm install @focura/auth-core express ioredis
23
- npm install -D @types/express
22
+ npm install @focura/auth-core ioredis
24
23
  ```
25
24
 
26
25
  ## Quick Start
27
26
 
28
27
  ```typescript
29
- import { MiddlewareFactory, AccountLockout, SessionManager } from "@focura/auth-core";
28
+ import { AuthService } from "@focura/auth-core";
30
29
  import Redis from "ioredis";
30
+ import fs from "fs";
31
31
 
32
32
  const redis = new Redis(process.env.REDIS_URL!);
33
33
 
34
- const auth = new MiddlewareFactory({
34
+ const auth = new AuthService({
35
35
  redis,
36
36
  userStore: {
37
37
  findById: (id) => prisma.user.findUnique({ where: { id } }),
@@ -39,34 +39,147 @@ const auth = new MiddlewareFactory({
39
39
  update: (id, data) => prisma.user.update({ where: { id }, data }),
40
40
  updateEmailVerified: (id, date) => prisma.user.update({ where: { id }, data: { emailVerified: date } }),
41
41
  },
42
- hmacSecret: process.env.NEXTAUTH_SECRET!,
42
+ hmacSecret: process.env.AUTH_SECRET!,
43
43
  jwt: {
44
44
  privateKey: fs.readFileSync("keys/private.pem", "utf8"),
45
45
  publicKey: fs.readFileSync("keys/public.pem", "utf8"),
46
46
  },
47
- cache: {
48
- get: (key) => redis.get(key).then(JSON.parse),
49
- set: (key, val, ttl) => redis.setex(key, ttl!, JSON.stringify(val)),
50
- delete: (key) => redis.del(key),
51
- },
52
- auditLogger: {
53
- log: (event, data) => prisma.auditLog.create({ data: { event, ...data } }),
54
- },
55
47
  });
48
+ ```
56
49
 
57
- // Auth routes
58
- app.post("/api/v1/auth/exchange", auth.createExchangeHandler());
59
- app.post("/api/v1/auth/refresh", auth.createRefreshHandler());
60
- app.post("/api/v1/auth/logout", auth.createLogoutHandler());
50
+ ## High-Level API
61
51
 
62
- // Protected routes
63
- app.get("/api/v1/profile", auth.createAuthenticateMiddleware(), (req, res) => {
64
- res.json({ user: req.user });
52
+ ### Token Exchange
53
+
54
+ Exchange a HMAC-signed proof for JWT tokens:
55
+
56
+ ```typescript
57
+ const tokens = await auth.exchange({
58
+ userId: user.id,
59
+ email: user.email,
60
+ role: user.role,
61
+ sessionId: crypto.randomUUID(),
62
+ timestamp: Date.now(),
63
+ signature: hmacSignature,
65
64
  });
66
65
 
67
- app.get("/api/v1/admin", auth.createAuthenticateMiddleware(), auth.createAuthorizeMiddleware("ADMIN"), (req, res) => {
68
- res.json({ admin: true });
66
+ // tokens.accessToken, tokens.refreshToken, tokens.sseToken, tokens.sessionId
67
+ ```
68
+
69
+ ### Verify Token
70
+
71
+ Verify an access token and load the user:
72
+
73
+ ```typescript
74
+ const { user, payload } = await auth.verifyToken({
75
+ token: accessToken,
76
+ ipAddress: "192.168.1.1",
77
+ userAgent: "Mozilla/5.0...",
78
+ });
79
+
80
+ // payload.id, payload.email, payload.role, payload.jti, payload.sessionId
81
+ ```
82
+
83
+ ### Refresh Tokens
84
+
85
+ Rotate refresh tokens atomically:
86
+
87
+ ```typescript
88
+ const newTokens = await auth.refresh({
89
+ refreshToken: currentRefreshToken,
69
90
  });
91
+
92
+ // newTokens.accessToken, newTokens.refreshToken, newTokens.sseToken
93
+ ```
94
+
95
+ ### Logout
96
+
97
+ ```typescript
98
+ // Single session
99
+ await auth.logout({
100
+ userId: user.id,
101
+ sessionId: sessionId,
102
+ accessTokenJti: jti,
103
+ accessToken: token,
104
+ });
105
+
106
+ // All sessions
107
+ await auth.logout({
108
+ userId: user.id,
109
+ sessionId: sessionId,
110
+ logoutAll: true,
111
+ });
112
+ ```
113
+
114
+ ### Two-Factor Authentication
115
+
116
+ ```typescript
117
+ // Setup — generate secret + URI for QR code
118
+ const { secret, uri } = auth.generateTwoFactor();
119
+ // Display uri as QR code to user, store secret in database
120
+
121
+ // Verify TOTP code
122
+ const valid = await auth.verifyTwoFactor({ token: "123456", secret });
123
+ ```
124
+
125
+ ### Session Management
126
+
127
+ ```typescript
128
+ // List active sessions
129
+ const sessions = await auth.getActiveSessions(userId);
130
+
131
+ // Revoke a specific session
132
+ await auth.revokeSession(userId, sessionId);
133
+ ```
134
+
135
+ ### Account Lockout
136
+
137
+ ```typescript
138
+ // Record failed login attempt
139
+ const result = await auth.recordLoginFailure(email);
140
+ if (result.locked) {
141
+ console.log(`Account locked until ${result.unlocksAt}`);
142
+ }
143
+
144
+ // Clear failures on successful login
145
+ await auth.clearLoginFailures(email);
146
+
147
+ // Check if account is locked
148
+ const status = await auth.isAccountLocked(email);
149
+ ```
150
+
151
+ ### Audit Logging
152
+
153
+ ```typescript
154
+ auth.log("WORKSPACE_CREATED", { userId, workspaceId });
155
+ ```
156
+
157
+ ## Express Integration
158
+
159
+ For Express.js applications, use `MiddlewareFactory` for HTTP middleware:
160
+
161
+ ```typescript
162
+ import { MiddlewareFactory } from "@focura/auth-core";
163
+
164
+ const factory = new MiddlewareFactory(config);
165
+
166
+ // Auth routes
167
+ app.post("/api/v1/auth/exchange", factory.createExchangeHandler());
168
+ app.post("/api/v1/auth/refresh", factory.createRefreshHandler());
169
+ app.post("/api/v1/auth/logout", factory.createLogoutHandler());
170
+
171
+ // Protected routes
172
+ app.get("/api/v1/profile",
173
+ factory.createAuthenticateMiddleware(),
174
+ (req, res) => { res.json({ user: req.user }); }
175
+ );
176
+
177
+ // Role-based access
178
+ app.get("/api/v1/admin",
179
+ factory.createAuthenticateMiddleware(),
180
+ factory.createAuthorizeMiddleware("ADMIN"),
181
+ (req, res) => { res.json({ admin: true }); }
182
+ );
70
183
  ```
71
184
 
72
185
  ## Adapters
@@ -79,8 +192,7 @@ Works with ioredis or any compatible client:
79
192
  import Redis from "ioredis";
80
193
  const redis = new Redis(process.env.REDIS_URL);
81
194
 
82
- // Pass directly ioredis satisfies the RedisAdapter interface
83
- const auth = new MiddlewareFactory({ redis, ... });
195
+ const auth = new AuthService({ redis, ... });
84
196
  ```
85
197
 
86
198
  ### UserStore
@@ -133,6 +245,276 @@ openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
133
245
  openssl rsa -in private.pem -pubout -out public.pem
134
246
  ```
135
247
 
248
+ ## API Hierarchy
249
+
250
+ ### Primary API (Recommended)
251
+
252
+ ```typescript
253
+ import { AuthService } from "@focura/auth-core";
254
+ ```
255
+
256
+ The `AuthService` class provides high-level, framework-agnostic authentication operations.
257
+
258
+ ### Extension API
259
+
260
+ Interfaces for integrating your infrastructure:
261
+
262
+ ```typescript
263
+ import type { UserStore, RedisAdapter, CacheAdapter, AuditLogger } from "@focura/auth-core";
264
+ ```
265
+
266
+ ### Advanced API
267
+
268
+ Low-level classes for custom integrations:
269
+
270
+ ```typescript
271
+ import { TokenManager, SessionManager, TotpManager, AccountLockout } from "@focura/auth-core";
272
+ ```
273
+
274
+ These are available but not required for normal application development.
275
+
276
+ ---
277
+
278
+ ## Default Configuration
279
+
280
+ ```typescript
281
+ import { DEFAULTS } from "@focura/auth-core";
282
+
283
+ // All default values:
284
+ DEFAULTS.keyPrefix; // "focura:"
285
+ DEFAULTS.issuer; // "focura-app"
286
+ DEFAULTS.audience; // "focura-backend"
287
+ DEFAULTS.accessTokenExpiry; // "15m"
288
+ DEFAULTS.refreshTokenExpiry; // "7d"
289
+ DEFAULTS.sseTokenExpiry; // "30s"
290
+ DEFAULTS.maxConcurrentSessions; // 5
291
+ DEFAULTS.lockoutMaxFailures; // 10
292
+ DEFAULTS.lockoutSeconds; // 900 (15 minutes)
293
+ DEFAULTS.lockoutWindowSeconds; // 3600 (1 hour)
294
+ DEFAULTS.inactivityTimeout; // 604800 (7 days)
295
+ DEFAULTS.absoluteTimeout; // 604800 (7 days)
296
+ ```
297
+
298
+ ### resolveConfig
299
+
300
+ Merges your config with defaults. Useful for advanced customization:
301
+
302
+ ```typescript
303
+ import { resolveConfig } from "@focura/auth-core";
304
+
305
+ const resolved = resolveConfig({
306
+ redis,
307
+ userStore,
308
+ hmacSecret: "...",
309
+ jwt: { privateKey: "...", publicKey: "..." },
310
+ lockout: { maxFailures: 5 }, // override default 10
311
+ });
312
+
313
+ // resolved contains all values with defaults applied
314
+ ```
315
+
316
+ ---
317
+
318
+ ## Error Handling
319
+
320
+ All errors include `code` and `statusCode` for API responses:
321
+
322
+ ```typescript
323
+ import {
324
+ UnauthorizedError, // 401, code: UNAUTHORIZED
325
+ TokenExpiredError, // 401, code: TOKEN_EXPIRED
326
+ InvalidTokenError, // 401, code: INVALID_TOKEN
327
+ TokenRevokedError, // 401, code: TOKEN_REVOKED
328
+ SessionHijackError, // 401, code: SESSION_HIJACK_DETECTED
329
+ EmailNotVerifiedError, // 403, code: EMAIL_NOT_VERIFIED
330
+ AccountBannedError, // 403, code: ACCOUNT_BANNED
331
+ ForbiddenError, // 403, code: FORBIDDEN
332
+ BadRequestError, // 400, code: BAD_REQUEST
333
+ ValidationError, // 400, code: VALIDATION_ERROR
334
+ } from "@focura/auth-core";
335
+ ```
336
+
337
+ ### Example: Catching errors
338
+
339
+ ```typescript
340
+ try {
341
+ const { user, payload } = await auth.verifyToken({ token, ipAddress, userAgent });
342
+ } catch (e) {
343
+ if (e instanceof TokenExpiredError) {
344
+ return res.status(401).json({ error: "Token expired", code: e.code });
345
+ }
346
+ if (e instanceof InvalidTokenError) {
347
+ return res.status(401).json({ error: "Invalid token", code: e.code });
348
+ }
349
+ if (e instanceof SessionHijackError) {
350
+ return res.status(401).json({ error: "Session hijack detected", code: e.code });
351
+ }
352
+ if (e instanceof AccountBannedError) {
353
+ return res.status(403).json({ error: "Account banned", reason: e.message });
354
+ }
355
+ if (e instanceof ValidationError) {
356
+ return res.status(400).json({ error: e.message, details: e.details });
357
+ }
358
+ return res.status(500).json({ error: "Internal server error" });
359
+ }
360
+ ```
361
+
362
+ ### defaultErrors Factory
363
+
364
+ Use as base for custom error classes:
365
+
366
+ ```typescript
367
+ import { defaultErrors } from "@focura/auth-core";
368
+
369
+ const customErrors = {
370
+ ...defaultErrors,
371
+ UnauthorizedError: (msg) => new MyCustomUnauthorizedError(msg),
372
+ };
373
+
374
+ const auth = new AuthService({ ..., errors: customErrors });
375
+ ```
376
+
377
+ ---
378
+
379
+ ## Input & Result Types
380
+
381
+ All types are exported for TypeScript autocompletion:
382
+
383
+ ```typescript
384
+ import type {
385
+ // Token exchange
386
+ ExchangeInput, // { userId, email, role, sessionId, timestamp, signature }
387
+ ExchangeResult, // { accessToken, refreshToken, sseToken, sessionId }
388
+
389
+ // Token verification
390
+ VerifyTokenInput, // { token, ipAddress?, userAgent? }
391
+ VerifyTokenResult, // { user, payload }
392
+
393
+ // Refresh
394
+ RefreshInput, // { refreshToken }
395
+ AuthRefreshResult, // { accessToken, refreshToken, sseToken }
396
+
397
+ // Logout
398
+ LogoutInput, // { userId, sessionId, accessTokenJti?, accessToken?, logoutAll? }
399
+
400
+ // 2FA
401
+ TwoFactorSetupResult, // { secret, uri }
402
+ TwoFactorVerifyInput, // { token, secret }
403
+
404
+ // Tokens
405
+ TokenPayload, // { id, email, role, type, version, jti, sessionId? }
406
+ TokenPair, // { accessToken, refreshToken, accessTokenExpiry, refreshTokenExpiry }
407
+
408
+ // Config sub-types
409
+ TokenConfig, // { privateKey, publicKey, issuer?, audience?, accessTokenExpiry?, ... }
410
+ SessionConfig, // { inactivityTimeout?, absoluteTimeout?, maxConcurrent?, metadataTtl? }
411
+ LockoutConfig, // { maxFailures?, lockoutSeconds?, windowSeconds? }
412
+
413
+ // Adapters
414
+ RedisAdapter,
415
+ RedisPipeline,
416
+ UserStore,
417
+ User,
418
+ CacheAdapter,
419
+ AuditLogger,
420
+ ObservabilitySink,
421
+ ErrorFactory,
422
+ ZodSchema,
423
+
424
+ // Session
425
+ SessionMetadata, // { deviceId, ipAddress, userAgent, location?, lastActivity }
426
+ SessionLifecycle, // { recordCreation, invalidate, isTracked, isInactive }
427
+ DeviceFingerprint, // { userAgent, acceptLanguage, acceptEncoding, ipAddress }
428
+ AuthRequest, // Extended request with user property
429
+ } from "@focura/auth-core";
430
+ ```
431
+
432
+ ---
433
+
434
+ ## MiddlewareFactory Methods
435
+
436
+ All HTTP middleware and route handlers:
437
+
438
+ ```typescript
439
+ import { MiddlewareFactory } from "@focura/auth-core";
440
+
441
+ const factory = new MiddlewareFactory(config);
442
+
443
+ // Route handlers
444
+ factory.createExchangeHandler(); // POST /api/v1/auth/exchange
445
+ factory.createRefreshHandler(); // POST /api/v1/auth/refresh
446
+ factory.createLogoutHandler(); // POST /api/v1/auth/logout
447
+
448
+ // Auth middleware
449
+ factory.createAuthenticateMiddleware(); // Verifies JWT, attaches req.user
450
+ factory.createAuthorizeMiddleware("ADMIN", "MOD"); // Role-based access
451
+
452
+ // Security middleware
453
+ factory.createCsrfMiddleware(); // CSRF token validation
454
+ factory.createRateLimitMiddleware(); // Sliding window rate limiting
455
+ factory.createSessionTimeoutMiddleware(); // Inactivity + absolute timeout
456
+ ```
457
+
458
+ ---
459
+
460
+ ## Utility Functions
461
+
462
+ Low-level helpers available for advanced use:
463
+
464
+ ```typescript
465
+ import {
466
+ generateDeviceFingerprint, // (req) => DeviceFingerprint
467
+ getClientIp, // (req) => string
468
+ isPrivateIp, // (ip) => boolean — checks 10.x, 172.16-31.x, 192.168.x, 127.x
469
+ normalizeUserAgent, // (ua) => string — truncates long user agents
470
+ createSessionMetadata, // (fingerprint, sessionId) => SessionMetadata
471
+ validateSessionBinding, // (metadata, fingerprint, options) => { bound, reason? }
472
+ looksLikeServerToServerRequest, // (req) => boolean
473
+ looksLikeServerToServerUA, // (ua) => boolean
474
+ } from "@focura/auth-core";
475
+ ```
476
+
477
+ ---
478
+
479
+ ## Audit Event Types
480
+
481
+ 50+ event types with severity levels:
482
+
483
+ ```typescript
484
+ import { AUDIT_SEVERITY } from "@focura/auth-core";
485
+
486
+ // AUDIT_SEVERITY maps event types to severity:
487
+ // "info" — LOGIN_SUCCESS, LOGOUT, TOKEN_REFRESHED, SESSION_BOUND, etc.
488
+ // "warn" — LOGIN_FAILED, DEVICE_MISMATCH, RATE_LIMIT_EXCEEDED, etc.
489
+ // "critical" — TOKEN_REPLAY_DETECTED, ACCOUNT_LOCKED, MALWARE_DETECTED, etc.
490
+ ```
491
+
492
+ ```typescript
493
+ import type { AuditEventType, AuditSeverity } from "@focura/auth-core";
494
+
495
+ // AuditEventType — union of all 50+ event strings
496
+ // AuditSeverity — "info" | "warn" | "critical"
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Zod Schemas
502
+
503
+ Validation schemas used internally (available for external use):
504
+
505
+ ```typescript
506
+ import { exchangeSchema, refreshSchema, logoutSchema } from "@focura/auth-core";
507
+
508
+ // exchangeSchema validates ExchangeInput
509
+ // refreshSchema validates RefreshInput
510
+ // logoutSchema validates LogoutInput
511
+
512
+ const result = exchangeSchema.safeParse(data);
513
+ if (!result.success) {
514
+ console.log(result.error.issues);
515
+ }
516
+ ```
517
+
136
518
  ## License
137
519
 
138
520
  MIT