astro-sessionkit 0.1.2

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 (52) hide show
  1. package/LICENSE +21 -0
  2. package/dist/core/config.d.ts +9 -0
  3. package/dist/core/config.d.ts.map +1 -0
  4. package/dist/core/config.js +43 -0
  5. package/dist/core/config.js.map +1 -0
  6. package/dist/core/context.d.ts +7 -0
  7. package/dist/core/context.d.ts.map +1 -0
  8. package/dist/core/context.js +11 -0
  9. package/dist/core/context.js.map +1 -0
  10. package/dist/core/guardMiddleware.d.ts +3 -0
  11. package/dist/core/guardMiddleware.d.ts.map +1 -0
  12. package/dist/core/guardMiddleware.js +56 -0
  13. package/dist/core/guardMiddleware.js.map +1 -0
  14. package/dist/core/matcher.d.ts +2 -0
  15. package/dist/core/matcher.d.ts.map +1 -0
  16. package/dist/core/matcher.js +38 -0
  17. package/dist/core/matcher.js.map +1 -0
  18. package/dist/core/sessionMiddleware.d.ts +3 -0
  19. package/dist/core/sessionMiddleware.d.ts.map +1 -0
  20. package/dist/core/sessionMiddleware.js +23 -0
  21. package/dist/core/sessionMiddleware.js.map +1 -0
  22. package/dist/core/types.d.ts +40 -0
  23. package/dist/core/types.d.ts.map +1 -0
  24. package/dist/core/types.js +2 -0
  25. package/dist/core/types.js.map +1 -0
  26. package/dist/core/validation.d.ts +5 -0
  27. package/dist/core/validation.d.ts.map +1 -0
  28. package/dist/core/validation.js +91 -0
  29. package/dist/core/validation.js.map +1 -0
  30. package/dist/guard.d.ts +2 -0
  31. package/dist/guard.d.ts.map +1 -0
  32. package/dist/guard.js +5 -0
  33. package/dist/guard.js.map +1 -0
  34. package/dist/index.d.ts +6 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +25 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/integration.d.ts +5 -0
  39. package/dist/integration.d.ts.map +1 -0
  40. package/dist/integration.js +22 -0
  41. package/dist/integration.js.map +1 -0
  42. package/dist/middleware.d.ts +2 -0
  43. package/dist/middleware.d.ts.map +1 -0
  44. package/dist/middleware.js +1 -0
  45. package/dist/middleware.js.map +1 -0
  46. package/dist/server.d.ts +13 -0
  47. package/dist/server.d.ts.map +1 -0
  48. package/dist/server.js +67 -0
  49. package/dist/server.js.map +1 -0
  50. package/package.json +89 -0
  51. package/readme.md +601 -0
  52. package/security.md +349 -0
package/security.md ADDED
@@ -0,0 +1,349 @@
1
+ # Security Guide
2
+
3
+ ## Overview
4
+
5
+ **SessionKit** is a session access and route protection library. It does **NOT** handle:
6
+ - Session creation/authentication (but provides helpers to register sessions)
7
+ - Session storage (cookies, Redis, database)
8
+ - Session expiration checking
9
+ - CSRF protection
10
+
11
+ SessionKit **DOES** provide:
12
+ - ✅ `setSession()` - Register session after authentication
13
+ - ✅ `clearSession()` - Clear session during logout
14
+ - ✅ `updateSession()` - Update session data
15
+ - ✅ Session access helpers throughout your app
16
+ - ✅ Route protection based on roles/permissions
17
+
18
+ These are **your responsibility** as the developer. This guide explains what you must implement.
19
+
20
+ ## What SessionKit Does
21
+
22
+ ✅ **Session structure validation** - Prevents crashes from malformed data
23
+ ✅ **DoS protection** - Limits array sizes and pattern complexity
24
+ ✅ **Safe session access** - AsyncLocalStorage-based session context
25
+ ✅ **Route protection** - Declarative guards based on roles/permissions
26
+
27
+ ## What You Must Implement
28
+
29
+ ### 1. 🔒 Secure Session Storage
30
+
31
+ SessionKit provides `setSession()` to register sessions, but **you** must store them securely.
32
+
33
+ #### ❌ NEVER do this (Plain cookies - easily tampered!)
34
+
35
+ ```ts
36
+ // INSECURE - Anyone can modify this!
37
+ import { setSession } from 'astro-sessionkit/server';
38
+
39
+ export const POST: APIRoute = async (context) => {
40
+ const user = await authenticateUser(credentials);
41
+
42
+ // Register with SessionKit
43
+ setSession(context, { userId: user.id, role: user.role });
44
+
45
+ // DANGEROUS - Plain cookie, no encryption!
46
+ context.cookies.set('session', JSON.stringify({
47
+ userId: user.id,
48
+ role: 'admin'
49
+ }));
50
+ };
51
+ ```
52
+
53
+ #### ✅ DO this (Signed/encrypted sessions)
54
+
55
+ ```ts
56
+ // Use a library like iron-session, lucia-auth, or @auth/astro
57
+ import { setSession } from 'astro-sessionkit/server';
58
+ import { encrypt } from 'iron-session';
59
+
60
+ export const POST: APIRoute = async (context) => {
61
+ const user = await authenticateUser(credentials);
62
+
63
+ // 1. Register with SessionKit
64
+ setSession(context, {
65
+ userId: user.id,
66
+ email: user.email,
67
+ role: user.role,
68
+ permissions: user.permissions
69
+ });
70
+
71
+ // 2. Store encrypted session ID
72
+ const sessionId = crypto.randomUUID();
73
+ await db.createSession({
74
+ id: sessionId,
75
+ userId: user.id,
76
+ expiresAt: Date.now() + 3600000 // 1 hour
77
+ });
78
+
79
+ // 3. Set secure cookie
80
+ const encryptedId = await encrypt(sessionId, {
81
+ password: process.env.SESSION_SECRET!,
82
+ ttl: 3600
83
+ });
84
+
85
+ context.cookies.set('session_id', encryptedId, {
86
+ httpOnly: true, // Prevent JavaScript access
87
+ secure: true, // HTTPS only
88
+ sameSite: 'lax', // CSRF protection
89
+ maxAge: 3600, // 1 hour
90
+ path: '/'
91
+ });
92
+ };
93
+ ```
94
+
95
+ #### Recommended Libraries
96
+
97
+ - **[lucia-auth](https://lucia-auth.com/)** - Modern, type-safe auth
98
+ - **[@auth/astro](https://authjs.dev/)** - Popular auth solution
99
+ - **[iron-session](https://github.com/vvo/iron-session)** - Encrypted cookies
100
+ - **[better-auth](https://www.better-auth.com/)** - Full-featured auth
101
+
102
+ ---
103
+
104
+ ### 2. ⏰ Session Expiration
105
+
106
+ SessionKit does **not** check expiration. You must implement this:
107
+
108
+ ```ts
109
+ // src/middleware.ts
110
+ import { defineMiddleware } from 'astro:middleware';
111
+ import { verifySession } from './auth'; // Your auth logic
112
+
113
+ export const onRequest = defineMiddleware(async (context, next) => {
114
+ const sessionCookie = context.cookies.get('session')?.value;
115
+
116
+ if (sessionCookie) {
117
+ try {
118
+ const session = await verifySession(sessionCookie);
119
+
120
+ // Check expiration
121
+ if (session.expiresAt && session.expiresAt < Date.now()) {
122
+ context.cookies.delete('session');
123
+ return next();
124
+ }
125
+
126
+ // Set for SessionKit to read
127
+ context.session.set('__session__', {
128
+ userId: session.userId,
129
+ email: session.email,
130
+ role: session.role,
131
+ permissions: session.permissions
132
+ });
133
+ } catch (error) {
134
+ // Invalid session - delete cookie
135
+ context.cookies.delete('session');
136
+ }
137
+ }
138
+
139
+ return next();
140
+ });
141
+ ```
142
+
143
+ ---
144
+
145
+ ### 3. 🛡️ CSRF Protection
146
+
147
+ For state-changing operations (POST, PUT, DELETE), implement CSRF tokens:
148
+
149
+ ```ts
150
+ // Generate CSRF token (in your auth middleware)
151
+ import { randomBytes } from 'crypto';
152
+
153
+ const csrfToken = randomBytes(32).toString('hex');
154
+ context.cookies.set('csrf_token', csrfToken, {
155
+ httpOnly: false, // Must be readable by JavaScript
156
+ sameSite: 'strict'
157
+ });
158
+
159
+ // Store in session for validation
160
+ context.locals.csrfToken = csrfToken;
161
+ ```
162
+
163
+ ```ts
164
+ // Validate CSRF token (in API routes)
165
+ export const POST: APIRoute = async ({ request, cookies, locals }) => {
166
+ const token = request.headers.get('x-csrf-token');
167
+ const expected = locals.csrfToken;
168
+
169
+ if (!token || token !== expected) {
170
+ return new Response('CSRF validation failed', { status: 403 });
171
+ }
172
+
173
+ // Process request...
174
+ };
175
+ ```
176
+
177
+ ```astro
178
+ <!-- Include in forms -->
179
+ <form method="POST">
180
+ <input type="hidden" name="csrf_token" value={locals.csrfToken} />
181
+ <!-- ... -->
182
+ </form>
183
+ ```
184
+
185
+ ---
186
+
187
+ ### 4. 🔄 Session Fixation Prevention
188
+
189
+ Regenerate session IDs after authentication:
190
+
191
+ ```ts
192
+ // After successful login
193
+ export const POST: APIRoute = async ({ request, cookies }) => {
194
+ const { email, password } = await request.json();
195
+
196
+ // Verify credentials
197
+ const user = await verifyCredentials(email, password);
198
+
199
+ if (user) {
200
+ // Delete old session if it exists
201
+ const oldSession = cookies.get('session')?.value;
202
+ if (oldSession) {
203
+ await deleteSession(oldSession); // Clean up server-side
204
+ }
205
+
206
+ // Generate NEW session ID
207
+ const newSessionId = crypto.randomUUID();
208
+ const session = await createSession(newSessionId, user.id);
209
+
210
+ // Set new cookie
211
+ cookies.set('session', await encryptSession(session), {
212
+ httpOnly: true,
213
+ secure: true,
214
+ sameSite: 'lax'
215
+ });
216
+ }
217
+ };
218
+ ```
219
+
220
+ ---
221
+
222
+ ### 5. 🚦 Rate Limiting
223
+
224
+ Protect authentication endpoints from brute force:
225
+
226
+ ```ts
227
+ import { RateLimiter } from 'rate-limiter-flexible';
228
+ import { Redis } from 'ioredis';
229
+
230
+ const redis = new Redis();
231
+ const limiter = new RateLimiterRedis({
232
+ storeClient: redis,
233
+ points: 5, // 5 attempts
234
+ duration: 900, // per 15 minutes
235
+ blockDuration: 900 // block for 15 minutes after
236
+ });
237
+
238
+ export const POST: APIRoute = async ({ request, clientAddress }) => {
239
+ try {
240
+ // Check rate limit
241
+ await limiter.consume(clientAddress);
242
+ } catch {
243
+ return new Response('Too many login attempts', {
244
+ status: 429,
245
+ headers: { 'Retry-After': '900' }
246
+ });
247
+ }
248
+
249
+ // Process login...
250
+ };
251
+ ```
252
+
253
+ ---
254
+
255
+ ### 6. 🧹 Input Sanitization
256
+
257
+ Never trust session data in HTML contexts:
258
+
259
+ ```astro
260
+ ---
261
+ import { getSession } from 'astro-sessionkit/server';
262
+
263
+ const session = getSession();
264
+ ---
265
+
266
+ <!-- Astro automatically escapes variables -->
267
+ <p>Welcome, {session?.email}</p> <!-- ✅ Safe -->
268
+
269
+ <!-- Be careful with set:html -->
270
+ <div set:html={session?.bio}></div> <!-- ❌ Dangerous if bio contains HTML -->
271
+
272
+ <!-- Sanitize user-generated HTML -->
273
+ <div set:html={sanitizeHtml(session?.bio)}></div> <!-- ✅ Safe -->
274
+ ```
275
+
276
+ ---
277
+
278
+ ## Security Checklist
279
+
280
+ Before deploying to production:
281
+
282
+ - [ ] **Sessions are encrypted/signed** (using iron-session, lucia, etc.)
283
+ - [ ] **Cookies have security flags** (HttpOnly, Secure, SameSite)
284
+ - [ ] **Session expiration is enforced** (both client and server)
285
+ - [ ] **CSRF protection** on all state-changing operations
286
+ - [ ] **Session IDs regenerated** after login/logout
287
+ - [ ] **Rate limiting** on authentication endpoints
288
+ - [ ] **HTTPS enforced** in production
289
+ - [ ] **Security headers configured**:
290
+ ```ts
291
+ headers: {
292
+ 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
293
+ 'X-Content-Type-Options': 'nosniff',
294
+ 'X-Frame-Options': 'DENY',
295
+ 'X-XSS-Protection': '1; mode=block',
296
+ 'Content-Security-Policy': "default-src 'self'"
297
+ }
298
+ ```
299
+ - [ ] **Password hashing** with bcrypt/argon2 (min 10 rounds)
300
+ - [ ] **Audit logging** for authentication events
301
+ - [ ] **Regular security updates** for dependencies
302
+
303
+ ---
304
+
305
+ ## Common Vulnerabilities to Avoid
306
+
307
+ ### ❌ Plain Text Sessions
308
+ ```ts
309
+ // NEVER store sensitive data in plain cookies
310
+ cookies.set('user', JSON.stringify({ role: 'admin' }));
311
+ ```
312
+
313
+ ### ❌ Trusting Client Data
314
+ ```ts
315
+ // NEVER trust data from forms/headers without validation
316
+ const role = request.headers.get('x-user-role'); // ❌ Attacker controlled!
317
+ ```
318
+
319
+ ### ❌ No Session Timeout
320
+ ```ts
321
+ // Sessions should expire!
322
+ cookies.set('session', token); // ❌ No maxAge = lives forever
323
+ ```
324
+
325
+ ### ❌ Weak Secrets
326
+ ```ts
327
+ // NEVER use weak or hardcoded secrets
328
+ const SECRET = 'password123'; // ❌ Use crypto.randomBytes(32)
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Questions?
334
+
335
+ If you're unsure about any security aspect:
336
+
337
+ 1. **Read the documentation** for your auth library
338
+ 2. **Use established libraries** instead of rolling your own
339
+ 3. **Consult OWASP** guidelines: https://owasp.org/
340
+ 4. **Get a security audit** before handling sensitive data
341
+
342
+ ---
343
+
344
+ ## Reporting Security Issues
345
+
346
+ If you discover a security vulnerability in SessionKit itself, please email:
347
+ **oa.mora [at] hotmail [dot] com** (🔒 Do not open public issues)
348
+
349
+ We'll respond within 48 hours and work with you on a fix.