@absolutejs/auth 0.21.1 → 0.22.0
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/.claude/settings.local.json +5 -0
- package/CLAUDE.md +91 -0
- package/dist/index.js +214 -46
- package/dist/index.js.map +14 -13
- package/dist/src/authorize.d.ts +2 -2
- package/dist/src/callback.d.ts +1 -1
- package/dist/src/constants.d.ts +1 -0
- package/dist/src/index.d.ts +11 -9
- package/dist/src/protectRoute.d.ts +1 -1
- package/dist/src/refresh.d.ts +3 -2
- package/dist/src/revoke.d.ts +1 -1
- package/dist/src/sessionCleanup.d.ts +38 -0
- package/dist/src/signout.d.ts +1 -1
- package/dist/src/typeGuards.d.ts +2 -1
- package/dist/src/types.d.ts +17 -4
- package/dist/src/utils.d.ts +8 -3
- package/package.json +1 -1
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Absolute Auth is a TypeScript OAuth2 authentication library for the Elysia web framework. It provides session management, multi-provider OAuth2 support, and route protection utilities. Built for Bun runtime.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run build # Bundle with Bun, generate TypeScript declarations
|
|
13
|
+
bun run typecheck # TypeScript type checking
|
|
14
|
+
bun run format # Prettier formatting
|
|
15
|
+
bun run dev # Watch and run example server
|
|
16
|
+
bun run release # Full pipeline: lint, format, build, publish
|
|
17
|
+
|
|
18
|
+
# Database (example app)
|
|
19
|
+
bun db:push # Drizzle migration push
|
|
20
|
+
bun db:migrate # Custom migration runner
|
|
21
|
+
bun db:studio # Drizzle Studio interface
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
### Core Module Pattern
|
|
27
|
+
|
|
28
|
+
Each auth feature (`authorize`, `callback`, `refresh`, `revoke`, `signout`, `userStatus`, `profile`) is a standalone module that returns an Elysia instance. The main `absoluteAuth<UserType>(config)` function composes all modules using Elysia's `.use()` pattern.
|
|
29
|
+
|
|
30
|
+
### OAuth2 Flow
|
|
31
|
+
|
|
32
|
+
1. **authorize.ts** - Sets state/code_verifier/origin cookies, redirects to provider
|
|
33
|
+
2. **callback.ts** - Validates authorization code, exchanges for tokens, creates/retrieves user, stores session
|
|
34
|
+
3. **userStatus.ts** - Returns current session status
|
|
35
|
+
4. **refresh.ts** - Refreshes tokens for providers that support it
|
|
36
|
+
5. **revoke.ts** - Revokes tokens for providers that support it
|
|
37
|
+
6. **signout.ts** - Cleans up session and cookies
|
|
38
|
+
|
|
39
|
+
### Session Management
|
|
40
|
+
|
|
41
|
+
- Sessions stored in Elysia state (`sessionStore.ts`)
|
|
42
|
+
- Session ID in httpOnly secure cookie (UUID v4)
|
|
43
|
+
- Two session types: `SessionRecord<UserType>` (authenticated) and `UnregisteredSessionRecord` (during OAuth flow)
|
|
44
|
+
- Always check `expiresAt` timestamp before using session
|
|
45
|
+
|
|
46
|
+
### Generic UserType Pattern
|
|
47
|
+
|
|
48
|
+
All modules are generic over `<UserType>`. The config requires a `getUser(decoded)` function that returns your user type from decoded token data. This keeps the library agnostic to your user model.
|
|
49
|
+
|
|
50
|
+
### Event Handler Pattern
|
|
51
|
+
|
|
52
|
+
Lifecycle hooks follow `On<Event><Success|Error>` naming:
|
|
53
|
+
- `OnCallbackSuccess`, `OnCallbackError`
|
|
54
|
+
- `OnAuthorizeError`, `OnRefreshSuccess`, etc.
|
|
55
|
+
|
|
56
|
+
Handlers receive context objects and can return `Response`, status object, or `undefined`.
|
|
57
|
+
|
|
58
|
+
## Code Conventions
|
|
59
|
+
|
|
60
|
+
### ESLint Rules (from @absolutejs plugin)
|
|
61
|
+
|
|
62
|
+
- No default exports (except eslint config)
|
|
63
|
+
- Minimum variable name length: 3 characters
|
|
64
|
+
- Enforce sorted exports and object keys
|
|
65
|
+
- Maximum nesting depth enforced
|
|
66
|
+
|
|
67
|
+
### Formatting
|
|
68
|
+
|
|
69
|
+
- Tabs (width 4), not spaces
|
|
70
|
+
- Single quotes, semicolons
|
|
71
|
+
- Trailing commas: none
|
|
72
|
+
- Print width: 80
|
|
73
|
+
|
|
74
|
+
### Type Safety
|
|
75
|
+
|
|
76
|
+
- Strict mode enabled
|
|
77
|
+
- Use provided type guards from `typeGuards.ts` for runtime validation
|
|
78
|
+
- Always return typed status responses or Response objects for errors
|
|
79
|
+
|
|
80
|
+
## Key Files
|
|
81
|
+
|
|
82
|
+
- `src/index.ts` - Main export, composes all auth routes
|
|
83
|
+
- `src/types.ts` - Core type definitions
|
|
84
|
+
- `src/utils.ts` - Session validation, instantiation helpers
|
|
85
|
+
- `src/constants.ts` - Cookie/session duration constants
|
|
86
|
+
- `example/server.ts` - Reference implementation
|
|
87
|
+
|
|
88
|
+
## Dependencies
|
|
89
|
+
|
|
90
|
+
- **elysia** - Web framework (peer dependency)
|
|
91
|
+
- **citra** - OAuth2 client management (provides provider configs, PKCE utilities)
|
package/dist/index.js
CHANGED
|
@@ -2087,7 +2087,7 @@ var createOAuth2Client = async (providerName, config) => {
|
|
|
2087
2087
|
};
|
|
2088
2088
|
|
|
2089
2089
|
// src/index.ts
|
|
2090
|
-
import { Elysia as
|
|
2090
|
+
import { Elysia as Elysia11 } from "elysia";
|
|
2091
2091
|
|
|
2092
2092
|
// src/authorize.ts
|
|
2093
2093
|
import { Elysia, t as t2 } from "elysia";
|
|
@@ -2102,6 +2102,7 @@ var MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MIN
|
|
|
2102
2102
|
var MILLISECONDS_IN_AN_HOUR = MILLISECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR;
|
|
2103
2103
|
var COOKIE_MINUTES = 30;
|
|
2104
2104
|
var COOKIE_DURATION = SECONDS_IN_A_MINUTE * COOKIE_MINUTES;
|
|
2105
|
+
var DEFAULT_MAX_SESSIONS = 1e4;
|
|
2105
2106
|
|
|
2106
2107
|
// src/typebox.ts
|
|
2107
2108
|
import { t } from "elysia";
|
|
@@ -2109,6 +2110,19 @@ var userSessionIdTypebox = t.Optional(t.TemplateLiteral("${string}-${string}-${s
|
|
|
2109
2110
|
var authProviderOption = t.Enum(Object.fromEntries(Object.keys(providers).filter(isValidProviderOption).map((key) => [key, key])));
|
|
2110
2111
|
|
|
2111
2112
|
// src/authorize.ts
|
|
2113
|
+
var parseReferer = (headerReferer) => {
|
|
2114
|
+
if (!headerReferer)
|
|
2115
|
+
return "/";
|
|
2116
|
+
try {
|
|
2117
|
+
const url = new URL(headerReferer);
|
|
2118
|
+
return url.pathname + url.search;
|
|
2119
|
+
} catch {
|
|
2120
|
+
if (headerReferer.startsWith("/") && !headerReferer.startsWith("//")) {
|
|
2121
|
+
return headerReferer;
|
|
2122
|
+
}
|
|
2123
|
+
return "/";
|
|
2124
|
+
}
|
|
2125
|
+
};
|
|
2112
2126
|
var authorize = ({
|
|
2113
2127
|
clientProviders,
|
|
2114
2128
|
authorizeRoute = "/oauth2/:provider/authorization",
|
|
@@ -2129,7 +2143,7 @@ var authorize = ({
|
|
|
2129
2143
|
if (!providerConfig)
|
|
2130
2144
|
return status("Unauthorized", "Client provider not found");
|
|
2131
2145
|
const { providerInstance, scope, searchParams } = providerConfig;
|
|
2132
|
-
const referer = headers["referer"]
|
|
2146
|
+
const referer = parseReferer(headers["referer"]);
|
|
2133
2147
|
origin_url.set({
|
|
2134
2148
|
httpOnly: true,
|
|
2135
2149
|
maxAge: COOKIE_DURATION,
|
|
@@ -2175,14 +2189,16 @@ var authorize = ({
|
|
|
2175
2189
|
});
|
|
2176
2190
|
return redirect(authorizationURL.toString());
|
|
2177
2191
|
} catch (err) {
|
|
2192
|
+
console.error("[authorize] Failed to create authorization URL:", {
|
|
2193
|
+
error: err instanceof Error ? err.message : err,
|
|
2194
|
+
provider,
|
|
2195
|
+
stack: err instanceof Error ? err.stack : undefined
|
|
2196
|
+
});
|
|
2178
2197
|
await onAuthorizeError?.({
|
|
2179
2198
|
authProvider: provider,
|
|
2180
2199
|
error: err
|
|
2181
2200
|
});
|
|
2182
|
-
|
|
2183
|
-
return status("Internal Server Error", `${err.message} - ${err.stack ?? ""}`);
|
|
2184
|
-
}
|
|
2185
|
-
return status("Internal Server Error", `Unknown status: ${err}`);
|
|
2201
|
+
return status("Internal Server Error", "Failed to create authorization URL");
|
|
2186
2202
|
}
|
|
2187
2203
|
}, {
|
|
2188
2204
|
cookie: t2.Cookie({
|
|
@@ -2209,6 +2225,8 @@ var sessionStore = () => {
|
|
|
2209
2225
|
|
|
2210
2226
|
// src/typeGuards.ts
|
|
2211
2227
|
var isValidUser = (user) => true;
|
|
2228
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2229
|
+
var isUserSessionId = (key) => UUID_PATTERN.test(key);
|
|
2212
2230
|
var isNonEmptyString = (str) => str !== null && str !== undefined && str.trim() !== "";
|
|
2213
2231
|
var isStatusResponse = (value) => typeof value === "object" && value !== null && ("status" in value) && typeof Reflect.get(value, "status") === "number";
|
|
2214
2232
|
|
|
@@ -2221,7 +2239,9 @@ var instantiateUserSession = async ({
|
|
|
2221
2239
|
tokenResponse,
|
|
2222
2240
|
providerInstance,
|
|
2223
2241
|
getUser,
|
|
2224
|
-
onNewUser
|
|
2242
|
+
onNewUser,
|
|
2243
|
+
sessionDurationMs = MILLISECONDS_IN_A_DAY,
|
|
2244
|
+
unregisteredSessionDurationMs = MILLISECONDS_IN_AN_HOUR
|
|
2225
2245
|
}) => {
|
|
2226
2246
|
let userIdentity;
|
|
2227
2247
|
let accessToken = tokenResponse.access_token;
|
|
@@ -2236,7 +2256,11 @@ var instantiateUserSession = async ({
|
|
|
2236
2256
|
userIdentity = await providerInstance.fetchUserProfile(tokenResponse.access_token);
|
|
2237
2257
|
}
|
|
2238
2258
|
const userSession = validateSession({ session, user_session_id });
|
|
2239
|
-
const userSessionId = getUserSessionId(
|
|
2259
|
+
const userSessionId = getUserSessionId({
|
|
2260
|
+
session,
|
|
2261
|
+
unregisteredSession,
|
|
2262
|
+
user_session_id
|
|
2263
|
+
});
|
|
2240
2264
|
let user = userSession?.user ?? await getUser(userIdentity);
|
|
2241
2265
|
const response = user ?? await onNewUser(userIdentity);
|
|
2242
2266
|
const isRedirectOrStatus = response instanceof Response || isStatusResponse(response);
|
|
@@ -2244,7 +2268,7 @@ var instantiateUserSession = async ({
|
|
|
2244
2268
|
user = response;
|
|
2245
2269
|
session[userSessionId] = {
|
|
2246
2270
|
accessToken,
|
|
2247
|
-
expiresAt: Date.now() +
|
|
2271
|
+
expiresAt: Date.now() + sessionDurationMs,
|
|
2248
2272
|
refreshToken,
|
|
2249
2273
|
user
|
|
2250
2274
|
};
|
|
@@ -2253,14 +2277,14 @@ var instantiateUserSession = async ({
|
|
|
2253
2277
|
const existingUnregistered = unregisteredSession[userSessionId];
|
|
2254
2278
|
if (existingUnregistered) {
|
|
2255
2279
|
existingUnregistered.accessToken = accessToken;
|
|
2256
|
-
existingUnregistered.expiresAt = Date.now() +
|
|
2280
|
+
existingUnregistered.expiresAt = Date.now() + unregisteredSessionDurationMs;
|
|
2257
2281
|
existingUnregistered.refreshToken = refreshToken;
|
|
2258
2282
|
existingUnregistered.userIdentity = userIdentity;
|
|
2259
2283
|
return response;
|
|
2260
2284
|
}
|
|
2261
2285
|
unregisteredSession[userSessionId] = {
|
|
2262
2286
|
accessToken,
|
|
2263
|
-
expiresAt: Date.now() +
|
|
2287
|
+
expiresAt: Date.now() + unregisteredSessionDurationMs,
|
|
2264
2288
|
refreshToken,
|
|
2265
2289
|
userIdentity
|
|
2266
2290
|
};
|
|
@@ -2305,17 +2329,28 @@ var validateSession = ({
|
|
|
2305
2329
|
}
|
|
2306
2330
|
return userSession;
|
|
2307
2331
|
};
|
|
2308
|
-
var
|
|
2332
|
+
var clearExistingSession = (existingId, session, unregisteredSession) => {
|
|
2333
|
+
if (session)
|
|
2334
|
+
delete session[existingId];
|
|
2335
|
+
if (unregisteredSession)
|
|
2336
|
+
delete unregisteredSession[existingId];
|
|
2337
|
+
};
|
|
2338
|
+
var getUserSessionId = ({
|
|
2339
|
+
user_session_id,
|
|
2340
|
+
session,
|
|
2341
|
+
unregisteredSession
|
|
2342
|
+
}) => {
|
|
2309
2343
|
const existingId = user_session_id?.value;
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
user_session_id.set({
|
|
2313
|
-
httpOnly: true,
|
|
2314
|
-
sameSite: "lax",
|
|
2315
|
-
secure: true,
|
|
2316
|
-
value: userSessionId
|
|
2317
|
-
});
|
|
2344
|
+
if (isNonEmptyString(existingId)) {
|
|
2345
|
+
clearExistingSession(existingId, session, unregisteredSession);
|
|
2318
2346
|
}
|
|
2347
|
+
const userSessionId = crypto.randomUUID();
|
|
2348
|
+
user_session_id.set({
|
|
2349
|
+
httpOnly: true,
|
|
2350
|
+
sameSite: "lax",
|
|
2351
|
+
secure: true,
|
|
2352
|
+
value: userSessionId
|
|
2353
|
+
});
|
|
2319
2354
|
return userSessionId;
|
|
2320
2355
|
};
|
|
2321
2356
|
|
|
@@ -2365,14 +2400,23 @@ var callback = ({
|
|
|
2365
2400
|
try {
|
|
2366
2401
|
tokenResponse = await providerInstance.validateAuthorizationCode(requiresPKCE ? { code, codeVerifier: verifier } : { code });
|
|
2367
2402
|
} catch (err) {
|
|
2403
|
+
console.error("[callback] Failed to validate authorization code:", {
|
|
2404
|
+
authProvider,
|
|
2405
|
+
error: err instanceof Error ? err.message : err,
|
|
2406
|
+
stack: err instanceof Error ? err.stack : undefined
|
|
2407
|
+
});
|
|
2368
2408
|
await onCallbackError?.({
|
|
2369
2409
|
authProvider,
|
|
2370
2410
|
error: err,
|
|
2371
2411
|
originUrl
|
|
2372
2412
|
});
|
|
2373
|
-
return
|
|
2413
|
+
return status("Internal Server Error", "Failed to validate authorization code");
|
|
2374
2414
|
}
|
|
2375
|
-
const userSessionId = getUserSessionId(
|
|
2415
|
+
const userSessionId = getUserSessionId({
|
|
2416
|
+
session,
|
|
2417
|
+
unregisteredSession,
|
|
2418
|
+
user_session_id
|
|
2419
|
+
});
|
|
2376
2420
|
const response = await onCallbackSuccess?.({
|
|
2377
2421
|
authProvider,
|
|
2378
2422
|
cookie,
|
|
@@ -2447,7 +2491,7 @@ var profile = ({
|
|
|
2447
2491
|
|
|
2448
2492
|
// src/protectRoute.ts
|
|
2449
2493
|
import { Elysia as Elysia5, t as t5 } from "elysia";
|
|
2450
|
-
var
|
|
2494
|
+
var protectRoutePlugin = () => new Elysia5().use(sessionStore()).guard({ cookie: t5.Cookie({ user_session_id: userSessionIdTypebox }) }).derive(({ store: { session }, cookie: { user_session_id }, status }) => ({
|
|
2451
2495
|
protectRoute: async (handleAuth, handleAuthFail) => {
|
|
2452
2496
|
const { user, error } = await getStatus(session, user_session_id);
|
|
2453
2497
|
if (error) {
|
|
@@ -2469,7 +2513,8 @@ var refresh = ({
|
|
|
2469
2513
|
clientProviders,
|
|
2470
2514
|
refreshRoute = "/oauth2/tokens",
|
|
2471
2515
|
onRefreshSuccess,
|
|
2472
|
-
onRefreshError
|
|
2516
|
+
onRefreshError,
|
|
2517
|
+
sessionDurationMs = MILLISECONDS_IN_A_DAY
|
|
2473
2518
|
}) => new Elysia6().use(sessionStore()).post(refreshRoute, async ({
|
|
2474
2519
|
status,
|
|
2475
2520
|
store: { session },
|
|
@@ -2504,6 +2549,9 @@ var refresh = ({
|
|
|
2504
2549
|
}
|
|
2505
2550
|
try {
|
|
2506
2551
|
const tokenResponse = await providerInstance.refreshAccessToken(refreshToken);
|
|
2552
|
+
userSession.accessToken = tokenResponse.access_token;
|
|
2553
|
+
userSession.expiresAt = Date.now() + sessionDurationMs;
|
|
2554
|
+
userSession.refreshToken = tokenResponse.refresh_token ?? userSession.refreshToken;
|
|
2507
2555
|
await onRefreshSuccess?.({
|
|
2508
2556
|
authProvider: auth_provider.value,
|
|
2509
2557
|
tokenResponse
|
|
@@ -2512,14 +2560,16 @@ var refresh = ({
|
|
|
2512
2560
|
status: 204
|
|
2513
2561
|
});
|
|
2514
2562
|
} catch (err) {
|
|
2563
|
+
console.error("[refresh] Failed to refresh token:", {
|
|
2564
|
+
authProvider: auth_provider.value,
|
|
2565
|
+
error: err instanceof Error ? err.message : err,
|
|
2566
|
+
stack: err instanceof Error ? err.stack : undefined
|
|
2567
|
+
});
|
|
2515
2568
|
await onRefreshError?.({
|
|
2516
2569
|
authProvider: auth_provider.value,
|
|
2517
2570
|
error: err
|
|
2518
2571
|
});
|
|
2519
|
-
|
|
2520
|
-
return status("Internal Server Error", `Failed to refresh token: ${err.message}`);
|
|
2521
|
-
}
|
|
2522
|
-
return status("Internal Server Error", `Failed to refresh token: Unknown status: ${err}`);
|
|
2572
|
+
return status("Internal Server Error", "Failed to refresh token");
|
|
2523
2573
|
}
|
|
2524
2574
|
}, { cookie: t6.Cookie({ user_session_id: userSessionIdTypebox }) });
|
|
2525
2575
|
|
|
@@ -2569,23 +2619,128 @@ var revoke = ({
|
|
|
2569
2619
|
status: 204
|
|
2570
2620
|
});
|
|
2571
2621
|
} catch (err) {
|
|
2622
|
+
console.error("[revoke] Failed to revoke token:", {
|
|
2623
|
+
authProvider: auth_provider.value,
|
|
2624
|
+
error: err instanceof Error ? err.message : err,
|
|
2625
|
+
stack: err instanceof Error ? err.stack : undefined
|
|
2626
|
+
});
|
|
2572
2627
|
await onRevocationError?.({
|
|
2573
2628
|
authProvider: auth_provider.value,
|
|
2574
2629
|
error: err
|
|
2575
2630
|
});
|
|
2576
|
-
|
|
2577
|
-
return status("Internal Server Error", `Failed to revoke token: ${err.message}`);
|
|
2578
|
-
}
|
|
2579
|
-
return status("Internal Server Error", `Failed to revoke token: Unknown status: ${err}`);
|
|
2631
|
+
return status("Internal Server Error", "Failed to revoke token");
|
|
2580
2632
|
}
|
|
2581
2633
|
}, { cookie: t7.Cookie({ user_session_id: userSessionIdTypebox }) });
|
|
2582
2634
|
|
|
2635
|
+
// src/sessionCleanup.ts
|
|
2636
|
+
import { Elysia as Elysia8 } from "elysia";
|
|
2637
|
+
var sessionCleanup = ({
|
|
2638
|
+
cleanupIntervalMs = MILLISECONDS_IN_AN_HOUR,
|
|
2639
|
+
maxSessions = DEFAULT_MAX_SESSIONS,
|
|
2640
|
+
onSessionCleanup
|
|
2641
|
+
}) => {
|
|
2642
|
+
let intervalId = null;
|
|
2643
|
+
return new Elysia8({ name: "sessionCleanup" }).use(sessionStore()).onStart(({ store: { session, unregisteredSession } }) => {
|
|
2644
|
+
intervalId = setInterval(async () => {
|
|
2645
|
+
await performCleanup(session, unregisteredSession, maxSessions, onSessionCleanup);
|
|
2646
|
+
}, cleanupIntervalMs);
|
|
2647
|
+
}).onStop(() => {
|
|
2648
|
+
if (intervalId) {
|
|
2649
|
+
clearInterval(intervalId);
|
|
2650
|
+
intervalId = null;
|
|
2651
|
+
}
|
|
2652
|
+
}).derive(({ store: { session, unregisteredSession } }) => ({
|
|
2653
|
+
cleanupSessions: async () => {
|
|
2654
|
+
await performCleanup(session, unregisteredSession, maxSessions, onSessionCleanup);
|
|
2655
|
+
}
|
|
2656
|
+
})).as("global");
|
|
2657
|
+
};
|
|
2658
|
+
var collectValidSessionEntries = (session) => {
|
|
2659
|
+
const entries = [];
|
|
2660
|
+
for (const [key, value] of Object.entries(session)) {
|
|
2661
|
+
if (!isUserSessionId(key))
|
|
2662
|
+
continue;
|
|
2663
|
+
entries.push([key, value]);
|
|
2664
|
+
}
|
|
2665
|
+
return entries;
|
|
2666
|
+
};
|
|
2667
|
+
var collectValidUnregisteredEntries = (unregisteredSession) => {
|
|
2668
|
+
const entries = [];
|
|
2669
|
+
for (const [key, value] of Object.entries(unregisteredSession)) {
|
|
2670
|
+
if (!isUserSessionId(key))
|
|
2671
|
+
continue;
|
|
2672
|
+
entries.push([key, value]);
|
|
2673
|
+
}
|
|
2674
|
+
return entries;
|
|
2675
|
+
};
|
|
2676
|
+
var removeExpiredSessions = (validEntries, session, now) => {
|
|
2677
|
+
const removed = new Map;
|
|
2678
|
+
for (const [sessionId, userSession] of validEntries) {
|
|
2679
|
+
if (userSession.expiresAt >= now)
|
|
2680
|
+
continue;
|
|
2681
|
+
removed.set(sessionId, userSession);
|
|
2682
|
+
delete session[sessionId];
|
|
2683
|
+
}
|
|
2684
|
+
return removed;
|
|
2685
|
+
};
|
|
2686
|
+
var removeExpiredUnregisteredSessions = (validEntries, unregisteredSession, now) => {
|
|
2687
|
+
const removed = new Map;
|
|
2688
|
+
for (const [sessionId, unregSession] of validEntries) {
|
|
2689
|
+
if (unregSession.expiresAt >= now)
|
|
2690
|
+
continue;
|
|
2691
|
+
removed.set(sessionId, unregSession);
|
|
2692
|
+
delete unregisteredSession[sessionId];
|
|
2693
|
+
}
|
|
2694
|
+
return removed;
|
|
2695
|
+
};
|
|
2696
|
+
var evictExcessSessions = (remainingEntries, session, maxSessions, removedSessions) => {
|
|
2697
|
+
const excessCount = remainingEntries.length - maxSessions;
|
|
2698
|
+
if (excessCount <= 0)
|
|
2699
|
+
return;
|
|
2700
|
+
const sortedByExpiry = remainingEntries.sort(([, entryA], [, entryB]) => entryA.expiresAt - entryB.expiresAt);
|
|
2701
|
+
const toEvict = sortedByExpiry.slice(0, excessCount);
|
|
2702
|
+
for (const [key, value] of toEvict) {
|
|
2703
|
+
removedSessions.set(key, value);
|
|
2704
|
+
delete session[key];
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
var evictExcessUnregisteredSessions = (remainingEntries, unregisteredSession, maxSessions, removedUnregisteredSessions) => {
|
|
2708
|
+
const excessCount = remainingEntries.length - maxSessions;
|
|
2709
|
+
if (excessCount <= 0)
|
|
2710
|
+
return;
|
|
2711
|
+
const sortedByExpiry = remainingEntries.sort(([, entryA], [, entryB]) => entryA.expiresAt - entryB.expiresAt);
|
|
2712
|
+
const toEvict = sortedByExpiry.slice(0, excessCount);
|
|
2713
|
+
for (const [key, value] of toEvict) {
|
|
2714
|
+
removedUnregisteredSessions.set(key, value);
|
|
2715
|
+
delete unregisteredSession[key];
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
var performCleanup = async (session, unregisteredSession, maxSessions, onSessionCleanup) => {
|
|
2719
|
+
const now = Date.now();
|
|
2720
|
+
const validSessionEntries = collectValidSessionEntries(session);
|
|
2721
|
+
const removedSessions = removeExpiredSessions(validSessionEntries, session, now);
|
|
2722
|
+
const validUnregisteredEntries = collectValidUnregisteredEntries(unregisteredSession);
|
|
2723
|
+
const removedUnregisteredSessions = removeExpiredUnregisteredSessions(validUnregisteredEntries, unregisteredSession, now);
|
|
2724
|
+
const remainingEntries = validSessionEntries.filter(([key]) => !removedSessions.has(key));
|
|
2725
|
+
evictExcessSessions(remainingEntries, session, maxSessions, removedSessions);
|
|
2726
|
+
const remainingUnregistered = validUnregisteredEntries.filter(([key]) => !removedUnregisteredSessions.has(key));
|
|
2727
|
+
evictExcessUnregisteredSessions(remainingUnregistered, unregisteredSession, maxSessions, removedUnregisteredSessions);
|
|
2728
|
+
try {
|
|
2729
|
+
await onSessionCleanup?.({
|
|
2730
|
+
removedSessions,
|
|
2731
|
+
removedUnregisteredSessions
|
|
2732
|
+
});
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
console.error("[sessionCleanup] onSessionCleanup handler failed:", err);
|
|
2735
|
+
}
|
|
2736
|
+
};
|
|
2737
|
+
|
|
2583
2738
|
// src/signout.ts
|
|
2584
|
-
import { Elysia as
|
|
2739
|
+
import { Elysia as Elysia9, t as t8 } from "elysia";
|
|
2585
2740
|
var signout = ({
|
|
2586
2741
|
signoutRoute = "/oauth2/signout",
|
|
2587
2742
|
onSignOut
|
|
2588
|
-
}) => new
|
|
2743
|
+
}) => new Elysia9().use(sessionStore()).delete(signoutRoute, async ({
|
|
2589
2744
|
status,
|
|
2590
2745
|
store: { session },
|
|
2591
2746
|
cookie: { user_session_id, auth_provider }
|
|
@@ -2606,10 +2761,12 @@ var signout = ({
|
|
|
2606
2761
|
userSessionId: user_session_id.value
|
|
2607
2762
|
});
|
|
2608
2763
|
} catch (err) {
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2764
|
+
console.error("[signout] Sign out operation failed:", {
|
|
2765
|
+
authProvider: auth_provider.value,
|
|
2766
|
+
error: err instanceof Error ? err.message : err,
|
|
2767
|
+
stack: err instanceof Error ? err.stack : undefined
|
|
2768
|
+
});
|
|
2769
|
+
return status("Internal Server Error", "Sign out operation failed");
|
|
2613
2770
|
}
|
|
2614
2771
|
delete session[user_session_id.value];
|
|
2615
2772
|
user_session_id.remove();
|
|
@@ -2622,11 +2779,11 @@ var signout = ({
|
|
|
2622
2779
|
});
|
|
2623
2780
|
|
|
2624
2781
|
// src/userStatus.ts
|
|
2625
|
-
import { Elysia as
|
|
2782
|
+
import { Elysia as Elysia10, t as t9 } from "elysia";
|
|
2626
2783
|
var userStatus = ({
|
|
2627
2784
|
statusRoute = "/oauth2/status",
|
|
2628
2785
|
onStatus
|
|
2629
|
-
}) => new
|
|
2786
|
+
}) => new Elysia10().use(sessionStore()).get(statusRoute, async ({ status, cookie: { user_session_id }, store: { session } }) => {
|
|
2630
2787
|
const { user, error } = await getStatus(session, user_session_id);
|
|
2631
2788
|
if (error) {
|
|
2632
2789
|
return status(error.code, error.message);
|
|
@@ -2649,6 +2806,9 @@ var absoluteAuth = async ({
|
|
|
2649
2806
|
statusRoute,
|
|
2650
2807
|
refreshRoute,
|
|
2651
2808
|
revokeRoute,
|
|
2809
|
+
cleanupIntervalMs,
|
|
2810
|
+
maxSessions,
|
|
2811
|
+
sessionDurationMs,
|
|
2652
2812
|
onAuthorizeSuccess,
|
|
2653
2813
|
onAuthorizeError,
|
|
2654
2814
|
onProfileSuccess,
|
|
@@ -2660,7 +2820,8 @@ var absoluteAuth = async ({
|
|
|
2660
2820
|
onRefreshError,
|
|
2661
2821
|
onSignOut,
|
|
2662
2822
|
onRevocationSuccess,
|
|
2663
|
-
onRevocationError
|
|
2823
|
+
onRevocationError,
|
|
2824
|
+
onSessionCleanup
|
|
2664
2825
|
}) => {
|
|
2665
2826
|
const entryPromises = [];
|
|
2666
2827
|
for (const [providerName, providerConfig] of Object.entries(providersConfiguration)) {
|
|
@@ -2676,7 +2837,11 @@ var absoluteAuth = async ({
|
|
|
2676
2837
|
]));
|
|
2677
2838
|
}
|
|
2678
2839
|
const clientProviders = Object.fromEntries(await Promise.all(entryPromises));
|
|
2679
|
-
return new
|
|
2840
|
+
return new Elysia11().use(sessionCleanup({
|
|
2841
|
+
cleanupIntervalMs,
|
|
2842
|
+
maxSessions,
|
|
2843
|
+
onSessionCleanup
|
|
2844
|
+
})).use(signout({ onSignOut, signoutRoute })).use(revoke({
|
|
2680
2845
|
clientProviders,
|
|
2681
2846
|
onRevocationError,
|
|
2682
2847
|
onRevocationSuccess,
|
|
@@ -2685,7 +2850,8 @@ var absoluteAuth = async ({
|
|
|
2685
2850
|
clientProviders,
|
|
2686
2851
|
onRefreshError,
|
|
2687
2852
|
onRefreshSuccess,
|
|
2688
|
-
refreshRoute
|
|
2853
|
+
refreshRoute,
|
|
2854
|
+
sessionDurationMs
|
|
2689
2855
|
})).use(authorize({
|
|
2690
2856
|
authorizeRoute,
|
|
2691
2857
|
clientProviders,
|
|
@@ -2701,22 +2867,24 @@ var absoluteAuth = async ({
|
|
|
2701
2867
|
onProfileError,
|
|
2702
2868
|
onProfileSuccess,
|
|
2703
2869
|
profileRoute
|
|
2704
|
-
})).use(
|
|
2870
|
+
})).use(protectRoutePlugin());
|
|
2705
2871
|
};
|
|
2706
2872
|
export {
|
|
2707
2873
|
validateSession,
|
|
2708
2874
|
userSessionIdTypebox,
|
|
2709
2875
|
sessionStore,
|
|
2876
|
+
sessionCleanup,
|
|
2710
2877
|
scopeRequiredProviderOptions,
|
|
2711
2878
|
revocableProviderOptions,
|
|
2712
2879
|
refreshableProviderOptions,
|
|
2713
2880
|
providers,
|
|
2714
2881
|
providerOptions,
|
|
2715
|
-
|
|
2882
|
+
protectRoutePlugin,
|
|
2716
2883
|
pkceProviderOptions,
|
|
2717
2884
|
oidcProviderOptions,
|
|
2718
2885
|
isValidUser,
|
|
2719
2886
|
isValidProviderOption,
|
|
2887
|
+
isUserSessionId,
|
|
2720
2888
|
isRevocableProviderOption,
|
|
2721
2889
|
isRevocableOAuth2Client,
|
|
2722
2890
|
isRefreshableProviderOption,
|
|
@@ -2734,5 +2902,5 @@ export {
|
|
|
2734
2902
|
absoluteAuth
|
|
2735
2903
|
};
|
|
2736
2904
|
|
|
2737
|
-
//# debugId=
|
|
2905
|
+
//# debugId=305D4B9D1F128FE664756E2164756E21
|
|
2738
2906
|
//# sourceMappingURL=index.js.map
|