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/readme.md
ADDED
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# 🔐 Astro SessionKit
|
|
2
|
+
|
|
3
|
+
Simple session access and route protection for Astro applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Simple API** - Access session data anywhere in your app
|
|
8
|
+
- 🛡️ **Route Protection** - Declarative route guards with roles/permissions
|
|
9
|
+
- 🚀 **Type Safe** - Full TypeScript support
|
|
10
|
+
- 🎯 **Flexible** - Works with any session storage (cookies, Redis, DB, etc.)
|
|
11
|
+
- ⚡ **Fast** - Uses AsyncLocalStorage for zero-overhead access
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install astro-sessionkit
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Configure the Integration
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// astro.config.mjs
|
|
25
|
+
import { defineConfig } from 'astro/config';
|
|
26
|
+
import sessionkit from 'astro-sessionkit';
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
integrations: [
|
|
30
|
+
sessionkit({
|
|
31
|
+
loginPath: '/login',
|
|
32
|
+
protect: [
|
|
33
|
+
// Protect admin routes
|
|
34
|
+
{ pattern: '/admin/**', role: 'admin' },
|
|
35
|
+
|
|
36
|
+
// Protect dashboard for authenticated users
|
|
37
|
+
{ pattern: '/dashboard', roles: ['user', 'admin'] },
|
|
38
|
+
|
|
39
|
+
// Protect by permission
|
|
40
|
+
{ pattern: '/settings', permission: 'settings:write' },
|
|
41
|
+
|
|
42
|
+
// Custom logic
|
|
43
|
+
{
|
|
44
|
+
pattern: '/premium/**',
|
|
45
|
+
allow: (session) => session?.subscription === 'premium'
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Set Up Your Session
|
|
54
|
+
|
|
55
|
+
SessionKit reads from `context.session.get('__session__')`. You set it up in your middleware:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
// src/middleware.ts
|
|
59
|
+
import { defineMiddleware } from 'astro:middleware';
|
|
60
|
+
|
|
61
|
+
export const onRequest = defineMiddleware(async (context, next) => {
|
|
62
|
+
// Get session from wherever you store it
|
|
63
|
+
// (cookies, Redis, database, etc.)
|
|
64
|
+
const sessionId = context.cookies.get('session_id')?.value;
|
|
65
|
+
|
|
66
|
+
if (sessionId) {
|
|
67
|
+
const user = await db.getUserBySessionId(sessionId);
|
|
68
|
+
|
|
69
|
+
// Set session for SessionKit to read
|
|
70
|
+
context.session.set('__session__', {
|
|
71
|
+
userId: user.id,
|
|
72
|
+
email: user.email,
|
|
73
|
+
role: user.role,
|
|
74
|
+
permissions: user.permissions
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return next();
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Register Sessions After Authentication
|
|
83
|
+
|
|
84
|
+
Use the provided helpers to register sessions after successful authentication:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// src/pages/api/login.ts
|
|
88
|
+
import type { APIRoute } from 'astro';
|
|
89
|
+
import { setSession } from 'astro-sessionkit/server';
|
|
90
|
+
|
|
91
|
+
export const POST: APIRoute = async (context) => {
|
|
92
|
+
const { email, password } = await context.request.json();
|
|
93
|
+
|
|
94
|
+
// Verify credentials (YOUR authentication logic)
|
|
95
|
+
const user = await verifyCredentials(email, password);
|
|
96
|
+
|
|
97
|
+
if (user) {
|
|
98
|
+
// Register session with SessionKit
|
|
99
|
+
setSession(context, {
|
|
100
|
+
userId: user.id,
|
|
101
|
+
email: user.email,
|
|
102
|
+
role: user.role,
|
|
103
|
+
permissions: user.permissions
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Store session ID (YOUR storage logic)
|
|
107
|
+
const sessionId = await createSessionInDatabase(user.id);
|
|
108
|
+
context.cookies.set('session_id', sessionId, {
|
|
109
|
+
httpOnly: true,
|
|
110
|
+
secure: true,
|
|
111
|
+
sameSite: 'lax',
|
|
112
|
+
maxAge: 60 * 60 * 24 * 7 // 7 days
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return new Response(JSON.stringify({ success: true }));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return new Response(JSON.stringify({ error: 'Invalid credentials' }), {
|
|
119
|
+
status: 401
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. Use Session in Your Pages
|
|
125
|
+
|
|
126
|
+
```astro
|
|
127
|
+
---
|
|
128
|
+
// src/pages/dashboard.astro
|
|
129
|
+
import { getSession, requireSession } from 'astro-sessionkit/server';
|
|
130
|
+
|
|
131
|
+
// Get session (returns null if not authenticated)
|
|
132
|
+
const session = getSession();
|
|
133
|
+
|
|
134
|
+
// Or require authentication (throws 401 if not authenticated)
|
|
135
|
+
const session = requireSession();
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
<h1>Welcome, {session.email}</h1>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## API Reference
|
|
142
|
+
|
|
143
|
+
### Session Management Functions
|
|
144
|
+
|
|
145
|
+
#### `setSession(context, session)`
|
|
146
|
+
|
|
147
|
+
Register a session after successful authentication.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
- `context: APIContext` - Astro API context
|
|
151
|
+
- `session: Session` - Session data to register
|
|
152
|
+
|
|
153
|
+
**Throws:** Error if session structure is invalid
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { setSession } from 'astro-sessionkit/server';
|
|
157
|
+
|
|
158
|
+
export const POST: APIRoute = async (context) => {
|
|
159
|
+
const user = await authenticateUser(credentials);
|
|
160
|
+
|
|
161
|
+
setSession(context, {
|
|
162
|
+
userId: user.id,
|
|
163
|
+
email: user.email,
|
|
164
|
+
role: user.role,
|
|
165
|
+
permissions: user.permissions
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Also store in cookies/database
|
|
169
|
+
context.cookies.set('session_id', sessionId);
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
#### `clearSession(context)`
|
|
174
|
+
|
|
175
|
+
Clear the session during logout.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { clearSession } from 'astro-sessionkit/server';
|
|
179
|
+
|
|
180
|
+
export const POST: APIRoute = async (context) => {
|
|
181
|
+
clearSession(context);
|
|
182
|
+
|
|
183
|
+
// Also delete from cookies/database
|
|
184
|
+
context.cookies.delete('session_id');
|
|
185
|
+
await db.deleteSession(sessionId);
|
|
186
|
+
|
|
187
|
+
return context.redirect('/');
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### `updateSession(context, updates)`
|
|
192
|
+
|
|
193
|
+
Update specific fields in the current session.
|
|
194
|
+
|
|
195
|
+
**Parameters:**
|
|
196
|
+
- `context: APIContext` - Astro API context
|
|
197
|
+
- `updates: Partial<Session>` - Fields to update
|
|
198
|
+
|
|
199
|
+
**Throws:** Error if no session exists or updated session is invalid
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
import { updateSession } from 'astro-sessionkit/server';
|
|
203
|
+
|
|
204
|
+
export const POST: APIRoute = async (context) => {
|
|
205
|
+
// Update user's role
|
|
206
|
+
updateSession(context, {
|
|
207
|
+
role: 'admin',
|
|
208
|
+
permissions: ['admin:read', 'admin:write']
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Also update in your storage
|
|
212
|
+
await db.updateSession(sessionId, updates);
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Session Access Functions
|
|
217
|
+
|
|
218
|
+
All functions are imported from `astro-sessionkit/server`:
|
|
219
|
+
|
|
220
|
+
#### `getSession()`
|
|
221
|
+
|
|
222
|
+
Get the current session (returns `null` if not authenticated).
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import { getSession } from 'astro-sessionkit/server';
|
|
226
|
+
|
|
227
|
+
const session = getSession();
|
|
228
|
+
if (session) {
|
|
229
|
+
console.log('User ID:', session.userId);
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
#### `requireSession()`
|
|
234
|
+
|
|
235
|
+
Get the current session or throw 401 if not authenticated.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { requireSession } from 'astro-sessionkit/server';
|
|
239
|
+
|
|
240
|
+
const session = requireSession();
|
|
241
|
+
// TypeScript knows session is not null here
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### `isAuthenticated()`
|
|
245
|
+
|
|
246
|
+
Check if the user is authenticated.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { isAuthenticated } from 'astro-sessionkit/server';
|
|
250
|
+
|
|
251
|
+
if (isAuthenticated()) {
|
|
252
|
+
// User is logged in
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
#### `hasRole(role: string)`
|
|
257
|
+
|
|
258
|
+
Check if user has a specific role.
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { hasRole } from 'astro-sessionkit/server';
|
|
262
|
+
|
|
263
|
+
if (hasRole('admin')) {
|
|
264
|
+
// User is an admin
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
#### `hasPermission(permission: string)`
|
|
269
|
+
|
|
270
|
+
Check if user has a specific permission.
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
import { hasPermission } from 'astro-sessionkit/server';
|
|
274
|
+
|
|
275
|
+
if (hasPermission('posts:delete')) {
|
|
276
|
+
// User can delete posts
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### `hasAllPermissions(...permissions: string[])`
|
|
281
|
+
|
|
282
|
+
Check if user has ALL specified permissions.
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
import { hasAllPermissions } from 'astro-sessionkit/server';
|
|
286
|
+
|
|
287
|
+
if (hasAllPermissions('posts:read', 'posts:write')) {
|
|
288
|
+
// User has both permissions
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### `hasAnyPermission(...permissions: string[])`
|
|
293
|
+
|
|
294
|
+
Check if user has ANY of the specified permissions.
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
import { hasAnyPermission } from 'astro-sessionkit/server';
|
|
298
|
+
|
|
299
|
+
if (hasAnyPermission('posts:delete', 'admin:panel')) {
|
|
300
|
+
// User has at least one permission
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Route Protection
|
|
305
|
+
|
|
306
|
+
### Protection Rules
|
|
307
|
+
|
|
308
|
+
#### By Role
|
|
309
|
+
|
|
310
|
+
Require a specific role:
|
|
311
|
+
|
|
312
|
+
```ts
|
|
313
|
+
{ pattern: '/admin/**', role: 'admin' }
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### By Multiple Roles
|
|
317
|
+
|
|
318
|
+
User must have ONE of these roles:
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
{ pattern: '/dashboard', roles: ['user', 'admin', 'moderator'] }
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### By Permission
|
|
325
|
+
|
|
326
|
+
Require a specific permission:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
{ pattern: '/settings', permission: 'settings:write' }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
#### By Multiple Permissions
|
|
333
|
+
|
|
334
|
+
User must have ALL of these permissions:
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
{ pattern: '/admin/users', permissions: ['users:read', 'users:write'] }
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
#### Custom Logic
|
|
341
|
+
|
|
342
|
+
Use a custom function for complex logic:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
{
|
|
346
|
+
pattern: '/premium/**',
|
|
347
|
+
allow: (session) => {
|
|
348
|
+
return session?.subscription === 'premium' && !session?.banned;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Custom Redirect
|
|
354
|
+
|
|
355
|
+
Override the default login path per rule:
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
{
|
|
359
|
+
pattern: '/admin/**',
|
|
360
|
+
role: 'admin',
|
|
361
|
+
redirectTo: '/unauthorized'
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Pattern Matching
|
|
366
|
+
|
|
367
|
+
Patterns support glob syntax:
|
|
368
|
+
|
|
369
|
+
- `/admin` - Exact match
|
|
370
|
+
- `/admin/*` - One or more segments (`/admin/users`, `/admin/users/123`)
|
|
371
|
+
- `/admin/**` - Any path under admin (`/admin`, `/admin/users`, `/admin/x/y/z`)
|
|
372
|
+
|
|
373
|
+
## Session Type
|
|
374
|
+
|
|
375
|
+
The session object must have this shape:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
interface Session {
|
|
379
|
+
userId: string; // Required
|
|
380
|
+
email?: string;
|
|
381
|
+
role?: string;
|
|
382
|
+
roles?: string[];
|
|
383
|
+
permissions?: string[];
|
|
384
|
+
[key: string]: unknown; // Add any custom fields
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
You control what goes in the session - SessionKit just reads it.
|
|
389
|
+
|
|
390
|
+
## Advanced Configuration
|
|
391
|
+
|
|
392
|
+
### Custom Access Hooks
|
|
393
|
+
|
|
394
|
+
Override how roles/permissions are extracted:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
sessionkit({
|
|
398
|
+
access: {
|
|
399
|
+
// Custom role extraction
|
|
400
|
+
getRole: (session) => session?.primaryRole ?? null,
|
|
401
|
+
|
|
402
|
+
// Custom permissions extraction
|
|
403
|
+
getPermissions: (session) => {
|
|
404
|
+
return [...session?.permissions ?? [], ...session?.dynamicPerms ?? []];
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
// Override all built-in checks
|
|
408
|
+
check: (rule, session) => {
|
|
409
|
+
// Your custom logic
|
|
410
|
+
return session?.customField === 'allowed';
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
})
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Logout Flow
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
// src/pages/api/logout.ts
|
|
420
|
+
import type { APIRoute } from 'astro';
|
|
421
|
+
import { clearSession } from 'astro-sessionkit/server';
|
|
422
|
+
|
|
423
|
+
export const POST: APIRoute = async (context) => {
|
|
424
|
+
const sessionId = context.cookies.get('session_id')?.value;
|
|
425
|
+
|
|
426
|
+
// Clear from SessionKit
|
|
427
|
+
clearSession(context);
|
|
428
|
+
|
|
429
|
+
// Delete from storage
|
|
430
|
+
if (sessionId) {
|
|
431
|
+
await db.deleteSession(sessionId);
|
|
432
|
+
}
|
|
433
|
+
context.cookies.delete('session_id');
|
|
434
|
+
|
|
435
|
+
return context.redirect('/login');
|
|
436
|
+
};
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Update Session Data
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
// src/pages/api/update-role.ts
|
|
443
|
+
import type { APIRoute } from 'astro';
|
|
444
|
+
import { updateSession } from 'astro-sessionkit/server';
|
|
445
|
+
|
|
446
|
+
export const POST: APIRoute = async (context) => {
|
|
447
|
+
const { newRole } = await context.request.json();
|
|
448
|
+
|
|
449
|
+
// Update in SessionKit
|
|
450
|
+
updateSession(context, { role: newRole });
|
|
451
|
+
|
|
452
|
+
// Also update in your database
|
|
453
|
+
const sessionId = context.cookies.get('session_id')?.value;
|
|
454
|
+
await db.updateUserRole(sessionId, newRole);
|
|
455
|
+
|
|
456
|
+
return new Response(JSON.stringify({ success: true }));
|
|
457
|
+
};
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Examples
|
|
461
|
+
|
|
462
|
+
### Complete Authentication Flow
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
// src/pages/api/auth/login.ts
|
|
466
|
+
import type { APIRoute } from 'astro';
|
|
467
|
+
import { setSession } from 'astro-sessionkit/server';
|
|
468
|
+
import { hashPassword, generateSessionId } from './utils';
|
|
469
|
+
|
|
470
|
+
export const POST: APIRoute = async (context) => {
|
|
471
|
+
const { email, password } = await context.request.json();
|
|
472
|
+
|
|
473
|
+
// 1. Verify credentials
|
|
474
|
+
const user = await db.findUserByEmail(email);
|
|
475
|
+
if (!user || !await hashPassword.verify(password, user.hashedPassword)) {
|
|
476
|
+
return new Response(JSON.stringify({ error: 'Invalid credentials' }), {
|
|
477
|
+
status: 401
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// 2. Create session ID
|
|
482
|
+
const sessionId = generateSessionId();
|
|
483
|
+
await db.createSession({
|
|
484
|
+
id: sessionId,
|
|
485
|
+
userId: user.id,
|
|
486
|
+
expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// 3. Register with SessionKit
|
|
490
|
+
setSession(context, {
|
|
491
|
+
userId: user.id,
|
|
492
|
+
email: user.email,
|
|
493
|
+
role: user.role,
|
|
494
|
+
permissions: user.permissions
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// 4. Set secure cookie
|
|
498
|
+
context.cookies.set('session_id', sessionId, {
|
|
499
|
+
httpOnly: true,
|
|
500
|
+
secure: import.meta.env.PROD,
|
|
501
|
+
sameSite: 'lax',
|
|
502
|
+
maxAge: 60 * 60 * 24 * 7, // 7 days
|
|
503
|
+
path: '/'
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
return new Response(JSON.stringify({
|
|
507
|
+
success: true,
|
|
508
|
+
user: { email: user.email, role: user.role }
|
|
509
|
+
}));
|
|
510
|
+
};
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
## Examples
|
|
514
|
+
|
|
515
|
+
### Protect Multiple Route Patterns
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
sessionkit({
|
|
519
|
+
protect: [
|
|
520
|
+
// Admin section
|
|
521
|
+
{ pattern: '/admin/**', role: 'admin', redirectTo: '/unauthorized' },
|
|
522
|
+
|
|
523
|
+
// User dashboard
|
|
524
|
+
{ pattern: '/dashboard', roles: ['user', 'admin'] },
|
|
525
|
+
|
|
526
|
+
// Settings require specific permission
|
|
527
|
+
{ pattern: '/settings/**', permission: 'settings:access' },
|
|
528
|
+
|
|
529
|
+
// Premium content
|
|
530
|
+
{
|
|
531
|
+
pattern: '/premium/**',
|
|
532
|
+
allow: (session) => session?.tier === 'premium'
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
})
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Conditional Rendering
|
|
539
|
+
|
|
540
|
+
```astro
|
|
541
|
+
---
|
|
542
|
+
import { hasRole, hasPermission } from 'astro-sessionkit/server';
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
{hasRole('admin') && (
|
|
546
|
+
<a href="/admin">Admin Panel</a>
|
|
547
|
+
)}
|
|
548
|
+
|
|
549
|
+
{hasPermission('posts:create') && (
|
|
550
|
+
<button>Create Post</button>
|
|
551
|
+
)}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### API Route Protection
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
// src/pages/api/admin.ts
|
|
558
|
+
import type { APIRoute } from 'astro';
|
|
559
|
+
import { requireSession, hasRole } from 'astro-sessionkit/server';
|
|
560
|
+
|
|
561
|
+
export const GET: APIRoute = async () => {
|
|
562
|
+
const session = requireSession();
|
|
563
|
+
|
|
564
|
+
if (!hasRole('admin')) {
|
|
565
|
+
return new Response('Forbidden', { status: 403 });
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Admin logic here
|
|
569
|
+
return new Response(JSON.stringify({ data: 'secret' }));
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## How It Works
|
|
574
|
+
|
|
575
|
+
1. **You set the session** in `context.locals.session` via your own middleware
|
|
576
|
+
2. **SessionKit reads it** and makes it available via AsyncLocalStorage
|
|
577
|
+
3. **Route guards** automatically protect paths based on your rules
|
|
578
|
+
4. **Helper functions** provide easy access throughout your app
|
|
579
|
+
|
|
580
|
+
## Security
|
|
581
|
+
|
|
582
|
+
⚠️ **Important**: SessionKit handles session access and route protection, but **does NOT handle**:
|
|
583
|
+
- Session creation/storage
|
|
584
|
+
- Authentication
|
|
585
|
+
- Session expiration
|
|
586
|
+
- CSRF protection
|
|
587
|
+
|
|
588
|
+
These are your responsibility. See [SECURITY.md](./SECURITY.md) for a complete security guide.
|
|
589
|
+
|
|
590
|
+
### Quick Security Checklist
|
|
591
|
+
|
|
592
|
+
Before production:
|
|
593
|
+
- ✅ Encrypt/sign your sessions (use lucia-auth, @auth/astro, or iron-session)
|
|
594
|
+
- ✅ Set secure cookie flags (HttpOnly, Secure, SameSite)
|
|
595
|
+
- ✅ Implement session expiration
|
|
596
|
+
- ✅ Add CSRF protection for state-changing operations
|
|
597
|
+
- ✅ Use HTTPS in production
|
|
598
|
+
|
|
599
|
+
## License
|
|
600
|
+
|
|
601
|
+
MIT License © Alex Mora
|