astro-sessionkit 0.1.15 → 0.1.16
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/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +27 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/context.js.map +1 -1
- package/dist/core/guardMiddleware.d.ts.map +1 -1
- package/dist/core/guardMiddleware.js +3 -2
- package/dist/core/guardMiddleware.js.map +1 -1
- package/dist/core/logger.js +6 -15
- package/dist/core/logger.js.map +1 -1
- package/dist/core/matcher.js.map +1 -1
- package/dist/core/sessionMiddleware.d.ts.map +1 -1
- package/dist/core/sessionMiddleware.js +4 -4
- package/dist/core/sessionMiddleware.js.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/core/validation.js.map +1 -1
- package/dist/guard.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integration.js.map +1 -1
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
- package/readme.md +40 -1
- package/src/core/config.ts +153 -0
- package/src/core/context.ts +30 -0
- package/src/core/guardMiddleware.ts +131 -0
- package/src/core/logger.ts +31 -0
- package/src/core/matcher.ts +61 -0
- package/src/core/sessionMiddleware.ts +65 -0
- package/src/core/types.ts +147 -0
- package/src/core/validation.ts +137 -0
- package/src/guard.ts +7 -0
- package/src/index.ts +78 -0
- package/src/integration.ts +58 -0
- package/src/middleware.ts +5 -0
- package/src/server.ts +245 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Astro Integration
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import type { AstroIntegration } from "astro";
|
|
6
|
+
import { setConfig } from "./core/config";
|
|
7
|
+
import type { SessionKitConfig } from "./core/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* SessionKit - Simple session access and route protection for Astro
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // astro.config.mjs
|
|
15
|
+
* import sessionkit from 'astro-sessionkit';
|
|
16
|
+
*
|
|
17
|
+
* export default defineConfig({
|
|
18
|
+
* integrations: [
|
|
19
|
+
* sessionkit({
|
|
20
|
+
* loginPath: '/login',
|
|
21
|
+
* protect: [
|
|
22
|
+
* { pattern: '/admin/**', role: 'admin' },
|
|
23
|
+
* { pattern: '/dashboard', roles: ['user', 'admin'] },
|
|
24
|
+
* { pattern: '/settings', permissions: ['settings:write'] }
|
|
25
|
+
* ]
|
|
26
|
+
* })
|
|
27
|
+
* ]
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export default function sessionKit(config: SessionKitConfig = {}): AstroIntegration {
|
|
32
|
+
// Store configuration
|
|
33
|
+
setConfig(config);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
name: "astro-sessionkit",
|
|
37
|
+
hooks: {
|
|
38
|
+
"astro:config:setup": ({ addMiddleware }) => {
|
|
39
|
+
// 1. Always add session context middleware first
|
|
40
|
+
addMiddleware({
|
|
41
|
+
entrypoint: "astro-sessionkit/middleware",
|
|
42
|
+
order: "pre",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 2. Add route guard if there are protection rules or global protection is enabled
|
|
46
|
+
if ((config.protect && config.protect.length > 0) || config.globalProtect) {
|
|
47
|
+
addMiddleware({
|
|
48
|
+
entrypoint: "astro-sessionkit/guard",
|
|
49
|
+
order: "pre",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Re-export types for convenience
|
|
58
|
+
export type { Session, ProtectionRule, SessionKitConfig } from "./core/types";
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Public Server API - Use these in your Astro components/endpoints
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
import {getContextStore} from "./core/context";
|
|
6
|
+
import {isValidSessionStructure} from "./core/validation";
|
|
7
|
+
import type {Session} from "./core/types";
|
|
8
|
+
import type {APIContext} from "astro";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get the current session (returns null if not authenticated)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // In .astro component
|
|
16
|
+
* const session = getSession();
|
|
17
|
+
* if (session) {
|
|
18
|
+
* console.log('User ID:', session.userId);
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export function getSession(): Session | null {
|
|
23
|
+
const context = getContextStore();
|
|
24
|
+
return context?.session ?? null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the current session or throw if not authenticated
|
|
29
|
+
*
|
|
30
|
+
* @throws {Response} 401 Unauthorized if no session
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // In API endpoint
|
|
35
|
+
* const session = requireSession();
|
|
36
|
+
* // TypeScript knows session is not null here
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function requireSession(): Session {
|
|
40
|
+
const session = getSession();
|
|
41
|
+
|
|
42
|
+
if (!session) {
|
|
43
|
+
throw new Response("Unauthorized", {status: 401});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return session;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if user is authenticated
|
|
51
|
+
*/
|
|
52
|
+
export function isAuthenticated(): boolean {
|
|
53
|
+
return getSession() !== null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if user has a specific role
|
|
58
|
+
*/
|
|
59
|
+
export function hasRole(role: string): boolean {
|
|
60
|
+
const session = getSession();
|
|
61
|
+
if (!session) return false;
|
|
62
|
+
|
|
63
|
+
// Check primary role
|
|
64
|
+
if (session.role === role) return true;
|
|
65
|
+
|
|
66
|
+
// Check additional roles
|
|
67
|
+
return session.roles?.includes(role) ?? false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if user has a specific permission
|
|
72
|
+
*/
|
|
73
|
+
export function hasPermission(permission: string): boolean {
|
|
74
|
+
const session = getSession();
|
|
75
|
+
if (!session) return false;
|
|
76
|
+
|
|
77
|
+
return session.permissions?.includes(permission) ?? false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if user has ALL of the specified permissions
|
|
82
|
+
*/
|
|
83
|
+
export function hasAllPermissions(...permissions: string[]): boolean {
|
|
84
|
+
const session = getSession();
|
|
85
|
+
if (!session) return false;
|
|
86
|
+
|
|
87
|
+
const userPermissions = session.permissions ?? [];
|
|
88
|
+
return permissions.every((p) => userPermissions.includes(p));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if user has ANY of the specified permissions
|
|
93
|
+
*/
|
|
94
|
+
export function hasAnyPermission(...permissions: string[]): boolean {
|
|
95
|
+
const session = getSession();
|
|
96
|
+
if (!session) return false;
|
|
97
|
+
|
|
98
|
+
const userPermissions = session.permissions ?? [];
|
|
99
|
+
return permissions.some((p) => userPermissions.includes(p));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// Session Management
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if a specific role has a specific permission.
|
|
108
|
+
*
|
|
109
|
+
* This checks if the current user has the specified role and if that role
|
|
110
|
+
* is associated with the specified permission.
|
|
111
|
+
*
|
|
112
|
+
* @param role - The role to check
|
|
113
|
+
* @param permission - The permission to check
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* if (hasRolePermission("admin", "delete users")) {
|
|
118
|
+
* // ...
|
|
119
|
+
* }
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export function hasRolePermission(role: string, permission: string): boolean {
|
|
123
|
+
return hasRole(role) && hasPermission(permission);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Set session data in context.locals.session
|
|
128
|
+
*
|
|
129
|
+
* Use this after successful authentication to register the user's session.
|
|
130
|
+
* This does NOT handle session storage (cookies, Redis, etc.) - you must do that separately.
|
|
131
|
+
*
|
|
132
|
+
* @param context - Astro API context
|
|
133
|
+
* @param session - Session data to set
|
|
134
|
+
*
|
|
135
|
+
* @throws {Error} If session structure is invalid
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```ts
|
|
139
|
+
* // In API endpoint after verifying credentials
|
|
140
|
+
* export const POST: APIRoute = async (context) => {
|
|
141
|
+
* const { email, password } = await context.request.json();
|
|
142
|
+
* const user = await verifyCredentials(email, password);
|
|
143
|
+
*
|
|
144
|
+
* if (user) {
|
|
145
|
+
* // Register session with SessionKit
|
|
146
|
+
* setSession(context, {
|
|
147
|
+
* userId: user.id,
|
|
148
|
+
* email: user.email,
|
|
149
|
+
* role: user.role,
|
|
150
|
+
* permissions: user.permissions
|
|
151
|
+
* });
|
|
152
|
+
*
|
|
153
|
+
* // YOU must also store the session (cookie, Redis, etc.)
|
|
154
|
+
* context.cookies.set('session_id', sessionId, { httpOnly: true });
|
|
155
|
+
*
|
|
156
|
+
* return new Response(JSON.stringify({ success: true }));
|
|
157
|
+
* }
|
|
158
|
+
* };
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export function setSession(context: APIContext, session: Session): void {
|
|
162
|
+
// Validate session structure
|
|
163
|
+
if (!isValidSessionStructure(session)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
'[SessionKit] Invalid session structure. Session must have a valid userId and follow the Session interface.'
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Set in context.locals for SessionKit middleware to read
|
|
170
|
+
context.session?.set('__session__', session);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Clear session from context.locals.session
|
|
175
|
+
*
|
|
176
|
+
* Use this during logout. This does NOT delete session storage (cookies, Redis, etc.) -
|
|
177
|
+
* you must do that separately.
|
|
178
|
+
*
|
|
179
|
+
* @param context - Astro API context
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* // In logout endpoint
|
|
184
|
+
* export const POST: APIRoute = async (context) => {
|
|
185
|
+
* // Clear from SessionKit
|
|
186
|
+
* clearSession(context);
|
|
187
|
+
*
|
|
188
|
+
* // YOU must also delete the session storage
|
|
189
|
+
* context.cookies.delete('session_id');
|
|
190
|
+
* await db.deleteSession(sessionId);
|
|
191
|
+
*
|
|
192
|
+
* return context.redirect('/');
|
|
193
|
+
* };
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function clearSession(context: APIContext): void {
|
|
197
|
+
context.session?.delete('__session__');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Update specific fields in the current session
|
|
202
|
+
*
|
|
203
|
+
* Useful for updating session data without replacing the entire session.
|
|
204
|
+
* The updated session is validated before being set.
|
|
205
|
+
*
|
|
206
|
+
* @param context - Astro API context
|
|
207
|
+
* @param updates - Partial session data to merge
|
|
208
|
+
*
|
|
209
|
+
* @throws {Error} If no session exists or updated session is invalid
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* ```ts
|
|
213
|
+
* // Update user's role after promotion
|
|
214
|
+
* export const POST: APIRoute = async (context) => {
|
|
215
|
+
* updateSession(context, {
|
|
216
|
+
* role: 'admin',
|
|
217
|
+
* permissions: ['admin:read', 'admin:write']
|
|
218
|
+
* });
|
|
219
|
+
*
|
|
220
|
+
* // YOU must also update session storage
|
|
221
|
+
* await db.updateSession(sessionId, updatedData);
|
|
222
|
+
*
|
|
223
|
+
* return new Response(JSON.stringify({ success: true }));
|
|
224
|
+
* };
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
export function updateSession(context: APIContext, updates: Partial<Session>): void {
|
|
228
|
+
const currentSession = context.session?.get<Session>('__session__');
|
|
229
|
+
|
|
230
|
+
if (!currentSession) {
|
|
231
|
+
throw new Error('[SessionKit] Cannot update session: no session exists');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Merge updates with current session
|
|
235
|
+
const updatedSession = {...currentSession, ...updates};
|
|
236
|
+
|
|
237
|
+
// Validate merged session
|
|
238
|
+
if (!isValidSessionStructure(updatedSession)) {
|
|
239
|
+
throw new Error(
|
|
240
|
+
'[SessionKit] Invalid session structure after update. Ensure all fields are valid.'
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
context.session?.set('__session__', updatedSession);
|
|
245
|
+
}
|