@veloxts/auth 0.3.3 → 0.3.4

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.
Files changed (54) hide show
  1. package/README.md +755 -30
  2. package/dist/adapter.d.ts +710 -0
  3. package/dist/adapter.d.ts.map +1 -0
  4. package/dist/adapter.js +581 -0
  5. package/dist/adapter.js.map +1 -0
  6. package/dist/adapters/better-auth.d.ts +271 -0
  7. package/dist/adapters/better-auth.d.ts.map +1 -0
  8. package/dist/adapters/better-auth.js +341 -0
  9. package/dist/adapters/better-auth.js.map +1 -0
  10. package/dist/adapters/index.d.ts +28 -0
  11. package/dist/adapters/index.d.ts.map +1 -0
  12. package/dist/adapters/index.js +28 -0
  13. package/dist/adapters/index.js.map +1 -0
  14. package/dist/csrf.d.ts +294 -0
  15. package/dist/csrf.d.ts.map +1 -0
  16. package/dist/csrf.js +396 -0
  17. package/dist/csrf.js.map +1 -0
  18. package/dist/guards.d.ts +139 -0
  19. package/dist/guards.d.ts.map +1 -0
  20. package/dist/guards.js +247 -0
  21. package/dist/guards.js.map +1 -0
  22. package/dist/hash.d.ts +85 -0
  23. package/dist/hash.d.ts.map +1 -0
  24. package/dist/hash.js +220 -0
  25. package/dist/hash.js.map +1 -0
  26. package/dist/index.d.ts +25 -32
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +63 -36
  29. package/dist/index.js.map +1 -1
  30. package/dist/jwt.d.ts +128 -0
  31. package/dist/jwt.d.ts.map +1 -0
  32. package/dist/jwt.js +363 -0
  33. package/dist/jwt.js.map +1 -0
  34. package/dist/middleware.d.ts +87 -0
  35. package/dist/middleware.d.ts.map +1 -0
  36. package/dist/middleware.js +241 -0
  37. package/dist/middleware.js.map +1 -0
  38. package/dist/plugin.d.ts +107 -0
  39. package/dist/plugin.d.ts.map +1 -0
  40. package/dist/plugin.js +174 -0
  41. package/dist/plugin.js.map +1 -0
  42. package/dist/policies.d.ts +137 -0
  43. package/dist/policies.d.ts.map +1 -0
  44. package/dist/policies.js +240 -0
  45. package/dist/policies.js.map +1 -0
  46. package/dist/session.d.ts +494 -0
  47. package/dist/session.d.ts.map +1 -0
  48. package/dist/session.js +795 -0
  49. package/dist/session.js.map +1 -0
  50. package/dist/types.d.ts +251 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +33 -0
  53. package/dist/types.js.map +1 -0
  54. package/package.json +38 -7
package/README.md CHANGED
@@ -4,51 +4,776 @@
4
4
 
5
5
  Authentication and authorization system for VeloxTS Framework.
6
6
 
7
- > **Note:** This package is a placeholder in v0.1.0. Full implementation coming in v1.1.
7
+ ## Features
8
8
 
9
- ## Status
9
+ - **Pluggable Auth Adapters** - Integrate external providers like BetterAuth, Clerk, Auth0
10
+ - **Session Management** - Cookie-based sessions with pluggable storage backends
11
+ - **JWT Authentication** - Stateless token-based authentication with refresh tokens
12
+ - **Password Hashing** - Secure bcrypt hashing with configurable cost factors
13
+ - **CSRF Protection** - Signed double-submit cookie pattern
14
+ - **Guards and Policies** - Declarative authorization for procedures
15
+ - **Rate Limiting** - Built-in request rate limiting middleware
10
16
 
11
- This package is reserved for future functionality. The current release includes only type definitions and stub implementations to allow other packages to reference it.
17
+ ## Table of Contents
12
18
 
13
- ## Planned Features (v1.1+)
19
+ - [Installation](#installation)
20
+ - [Auth Adapters](#auth-adapters)
21
+ - [Authentication Strategies](#authentication-strategies)
22
+ - [Session Management](#session-management)
23
+ - [JWT Authentication](#jwt-authentication)
24
+ - [CSRF Protection](#csrf-protection)
25
+ - [Guards and Policies](#guards-and-policies)
26
+ - [Password Hashing](#password-hashing)
27
+ - [Rate Limiting](#rate-limiting)
14
28
 
15
- - **Session Management** - Secure session handling with configurable stores
16
- - **JWT Authentication** - Token-based authentication with refresh tokens
17
- - **Guards and Policies** - Declarative authorization for procedures
18
- - **Role-Based Access Control** - Define roles and permissions
19
- - **User Model Integration** - Seamless integration with Prisma user models
20
- - **OAuth Providers** - Social login support (Google, GitHub, etc.)
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @veloxts/auth
33
+ ```
34
+
35
+ Required peer dependencies:
36
+
37
+ ```bash
38
+ npm install @veloxts/core @veloxts/router fastify @fastify/cookie
39
+ ```
40
+
41
+ ## Auth Adapters
42
+
43
+ Auth adapters allow you to integrate external authentication providers like BetterAuth, Clerk, or Auth0 with VeloxTS. Instead of building authentication from scratch, you can leverage battle-tested auth solutions.
44
+
45
+ ### BetterAuth Adapter
46
+
47
+ [BetterAuth](https://better-auth.com) is a comprehensive, framework-agnostic TypeScript authentication library. The BetterAuth adapter seamlessly integrates it with VeloxTS.
48
+
49
+ #### Installation
50
+
51
+ ```bash
52
+ npm install better-auth @veloxts/auth
53
+ ```
54
+
55
+ #### Basic Setup
56
+
57
+ ```typescript
58
+ import { createVeloxApp } from '@veloxts/core';
59
+ import { createAuthAdapterPlugin, createBetterAuthAdapter } from '@veloxts/auth';
60
+ import { betterAuth } from 'better-auth';
61
+ import { prismaAdapter } from 'better-auth/adapters/prisma';
62
+ import { PrismaClient } from '@prisma/client';
63
+
64
+ const prisma = new PrismaClient();
65
+
66
+ // Create BetterAuth instance
67
+ const auth = betterAuth({
68
+ database: prismaAdapter(prisma, {
69
+ provider: 'postgresql', // or 'mysql', 'sqlite'
70
+ }),
71
+ trustedOrigins: ['http://localhost:3000'],
72
+ emailAndPassword: {
73
+ enabled: true,
74
+ },
75
+ });
76
+
77
+ // Create the adapter
78
+ const betterAuthAdapter = createBetterAuthAdapter({
79
+ name: 'better-auth',
80
+ auth,
81
+ debug: process.env.NODE_ENV === 'development',
82
+ });
83
+
84
+ // Create the plugin
85
+ const authPlugin = createAuthAdapterPlugin({
86
+ adapter: betterAuthAdapter,
87
+ config: betterAuthAdapter.config,
88
+ });
89
+
90
+ // Create app and register plugin
91
+ const app = createVeloxApp();
92
+ await app.register(authPlugin);
93
+ ```
94
+
95
+ #### Using Authentication in Procedures
96
+
97
+ ```typescript
98
+ import { createAdapterAuthMiddleware } from '@veloxts/auth';
99
+ import { defineProcedures, procedure } from '@veloxts/router';
100
+
101
+ const authMiddleware = createAdapterAuthMiddleware();
102
+
103
+ export const userProcedures = defineProcedures('users', {
104
+ // Require authentication - throws 401 if not logged in
105
+ getProfile: procedure
106
+ .use(authMiddleware.requireAuth())
107
+ .query(async ({ ctx }) => {
108
+ // ctx.user is guaranteed to exist
109
+ return {
110
+ id: ctx.user.id,
111
+ email: ctx.user.email,
112
+ name: ctx.user.name,
113
+ };
114
+ }),
115
+
116
+ // Optional authentication - user may or may not be logged in
117
+ getPublicPosts: procedure
118
+ .use(authMiddleware.optionalAuth())
119
+ .query(async ({ ctx }) => {
120
+ const posts = await db.post.findMany({ where: { published: true } });
121
+ return {
122
+ posts,
123
+ isAuthenticated: ctx.isAuthenticated,
124
+ userId: ctx.user?.id,
125
+ };
126
+ }),
127
+ });
128
+ ```
129
+
130
+ #### BetterAuth Configuration Options
131
+
132
+ ```typescript
133
+ const adapter = createBetterAuthAdapter({
134
+ // Required: Adapter name (for logging)
135
+ name: 'better-auth',
136
+
137
+ // Required: BetterAuth instance
138
+ auth: betterAuth({ ... }),
139
+
140
+ // Optional: Base path for auth routes (default: '/api/auth')
141
+ basePath: '/api/auth',
142
+
143
+ // Optional: Enable debug logging
144
+ debug: true,
145
+
146
+ // Optional: Handle all HTTP methods (default: GET, POST only)
147
+ handleAllMethods: true,
148
+
149
+ // Optional: Routes to exclude from session loading
150
+ excludeRoutes: ['/api/health', '/api/public/*'],
151
+
152
+ // Optional: Custom user transformation
153
+ transformUser: (adapterUser) => ({
154
+ id: adapterUser.id,
155
+ email: adapterUser.email,
156
+ role: adapterUser.providerData?.role as string ?? 'user',
157
+ permissions: adapterUser.providerData?.permissions as string[] ?? [],
158
+ }),
159
+ });
160
+ ```
161
+
162
+ #### Auth Routes
163
+
164
+ BetterAuth automatically mounts its routes at the configured base path. Common routes include:
165
+
166
+ - `POST /api/auth/sign-up` - User registration
167
+ - `POST /api/auth/sign-in/email` - Email/password login
168
+ - `POST /api/auth/sign-out` - Logout
169
+ - `GET /api/auth/session` - Get current session
170
+ - `POST /api/auth/magic-link` - Send magic link (if enabled)
171
+ - `GET /api/auth/callback/:provider` - OAuth callbacks
172
+
173
+ See the [BetterAuth documentation](https://better-auth.com/docs) for all available routes and configuration options.
174
+
175
+ ### Creating Custom Adapters
176
+
177
+ You can create adapters for other authentication providers by implementing the `AuthAdapter` interface:
178
+
179
+ ```typescript
180
+ import {
181
+ AuthAdapter,
182
+ AuthAdapterConfig,
183
+ AdapterSessionResult,
184
+ BaseAuthAdapter,
185
+ defineAuthAdapter,
186
+ } from '@veloxts/auth';
187
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
188
+
189
+ // Define your adapter-specific config
190
+ interface MyAuthConfig extends AuthAdapterConfig {
191
+ apiKey: string;
192
+ domain: string;
193
+ }
194
+
195
+ // Option 1: Use defineAuthAdapter helper
196
+ export const myAuthAdapter = defineAuthAdapter<MyAuthConfig>({
197
+ name: 'my-auth',
198
+ version: '1.0.0',
21
199
 
22
- ## Current Exports
200
+ async initialize(fastify: FastifyInstance, config: MyAuthConfig) {
201
+ // Initialize your auth client
202
+ this.client = new MyAuthClient({
203
+ apiKey: config.apiKey,
204
+ domain: config.domain,
205
+ });
206
+ },
23
207
 
24
- The following are available but not fully implemented:
208
+ async getSession(request: FastifyRequest): Promise<AdapterSessionResult | null> {
209
+ const token = request.headers.authorization?.replace('Bearer ', '');
210
+ if (!token) return null;
211
+
212
+ const session = await this.client.verifySession(token);
213
+ if (!session) return null;
214
+
215
+ return {
216
+ user: {
217
+ id: session.user.id,
218
+ email: session.user.email,
219
+ name: session.user.name,
220
+ },
221
+ session: {
222
+ sessionId: session.id,
223
+ userId: session.user.id,
224
+ expiresAt: session.expiresAt,
225
+ isActive: true,
226
+ },
227
+ };
228
+ },
229
+
230
+ getRoutes() {
231
+ return [
232
+ {
233
+ path: '/api/auth/*',
234
+ methods: ['GET', 'POST'],
235
+ handler: async (request, reply) => {
236
+ // Forward to your auth provider
237
+ },
238
+ },
239
+ ];
240
+ },
241
+ });
242
+
243
+ // Option 2: Extend BaseAuthAdapter class
244
+ class MyAuthAdapter extends BaseAuthAdapter<MyAuthConfig> {
245
+ private client: MyAuthClient | null = null;
246
+
247
+ constructor() {
248
+ super('my-auth', '1.0.0');
249
+ }
250
+
251
+ override async initialize(fastify: FastifyInstance, config: MyAuthConfig) {
252
+ await super.initialize(fastify, config);
253
+ this.client = new MyAuthClient(config);
254
+ }
255
+
256
+ override async getSession(request: FastifyRequest): Promise<AdapterSessionResult | null> {
257
+ // Implementation...
258
+ }
259
+
260
+ override getRoutes() {
261
+ return [];
262
+ }
263
+ }
264
+ ```
265
+
266
+ ### Adapter Type Utilities
25
267
 
26
268
  ```typescript
27
- import { createAuth, guard, User } from '@veloxts/auth';
269
+ import {
270
+ isAuthAdapter,
271
+ InferAdapterConfig,
272
+ AuthAdapterError,
273
+ } from '@veloxts/auth';
274
+
275
+ // Type guard to check if value is a valid adapter
276
+ if (isAuthAdapter(maybeAdapter)) {
277
+ const plugin = createAuthAdapterPlugin({
278
+ adapter: maybeAdapter,
279
+ config: maybeAdapter.config,
280
+ });
281
+ }
28
282
 
29
- // User interface for type definitions
30
- interface User {
31
- id: string;
32
- email: string;
33
- [key: string]: unknown;
283
+ // Infer config type from adapter
284
+ type BetterAuthConfig = InferAdapterConfig<typeof betterAuthAdapter>;
285
+
286
+ // Handle adapter errors
287
+ try {
288
+ const session = await adapter.getSession(request);
289
+ } catch (error) {
290
+ if (error instanceof AuthAdapterError) {
291
+ console.error(`Adapter error: ${error.code} - ${error.message}`);
292
+ console.error(`Cause:`, error.cause);
293
+ }
34
294
  }
295
+ ```
296
+
297
+ ## Authentication Strategies
298
+
299
+ VeloxTS Auth provides two primary authentication strategies. Choose the one that fits your architecture:
300
+
301
+ ### Session-Based Authentication
302
+
303
+ **Use when:**
304
+ - Building traditional server-rendered applications
305
+ - You need server-side state and fine-grained session control
306
+ - Single-server or shared session store architecture (Redis, database)
307
+ - You want Laravel-style flash data and session management
308
+
309
+ **Advantages:**
310
+ - Server controls session lifecycle (can revoke sessions immediately)
311
+ - No token storage needed on client
312
+ - Easy "logout all devices" functionality
313
+ - Built-in flash data support
314
+
315
+ **Trade-offs:**
316
+ - Requires session store (Redis, database, etc. for production)
317
+ - Slightly more server load (session lookups)
318
+ - Requires sticky sessions or shared storage for horizontal scaling
319
+
320
+ ### JWT-Based Authentication
321
+
322
+ **Use when:**
323
+ - Building stateless APIs for mobile apps or SPAs
324
+ - Microservices architecture with distributed authentication
325
+ - You need cross-domain authentication
326
+ - Horizontal scaling without shared state
327
+
328
+ **Advantages:**
329
+ - Stateless (no server-side storage required)
330
+ - Works seamlessly across multiple servers
331
+ - Can include custom claims and metadata
332
+
333
+ **Trade-offs:**
334
+ - Cannot revoke tokens before expiration (without additional infrastructure)
335
+ - Requires secure client-side token storage
336
+ - Larger payload size (tokens in every request)
337
+
338
+ ## Session Management
339
+
340
+ Cookie-based session management with secure defaults and pluggable storage backends.
341
+
342
+ ### Quick Start
343
+
344
+ ```typescript
345
+ import { createSessionMiddleware, createInMemorySessionStore } from '@veloxts/auth';
346
+ import { defineProcedures, procedure } from '@veloxts/router';
347
+
348
+ // Create session middleware
349
+ const session = createSessionMiddleware({
350
+ secret: process.env.SESSION_SECRET!, // Min 32 characters
351
+ cookie: {
352
+ secure: process.env.NODE_ENV === 'production',
353
+ sameSite: 'lax',
354
+ },
355
+ expiration: {
356
+ ttl: 86400, // 24 hours
357
+ sliding: true, // Refresh on each request
358
+ },
359
+ });
360
+
361
+ // Use in procedures
362
+ export const userProcedures = defineProcedures('users', {
363
+ // Get shopping cart from session
364
+ getCart: procedure
365
+ .use(session.middleware())
366
+ .query(async ({ ctx }) => {
367
+ return ctx.session.get('cart') ?? [];
368
+ }),
369
+
370
+ // Add item to cart
371
+ addToCart: procedure
372
+ .use(session.middleware())
373
+ .input(AddToCartSchema)
374
+ .mutation(async ({ input, ctx }) => {
375
+ const cart = ctx.session.get('cart') ?? [];
376
+ cart.push(input.item);
377
+ ctx.session.set('cart', cart);
378
+ return { success: true };
379
+ }),
380
+ });
381
+ ```
382
+
383
+ ### Configuration Options
384
+
385
+ ```typescript
386
+ import { createSessionManager, createInMemorySessionStore } from '@veloxts/auth';
387
+
388
+ const sessionManager = createSessionManager({
389
+ // Required: Cryptographically secure secret (min 32 chars)
390
+ // Generate with: openssl rand -base64 32
391
+ secret: process.env.SESSION_SECRET!,
392
+
393
+ // Optional: Storage backend (default: InMemorySessionStore)
394
+ store: createInMemorySessionStore(),
395
+
396
+ // Optional: Cookie configuration
397
+ cookie: {
398
+ name: 'myapp.session', // Cookie name (default: 'velox.session')
399
+ path: '/', // Cookie path (default: '/')
400
+ domain: 'example.com', // Cookie domain (optional)
401
+ secure: true, // HTTPS only (default: NODE_ENV === 'production')
402
+ httpOnly: true, // Prevent JS access (default: true)
403
+ sameSite: 'strict', // CSRF protection (default: 'lax')
404
+ },
405
+
406
+ // Optional: Expiration configuration
407
+ expiration: {
408
+ ttl: 3600, // Session TTL in seconds (default: 86400)
409
+ sliding: true, // Refresh TTL on each request (default: true)
410
+ absoluteTimeout: 604800, // Max session lifetime (7 days), forces re-auth
411
+ },
412
+
413
+ // Optional: User loader function
414
+ userLoader: async (userId) => {
415
+ return db.user.findUnique({ where: { id: userId } });
416
+ },
417
+ });
418
+ ```
419
+
420
+ ### Middleware Variants
421
+
422
+ ```typescript
423
+ const session = createSessionMiddleware(config);
424
+
425
+ // Basic session middleware - creates session for all requests
426
+ const getPreferences = procedure
427
+ .use(session.middleware())
428
+ .query(async ({ ctx }) => {
429
+ return ctx.session.get('theme') ?? 'light';
430
+ });
431
+
432
+ // Require authentication - throws 401 if no userId in session
433
+ const getProfile = procedure
434
+ .use(session.requireAuth())
435
+ .query(async ({ ctx }) => {
436
+ // ctx.user is guaranteed to exist
437
+ return ctx.user;
438
+ });
439
+
440
+ // Optional authentication - user may or may not be logged in
441
+ const getHomePage = procedure
442
+ .use(session.optionalAuth())
443
+ .query(async ({ ctx }) => {
444
+ if (ctx.isAuthenticated) {
445
+ return { greeting: `Welcome back, ${ctx.user.email}!` };
446
+ }
447
+ return { greeting: 'Welcome, guest!' };
448
+ });
449
+ ```
450
+
451
+ ### Login and Logout
452
+
453
+ ```typescript
454
+ import { loginSession, logoutSession } from '@veloxts/auth';
455
+ import { hashPassword, verifyPassword } from '@veloxts/auth';
456
+
457
+ export const authProcedures = defineProcedures('auth', {
458
+ // Login procedure
459
+ login: procedure
460
+ .use(session.middleware())
461
+ .input(z.object({ email: z.string().email(), password: z.string() }))
462
+ .mutation(async ({ input, ctx }) => {
463
+ // Find user
464
+ const user = await db.user.findUnique({ where: { email: input.email } });
465
+ if (!user) {
466
+ throw new AuthError('Invalid credentials', 401);
467
+ }
468
+
469
+ // Verify password
470
+ const valid = await verifyPassword(input.password, user.passwordHash);
471
+ if (!valid) {
472
+ throw new AuthError('Invalid credentials', 401);
473
+ }
474
+
475
+ // Login - regenerates session ID to prevent fixation attacks
476
+ await loginSession(ctx.session, user);
477
+
478
+ return { success: true, user };
479
+ }),
480
+
481
+ // Logout procedure
482
+ logout: procedure
483
+ .use(session.requireAuth())
484
+ .mutation(async ({ ctx }) => {
485
+ await logoutSession(ctx.session);
486
+ return { success: true };
487
+ }),
488
+
489
+ // Logout from all devices
490
+ logoutAll: procedure
491
+ .use(session.requireAuth())
492
+ .mutation(async ({ ctx }) => {
493
+ await session.manager.destroyUserSessions(ctx.user.id);
494
+ return { success: true };
495
+ }),
496
+ });
497
+ ```
498
+
499
+ ### Flash Data
500
+
501
+ Flash data persists for exactly one request - perfect for success messages after redirects.
502
+
503
+ ```typescript
504
+ // Set flash data
505
+ const createPost = procedure
506
+ .use(session.middleware())
507
+ .input(CreatePostSchema)
508
+ .mutation(async ({ input, ctx }) => {
509
+ const post = await db.post.create({ data: input });
510
+
511
+ // Flash message for next request
512
+ ctx.session.flash('success', 'Post created successfully!');
513
+
514
+ return post;
515
+ });
516
+
517
+ // Read flash data (automatically cleared after this request)
518
+ const getFlashMessages = procedure
519
+ .use(session.middleware())
520
+ .query(async ({ ctx }) => {
521
+ const messages = ctx.session.getAllFlash();
522
+ return messages;
523
+ });
524
+ ```
525
+
526
+ ### Session Handle API
527
+
528
+ ```typescript
529
+ // Get value
530
+ const theme = session.get('theme');
531
+
532
+ // Set value (marks session as modified)
533
+ session.set('theme', 'dark');
534
+
535
+ // Delete value
536
+ session.delete('theme');
537
+
538
+ // Check if key exists
539
+ if (session.has('cart')) {
540
+ // ...
541
+ }
542
+
543
+ // Flash data
544
+ session.flash('message', 'Success!');
545
+ const message = session.getFlash('message');
546
+
547
+ // Regenerate session ID (security: call after privilege changes)
548
+ await session.regenerate();
549
+
550
+ // Destroy session completely
551
+ await session.destroy();
552
+
553
+ // Save session manually (auto-saved by middleware)
554
+ await session.save();
555
+
556
+ // Reload session from store
557
+ await session.reload();
558
+
559
+ // Session metadata
560
+ session.id; // Session ID
561
+ session.isNew; // True for new sessions
562
+ session.isModified; // True if data changed
563
+ session.isDestroyed; // True after destroy()
564
+ ```
565
+
566
+ ### Custom Session Storage
567
+
568
+ For production, implement a custom store backed by Redis, PostgreSQL, or other persistent storage.
569
+
570
+ ```typescript
571
+ import { SessionStore, StoredSession } from '@veloxts/auth';
572
+ import { Redis } from 'ioredis';
573
+
574
+ class RedisSessionStore implements SessionStore {
575
+ constructor(private redis: Redis) {}
576
+
577
+ async get(sessionId: string): Promise<StoredSession | null> {
578
+ const data = await this.redis.get(`session:${sessionId}`);
579
+ return data ? JSON.parse(data) : null;
580
+ }
581
+
582
+ async set(sessionId: string, session: StoredSession): Promise<void> {
583
+ const ttl = Math.ceil((session.expiresAt - Date.now()) / 1000);
584
+ await this.redis.setex(
585
+ `session:${sessionId}`,
586
+ ttl,
587
+ JSON.stringify(session)
588
+ );
589
+ }
590
+
591
+ async delete(sessionId: string): Promise<void> {
592
+ await this.redis.del(`session:${sessionId}`);
593
+ }
594
+
595
+ async touch(sessionId: string, expiresAt: number): Promise<void> {
596
+ const session = await this.get(sessionId);
597
+ if (session) {
598
+ session.expiresAt = expiresAt;
599
+ session.data._lastAccessedAt = Date.now();
600
+ await this.set(sessionId, session);
601
+ }
602
+ }
603
+
604
+ async clear(): Promise<void> {
605
+ const keys = await this.redis.keys('session:*');
606
+ if (keys.length > 0) {
607
+ await this.redis.del(...keys);
608
+ }
609
+ }
610
+
611
+ async getSessionsByUser(userId: string): Promise<string[]> {
612
+ return this.redis.smembers(`user:${userId}:sessions`);
613
+ }
614
+
615
+ async deleteSessionsByUser(userId: string): Promise<void> {
616
+ const sessions = await this.getSessionsByUser(userId);
617
+ if (sessions.length > 0) {
618
+ await this.redis.del(...sessions.map(id => `session:${id}`));
619
+ await this.redis.del(`user:${userId}:sessions`);
620
+ }
621
+ }
622
+ }
623
+
624
+ // Use custom store
625
+ const redisStore = new RedisSessionStore(redisClient);
626
+ const session = createSessionMiddleware({
627
+ secret: process.env.SESSION_SECRET!,
628
+ store: redisStore,
629
+ });
630
+ ```
631
+
632
+ ### Security Best Practices
633
+
634
+ **1. Always regenerate session ID after login**
635
+
636
+ ```typescript
637
+ // loginSession() does this automatically
638
+ await loginSession(ctx.session, user);
639
+ ```
640
+
641
+ **2. Destroy sessions on logout**
642
+
643
+ ```typescript
644
+ await logoutSession(ctx.session);
645
+ ```
646
+
647
+ **3. Use strong, random secrets**
648
+
649
+ ```bash
650
+ # Generate a secure secret
651
+ openssl rand -base64 32
652
+ ```
653
+
654
+ **4. Enable secure cookies in production**
655
+
656
+ ```typescript
657
+ cookie: {
658
+ secure: process.env.NODE_ENV === 'production',
659
+ httpOnly: true,
660
+ sameSite: 'strict', // or 'lax'
661
+ }
662
+ ```
663
+
664
+ **5. Set absolute timeout for sensitive operations**
665
+
666
+ ```typescript
667
+ expiration: {
668
+ absoluteTimeout: 3600, // Force re-auth after 1 hour
669
+ }
670
+ ```
671
+
672
+ **6. Use environment variables for secrets**
673
+
674
+ ```typescript
675
+ // NEVER hardcode secrets
676
+ secret: process.env.SESSION_SECRET!,
677
+ ```
678
+
679
+ **7. Implement CSRF protection for state-changing operations**
680
+
681
+ See [CSRF Protection](#csrf-protection) section below.
682
+
683
+ **Built-in Security Features:**
684
+
685
+ The session implementation includes several security protections by default:
686
+
687
+ - **HMAC-SHA256 signing** - All session IDs are cryptographically signed to prevent tampering
688
+ - **Timing-safe comparison** - Session ID verification uses constant-time comparison to prevent timing attacks
689
+ - **Entropy validation** - Session IDs are validated for sufficient randomness (32 bytes, 256 bits)
690
+ - **Session fixation protection** - `loginSession()` automatically regenerates session IDs
691
+ - **SameSite enforcement** - `SameSite=none` requires `Secure` flag per RFC 6265bis
692
+
693
+ ### When to Use Sessions vs JWT
694
+
695
+ **Choose Sessions when:**
696
+ - You need immediate session revocation (logout all devices)
697
+ - Building server-rendered applications with traditional workflows
698
+ - Flash data and server-side state are important to your application
699
+ - You have infrastructure for shared session storage (Redis, etc.)
700
+
701
+ **Choose JWT when:**
702
+ - Building stateless APIs for mobile apps or microservices
703
+ - You need cross-domain authentication
704
+ - Horizontal scaling without shared state is critical
705
+ - You prefer client-side session storage
706
+
707
+ ## JWT Authentication
708
+
709
+ Coming soon in v1.1.0. For now, use session-based authentication.
710
+
711
+ ## CSRF Protection
712
+
713
+ CSRF protection is already implemented using the signed double-submit cookie pattern with timing-safe comparison and entropy validation.
714
+
715
+ ```typescript
716
+ import { createCsrfMiddleware } from '@veloxts/auth';
717
+
718
+ const csrf = createCsrfMiddleware({
719
+ secret: process.env.CSRF_SECRET!,
720
+ });
721
+
722
+ // Use in procedures that modify state
723
+ const deletePost = procedure
724
+ .use(csrf.middleware())
725
+ .mutation(async ({ ctx }) => {
726
+ // CSRF token validated automatically
727
+ });
728
+ ```
729
+
730
+ See the CSRF documentation for complete details on configuration and usage.
731
+
732
+ ## Guards and Policies
733
+
734
+ Guards and policies provide declarative authorization for procedures.
735
+
736
+ ```typescript
737
+ import { authenticated, hasRole, definePolicy } from '@veloxts/auth';
738
+
739
+ // Use built-in guards
740
+ const adminOnly = procedure
741
+ .use(session.requireAuth())
742
+ .use(guard(hasRole('admin')))
743
+ .query(async ({ ctx }) => {
744
+ // Only admins can access
745
+ });
746
+
747
+ // Define custom policies
748
+ const postPolicy = definePolicy<{ postId: string }>('post', {
749
+ view: async (user, { postId }) => {
750
+ // Anyone can view public posts
751
+ return true;
752
+ },
753
+ edit: async (user, { postId }) => {
754
+ const post = await db.post.findUnique({ where: { id: postId } });
755
+ return post?.authorId === user.id;
756
+ },
757
+ });
758
+ ```
759
+
760
+ ## Password Hashing
761
+
762
+ Secure password hashing with bcrypt:
763
+
764
+ ```typescript
765
+ import { hashPassword, verifyPassword } from '@veloxts/auth';
35
766
 
36
- // Placeholder - returns stub implementation
37
- const auth = createAuth({ secret: 'your-secret' });
767
+ // Hash password
768
+ const hash = await hashPassword('user-password', { cost: 12 });
38
769
 
39
- // Placeholder decorator
40
- @guard(['admin'])
41
- class AdminProcedures { }
770
+ // Verify password
771
+ const valid = await verifyPassword('user-password', hash);
42
772
  ```
43
773
 
44
- ## Roadmap
774
+ ## Rate Limiting
45
775
 
46
- | Version | Features |
47
- |---------|----------|
48
- | v0.1.0 | Placeholder with type definitions |
49
- | v1.1.0 | JWT authentication, session management |
50
- | v1.2.0 | Guards, policies, RBAC |
51
- | v1.3.0 | OAuth providers |
776
+ Coming soon.
52
777
 
53
778
  ## Related Packages
54
779