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.
- package/LICENSE +21 -0
- package/dist/core/config.d.ts +9 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +43 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/context.d.ts +7 -0
- package/dist/core/context.d.ts.map +1 -0
- package/dist/core/context.js +11 -0
- package/dist/core/context.js.map +1 -0
- package/dist/core/guardMiddleware.d.ts +3 -0
- package/dist/core/guardMiddleware.d.ts.map +1 -0
- package/dist/core/guardMiddleware.js +56 -0
- package/dist/core/guardMiddleware.js.map +1 -0
- package/dist/core/matcher.d.ts +2 -0
- package/dist/core/matcher.d.ts.map +1 -0
- package/dist/core/matcher.js +38 -0
- package/dist/core/matcher.js.map +1 -0
- package/dist/core/sessionMiddleware.d.ts +3 -0
- package/dist/core/sessionMiddleware.d.ts.map +1 -0
- package/dist/core/sessionMiddleware.js +23 -0
- package/dist/core/sessionMiddleware.js.map +1 -0
- package/dist/core/types.d.ts +40 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validation.d.ts +5 -0
- package/dist/core/validation.d.ts.map +1 -0
- package/dist/core/validation.js +91 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/guard.d.ts +2 -0
- package/dist/guard.d.ts.map +1 -0
- package/dist/guard.js +5 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +5 -0
- package/dist/integration.d.ts.map +1 -0
- package/dist/integration.js +22 -0
- package/dist/integration.js.map +1 -0
- package/dist/middleware.d.ts +2 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +1 -0
- package/dist/middleware.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/package.json +89 -0
- package/readme.md +601 -0
- 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.
|