@absolutejs/auth 0.56.11 → 0.56.12

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.
@@ -16,7 +16,7 @@ export declare const createAuthContext: <UserType>({ agentAuth, authSessionStore
16
16
  unregisteredSession: import("./types").UnregisteredSessionRecord;
17
17
  };
18
18
  derive: ({
19
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
19
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
20
20
  readonly code: "Bad Request";
21
21
  readonly message: "Cookies are missing";
22
22
  } | {
@@ -34,7 +34,7 @@ export declare const createAuthContext: <UserType>({ agentAuth, authSessionStore
34
34
  message: "Agent is not authenticated" | "Insufficient agent scopes";
35
35
  }) => AuthFailReturn_2 | Promise<AuthFailReturn_2>) => Promise<Response | AuthReturn | NonNullable<Awaited<AuthFailReturn_2>>>;
36
36
  }) | ({
37
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
37
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
38
38
  readonly code: "Bad Request";
39
39
  readonly message: "Cookies are missing";
40
40
  } | {
@@ -88,7 +88,7 @@ export declare const createAuthContext: <UserType>({ agentAuth, authSessionStore
88
88
  macroFn: {};
89
89
  parser: {};
90
90
  response: import("elysia").ExtractErrorFromHandle<{
91
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
91
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
92
92
  readonly code: "Bad Request";
93
93
  readonly message: "Cookies are missing";
94
94
  } | {
@@ -38,7 +38,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
38
38
  unregisteredSession: import("..").UnregisteredSessionRecord;
39
39
  };
40
40
  derive: {
41
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
41
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
42
42
  readonly code: "Bad Request";
43
43
  readonly message: "Cookies are missing";
44
44
  } | {
@@ -68,7 +68,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
68
68
  macroFn: {};
69
69
  parser: {};
70
70
  response: import("elysia").ExtractErrorFromHandle<{
71
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
71
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
72
72
  readonly code: "Bad Request";
73
73
  readonly message: "Cookies are missing";
74
74
  } | {
@@ -231,7 +231,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
231
231
  response: {
232
232
  400: "Cookies are missing";
233
233
  401: "User is not authenticated";
234
- 200: {} | Response;
234
+ 200: Response;
235
235
  422: {
236
236
  type: "validation";
237
237
  on: string;
@@ -260,7 +260,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
260
260
  response: {
261
261
  400: "Cookies are missing";
262
262
  401: "User is not authenticated";
263
- 200: {} | Response;
263
+ 200: Response;
264
264
  422: {
265
265
  type: "validation";
266
266
  on: string;
@@ -290,7 +290,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
290
290
  response: {
291
291
  400: "Cookies are missing";
292
292
  401: "User is not authenticated";
293
- 200: {} | Response;
293
+ 200: Response;
294
294
  422: {
295
295
  type: "validation";
296
296
  on: string;
@@ -319,7 +319,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
319
319
  response: {
320
320
  400: "Cookies are missing";
321
321
  401: "User is not authenticated";
322
- 200: {} | Response;
322
+ 200: Response;
323
323
  422: {
324
324
  type: "validation";
325
325
  on: string;
@@ -348,7 +348,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
348
348
  response: {
349
349
  400: "Cookies are missing";
350
350
  401: "User is not authenticated";
351
- 200: {} | Response;
351
+ 200: Response;
352
352
  422: {
353
353
  type: "validation";
354
354
  on: string;
@@ -403,7 +403,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
403
403
  response: {
404
404
  400: "Cookies are missing";
405
405
  401: "User is not authenticated";
406
- 200: {} | Response;
406
+ 200: Response;
407
407
  422: {
408
408
  type: "validation";
409
409
  on: string;
@@ -434,7 +434,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
434
434
  response: {
435
435
  400: "Cookies are missing";
436
436
  401: "User is not authenticated";
437
- 200: {} | Response;
437
+ 200: Response;
438
438
  422: {
439
439
  type: "validation";
440
440
  on: string;
@@ -484,7 +484,7 @@ export declare const createConfiguredAuthHtmxRoutes: <UserType>({ authSessionSto
484
484
  response: {
485
485
  400: "Cookies are missing";
486
486
  401: "User is not authenticated";
487
- 200: {} | Response;
487
+ 200: Response;
488
488
  422: {
489
489
  type: "validation";
490
490
  on: string;
@@ -10,7 +10,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
10
10
  unregisteredSession: import("..").UnregisteredSessionRecord;
11
11
  };
12
12
  derive: {
13
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
13
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
14
14
  readonly code: "Bad Request";
15
15
  readonly message: "Cookies are missing";
16
16
  } | {
@@ -40,7 +40,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
40
40
  macroFn: {};
41
41
  parser: {};
42
42
  response: import("elysia").ExtractErrorFromHandle<{
43
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
43
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
44
44
  readonly code: "Bad Request";
45
45
  readonly message: "Cookies are missing";
46
46
  } | {
@@ -203,7 +203,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
203
203
  response: {
204
204
  400: "Cookies are missing";
205
205
  401: "User is not authenticated";
206
- 200: {} | Response;
206
+ 200: Response;
207
207
  422: {
208
208
  type: "validation";
209
209
  on: string;
@@ -232,7 +232,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
232
232
  response: {
233
233
  400: "Cookies are missing";
234
234
  401: "User is not authenticated";
235
- 200: {} | Response;
235
+ 200: Response;
236
236
  422: {
237
237
  type: "validation";
238
238
  on: string;
@@ -262,7 +262,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
262
262
  response: {
263
263
  400: "Cookies are missing";
264
264
  401: "User is not authenticated";
265
- 200: {} | Response;
265
+ 200: Response;
266
266
  422: {
267
267
  type: "validation";
268
268
  on: string;
@@ -291,7 +291,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
291
291
  response: {
292
292
  400: "Cookies are missing";
293
293
  401: "User is not authenticated";
294
- 200: {} | Response;
294
+ 200: Response;
295
295
  422: {
296
296
  type: "validation";
297
297
  on: string;
@@ -320,7 +320,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
320
320
  response: {
321
321
  400: "Cookies are missing";
322
322
  401: "User is not authenticated";
323
- 200: {} | Response;
323
+ 200: Response;
324
324
  422: {
325
325
  type: "validation";
326
326
  on: string;
@@ -375,7 +375,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
375
375
  response: {
376
376
  400: "Cookies are missing";
377
377
  401: "User is not authenticated";
378
- 200: {} | Response;
378
+ 200: Response;
379
379
  422: {
380
380
  type: "validation";
381
381
  on: string;
@@ -406,7 +406,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
406
406
  response: {
407
407
  400: "Cookies are missing";
408
408
  401: "User is not authenticated";
409
- 200: {} | Response;
409
+ 200: Response;
410
410
  422: {
411
411
  type: "validation";
412
412
  on: string;
@@ -456,7 +456,7 @@ export declare const createAuthHtmxRoutes: <UserType extends AuthHtmxUser>(confi
456
456
  response: {
457
457
  400: "Cookies are missing";
458
458
  401: "User is not authenticated";
459
- 200: {} | Response;
459
+ 200: Response;
460
460
  422: {
461
461
  type: "validation";
462
462
  on: string;
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export declare const auth: <UserType>(configuration: AuthConfig<UserType>) => Pr
7
7
  unregisteredSession: import("./types").UnregisteredSessionRecord;
8
8
  };
9
9
  derive: ({
10
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
10
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
11
11
  readonly code: "Bad Request";
12
12
  readonly message: "Cookies are missing";
13
13
  } | {
@@ -25,7 +25,7 @@ export declare const auth: <UserType>(configuration: AuthConfig<UserType>) => Pr
25
25
  message: "Agent is not authenticated" | "Insufficient agent scopes";
26
26
  }) => AuthFailReturn_2 | Promise<AuthFailReturn_2>) => Promise<Response | AuthReturn | NonNullable<Awaited<AuthFailReturn_2>>>;
27
27
  }) | ({
28
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
28
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
29
29
  readonly code: "Bad Request";
30
30
  readonly message: "Cookies are missing";
31
31
  } | {
@@ -86,7 +86,7 @@ export declare const auth: <UserType>(configuration: AuthConfig<UserType>) => Pr
86
86
  macroFn: {};
87
87
  parser: {};
88
88
  response: import("elysia").ExtractErrorFromHandle<{
89
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
89
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
90
90
  readonly code: "Bad Request";
91
91
  readonly message: "Cookies are missing";
92
92
  } | {
@@ -178,7 +178,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
178
178
  unregisteredSession: import("./types").UnregisteredSessionRecord;
179
179
  };
180
180
  derive: ({
181
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
181
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
182
182
  readonly code: "Bad Request";
183
183
  readonly message: "Cookies are missing";
184
184
  } | {
@@ -196,7 +196,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
196
196
  message: "Agent is not authenticated" | "Insufficient agent scopes";
197
197
  }) => AuthFailReturn_2 | Promise<AuthFailReturn_2>) => Promise<Response | AuthReturn | NonNullable<Awaited<AuthFailReturn_2>>>;
198
198
  }) | ({
199
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
199
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
200
200
  readonly code: "Bad Request";
201
201
  readonly message: "Cookies are missing";
202
202
  } | {
@@ -250,7 +250,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
250
250
  macroFn: {};
251
251
  parser: {};
252
252
  response: import("elysia").ExtractErrorFromHandle<{
253
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
253
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
254
254
  readonly code: "Bad Request";
255
255
  readonly message: "Cookies are missing";
256
256
  } | {
@@ -665,7 +665,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
665
665
  unregisteredSession: import("./types").UnregisteredSessionRecord;
666
666
  };
667
667
  derive: {} | {
668
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: import(".").AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
668
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: import(".").AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
669
669
  readonly code: "Bad Request";
670
670
  readonly message: "Cookies are missing";
671
671
  } | {
@@ -719,7 +719,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
719
719
  macroFn: {};
720
720
  parser: {};
721
721
  response: import("elysia").ExtractErrorFromHandle<{
722
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: import(".").AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
722
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: import(".").AuthHtmxUser) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: ((error: {
723
723
  readonly code: "Bad Request";
724
724
  readonly message: "Cookies are missing";
725
725
  } | {
@@ -3027,7 +3027,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3027
3027
  response: {
3028
3028
  400: "Cookies are missing";
3029
3029
  401: "User is not authenticated";
3030
- 200: {} | Response;
3030
+ 200: Response;
3031
3031
  422: {
3032
3032
  type: "validation";
3033
3033
  on: string;
@@ -3056,7 +3056,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3056
3056
  response: {
3057
3057
  400: "Cookies are missing";
3058
3058
  401: "User is not authenticated";
3059
- 200: {} | Response;
3059
+ 200: Response;
3060
3060
  422: {
3061
3061
  type: "validation";
3062
3062
  on: string;
@@ -3086,7 +3086,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3086
3086
  response: {
3087
3087
  400: "Cookies are missing";
3088
3088
  401: "User is not authenticated";
3089
- 200: {} | Response;
3089
+ 200: Response;
3090
3090
  422: {
3091
3091
  type: "validation";
3092
3092
  on: string;
@@ -3115,7 +3115,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3115
3115
  response: {
3116
3116
  400: "Cookies are missing";
3117
3117
  401: "User is not authenticated";
3118
- 200: {} | Response;
3118
+ 200: Response;
3119
3119
  422: {
3120
3120
  type: "validation";
3121
3121
  on: string;
@@ -3144,7 +3144,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3144
3144
  response: {
3145
3145
  400: "Cookies are missing";
3146
3146
  401: "User is not authenticated";
3147
- 200: {} | Response;
3147
+ 200: Response;
3148
3148
  422: {
3149
3149
  type: "validation";
3150
3150
  on: string;
@@ -3199,7 +3199,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3199
3199
  response: {
3200
3200
  400: "Cookies are missing";
3201
3201
  401: "User is not authenticated";
3202
- 200: {} | Response;
3202
+ 200: Response;
3203
3203
  422: {
3204
3204
  type: "validation";
3205
3205
  on: string;
@@ -3230,7 +3230,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3230
3230
  response: {
3231
3231
  400: "Cookies are missing";
3232
3232
  401: "User is not authenticated";
3233
- 200: {} | Response;
3233
+ 200: Response;
3234
3234
  422: {
3235
3235
  type: "validation";
3236
3236
  on: string;
@@ -3280,7 +3280,7 @@ export declare const createAuthApplications: <UserType>(configuration: AuthConfi
3280
3280
  response: {
3281
3281
  400: "Cookies are missing";
3282
3282
  401: "User is not authenticated";
3283
- 200: {} | Response;
3283
+ 200: Response;
3284
3284
  422: {
3285
3285
  type: "validation";
3286
3286
  on: string;
package/dist/index.js.map CHANGED
@@ -23,7 +23,7 @@
23
23
  "import type { Cookie } from 'elysia';\nimport { isUserSessionId } from '../typeGuards';\nimport type {\n\tSessionData,\n\tSessionRecord,\n\tUnregisteredSessionData,\n\tUnregisteredSessionRecord,\n\tUserSessionId\n} from '../types';\nimport type { AuthSessionSource, AuthSessionStore } from './types';\n\nconst collectSessionEntries = <UserType>(session: SessionRecord<UserType>) =>\n\tObject.entries(session).filter(\n\t\t(entry): entry is [UserSessionId, SessionData<UserType>] =>\n\t\t\tisUserSessionId(entry[0])\n\t);\n\nconst collectUnregisteredEntries = (\n\tunregisteredSession: UnregisteredSessionRecord\n) =>\n\tObject.entries(unregisteredSession).filter(\n\t\t(entry): entry is [UserSessionId, UnregisteredSessionData] =>\n\t\t\tisUserSessionId(entry[0])\n\t);\n\nconst removeStaleIds = (\n\tinitialIds: Set<UserSessionId>,\n\tnextIds: Set<UserSessionId>,\n\tremove: (id: UserSessionId) => Promise<void> | void\n) =>\n\tPromise.all(\n\t\t[...initialIds]\n\t\t\t.filter((initialId) => !nextIds.has(initialId))\n\t\t\t.map((initialId) => remove(initialId))\n\t);\n\nexport const createSessionCompatibilityLayer = async <UserType>({\n\tauthSessionStore,\n\tuserSessionId\n}: {\n\tauthSessionStore?: AuthSessionStore<UserType>;\n\tuserSessionId?: UserSessionId;\n}): Promise<{\n\tsession: SessionRecord<UserType>;\n\tunregisteredSession: UnregisteredSessionRecord;\n\tpersist: () => Promise<void>;\n}> => {\n\tconst session: SessionRecord<UserType> = {};\n\tconst unregisteredSession: UnregisteredSessionRecord = {};\n\n\tif (!authSessionStore) {\n\t\treturn {\n\t\t\tsession,\n\t\t\tunregisteredSession,\n\t\t\tpersist: async () => undefined\n\t\t};\n\t}\n\n\tconst initialSessionIds = new Set<UserSessionId>();\n\tconst initialUnregisteredIds = new Set<UserSessionId>();\n\n\tconst currentSession = userSessionId\n\t\t? await authSessionStore.getSession(userSessionId)\n\t\t: undefined;\n\tif (userSessionId && currentSession) {\n\t\tsession[userSessionId] = currentSession;\n\t\tinitialSessionIds.add(userSessionId);\n\t}\n\n\tconst currentUnregistered = userSessionId\n\t\t? await authSessionStore.getUnregisteredSession(userSessionId)\n\t\t: undefined;\n\tif (userSessionId && currentUnregistered) {\n\t\tunregisteredSession[userSessionId] = currentUnregistered;\n\t\tinitialUnregisteredIds.add(userSessionId);\n\t}\n\n\tconst persist = async () => {\n\t\tconst sessionEntries = collectSessionEntries(session);\n\t\tconst nextSessionIds = new Set(sessionEntries.map(([key]) => key));\n\t\tawait Promise.all(\n\t\t\tsessionEntries.map(([key, value]) =>\n\t\t\t\tauthSessionStore.setSession(key, value)\n\t\t\t)\n\t\t);\n\t\tawait removeStaleIds(initialSessionIds, nextSessionIds, (initialId) =>\n\t\t\tauthSessionStore.removeSession(initialId)\n\t\t);\n\n\t\tconst unregisteredEntries =\n\t\t\tcollectUnregisteredEntries(unregisteredSession);\n\t\tconst nextUnregisteredIds = new Set(\n\t\t\tunregisteredEntries.map(([key]) => key)\n\t\t);\n\t\tawait Promise.all(\n\t\t\tunregisteredEntries.map(([key, value]) =>\n\t\t\t\tauthSessionStore.setUnregisteredSession(key, value)\n\t\t\t)\n\t\t);\n\t\tawait removeStaleIds(\n\t\t\tinitialUnregisteredIds,\n\t\t\tnextUnregisteredIds,\n\t\t\t(initialId) => authSessionStore.removeUnregisteredSession(initialId)\n\t\t);\n\t};\n\n\treturn {\n\t\tpersist,\n\t\tsession,\n\t\tunregisteredSession\n\t};\n};\nconst removeSessionFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuserSessionId\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuserSessionId: UserSessionId;\n}) => {\n\tif (authSessionStore) {\n\t\tawait authSessionStore.removeSession(userSessionId);\n\n\t\treturn;\n\t}\n\n\tif (session) {\n\t\tdelete session[userSessionId];\n\t}\n};\nexport const getStatusFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuser_session_id\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n}) => {\n\tif (user_session_id === undefined) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'Bad Request',\n\t\t\t\tmessage: 'Cookies are missing'\n\t\t\t} as const,\n\t\t\tuser: null\n\t\t};\n\t}\n\n\tconst userSessionId = user_session_id.value;\n\tconst userSession = await loadSessionFromSource({\n\t\tauthSessionStore,\n\t\tsession,\n\t\tuserSessionId\n\t});\n\n\tif (!userSession && userSessionId) {\n\t\tuser_session_id.remove();\n\t}\n\n\treturn {\n\t\terror: null,\n\t\t// Surfaced so callers (userStatus, protectRoute) can show an \"impersonating\" banner\n\t\t// and gate sensitive actions — without it the impersonation is invisible to the app.\n\t\timpersonator: userSession?.impersonator,\n\t\tuser: userSession?.user ?? null\n\t};\n};\nexport const loadSessionFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuserSessionId,\n\tremoveExpired = true\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuserSessionId?: UserSessionId;\n\tremoveExpired?: boolean;\n}) => {\n\tif (!userSessionId) return undefined;\n\n\tconst userSession = authSessionStore\n\t\t? await authSessionStore.getSession(userSessionId)\n\t\t: session?.[userSessionId];\n\tif (!userSession) return undefined;\n\n\tif (removeExpired && userSession.expiresAt < Date.now()) {\n\t\tawait removeSessionFromSource({\n\t\t\tauthSessionStore,\n\t\t\tsession,\n\t\t\tuserSessionId\n\t\t});\n\n\t\treturn undefined;\n\t}\n\n\treturn userSession;\n};\n",
24
24
  "import { Elysia } from 'elysia';\nimport type { SessionRecord, UnregisteredSessionRecord } from '../types';\n\nexport const sessionStore = <UserType>() => {\n\tconst initialSession: SessionRecord<UserType> = {};\n\tconst initialUnregisteredSession: UnregisteredSessionRecord = {};\n\n\treturn new Elysia({ name: 'sessionStore' }).state({\n\t\tsession: initialSession,\n\t\tunregisteredSession: initialUnregisteredSession\n\t});\n};\n",
25
25
  "import { t } from 'elysia';\n\nexport const authClientOption = t.Optional(t.String());\nexport const authIntentOption = t.Optional(\n\tt.Union([\n\t\tt.Literal('login'),\n\t\tt.Literal('link_identity'),\n\t\tt.Literal('link_connector')\n\t])\n);\n// Any configured provider name — built-in OR customProviders key. The real\n// gate is resolveClientProviderEntry (unknown names 404); a static enum of\n// citra built-ins would wrongly reject caller-defined providers.\nexport const authProviderOption = t.String({ minLength: 1 });\nexport const userSessionIdTypebox = t.Optional(\n\tt.TemplateLiteral('${string}-${string}-${string}-${string}-${string}')\n);\n",
26
- "import { Elysia, t } from 'elysia';\nimport { getStatusFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype AuthFailError =\n\t| {\n\t\t\treadonly code: 'Bad Request';\n\t\t\treadonly message: 'Cookies are missing';\n\t }\n\t| {\n\t\t\treadonly code: 'Unauthorized';\n\t\t\treadonly message: 'User is not authenticated';\n\t };\n\nexport const protectRoutePlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/protect-route',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\tprotectRoute: <AuthReturn, AuthFailReturn>(\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: AuthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tgetStatusFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser_session_id\n\t\t\t\t\t}).then(async ({ user, error }) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.(error) ??\n\t\t\t\t\t\t\t\tstatus(error.code, error.message)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'User is not authenticated'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'User is not authenticated'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
26
+ "import { Elysia, t } from 'elysia';\nimport { getStatusFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype AuthFailError =\n\t| {\n\t\t\treadonly code: 'Bad Request';\n\t\t\treadonly message: 'Cookies are missing';\n\t }\n\t| {\n\t\t\treadonly code: 'Unauthorized';\n\t\t\treadonly message: 'User is not authenticated';\n\t };\n\nexport const protectRoutePlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/protect-route',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\tprotectRoute: <AuthReturn, AuthFailReturn = never>(\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: AuthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tgetStatusFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser_session_id\n\t\t\t\t\t}).then(async ({ user, error }) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.(error) ??\n\t\t\t\t\t\t\t\tstatus(error.code, error.message)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'User is not authenticated'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'User is not authenticated'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
27
27
  "import { Elysia, t } from 'elysia';\nimport { loadSessionFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype ReauthFailError = {\n\treadonly code: 'Unauthorized';\n\treadonly message: 'Recent authentication required';\n};\n\n// Step-up re-auth, usable alongside `protectRoute`. `requireRecentAuth(maxAgeMs, …)`\n// runs the handler only when the session was authenticated within the window — a token\n// refresh does NOT count, so sensitive actions can demand a fresh login / MFA.\nexport const stepUpPlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/step-up',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\trequireRecentAuth: <AuthReturn, AuthFailReturn>(\n\t\t\t\t\tmaxAgeMs: number,\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: ReauthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tloadSessionFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t\t}).then((userSession) => {\n\t\t\t\t\t\tconst authenticatedAt = userSession?.authenticatedAt;\n\t\t\t\t\t\tconst isRecent =\n\t\t\t\t\t\t\tauthenticatedAt !== undefined &&\n\t\t\t\t\t\t\tDate.now() - authenticatedAt <= maxAgeMs;\n\n\t\t\t\t\t\tif (!userSession || !isRecent) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'Recent authentication required'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'Recent authentication required'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(userSession.user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
28
28
  "import { Elysia, t } from 'elysia';\nimport { loadSessionFromSource } from '../session/access';\nimport { clearSession } from '../session/promote';\nimport { sessionStore } from '../session/state';\nimport { revokeUserSessions } from '../session/userSessions';\nimport { userSessionIdTypebox } from '../typebox';\nimport type { CompliancePluginProps } from './config';\n\nconst DEFAULT_COMPLIANCE_ROUTE = '/auth/account';\n\n// `GET {complianceRoute}/export` returns the caller's data (right to access). `DELETE\n// {complianceRoute}` erases it (right to erasure): it runs the consumer's delete hook, revokes\n// every session the user holds, and clears the caller's cookie. Both require an authenticated\n// caller and emit an audit event when `auth()` has an audit block.\nexport const complianceRoutes = <UserType>({\n\tauthSessionStore,\n\tcomplianceRoute = DEFAULT_COMPLIANCE_ROUTE,\n\tdeleteUserData,\n\temit,\n\texportUserData,\n\tgetUserId\n}: CompliancePluginProps<UserType>) =>\n\tnew Elysia()\n\t\t.use(sessionStore<UserType>())\n\t\t.get(\n\t\t\t`${complianceRoute}/export`,\n\t\t\tasync ({\n\t\t\t\tcookie: { user_session_id },\n\t\t\t\tstatus,\n\t\t\t\tstore: { session }\n\t\t\t}) => {\n\t\t\t\tconst current = await loadSessionFromSource({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tsession,\n\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t});\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn status('Unauthorized', 'Authentication required');\n\t\t\t\t}\n\n\t\t\t\tconst data = await exportUserData({ user: current.user });\n\t\t\t\tawait emit?.({\n\t\t\t\t\tat: Date.now(),\n\t\t\t\t\ttype: 'data_exported',\n\t\t\t\t\tuserId: getUserId?.(current.user)\n\t\t\t\t});\n\n\t\t\t\treturn status('OK', data);\n\t\t\t},\n\t\t\t{ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) }\n\t\t)\n\t\t.delete(\n\t\t\tcomplianceRoute,\n\t\t\tasync ({\n\t\t\t\tcookie: { user_session_id },\n\t\t\t\tstatus,\n\t\t\t\tstore: { session }\n\t\t\t}) => {\n\t\t\t\tconst current = await loadSessionFromSource({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tsession,\n\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t});\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn status('Unauthorized', 'Authentication required');\n\t\t\t\t}\n\n\t\t\t\tconst userId = getUserId?.(current.user);\n\t\t\t\tawait deleteUserData({ user: current.user, userId });\n\t\t\t\tif (authSessionStore && getUserId && userId !== undefined) {\n\t\t\t\t\tawait revokeUserSessions({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tgetUserId,\n\t\t\t\t\t\tuserId\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait clearSession({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tcookie: user_session_id,\n\t\t\t\t\tinMemorySession: session\n\t\t\t\t});\n\t\t\t\tawait emit?.({\n\t\t\t\t\tat: Date.now(),\n\t\t\t\t\ttype: 'account_deleted',\n\t\t\t\t\tuserId\n\t\t\t\t});\n\n\t\t\t\treturn status('OK', { deleted: true });\n\t\t\t},\n\t\t\t{ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) }\n\t\t);\n",
29
29
  "import {\n\tdecodeJWT,\n\tisValidProviderOption,\n\tnormalizeProviderIdentity,\n\tOAuth2Client,\n\tOAuth2TokenResponse,\n\tProviderConfig,\n\tProviderOption,\n\tproviders\n} from 'citra';\nimport { Cookie } from 'elysia';\nimport { MILLISECONDS_IN_A_DAY, MILLISECONDS_IN_AN_HOUR } from './constants';\nimport { AuthHtmxConfig } from './htmx/types';\nimport { isNonEmptyString, isStatusResponse } from './typeGuards';\nimport {\n\tAuthConfig,\n\tAuthSettings,\n\tInsantiateUserSessionProps,\n\tOAuth2ConfigurationOptions,\n\tResolvedOAuthAuthorization,\n\tSessionRecord,\n\tUnregisteredSessionRecord,\n\tUserSessionId\n} from './types';\n\nconst MILLISECONDS_IN_A_SECOND = 1000;\n\nconst readOptionalString = (value: object, property: string) => {\n\tconst candidate: unknown = Reflect.get(value, property);\n\n\treturn typeof candidate === 'string' ? candidate : undefined;\n};\n\nexport const defineAuthConfig = <UserType>(\n\tconfiguration: AuthConfig<UserType>\n) => configuration;\nexport const defineAuthHtmxConfig = (htmxConfig: AuthHtmxConfig) => htmxConfig;\nexport const defineAuthSettings = (settings: AuthSettings) => settings;\nexport const defineProvidersConfiguration = (\n\tprovidersConfiguration: OAuth2ConfigurationOptions\n) => providersConfiguration;\nexport const getStatus = async <UserType>(\n\tsession: SessionRecord<UserType>,\n\tuser_session_id: Cookie<UserSessionId | undefined>\n) => {\n\tif (user_session_id === undefined) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'Bad Request',\n\t\t\t\tmessage: 'Cookies are missing'\n\t\t\t} as const,\n\t\t\tuser: null\n\t\t};\n\t}\n\n\tconst userSession = validateSession({ session, user_session_id });\n\tconst user = userSession?.user ?? null;\n\n\treturn {\n\t\terror: null,\n\t\tuser\n\t};\n};\nexport const instantiateUserSession = async <UserType>({\n\tauthProvider,\n\tproviderConfiguration,\n\tcookieSecure,\n\tsession,\n\tuser_session_id,\n\tunregisteredSession,\n\ttokenResponse,\n\tproviderInstance,\n\tgetUser,\n\tonNewUser,\n\tresolvedAuthorization,\n\tsessionDurationMs = MILLISECONDS_IN_A_DAY,\n\tunregisteredSessionDurationMs = MILLISECONDS_IN_AN_HOUR\n}: InsantiateUserSessionProps<UserType>) => {\n\tconst authorization =\n\t\tresolvedAuthorization ??\n\t\t(await resolveOAuthAuthorization({\n\t\t\tauthProvider,\n\t\t\tproviderConfiguration,\n\t\t\tproviderInstance,\n\t\t\ttokenResponse\n\t\t}));\n\tconst { accessToken, refreshToken, userIdentity } = authorization;\n\n\tconst userSession = validateSession({ session, user_session_id });\n\tconst userSessionId = getUserSessionId({\n\t\tcookieSecure,\n\t\tsession,\n\t\tunregisteredSession,\n\t\tuser_session_id\n\t});\n\n\tlet user = userSession?.user ?? (await getUser(userIdentity));\n\tconst response = user ?? (await onNewUser(userIdentity));\n\n\tconst isRedirectOrStatus =\n\t\tresponse instanceof Response || isStatusResponse(response);\n\n\tif (!isRedirectOrStatus) {\n\t\tuser = response;\n\n\t\tsession[userSessionId] = {\n\t\t\taccessToken,\n\t\t\tauthenticatedAt: Date.now(),\n\t\t\texpiresAt: Date.now() + sessionDurationMs,\n\t\t\trefreshToken,\n\t\t\tuser\n\t\t};\n\n\t\treturn void 0;\n\t}\n\n\tconst existingUnregistered = unregisteredSession[userSessionId];\n\n\tif (existingUnregistered) {\n\t\texistingUnregistered.accessToken = accessToken;\n\t\texistingUnregistered.expiresAt =\n\t\t\tDate.now() + unregisteredSessionDurationMs;\n\t\texistingUnregistered.refreshToken = refreshToken;\n\t\texistingUnregistered.userIdentity = userIdentity;\n\n\t\treturn response;\n\t}\n\n\tunregisteredSession[userSessionId] = {\n\t\taccessToken,\n\t\texpiresAt: Date.now() + unregisteredSessionDurationMs,\n\t\trefreshToken,\n\t\tuserIdentity\n\t};\n\n\treturn response;\n};\n// Cookie Secure flag resolution. Default convention: only set Secure=true in production\n// (matches express-session, iron-session, lucia, better-auth). Hardcoding Secure=true broke\n// non-browser HTTP clients (curl, SSR fetch, Playwright API contexts, test runners) on\n// http://localhost in dev, because they don't honor the browser's \"localhost is a secure\n// context\" exemption. An explicit `cookieSecure` on AuthConfig overrides; otherwise we\n// look at NODE_ENV. Consumers running prod without NODE_ENV=production should set\n// `cookieSecure: true` explicitly.\nexport const resolveCookieSecure = (override?: boolean) =>\n\toverride ?? process.env.NODE_ENV === 'production';\nexport const resolveOAuthAuthorization = async ({\n\tauthProvider,\n\tproviderConfiguration,\n\tproviderInstance,\n\ttokenResponse,\n\tnow = Date.now()\n}: {\n\tauthProvider: string;\n\tproviderConfiguration?: ProviderConfig;\n\tproviderInstance: Pick<OAuth2Client<ProviderOption>, 'fetchUserProfile'>;\n\ttokenResponse: OAuth2TokenResponse;\n\tnow?: number;\n}): Promise<ResolvedOAuthAuthorization> => {\n\t// Custom providers aren't in the citra registry — their config must be\n\t// supplied by the caller (the callback context carries it).\n\tconst meta =\n\t\tproviderConfiguration ??\n\t\t(isValidProviderOption(authProvider)\n\t\t\t? providers[authProvider]\n\t\t\t: undefined);\n\tif (!meta) {\n\t\tthrow new Error(\n\t\t\t`No provider configuration for \"${authProvider}\" — pass providerConfiguration (custom provider) or resolvedAuthorization`\n\t\t);\n\t}\n\tlet userIdentity;\n\tlet accessToken = tokenResponse.access_token;\n\tlet refreshToken = tokenResponse.refresh_token;\n\tconst withingsBody: unknown = Reflect.get(tokenResponse, 'body');\n\tconst withingsAccessToken =\n\t\tauthProvider === 'withings' &&\n\t\twithingsBody !== null &&\n\t\ttypeof withingsBody === 'object'\n\t\t\t? readOptionalString(withingsBody, 'access_token')\n\t\t\t: undefined;\n\tif (!accessToken && withingsAccessToken !== undefined) {\n\t\taccessToken = withingsAccessToken;\n\t}\n\tif (typeof accessToken !== 'string' || accessToken.length === 0) {\n\t\tthrow new Error(\n\t\t\t'OAuth authorization response contains no access_token'\n\t\t);\n\t}\n\n\tif (tokenResponse.id_token) {\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: decodeJWT(tokenResponse.id_token),\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'idToken'\n\t\t});\n\t} else if (meta.subjectBySource?.tokenResponse) {\n\t\t// Providers whose connected identity is returned as top-level fields in\n\t\t// the token-exchange response (e.g. GoHighLevel's locationId) rather than\n\t\t// via a profile endpoint. Read the subject straight from the token\n\t\t// response and skip the profile call.\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: tokenResponse,\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'tokenResponse'\n\t\t});\n\t} else if (authProvider === 'withings') {\n\t\tif (withingsBody === null || typeof withingsBody !== 'object') {\n\t\t\tthrow new Error('Withings OAuth response contains no body');\n\t\t}\n\t\tuserIdentity = { userid: Reflect.get(withingsBody, 'userid') };\n\t\taccessToken =\n\t\t\treadOptionalString(withingsBody, 'access_token') ?? accessToken;\n\t\trefreshToken =\n\t\t\treadOptionalString(withingsBody, 'refresh_token') ?? refreshToken;\n\t} else {\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: await providerInstance.fetchUserProfile(accessToken),\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'profile'\n\t\t});\n\t}\n\n\tconst tokenType: unknown = Reflect.get(tokenResponse, 'token_type');\n\n\treturn {\n\t\taccessToken,\n\t\texpiresAt: resolveOAuthTokenExpiresAt(tokenResponse, now),\n\t\trefreshToken,\n\t\ttokenType: typeof tokenType === 'string' ? tokenType : undefined,\n\t\tuserIdentity\n\t};\n};\nconst parseExpiresInSeconds = (expiresIn: unknown) => {\n\tif (typeof expiresIn === 'number') {\n\t\treturn expiresIn;\n\t}\n\n\tif (typeof expiresIn === 'string' && expiresIn.trim().length > 0) {\n\t\treturn Number(expiresIn);\n\t}\n\n\treturn Number.NaN;\n};\n\nexport const resolveOAuthTokenExpiresAt = (\n\ttokenResponse: OAuth2TokenResponse,\n\tnow = Date.now()\n) => {\n\tconst expiresIn: unknown = Reflect.get(tokenResponse, 'expires_in');\n\tconst expiresInSeconds = parseExpiresInSeconds(expiresIn);\n\n\tif (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) {\n\t\treturn undefined;\n\t}\n\n\treturn now + expiresInSeconds * MILLISECONDS_IN_A_SECOND;\n};\n\ntype ValidateSessionProps<\n\tSessionType extends Record<string, unknown> & { expiresAt: number }\n> = {\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n\tsession: Record<UserSessionId, SessionType>;\n};\n\nexport const validateSession = <\n\tSessionType extends Record<string, unknown> & { expiresAt: number }\n>({\n\tuser_session_id,\n\tsession\n}: ValidateSessionProps<SessionType>) => {\n\tconst userSessionId = user_session_id.value;\n\tif (!userSessionId) {\n\t\treturn undefined;\n\t}\n\n\tconst userSession = session[userSessionId];\n\tif (!userSession) {\n\t\treturn undefined;\n\t}\n\n\tconst isExpired = userSession.expiresAt < Date.now();\n\tif (isExpired) {\n\t\tdelete session[userSessionId];\n\t\tuser_session_id.remove();\n\n\t\treturn undefined;\n\t}\n\n\treturn userSession;\n};\n\ntype GetUserSessionIdProps<UserType> = {\n\tcookieSecure?: boolean;\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n\tsession?: SessionRecord<UserType>;\n\tunregisteredSession?: UnregisteredSessionRecord;\n};\n\nconst clearExistingSession = <UserType>(\n\texistingId: UserSessionId,\n\tsession?: SessionRecord<UserType>,\n\tunregisteredSession?: UnregisteredSessionRecord\n) => {\n\tif (session) delete session[existingId];\n\tif (unregisteredSession) delete unregisteredSession[existingId];\n};\n\nexport const getUserSessionId = <UserType>({\n\tcookieSecure,\n\tuser_session_id,\n\tsession,\n\tunregisteredSession\n}: GetUserSessionIdProps<UserType>) => {\n\tconst existingId = user_session_id?.value;\n\n\tif (isNonEmptyString(existingId)) {\n\t\tclearExistingSession(existingId, session, unregisteredSession);\n\t}\n\n\tconst userSessionId = crypto.randomUUID();\n\n\tuser_session_id.set({\n\t\thttpOnly: true,\n\t\tsameSite: 'lax',\n\t\tsecure: resolveCookieSecure(cookieSecure),\n\t\tvalue: userSessionId\n\t});\n\n\treturn userSessionId;\n};\n",
@@ -16,7 +16,7 @@ export declare const protectRoutePlugin: <UserType>({ authSessionStore }?: {
16
16
  unregisteredSession: import("..").UnregisteredSessionRecord;
17
17
  };
18
18
  derive: {
19
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: (error: AuthFailError) => AuthFailReturn) => Promise<import("elysia").ElysiaCustomStatusResponse<"Bad Request", "Cookies are missing", 400> | import("elysia").ElysiaCustomStatusResponse<"Unauthorized", "User is not authenticated", 401> | AuthReturn | NonNullable<AuthFailReturn>>;
19
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: (error: AuthFailError) => AuthFailReturn) => Promise<import("elysia").ElysiaCustomStatusResponse<"Bad Request", "Cookies are missing", 400> | import("elysia").ElysiaCustomStatusResponse<"Unauthorized", "User is not authenticated", 401> | AuthReturn | NonNullable<AuthFailReturn>>;
20
20
  };
21
21
  resolve: {};
22
22
  }, {
@@ -33,7 +33,7 @@ export declare const protectRoutePlugin: <UserType>({ authSessionStore }?: {
33
33
  macroFn: {};
34
34
  parser: {};
35
35
  response: import("elysia").ExtractErrorFromHandle<{
36
- readonly protectRoute: <AuthReturn, AuthFailReturn>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: (error: AuthFailError) => AuthFailReturn) => Promise<import("elysia").ElysiaCustomStatusResponse<"Bad Request", "Cookies are missing", 400> | import("elysia").ElysiaCustomStatusResponse<"Unauthorized", "User is not authenticated", 401> | AuthReturn | NonNullable<AuthFailReturn>>;
36
+ readonly protectRoute: <AuthReturn, AuthFailReturn = never>(handleAuth: (user: UserType) => AuthReturn | Promise<AuthReturn>, handleAuthFail?: (error: AuthFailError) => AuthFailReturn) => Promise<import("elysia").ElysiaCustomStatusResponse<"Bad Request", "Cookies are missing", 400> | import("elysia").ElysiaCustomStatusResponse<"Unauthorized", "User is not authenticated", 401> | AuthReturn | NonNullable<AuthFailReturn>>;
37
37
  }>;
38
38
  }, {}, {
39
39
  derive: {};
@@ -23,7 +23,7 @@
23
23
  "import type { Cookie } from 'elysia';\nimport { isUserSessionId } from '../typeGuards';\nimport type {\n\tSessionData,\n\tSessionRecord,\n\tUnregisteredSessionData,\n\tUnregisteredSessionRecord,\n\tUserSessionId\n} from '../types';\nimport type { AuthSessionSource, AuthSessionStore } from './types';\n\nconst collectSessionEntries = <UserType>(session: SessionRecord<UserType>) =>\n\tObject.entries(session).filter(\n\t\t(entry): entry is [UserSessionId, SessionData<UserType>] =>\n\t\t\tisUserSessionId(entry[0])\n\t);\n\nconst collectUnregisteredEntries = (\n\tunregisteredSession: UnregisteredSessionRecord\n) =>\n\tObject.entries(unregisteredSession).filter(\n\t\t(entry): entry is [UserSessionId, UnregisteredSessionData] =>\n\t\t\tisUserSessionId(entry[0])\n\t);\n\nconst removeStaleIds = (\n\tinitialIds: Set<UserSessionId>,\n\tnextIds: Set<UserSessionId>,\n\tremove: (id: UserSessionId) => Promise<void> | void\n) =>\n\tPromise.all(\n\t\t[...initialIds]\n\t\t\t.filter((initialId) => !nextIds.has(initialId))\n\t\t\t.map((initialId) => remove(initialId))\n\t);\n\nexport const createSessionCompatibilityLayer = async <UserType>({\n\tauthSessionStore,\n\tuserSessionId\n}: {\n\tauthSessionStore?: AuthSessionStore<UserType>;\n\tuserSessionId?: UserSessionId;\n}): Promise<{\n\tsession: SessionRecord<UserType>;\n\tunregisteredSession: UnregisteredSessionRecord;\n\tpersist: () => Promise<void>;\n}> => {\n\tconst session: SessionRecord<UserType> = {};\n\tconst unregisteredSession: UnregisteredSessionRecord = {};\n\n\tif (!authSessionStore) {\n\t\treturn {\n\t\t\tsession,\n\t\t\tunregisteredSession,\n\t\t\tpersist: async () => undefined\n\t\t};\n\t}\n\n\tconst initialSessionIds = new Set<UserSessionId>();\n\tconst initialUnregisteredIds = new Set<UserSessionId>();\n\n\tconst currentSession = userSessionId\n\t\t? await authSessionStore.getSession(userSessionId)\n\t\t: undefined;\n\tif (userSessionId && currentSession) {\n\t\tsession[userSessionId] = currentSession;\n\t\tinitialSessionIds.add(userSessionId);\n\t}\n\n\tconst currentUnregistered = userSessionId\n\t\t? await authSessionStore.getUnregisteredSession(userSessionId)\n\t\t: undefined;\n\tif (userSessionId && currentUnregistered) {\n\t\tunregisteredSession[userSessionId] = currentUnregistered;\n\t\tinitialUnregisteredIds.add(userSessionId);\n\t}\n\n\tconst persist = async () => {\n\t\tconst sessionEntries = collectSessionEntries(session);\n\t\tconst nextSessionIds = new Set(sessionEntries.map(([key]) => key));\n\t\tawait Promise.all(\n\t\t\tsessionEntries.map(([key, value]) =>\n\t\t\t\tauthSessionStore.setSession(key, value)\n\t\t\t)\n\t\t);\n\t\tawait removeStaleIds(initialSessionIds, nextSessionIds, (initialId) =>\n\t\t\tauthSessionStore.removeSession(initialId)\n\t\t);\n\n\t\tconst unregisteredEntries =\n\t\t\tcollectUnregisteredEntries(unregisteredSession);\n\t\tconst nextUnregisteredIds = new Set(\n\t\t\tunregisteredEntries.map(([key]) => key)\n\t\t);\n\t\tawait Promise.all(\n\t\t\tunregisteredEntries.map(([key, value]) =>\n\t\t\t\tauthSessionStore.setUnregisteredSession(key, value)\n\t\t\t)\n\t\t);\n\t\tawait removeStaleIds(\n\t\t\tinitialUnregisteredIds,\n\t\t\tnextUnregisteredIds,\n\t\t\t(initialId) => authSessionStore.removeUnregisteredSession(initialId)\n\t\t);\n\t};\n\n\treturn {\n\t\tpersist,\n\t\tsession,\n\t\tunregisteredSession\n\t};\n};\nconst removeSessionFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuserSessionId\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuserSessionId: UserSessionId;\n}) => {\n\tif (authSessionStore) {\n\t\tawait authSessionStore.removeSession(userSessionId);\n\n\t\treturn;\n\t}\n\n\tif (session) {\n\t\tdelete session[userSessionId];\n\t}\n};\nexport const getStatusFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuser_session_id\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n}) => {\n\tif (user_session_id === undefined) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'Bad Request',\n\t\t\t\tmessage: 'Cookies are missing'\n\t\t\t} as const,\n\t\t\tuser: null\n\t\t};\n\t}\n\n\tconst userSessionId = user_session_id.value;\n\tconst userSession = await loadSessionFromSource({\n\t\tauthSessionStore,\n\t\tsession,\n\t\tuserSessionId\n\t});\n\n\tif (!userSession && userSessionId) {\n\t\tuser_session_id.remove();\n\t}\n\n\treturn {\n\t\terror: null,\n\t\t// Surfaced so callers (userStatus, protectRoute) can show an \"impersonating\" banner\n\t\t// and gate sensitive actions — without it the impersonation is invisible to the app.\n\t\timpersonator: userSession?.impersonator,\n\t\tuser: userSession?.user ?? null\n\t};\n};\nexport const loadSessionFromSource = async <UserType>({\n\tauthSessionStore,\n\tsession,\n\tuserSessionId,\n\tremoveExpired = true\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n\tsession?: SessionRecord<UserType>;\n\tuserSessionId?: UserSessionId;\n\tremoveExpired?: boolean;\n}) => {\n\tif (!userSessionId) return undefined;\n\n\tconst userSession = authSessionStore\n\t\t? await authSessionStore.getSession(userSessionId)\n\t\t: session?.[userSessionId];\n\tif (!userSession) return undefined;\n\n\tif (removeExpired && userSession.expiresAt < Date.now()) {\n\t\tawait removeSessionFromSource({\n\t\t\tauthSessionStore,\n\t\t\tsession,\n\t\t\tuserSessionId\n\t\t});\n\n\t\treturn undefined;\n\t}\n\n\treturn userSession;\n};\n",
24
24
  "import { Elysia } from 'elysia';\nimport type { SessionRecord, UnregisteredSessionRecord } from '../types';\n\nexport const sessionStore = <UserType>() => {\n\tconst initialSession: SessionRecord<UserType> = {};\n\tconst initialUnregisteredSession: UnregisteredSessionRecord = {};\n\n\treturn new Elysia({ name: 'sessionStore' }).state({\n\t\tsession: initialSession,\n\t\tunregisteredSession: initialUnregisteredSession\n\t});\n};\n",
25
25
  "import { t } from 'elysia';\n\nexport const authClientOption = t.Optional(t.String());\nexport const authIntentOption = t.Optional(\n\tt.Union([\n\t\tt.Literal('login'),\n\t\tt.Literal('link_identity'),\n\t\tt.Literal('link_connector')\n\t])\n);\n// Any configured provider name — built-in OR customProviders key. The real\n// gate is resolveClientProviderEntry (unknown names 404); a static enum of\n// citra built-ins would wrongly reject caller-defined providers.\nexport const authProviderOption = t.String({ minLength: 1 });\nexport const userSessionIdTypebox = t.Optional(\n\tt.TemplateLiteral('${string}-${string}-${string}-${string}-${string}')\n);\n",
26
- "import { Elysia, t } from 'elysia';\nimport { getStatusFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype AuthFailError =\n\t| {\n\t\t\treadonly code: 'Bad Request';\n\t\t\treadonly message: 'Cookies are missing';\n\t }\n\t| {\n\t\t\treadonly code: 'Unauthorized';\n\t\t\treadonly message: 'User is not authenticated';\n\t };\n\nexport const protectRoutePlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/protect-route',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\tprotectRoute: <AuthReturn, AuthFailReturn>(\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: AuthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tgetStatusFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser_session_id\n\t\t\t\t\t}).then(async ({ user, error }) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.(error) ??\n\t\t\t\t\t\t\t\tstatus(error.code, error.message)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'User is not authenticated'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'User is not authenticated'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
26
+ "import { Elysia, t } from 'elysia';\nimport { getStatusFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype AuthFailError =\n\t| {\n\t\t\treadonly code: 'Bad Request';\n\t\t\treadonly message: 'Cookies are missing';\n\t }\n\t| {\n\t\t\treadonly code: 'Unauthorized';\n\t\t\treadonly message: 'User is not authenticated';\n\t };\n\nexport const protectRoutePlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/protect-route',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\tprotectRoute: <AuthReturn, AuthFailReturn = never>(\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: AuthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tgetStatusFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuser_session_id\n\t\t\t\t\t}).then(async ({ user, error }) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.(error) ??\n\t\t\t\t\t\t\t\tstatus(error.code, error.message)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!user) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'User is not authenticated'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'User is not authenticated'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
27
27
  "import { Elysia, t } from 'elysia';\nimport { loadSessionFromSource } from '../session/access';\nimport { sessionStore } from '../session/state';\nimport type { AuthSessionSource } from '../session/types';\nimport { userSessionIdTypebox } from '../typebox';\nimport { pluginDependencySeed } from '../pluginIdentity';\n\ntype ReauthFailError = {\n\treadonly code: 'Unauthorized';\n\treadonly message: 'Recent authentication required';\n};\n\n// Step-up re-auth, usable alongside `protectRoute`. `requireRecentAuth(maxAgeMs, …)`\n// runs the handler only when the session was authenticated within the window — a token\n// refresh does NOT count, so sensitive actions can demand a fresh login / MFA.\nexport const stepUpPlugin = <UserType>({\n\tauthSessionStore\n}: {\n\tauthSessionStore?: AuthSessionSource<UserType>;\n} = {}) =>\n\tnew Elysia({\n\t\tname: '@absolutejs/auth/step-up',\n\t\tseed: pluginDependencySeed(authSessionStore)\n\t})\n\t\t.use(sessionStore<UserType>())\n\t\t.guard({ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) })\n\t\t.derive(\n\t\t\t({ store: { session }, cookie: { user_session_id }, status }) => ({\n\t\t\t\trequireRecentAuth: <AuthReturn, AuthFailReturn>(\n\t\t\t\t\tmaxAgeMs: number,\n\t\t\t\t\thandleAuth: (\n\t\t\t\t\t\tuser: UserType\n\t\t\t\t\t) => AuthReturn | Promise<AuthReturn>,\n\t\t\t\t\thandleAuthFail?: (error: ReauthFailError) => AuthFailReturn\n\t\t\t\t) =>\n\t\t\t\t\tloadSessionFromSource<UserType>({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tsession,\n\t\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t\t}).then((userSession) => {\n\t\t\t\t\t\tconst authenticatedAt = userSession?.authenticatedAt;\n\t\t\t\t\t\tconst isRecent =\n\t\t\t\t\t\t\tauthenticatedAt !== undefined &&\n\t\t\t\t\t\t\tDate.now() - authenticatedAt <= maxAgeMs;\n\n\t\t\t\t\t\tif (!userSession || !isRecent) {\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\thandleAuthFail?.({\n\t\t\t\t\t\t\t\t\tcode: 'Unauthorized',\n\t\t\t\t\t\t\t\t\tmessage: 'Recent authentication required'\n\t\t\t\t\t\t\t\t}) ??\n\t\t\t\t\t\t\t\tstatus(\n\t\t\t\t\t\t\t\t\t'Unauthorized',\n\t\t\t\t\t\t\t\t\t'Recent authentication required'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn handleAuth(userSession.user);\n\t\t\t\t\t})\n\t\t\t})\n\t\t)\n\t\t.as('global');\n",
28
28
  "import { Elysia, t } from 'elysia';\nimport { loadSessionFromSource } from '../session/access';\nimport { clearSession } from '../session/promote';\nimport { sessionStore } from '../session/state';\nimport { revokeUserSessions } from '../session/userSessions';\nimport { userSessionIdTypebox } from '../typebox';\nimport type { CompliancePluginProps } from './config';\n\nconst DEFAULT_COMPLIANCE_ROUTE = '/auth/account';\n\n// `GET {complianceRoute}/export` returns the caller's data (right to access). `DELETE\n// {complianceRoute}` erases it (right to erasure): it runs the consumer's delete hook, revokes\n// every session the user holds, and clears the caller's cookie. Both require an authenticated\n// caller and emit an audit event when `auth()` has an audit block.\nexport const complianceRoutes = <UserType>({\n\tauthSessionStore,\n\tcomplianceRoute = DEFAULT_COMPLIANCE_ROUTE,\n\tdeleteUserData,\n\temit,\n\texportUserData,\n\tgetUserId\n}: CompliancePluginProps<UserType>) =>\n\tnew Elysia()\n\t\t.use(sessionStore<UserType>())\n\t\t.get(\n\t\t\t`${complianceRoute}/export`,\n\t\t\tasync ({\n\t\t\t\tcookie: { user_session_id },\n\t\t\t\tstatus,\n\t\t\t\tstore: { session }\n\t\t\t}) => {\n\t\t\t\tconst current = await loadSessionFromSource({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tsession,\n\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t});\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn status('Unauthorized', 'Authentication required');\n\t\t\t\t}\n\n\t\t\t\tconst data = await exportUserData({ user: current.user });\n\t\t\t\tawait emit?.({\n\t\t\t\t\tat: Date.now(),\n\t\t\t\t\ttype: 'data_exported',\n\t\t\t\t\tuserId: getUserId?.(current.user)\n\t\t\t\t});\n\n\t\t\t\treturn status('OK', data);\n\t\t\t},\n\t\t\t{ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) }\n\t\t)\n\t\t.delete(\n\t\t\tcomplianceRoute,\n\t\t\tasync ({\n\t\t\t\tcookie: { user_session_id },\n\t\t\t\tstatus,\n\t\t\t\tstore: { session }\n\t\t\t}) => {\n\t\t\t\tconst current = await loadSessionFromSource({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tsession,\n\t\t\t\t\tuserSessionId: user_session_id.value\n\t\t\t\t});\n\t\t\t\tif (!current) {\n\t\t\t\t\treturn status('Unauthorized', 'Authentication required');\n\t\t\t\t}\n\n\t\t\t\tconst userId = getUserId?.(current.user);\n\t\t\t\tawait deleteUserData({ user: current.user, userId });\n\t\t\t\tif (authSessionStore && getUserId && userId !== undefined) {\n\t\t\t\t\tawait revokeUserSessions({\n\t\t\t\t\t\tauthSessionStore,\n\t\t\t\t\t\tgetUserId,\n\t\t\t\t\t\tuserId\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tawait clearSession({\n\t\t\t\t\tauthSessionStore,\n\t\t\t\t\tcookie: user_session_id,\n\t\t\t\t\tinMemorySession: session\n\t\t\t\t});\n\t\t\t\tawait emit?.({\n\t\t\t\t\tat: Date.now(),\n\t\t\t\t\ttype: 'account_deleted',\n\t\t\t\t\tuserId\n\t\t\t\t});\n\n\t\t\t\treturn status('OK', { deleted: true });\n\t\t\t},\n\t\t\t{ cookie: t.Cookie({ user_session_id: userSessionIdTypebox }) }\n\t\t);\n",
29
29
  "import {\n\tdecodeJWT,\n\tisValidProviderOption,\n\tnormalizeProviderIdentity,\n\tOAuth2Client,\n\tOAuth2TokenResponse,\n\tProviderConfig,\n\tProviderOption,\n\tproviders\n} from 'citra';\nimport { Cookie } from 'elysia';\nimport { MILLISECONDS_IN_A_DAY, MILLISECONDS_IN_AN_HOUR } from './constants';\nimport { AuthHtmxConfig } from './htmx/types';\nimport { isNonEmptyString, isStatusResponse } from './typeGuards';\nimport {\n\tAuthConfig,\n\tAuthSettings,\n\tInsantiateUserSessionProps,\n\tOAuth2ConfigurationOptions,\n\tResolvedOAuthAuthorization,\n\tSessionRecord,\n\tUnregisteredSessionRecord,\n\tUserSessionId\n} from './types';\n\nconst MILLISECONDS_IN_A_SECOND = 1000;\n\nconst readOptionalString = (value: object, property: string) => {\n\tconst candidate: unknown = Reflect.get(value, property);\n\n\treturn typeof candidate === 'string' ? candidate : undefined;\n};\n\nexport const defineAuthConfig = <UserType>(\n\tconfiguration: AuthConfig<UserType>\n) => configuration;\nexport const defineAuthHtmxConfig = (htmxConfig: AuthHtmxConfig) => htmxConfig;\nexport const defineAuthSettings = (settings: AuthSettings) => settings;\nexport const defineProvidersConfiguration = (\n\tprovidersConfiguration: OAuth2ConfigurationOptions\n) => providersConfiguration;\nexport const getStatus = async <UserType>(\n\tsession: SessionRecord<UserType>,\n\tuser_session_id: Cookie<UserSessionId | undefined>\n) => {\n\tif (user_session_id === undefined) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'Bad Request',\n\t\t\t\tmessage: 'Cookies are missing'\n\t\t\t} as const,\n\t\t\tuser: null\n\t\t};\n\t}\n\n\tconst userSession = validateSession({ session, user_session_id });\n\tconst user = userSession?.user ?? null;\n\n\treturn {\n\t\terror: null,\n\t\tuser\n\t};\n};\nexport const instantiateUserSession = async <UserType>({\n\tauthProvider,\n\tproviderConfiguration,\n\tcookieSecure,\n\tsession,\n\tuser_session_id,\n\tunregisteredSession,\n\ttokenResponse,\n\tproviderInstance,\n\tgetUser,\n\tonNewUser,\n\tresolvedAuthorization,\n\tsessionDurationMs = MILLISECONDS_IN_A_DAY,\n\tunregisteredSessionDurationMs = MILLISECONDS_IN_AN_HOUR\n}: InsantiateUserSessionProps<UserType>) => {\n\tconst authorization =\n\t\tresolvedAuthorization ??\n\t\t(await resolveOAuthAuthorization({\n\t\t\tauthProvider,\n\t\t\tproviderConfiguration,\n\t\t\tproviderInstance,\n\t\t\ttokenResponse\n\t\t}));\n\tconst { accessToken, refreshToken, userIdentity } = authorization;\n\n\tconst userSession = validateSession({ session, user_session_id });\n\tconst userSessionId = getUserSessionId({\n\t\tcookieSecure,\n\t\tsession,\n\t\tunregisteredSession,\n\t\tuser_session_id\n\t});\n\n\tlet user = userSession?.user ?? (await getUser(userIdentity));\n\tconst response = user ?? (await onNewUser(userIdentity));\n\n\tconst isRedirectOrStatus =\n\t\tresponse instanceof Response || isStatusResponse(response);\n\n\tif (!isRedirectOrStatus) {\n\t\tuser = response;\n\n\t\tsession[userSessionId] = {\n\t\t\taccessToken,\n\t\t\tauthenticatedAt: Date.now(),\n\t\t\texpiresAt: Date.now() + sessionDurationMs,\n\t\t\trefreshToken,\n\t\t\tuser\n\t\t};\n\n\t\treturn void 0;\n\t}\n\n\tconst existingUnregistered = unregisteredSession[userSessionId];\n\n\tif (existingUnregistered) {\n\t\texistingUnregistered.accessToken = accessToken;\n\t\texistingUnregistered.expiresAt =\n\t\t\tDate.now() + unregisteredSessionDurationMs;\n\t\texistingUnregistered.refreshToken = refreshToken;\n\t\texistingUnregistered.userIdentity = userIdentity;\n\n\t\treturn response;\n\t}\n\n\tunregisteredSession[userSessionId] = {\n\t\taccessToken,\n\t\texpiresAt: Date.now() + unregisteredSessionDurationMs,\n\t\trefreshToken,\n\t\tuserIdentity\n\t};\n\n\treturn response;\n};\n// Cookie Secure flag resolution. Default convention: only set Secure=true in production\n// (matches express-session, iron-session, lucia, better-auth). Hardcoding Secure=true broke\n// non-browser HTTP clients (curl, SSR fetch, Playwright API contexts, test runners) on\n// http://localhost in dev, because they don't honor the browser's \"localhost is a secure\n// context\" exemption. An explicit `cookieSecure` on AuthConfig overrides; otherwise we\n// look at NODE_ENV. Consumers running prod without NODE_ENV=production should set\n// `cookieSecure: true` explicitly.\nexport const resolveCookieSecure = (override?: boolean) =>\n\toverride ?? process.env.NODE_ENV === 'production';\nexport const resolveOAuthAuthorization = async ({\n\tauthProvider,\n\tproviderConfiguration,\n\tproviderInstance,\n\ttokenResponse,\n\tnow = Date.now()\n}: {\n\tauthProvider: string;\n\tproviderConfiguration?: ProviderConfig;\n\tproviderInstance: Pick<OAuth2Client<ProviderOption>, 'fetchUserProfile'>;\n\ttokenResponse: OAuth2TokenResponse;\n\tnow?: number;\n}): Promise<ResolvedOAuthAuthorization> => {\n\t// Custom providers aren't in the citra registry — their config must be\n\t// supplied by the caller (the callback context carries it).\n\tconst meta =\n\t\tproviderConfiguration ??\n\t\t(isValidProviderOption(authProvider)\n\t\t\t? providers[authProvider]\n\t\t\t: undefined);\n\tif (!meta) {\n\t\tthrow new Error(\n\t\t\t`No provider configuration for \"${authProvider}\" — pass providerConfiguration (custom provider) or resolvedAuthorization`\n\t\t);\n\t}\n\tlet userIdentity;\n\tlet accessToken = tokenResponse.access_token;\n\tlet refreshToken = tokenResponse.refresh_token;\n\tconst withingsBody: unknown = Reflect.get(tokenResponse, 'body');\n\tconst withingsAccessToken =\n\t\tauthProvider === 'withings' &&\n\t\twithingsBody !== null &&\n\t\ttypeof withingsBody === 'object'\n\t\t\t? readOptionalString(withingsBody, 'access_token')\n\t\t\t: undefined;\n\tif (!accessToken && withingsAccessToken !== undefined) {\n\t\taccessToken = withingsAccessToken;\n\t}\n\tif (typeof accessToken !== 'string' || accessToken.length === 0) {\n\t\tthrow new Error(\n\t\t\t'OAuth authorization response contains no access_token'\n\t\t);\n\t}\n\n\tif (tokenResponse.id_token) {\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: decodeJWT(tokenResponse.id_token),\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'idToken'\n\t\t});\n\t} else if (meta.subjectBySource?.tokenResponse) {\n\t\t// Providers whose connected identity is returned as top-level fields in\n\t\t// the token-exchange response (e.g. GoHighLevel's locationId) rather than\n\t\t// via a profile endpoint. Read the subject straight from the token\n\t\t// response and skip the profile call.\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: tokenResponse,\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'tokenResponse'\n\t\t});\n\t} else if (authProvider === 'withings') {\n\t\tif (withingsBody === null || typeof withingsBody !== 'object') {\n\t\t\tthrow new Error('Withings OAuth response contains no body');\n\t\t}\n\t\tuserIdentity = { userid: Reflect.get(withingsBody, 'userid') };\n\t\taccessToken =\n\t\t\treadOptionalString(withingsBody, 'access_token') ?? accessToken;\n\t\trefreshToken =\n\t\t\treadOptionalString(withingsBody, 'refresh_token') ?? refreshToken;\n\t} else {\n\t\tuserIdentity = normalizeProviderIdentity({\n\t\t\tidentity: await providerInstance.fetchUserProfile(accessToken),\n\t\t\tproviderConfiguration: meta,\n\t\t\tsource: 'profile'\n\t\t});\n\t}\n\n\tconst tokenType: unknown = Reflect.get(tokenResponse, 'token_type');\n\n\treturn {\n\t\taccessToken,\n\t\texpiresAt: resolveOAuthTokenExpiresAt(tokenResponse, now),\n\t\trefreshToken,\n\t\ttokenType: typeof tokenType === 'string' ? tokenType : undefined,\n\t\tuserIdentity\n\t};\n};\nconst parseExpiresInSeconds = (expiresIn: unknown) => {\n\tif (typeof expiresIn === 'number') {\n\t\treturn expiresIn;\n\t}\n\n\tif (typeof expiresIn === 'string' && expiresIn.trim().length > 0) {\n\t\treturn Number(expiresIn);\n\t}\n\n\treturn Number.NaN;\n};\n\nexport const resolveOAuthTokenExpiresAt = (\n\ttokenResponse: OAuth2TokenResponse,\n\tnow = Date.now()\n) => {\n\tconst expiresIn: unknown = Reflect.get(tokenResponse, 'expires_in');\n\tconst expiresInSeconds = parseExpiresInSeconds(expiresIn);\n\n\tif (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) {\n\t\treturn undefined;\n\t}\n\n\treturn now + expiresInSeconds * MILLISECONDS_IN_A_SECOND;\n};\n\ntype ValidateSessionProps<\n\tSessionType extends Record<string, unknown> & { expiresAt: number }\n> = {\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n\tsession: Record<UserSessionId, SessionType>;\n};\n\nexport const validateSession = <\n\tSessionType extends Record<string, unknown> & { expiresAt: number }\n>({\n\tuser_session_id,\n\tsession\n}: ValidateSessionProps<SessionType>) => {\n\tconst userSessionId = user_session_id.value;\n\tif (!userSessionId) {\n\t\treturn undefined;\n\t}\n\n\tconst userSession = session[userSessionId];\n\tif (!userSession) {\n\t\treturn undefined;\n\t}\n\n\tconst isExpired = userSession.expiresAt < Date.now();\n\tif (isExpired) {\n\t\tdelete session[userSessionId];\n\t\tuser_session_id.remove();\n\n\t\treturn undefined;\n\t}\n\n\treturn userSession;\n};\n\ntype GetUserSessionIdProps<UserType> = {\n\tcookieSecure?: boolean;\n\tuser_session_id: Cookie<UserSessionId | undefined>;\n\tsession?: SessionRecord<UserType>;\n\tunregisteredSession?: UnregisteredSessionRecord;\n};\n\nconst clearExistingSession = <UserType>(\n\texistingId: UserSessionId,\n\tsession?: SessionRecord<UserType>,\n\tunregisteredSession?: UnregisteredSessionRecord\n) => {\n\tif (session) delete session[existingId];\n\tif (unregisteredSession) delete unregisteredSession[existingId];\n};\n\nexport const getUserSessionId = <UserType>({\n\tcookieSecure,\n\tuser_session_id,\n\tsession,\n\tunregisteredSession\n}: GetUserSessionIdProps<UserType>) => {\n\tconst existingId = user_session_id?.value;\n\n\tif (isNonEmptyString(existingId)) {\n\t\tclearExistingSession(existingId, session, unregisteredSession);\n\t}\n\n\tconst userSessionId = crypto.randomUUID();\n\n\tuser_session_id.set({\n\t\thttpOnly: true,\n\t\tsameSite: 'lax',\n\t\tsecure: resolveCookieSecure(cookieSecure),\n\t\tvalue: userSessionId\n\t});\n\n\treturn userSessionId;\n};\n",
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.56.11",
2
+ "version": "0.56.12",
3
3
  "name": "@absolutejs/auth",
4
4
  "description": "An authorization library for absolutejs",
5
5
  "repository": {