@crossauth/sveltekit 1.1.0 → 1.1.1

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.
Files changed (46) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +16 -6181
  3. package/dist/sveltekitadminclientendpoints.d.ts +13 -12
  4. package/dist/sveltekitadminclientendpoints.js +187 -0
  5. package/dist/sveltekitadminendpoints.d.ts +5 -4
  6. package/dist/sveltekitadminendpoints.js +766 -0
  7. package/dist/sveltekitapikey.d.ts +4 -4
  8. package/dist/sveltekitapikey.js +81 -0
  9. package/dist/sveltekitoauthclient.d.ts +6 -5
  10. package/dist/sveltekitoauthclient.js +2309 -0
  11. package/dist/sveltekitoauthserver.d.ts +4 -4
  12. package/dist/sveltekitoauthserver.js +1350 -0
  13. package/dist/sveltekitresserver.d.ts +6 -5
  14. package/dist/sveltekitresserver.js +286 -0
  15. package/dist/sveltekitserver.d.ts +11 -10
  16. package/dist/sveltekitserver.js +393 -0
  17. package/dist/sveltekitsession.d.ts +5 -5
  18. package/dist/sveltekitsession.js +1112 -0
  19. package/dist/sveltekitsessionadapter.d.ts +2 -3
  20. package/dist/sveltekitsessionadapter.js +2 -0
  21. package/dist/sveltekitsharedclientendpoints.d.ts +7 -6
  22. package/dist/sveltekitsharedclientendpoints.js +630 -0
  23. package/dist/sveltekituserclientendpoints.d.ts +13 -12
  24. package/dist/sveltekituserclientendpoints.js +270 -0
  25. package/dist/sveltekituserendpoints.d.ts +6 -5
  26. package/dist/sveltekituserendpoints.js +1813 -0
  27. package/dist/tests/sveltekitadminclientendpoints.test.js +330 -0
  28. package/dist/tests/sveltekitadminendpoints.test.js +242 -0
  29. package/dist/tests/sveltekitapikeyserver.test.js +44 -0
  30. package/dist/tests/sveltekitoauthclient.test.d.ts +5 -5
  31. package/dist/tests/sveltekitoauthclient.test.js +1016 -0
  32. package/dist/tests/sveltekitoauthresserver.test.d.ts +4 -4
  33. package/dist/tests/sveltekitoauthresserver.test.js +185 -0
  34. package/dist/tests/sveltekitoauthserver.test.js +673 -0
  35. package/dist/tests/sveltekituserclientendpoints.test.js +244 -0
  36. package/dist/tests/sveltekituserendpoints.test.js +152 -0
  37. package/dist/tests/sveltemock.test.js +36 -0
  38. package/dist/tests/sveltemocks.d.ts +2 -3
  39. package/dist/tests/sveltemocks.js +114 -0
  40. package/dist/tests/sveltesessionhooks.test.js +224 -0
  41. package/dist/tests/testshared.d.ts +8 -8
  42. package/dist/tests/testshared.js +344 -0
  43. package/dist/utils.d.ts +1 -2
  44. package/dist/utils.js +123 -0
  45. package/package.json +6 -4
  46. package/dist/index.cjs +0 -1
@@ -0,0 +1,1112 @@
1
+ // Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
2
+ import { minimatch } from 'minimatch';
3
+ import { KeyStorage, UserStorage, OAuthClientStorage, SessionManager, Authenticator, Crypto, setParameter, ParamType, toCookieSerializeOptions } from '@crossauth/backend';
4
+ import { CrossauthError, CrossauthLogger, j, ErrorCode, httpStatus } from '@crossauth/common';
5
+ import { UserState } from '@crossauth/common';
6
+ import { error, redirect } from '@sveltejs/kit';
7
+ import { JsonOrFormData } from './utils';
8
+ import { SvelteKitUserEndpoints } from './sveltekituserendpoints';
9
+ import { SvelteKitAdminEndpoints } from './sveltekitadminendpoints';
10
+ import { SvelteKitUserClientEndpoints } from './sveltekituserclientendpoints';
11
+ import { SvelteKitAdminClientEndpoints } from './sveltekitadminclientendpoints';
12
+ import { SvelteKitSessionAdapter } from './sveltekitsessionadapter';
13
+ import { SvelteKitServer } from './sveltekitserver';
14
+ import {} from './tests/sveltemocks';
15
+ export const CSRFHEADER = "X-CROSSAUTH-CSRF";
16
+ /////////////////////////////////////////////////////////////////////////////
17
+ // DEFAULT FUNCTIONS
18
+ /**
19
+ * Default User validator. Doesn't validate password
20
+ *
21
+ * Username must be at least two characters.
22
+ * @param password The password to validate
23
+ * @returns an array of errors. If there were no errors, returns an empty array
24
+ */
25
+ function defaultUserValidator(user) {
26
+ let errors = [];
27
+ if (user.username == undefined)
28
+ errors.push("Username must be given");
29
+ else if (user.username.length < 2)
30
+ errors.push("Username must be at least 2 characters");
31
+ else if (user.username.length > 254)
32
+ errors.push("Username must be no longer than 254 characters");
33
+ return errors;
34
+ }
35
+ /**
36
+ * Default function for creating users. Can be overridden.
37
+ *
38
+ * Takes any field beginning with `user_` and that is also in
39
+ * `userEditableFields` (without the `user_` prefix).
40
+ *
41
+ * @param event the SvelteKit request event
42
+ * @param userEditableFields the fields a user may edit
43
+ * @returns the new user
44
+ */
45
+ function defaultCreateUser(event, data, userEditableFields, allowableFactor1 = ["localpassword"]) {
46
+ let state = "active";
47
+ let user = {
48
+ username: data.username ?? "",
49
+ state: state,
50
+ };
51
+ const callerIsAdmin = event.locals.user && SvelteKitServer.isAdminFn(event.locals.user);
52
+ for (let field in data) {
53
+ let name = field.replace(/^user_/, "");
54
+ if (field.startsWith("user_") &&
55
+ (callerIsAdmin || userEditableFields.includes(name))) {
56
+ if ("type_" + name in data) {
57
+ if (data["type_" + name] == "string") {
58
+ user[name] = data[field];
59
+ }
60
+ else if (data["type_" + name] == "number" || data["type_" + name] == "integer" || data["type_" + name] == "float") {
61
+ user[name] = Number(data[field]);
62
+ }
63
+ else if (data["type_" + name] == "boolean") {
64
+ const c = data[field]?.toLocaleLowerCase();
65
+ user[name] = (c == "1" || c == "y" || c == "t" || c == "yes" || c == "true");
66
+ }
67
+ }
68
+ else {
69
+ user[name] = data[field];
70
+ }
71
+ }
72
+ }
73
+ user.factor1 = "localpassword";
74
+ if (data.factor1 && allowableFactor1.includes(data.factor1)) {
75
+ user.factor1 = data.factor1;
76
+ }
77
+ user.factor2 = data.factor2;
78
+ return user;
79
+ }
80
+ /**
81
+ * Default function for creating users. Can be overridden.
82
+ *
83
+ * Takes any field beginning with `user_` and that is also in
84
+ * `userEditableFields` (without the `user_` prefix).
85
+ *
86
+ * @param user the user to update
87
+ * @param event the SvelteKit request event
88
+ * @param userEditableFields the fields a user may edit
89
+ * @returns the new user
90
+ */
91
+ function defaultUpdateUser(user, event, data, userEditableFields) {
92
+ const callerIsAdmin = event.locals.user && SvelteKitServer.isAdminFn(event.locals.user);
93
+ for (let field in data) {
94
+ let name = field.replace(/^user_/, "");
95
+ if (field.startsWith("user_") &&
96
+ (callerIsAdmin || userEditableFields.includes(name))) {
97
+ if ("type_" + name in data) {
98
+ if (data["type_" + name] == "string") {
99
+ user[name] = data[field];
100
+ }
101
+ else if (data["type_" + name] == "number" || data["type_" + name] == "integer" || data["type_" + name] == "float") {
102
+ user[name] = Number(data[field]);
103
+ }
104
+ else if (data["type_" + name] == "boolean") {
105
+ const c = data[field]?.toLocaleLowerCase();
106
+ user[name] = (c == "1" || c == "y" || c == "t" || c == "yes" || c == "true");
107
+ }
108
+ }
109
+ else {
110
+ user[name] = data[field];
111
+ }
112
+ }
113
+ }
114
+ return user;
115
+ }
116
+ /**
117
+ * The Sveltekit session server.
118
+ *
119
+ * You shouldn't have to instantiate this directly. It is created when
120
+ * you create a {@link SvelteKitServer} object.
121
+
122
+ * **Middleware**
123
+ *
124
+ * This class registers one middleware function to fill in the following
125
+ * fields in the request:
126
+ *
127
+ * - `user` a {@link @crossauth/common!User}` object
128
+ * - `authType`: set to `cookie` or undefined
129
+ * - `csrfToken`: a CSRF token that can be used in POST requests
130
+ * - `sessionId` a session ID if one is created
131
+ */
132
+ export class SvelteKitSessionServer {
133
+ /**
134
+ * Hook to check if the user is logged in and set data in `locals`
135
+ * accordingly.
136
+ */
137
+ sessionHook;
138
+ twoFAHook;
139
+ /**
140
+ * Key storage taken from constructor args.
141
+ * See {@link SvelteKitSessionServer.constructor}.
142
+ */
143
+ keyStorage;
144
+ /**
145
+ * Session Manager taken from constructor args.
146
+ * See {@link SvelteKitSessionServer.constructor}.
147
+ */
148
+ sessionManager;
149
+ /**
150
+ * User storage taken from constructor args.
151
+ * See {@link SvelteKitSessionServer.constructor}.
152
+ */
153
+ userStorage;
154
+ /**
155
+ * User storage taken from constructor args.
156
+ * See {@link SvelteKitSessionServer.constructor}.
157
+ */
158
+ clientStorage;
159
+ /**
160
+ * Funtion to validate users upon creation. Taken from the options during
161
+ * construction or the default value.
162
+ * See {@link SvelteKitSessionServerOptions}.
163
+ */
164
+ validateUserFn = defaultUserValidator;
165
+ /**
166
+ * Funtion to create a user record from form fields. Taken from the options during
167
+ * construction or the default value.
168
+ * See {@link SvelteKitSessionServerOptions}.
169
+ */
170
+ createUserFn = defaultCreateUser;
171
+ /**
172
+ * Funtion to update a user record from form fields. Taken from the options during
173
+ * construction or the default value.
174
+ * See {@link SvelteKitSessionServerOptions}.
175
+ */
176
+ updateUserFn = defaultUpdateUser;
177
+ /**
178
+ * The set of authenticators taken from constructor args.
179
+ * See {@link SvelteKitSessionServer.constructor}.
180
+ */
181
+ authenticators;
182
+ /**
183
+ * The set of allowed authenticators taken from the options during
184
+ * construction.
185
+ *
186
+ * The default is `[{name: "none", friendlyName: "none"}]`
187
+ */
188
+ allowedFactor2 = [];
189
+ /**
190
+ * The set of allowed authenticators taken from the options during
191
+ * construction.
192
+ *
193
+ * The default is `["none"]`.
194
+ */
195
+ allowedFactor2Names = [];
196
+ /** Called when a new session token is going to be saved
197
+ * Add additional fields to your session storage here. Return a map of
198
+ * keys to values */
199
+ addToSession;
200
+ /**
201
+ * The set of allowed authenticators taken from the options during
202
+ * construction.
203
+ */
204
+ validateSession;
205
+ factor2ProtectedPageEndpoints = [];
206
+ factor2ProtectedApiEndpoints = [];
207
+ loginProtectedPageEndpoints = [];
208
+ loginProtectedApiEndpoints = [];
209
+ loginProtectedExceptionPageEndpoints = [];
210
+ loginProtectedExceptionApiEndpoints = [];
211
+ adminPageEndpoints = [];
212
+ adminApiEndpoints = [];
213
+ adminProtectedExceptionPageEndpoints = [];
214
+ adminProtectedExceptionApiEndpoints = [];
215
+ unauthorizedUrl = undefined;
216
+ enableCsrfProtection = true;
217
+ /** Whether email verification is enabled.
218
+ *
219
+ * Reads from constructor options
220
+ */
221
+ enableEmailVerification = false;
222
+ /** Whether password reset is enabled.
223
+ *
224
+ * Reads from constructor options
225
+ */
226
+ enablePasswordReset = false;
227
+ factor2Url = "/factor2";
228
+ loginUrl = "/login";
229
+ logoutUrl = "/logout";
230
+ /**
231
+ * Use these to access the `load` and `action` endpoints for functions
232
+ * provided by Crossauth. These are the ones intended for users to
233
+ * have access to.
234
+ *
235
+ * See {@link SvelteKitUserEndpoints}
236
+ */
237
+ userEndpoints;
238
+ /**
239
+ * Use these to access the `load` and `action` endpoints for functions
240
+ * provided by Crossauth that relate to manipulating OAuth clients in the
241
+ * database. These are the ones intended for users to
242
+ * have access to.
243
+ *
244
+ * See {@link SvelteKitUserEndpoints}
245
+ */
246
+ userClientEndpoints;
247
+ /**
248
+ * Use these to access the `load` and `action` endpoints for functions
249
+ * provided by Crossauth that relate to manipulating OAuth clients in the
250
+ * database as admin. These are the ones intended for users to
251
+ * have access to.
252
+ *
253
+ * See {@link SvelteKitAdminEndpoints}
254
+ */
255
+ adminClientEndpoints;
256
+ /**
257
+ * Use these to access the `load` and `action` endpoints for functions
258
+ * provides by Crossauth. These are the ones intended for admins to
259
+ * have access to.
260
+ *
261
+ * See {@link SvelteKitAdminEndpoints}
262
+ */
263
+ adminEndpoints;
264
+ redirect;
265
+ error;
266
+ /**
267
+ * This is read from options during construction.
268
+ *
269
+ * See {@link SvelteKitServerOptions}.
270
+ */
271
+ editUserScope;
272
+ userAllowedFactor1 = ["localpassword"];
273
+ adminAllowedFactor1 = ["localpassword"];
274
+ /**
275
+ * Constructor
276
+ * @param keyStorage where session IDs, email verification and reset tokens are stored
277
+ * @param authenticators valid authenticators that can be in `factor1` or `factor2`
278
+ * of the user. See class documentation for {@link SvelteKitServer} for an example.
279
+ * @param options See {@link SvelteKitSessionServerOptions}.
280
+ */
281
+ constructor(keyStorage, authenticators, options = {}) {
282
+ this.keyStorage = keyStorage;
283
+ this.userStorage = options.userStorage;
284
+ this.clientStorage = options.clientStorage;
285
+ this.authenticators = authenticators;
286
+ this.sessionManager = new SessionManager(keyStorage, authenticators, options);
287
+ this.redirect = options.redirect ?? redirect;
288
+ this.error = options.error ?? error;
289
+ setParameter("factor2Url", ParamType.String, this, options, "FACTOR2_URL");
290
+ if (!this.factor2Url.endsWith("/"))
291
+ this.factor2Url += "/";
292
+ setParameter("factor2ProtectedPageEndpoints", ParamType.JsonArray, this, options, "FACTOR2_PROTECTED_PAGE_ENDPOINTS");
293
+ setParameter("factor2ProtectedApiEndpoints", ParamType.JsonArray, this, options, "FACTOR2_PROTECTED_API_ENDPOINTS");
294
+ setParameter("loginProtectedPageEndpoints", ParamType.JsonArray, this, options, "LOGIN_PROTECTED_PAGE_ENDPOINTS");
295
+ setParameter("loginProtectedApiEndpoints", ParamType.JsonArray, this, options, "LOGIN_PROTECTED_API_ENDPOINTS");
296
+ setParameter("loginProtectedExceptionPageEndpoints", ParamType.JsonArray, this, options, "LOGIN_PROTECTED_EXCEPTION_PAGE_ENDPOINTS");
297
+ setParameter("loginProtectedExceptionApiEndpoints", ParamType.JsonArray, this, options, "LOGIN_PROTECTED_EXCEPTION_API_ENDPOINTS");
298
+ setParameter("adminPageEndpoints", ParamType.JsonArray, this, options, "ADMIN_PAGE_ENDPOINTS");
299
+ setParameter("adminApiEndpoints", ParamType.JsonArray, this, options, "ADMIN_API_ENDPOINTS");
300
+ setParameter("adminProtectedExceptionPageEndpoints", ParamType.JsonArray, this, options, "ADMIN_PROTECTED_EXCEPTION_PAGE_ENDPOINTS");
301
+ setParameter("adminProtectedExceptionApiEndpoints", ParamType.JsonArray, this, options, "ADMIN_PROTECTED_EXCEPTION_API_ENDPOINTS");
302
+ setParameter("loginUrl", ParamType.JsonArray, this, options, "LOGIN_URL");
303
+ setParameter("logoutUrl", ParamType.JsonArray, this, options, "LOGOUT_URL");
304
+ setParameter("unauthorizedUrl", ParamType.JsonArray, this, options, "UNAUTHORIZED_PAGE");
305
+ setParameter("userAllowedFactor1", ParamType.JsonArray, this, options, "USER_ALLOWED_FACTOR1");
306
+ setParameter("adminAllowedFactor1", ParamType.JsonArray, this, options, "ADMIN_ALLOWED_FACTOR1");
307
+ let options1 = {};
308
+ setParameter("allowedFactor2", ParamType.JsonArray, options1, options, "ALLOWED_FACTOR2");
309
+ this.allowedFactor2Names = options.allowedFactor2 ?? ["none"];
310
+ if (options1.allowedFactor2) {
311
+ for (let factor of options1.allowedFactor2) {
312
+ if (factor in this.authenticators) {
313
+ this.allowedFactor2.push({
314
+ name: factor,
315
+ friendlyName: this.authenticators[factor].friendlyName,
316
+ configurable: this.authenticators[factor].secretNames().length > 0,
317
+ });
318
+ }
319
+ else if (factor == "none") {
320
+ this.allowedFactor2.push({
321
+ name: "none",
322
+ friendlyName: "None",
323
+ configurable: false
324
+ });
325
+ }
326
+ }
327
+ }
328
+ setParameter("enableEmailVerification", ParamType.Boolean, this, options, "ENABLE_EMAIL_VERIFICATION");
329
+ setParameter("enablePasswordReset", ParamType.Boolean, this, options, "ENABLE_PASSWORD_RESET");
330
+ setParameter("enableCsrfProtection", ParamType.Boolean, this, options, "ENABLE_CSRF_PROTECTION");
331
+ setParameter("editUserScope", ParamType.String, this, options, "EDIT_USER_SCOPE");
332
+ if (options.validateUserFn)
333
+ this.validateUserFn = options.validateUserFn;
334
+ if (options.createUserFn)
335
+ this.createUserFn = options.createUserFn;
336
+ if (options.updateUserFn)
337
+ this.updateUserFn = options.updateUserFn;
338
+ if (options.addToSession)
339
+ this.addToSession = options.addToSession;
340
+ if (options.validateSession)
341
+ this.validateSession = options.validateSession;
342
+ this.userEndpoints = new SvelteKitUserEndpoints(this, options);
343
+ this.adminEndpoints = new SvelteKitAdminEndpoints(this, options);
344
+ this.userClientEndpoints = new SvelteKitUserClientEndpoints(this, options);
345
+ this.adminClientEndpoints = new SvelteKitAdminClientEndpoints(this, options);
346
+ this.sessionHook = async ({ event } /*, response*/) => {
347
+ CrossauthLogger.logger.debug(j({ msg: "Session hook" }));
348
+ let headers = [];
349
+ let status = undefined;
350
+ const csrfCookieName = this.sessionManager.csrfCookieName;
351
+ const sessionCookieName = this.sessionManager.sessionCookieName;
352
+ //const response = await resolve(event);
353
+ // check if CSRF token is in cookie (and signature is valid)
354
+ // remove it if it is not.
355
+ // we are not checking it matches the CSRF token in the header or
356
+ // body at this stage - just removing invalid cookies
357
+ if (this.enableCsrfProtection) {
358
+ CrossauthLogger.logger.debug(j({ msg: "Getting csrf cookie" }));
359
+ let cookieValue;
360
+ try {
361
+ cookieValue = this.getCsrfCookieValue(event);
362
+ if (cookieValue)
363
+ this.sessionManager.validateCsrfCookie(cookieValue);
364
+ }
365
+ catch (e) {
366
+ CrossauthLogger.logger.warn(j({ msg: "Invalid csrf cookie received", cerr: e, hashedCsrfCookie: this.getHashOfCsrfCookie(event) }));
367
+ try {
368
+ this.clearCookie(csrfCookieName, this.sessionManager.csrfCookiePath, event);
369
+ }
370
+ catch (e2) {
371
+ CrossauthLogger.logger.debug(j({ err: e2 }));
372
+ CrossauthLogger.logger.error(j({ cerr: e2, msg: "Couldn't delete CSRF cookie", ip: event.request.referrer, hashedCsrfCookie: this.getHashOfCsrfCookie(event) }));
373
+ }
374
+ cookieValue = undefined;
375
+ event.locals.csrfToken = undefined;
376
+ }
377
+ if (["GET", "OPTIONS", "HEAD"].includes(event.request.method)) {
378
+ // for get methods, create a CSRF token in the request object and response header
379
+ try {
380
+ if (!cookieValue) {
381
+ CrossauthLogger.logger.debug(j({ msg: "Invalid CSRF cookie - recreating" }));
382
+ const { csrfCookie, csrfFormOrHeaderValue } = await this.sessionManager.createCsrfToken();
383
+ this.setCsrfCookie(csrfCookie, event);
384
+ event.locals.csrfToken = csrfFormOrHeaderValue;
385
+ }
386
+ else {
387
+ CrossauthLogger.logger.debug(j({ msg: "Valid CSRF cookie - creating token" }));
388
+ const csrfFormOrHeaderValue = await this.sessionManager.createCsrfFormOrHeaderValue(cookieValue);
389
+ event.locals.csrfToken = csrfFormOrHeaderValue;
390
+ }
391
+ this.setHeader(CSRFHEADER, event.locals.csrfToken, headers);
392
+ //response.headers.set(CSRFHEADER, event.locals.csrfToken);
393
+ }
394
+ catch (e) {
395
+ CrossauthLogger.logger.error(j({ msg: "Couldn't create CSRF token", cerr: e, user: event.locals.user?.username, hashedSessionCookie: this.getHashOfSessionCookie(event) }));
396
+ CrossauthLogger.logger.debug(j({ err: e }));
397
+ this.clearCookie(csrfCookieName, this.sessionManager.csrfCookiePath, event);
398
+ event.locals.csrfToken = undefined;
399
+ }
400
+ }
401
+ else {
402
+ // for other methods, create a new token only if there is already a valid one
403
+ if (cookieValue) {
404
+ try {
405
+ await this.csrfToken(event, headers);
406
+ }
407
+ catch (e) {
408
+ CrossauthLogger.logger.error(j({ msg: "Couldn't create CSRF token", cerr: e, user: event.locals.user?.username, hashedSessionCookie: this.getHashOfSessionCookie(event) }));
409
+ CrossauthLogger.logger.debug(j({ err: e }));
410
+ }
411
+ }
412
+ }
413
+ }
414
+ // we now either have a valid CSRF token, or none at all (or CSRF
415
+ // protection has been disabled, in which case the CSRF cookie
416
+ // is ignored)
417
+ // validate any session cookie. Remove if invalid
418
+ event.locals.user = undefined;
419
+ event.locals.authType = undefined;
420
+ const sessionCookieValue = this.getSessionCookieValue(event);
421
+ CrossauthLogger.logger.debug(j({ msg: "Getting session cookie" }));
422
+ if (sessionCookieValue) {
423
+ try {
424
+ const sessionId = this.sessionManager.getSessionId(sessionCookieValue);
425
+ let { key, user } = await this.sessionManager.userForSessionId(sessionId);
426
+ if (this.validateSession)
427
+ this.validateSession(key, user, event);
428
+ const endpoint = event.url.pathname;
429
+ CrossauthLogger.logger.debug(j({ msg: "Session cookie is for user " + user }));
430
+ if (user) { // XXX
431
+ if (this.allowedFactor2.length > 0 &&
432
+ (user.state == UserState.factor2ResetNeeded ||
433
+ !this.allowedFactor2Names.includes(user.factor2 ? user.factor2 : "none"))) {
434
+ if (!this.userEndpoints.configureFactor2Url)
435
+ throw new CrossauthError(ErrorCode.Configuration, "Must set configureFactor2Url in session server");
436
+ if (!this.userEndpoints.changeFactor2Url)
437
+ throw new CrossauthError(ErrorCode.Configuration, "Must set changeFactor2Url in session server");
438
+ if (!this.logoutUrl)
439
+ throw new CrossauthError(ErrorCode.Configuration, "Must set logoutUrl in session server");
440
+ if (!([this.userEndpoints.changeFactor2Url, this.userEndpoints.configureFactor2Url, this.loginUrl, this.logoutUrl].includes(endpoint))) {
441
+ status = 302;
442
+ headers.push({ name: "location", value: this.userEndpoints.changeFactor2Url + "?required=true&next=" + encodeURIComponent("login?next=" + event.url) });
443
+ //this.redirect(302, this.userEndpoints.changeFactor2Url + "?required=true&next="+encodeURIComponent("login?next="+event.url));
444
+ }
445
+ }
446
+ }
447
+ event.locals.sessionId = sessionId;
448
+ event.locals.user = user;
449
+ event.locals.authType = "cookie";
450
+ CrossauthLogger.logger.debug(j({ msg: "Valid session id", user: user?.username }));
451
+ }
452
+ catch (e) {
453
+ CrossauthLogger.logger.warn(j({ msg: "Invalid session cookie received", hashedSessionCookie: this.getHashOfSessionCookie(event) }));
454
+ this.clearCookie(sessionCookieName, this.sessionManager.sessionCookiePath, event);
455
+ }
456
+ }
457
+ //return response;
458
+ return { headers, status };
459
+ };
460
+ this.twoFAHook = async ({ event }) => {
461
+ CrossauthLogger.logger.debug(j({ msg: "twoFAHook", username: event.locals.user?.username }));
462
+ if (!this.userStorage)
463
+ throw this.error(500, "No user storage defined"); // shouldn't happen as checked in SvelteKitServer
464
+ const sessionCookieValue = this.getSessionCookieValue(event);
465
+ const isFactor2PageProtected = this.isFactor2PageProtected(event);
466
+ const isFactor2ApiProtected = this.isFactor2ApiProtected(event);
467
+ let user;
468
+ if (sessionCookieValue) {
469
+ if (event.locals.user)
470
+ user = event.locals.user;
471
+ else {
472
+ const anonUser = await this.getSessionData(event, "user");
473
+ if (anonUser) {
474
+ const resp = await this.userStorage.getUserByUsername(anonUser.username, { skipActiveCheck: true });
475
+ if (resp.user.status == UserState.active || resp.user.state == UserState.factor2ResetNeeded)
476
+ user = resp.user;
477
+ }
478
+ }
479
+ }
480
+ if (user && sessionCookieValue && user.factor2 != "" && (isFactor2PageProtected || isFactor2ApiProtected)) {
481
+ CrossauthLogger.logger.debug(j({ msg: "Factor2-protected endpoint visited" }));
482
+ if (!(["GET", "OPTIONS", "HEAD"].includes(event.request.method))) {
483
+ const sessionId = this.sessionManager.getSessionId(sessionCookieValue);
484
+ const sessionData = await this.sessionManager.dataForSessionId(sessionId);
485
+ if (("pre2fa") in sessionData) {
486
+ // 2FA has started - validate it
487
+ CrossauthLogger.logger.debug(j({ msg: "Completing 2FA" }));
488
+ // get secrets from the request body
489
+ const authenticator = this.authenticators[sessionData.pre2fa.factor2];
490
+ const secretNames = [...authenticator.secretNames(), ...authenticator.transientSecretNames()];
491
+ let secrets = {};
492
+ const bodyData = new JsonOrFormData();
493
+ await bodyData.loadData(event);
494
+ for (let field of bodyData.keys()) {
495
+ if (secretNames.includes(field))
496
+ secrets[field] = bodyData.get(field) ?? "";
497
+ }
498
+ const sessionCookieValue = this.getSessionCookieValue(event);
499
+ if (!sessionCookieValue)
500
+ throw new CrossauthError(ErrorCode.Unauthorized, "No session cookie found");
501
+ let error1 = undefined;
502
+ try {
503
+ await this.sessionManager.completeTwoFactorPageVisit(secrets, event.locals.sessionId ?? "");
504
+ }
505
+ catch (e) {
506
+ error1 = CrossauthError.asCrossauthError(e);
507
+ CrossauthLogger.logger.debug(j({ err: e }));
508
+ const ce = CrossauthError.asCrossauthError(e);
509
+ CrossauthLogger.logger.error(j({ msg: error1.message, cerr: e, user: bodyData.get("username"), errorCode: ce.code, errorCodeName: ce.codeName }));
510
+ }
511
+ if (error1) {
512
+ if (error1.code == ErrorCode.Expired) {
513
+ // user will not be able to complete this process - delete
514
+ CrossauthLogger.logger.debug(j({ msg: "Error - cancelling 2FA" }));
515
+ // the 2FA data and start again
516
+ try {
517
+ await this.sessionManager.cancelTwoFactorPageVisit(sessionCookieValue);
518
+ }
519
+ catch (e) {
520
+ CrossauthLogger.logger.error(j({ msg: "Failed cancelling 2FA", cerr: e, user: user.username, hashedSessionCookie: this.getHashOfSessionCookie(event) }));
521
+ CrossauthLogger.logger.debug(j({ err: e }));
522
+ }
523
+ this.error(401, { message: "Sorry, your code has expired" });
524
+ return { ok: false, twofa: true };
525
+ }
526
+ else {
527
+ if (isFactor2PageProtected) {
528
+ return {
529
+ twofa: true,
530
+ ok: false,
531
+ response: new Response('', {
532
+ status: 302,
533
+ statusText: httpStatus(302),
534
+ headers: { Location: this.factor2Url + "?error=" + ErrorCode[error1.code] }
535
+ })
536
+ };
537
+ }
538
+ else {
539
+ return {
540
+ twofa: true,
541
+ ok: false,
542
+ response: new Response(JSON.stringify({
543
+ ok: false,
544
+ errorMessage: error1.message,
545
+ errorMessages: error1.messages,
546
+ errorCode: error1.code,
547
+ errorCodeName: ErrorCode[error1.code]
548
+ }), {
549
+ status: error1.httpStatus,
550
+ statusText: httpStatus(error1.httpStatus),
551
+ headers: { 'content-tyoe': 'application/json' },
552
+ })
553
+ };
554
+ }
555
+ }
556
+ }
557
+ // restore original request body
558
+ SvelteKitSessionServer.updateRequest(event, sessionData.pre2fa.body, sessionData.pre2fa["content-type"]);
559
+ return { twofa: true, ok: true };
560
+ }
561
+ else {
562
+ // 2FA has not started - start it
563
+ CrossauthLogger.logger.debug(j({ msg: "Starting 2FA", username: user.username }));
564
+ if (this.enableCsrfProtection && !event.locals.csrfToken) {
565
+ const error = new CrossauthError(ErrorCode.Forbidden, "CSRF token missing");
566
+ return {
567
+ twofa: true,
568
+ ok: false,
569
+ response: new Response(JSON.stringify({
570
+ ok: false,
571
+ errorMessage: error.message,
572
+ errorMessages: error.messages,
573
+ errorCode: error.code,
574
+ errorCodeName: ErrorCode[error.code]
575
+ }), {
576
+ status: error.httpStatus,
577
+ statusText: httpStatus(error.httpStatus),
578
+ headers: {
579
+ ...{ 'content-tyoe': 'application/json' },
580
+ }
581
+ })
582
+ };
583
+ }
584
+ const bodyData = new JsonOrFormData();
585
+ await bodyData.loadData(event);
586
+ let contentType = event.request.headers.get("content-type");
587
+ await this.sessionManager.initiateTwoFactorPageVisit(user, event.locals.sessionId ?? "", bodyData.toObject(), event.request.url.replace(/\?.*$/, ""), contentType ? contentType : undefined);
588
+ if (isFactor2PageProtected) {
589
+ return {
590
+ twofa: true,
591
+ ok: true,
592
+ response: new Response('', {
593
+ status: 302,
594
+ statusText: httpStatus(302),
595
+ headers: { Location: this.factor2Url }
596
+ })
597
+ };
598
+ }
599
+ else {
600
+ return {
601
+ twofa: true,
602
+ ok: true,
603
+ response: new Response(JSON.stringify({
604
+ ok: true,
605
+ factor2Required: true
606
+ }), {
607
+ headers: {
608
+ ...{ 'content-tyoe': 'application/json' },
609
+ }
610
+ })
611
+ };
612
+ }
613
+ }
614
+ }
615
+ else {
616
+ CrossauthLogger.logger.debug(j({ msg: "Factor2-protected GET endpoint - cancelling 2FA" }));
617
+ // if we have a get request to one of the protected urls, cancel any pending 2FA
618
+ const sessionCookieValue = this.getSessionCookieValue(event);
619
+ if (sessionCookieValue) {
620
+ const sessionId = this.sessionManager.getSessionId(sessionCookieValue);
621
+ const sessionData = await this.sessionManager.dataForSessionId(sessionId);
622
+ if (("pre2fa") in sessionData) {
623
+ CrossauthLogger.logger.debug(j({ msg: "Cancelling 2FA" }));
624
+ try {
625
+ await this.sessionManager.cancelTwoFactorPageVisit(sessionCookieValue);
626
+ }
627
+ catch (e) {
628
+ CrossauthLogger.logger.debug(j({ err: e }));
629
+ CrossauthLogger.logger.error(j({ msg: "Failed cancelling 2FA", cerr: e, user: user.username, hashedSessionCookie: this.getHashOfSessionCookie(event) }));
630
+ }
631
+ }
632
+ }
633
+ }
634
+ }
635
+ return { twofa: false, ok: true };
636
+ };
637
+ }
638
+ //////////////
639
+ // Helpers
640
+ /**
641
+ * Returns the session cookie value from the Sveltekit request event
642
+ * @param event the request event
643
+ * @returns the whole cookie value
644
+ */
645
+ getSessionCookieValue(event) {
646
+ //let allCookies = event.cookies.getAll();
647
+ if (event.cookies && event.cookies.get(this.sessionManager.sessionCookieName)) {
648
+ return event.cookies.get(this.sessionManager.sessionCookieName);
649
+ }
650
+ return undefined;
651
+ }
652
+ /**
653
+ * Returns the session cookie value from the Sveltekit request event
654
+ * @param event the request event
655
+ * @returns the whole cookie value
656
+ */
657
+ getCsrfCookieValue(event) {
658
+ if (event.cookies) {
659
+ const cookie = event.cookies.get(this.sessionManager.csrfCookieName);
660
+ if (cookie)
661
+ return event.cookies.get(this.sessionManager.csrfCookieName);
662
+ }
663
+ return undefined;
664
+ }
665
+ clearCookie(name, path, event) {
666
+ event.cookies.delete(name, { path });
667
+ }
668
+ /**
669
+ * Sets headers in the request event.
670
+ *
671
+ * Used internally by {@link SvelteKitServer}. Shouldn't be necessary
672
+ * to call this directly.
673
+ * @param headers the headres to set
674
+ * @param resp the response object to set them in
675
+ */
676
+ setHeaders(headers, resp) {
677
+ for (let header of headers) {
678
+ resp.headers.append(header.name, header.value);
679
+ }
680
+ }
681
+ /**
682
+ * Sets the CSRF cookie.
683
+ *
684
+ * Used internally. Shouldn't be necessary
685
+ * to call this directly.
686
+ * @param cookie the new cookie and parameters
687
+ * @param event the request event
688
+ */
689
+ setCsrfCookie(cookie, event) {
690
+ event.cookies.set(cookie.name, cookie.value, toCookieSerializeOptions(cookie.options));
691
+ }
692
+ setHeader(name, value, headers) {
693
+ headers.push({
694
+ name: name,
695
+ value: value,
696
+ });
697
+ }
698
+ /**
699
+ * Returns a hash of the session cookie value.
700
+ *
701
+ * Used only in reporting, so that logs don't contain the actual session ID.
702
+ *
703
+ * @param event the Sveltelkit request event
704
+ * @returns a stering hash of the cookie value
705
+ */
706
+ getHashOfSessionCookie(event) {
707
+ const cookieValue = this.getSessionCookieValue(event);
708
+ if (!cookieValue)
709
+ return "";
710
+ try {
711
+ return Crypto.hash(cookieValue);
712
+ }
713
+ catch (e) { }
714
+ return "";
715
+ }
716
+ /**
717
+ * Returns a hash of the CSRF cookie value.
718
+ *
719
+ * Used only in reporting, so that logs don't contain the actual CSRF cookie value.
720
+ *
721
+ * @param event the Sveltelkit request event
722
+ * @returns a stering hash of the cookie value
723
+ */
724
+ getHashOfCsrfCookie(event) {
725
+ const cookieValue = this.getCsrfCookieValue(event);
726
+ if (!cookieValue)
727
+ return "";
728
+ try {
729
+ return Crypto.hash(cookieValue);
730
+ }
731
+ catch (e) { }
732
+ return "";
733
+ }
734
+ /**
735
+ * Returns a CSRF token if the CSRF cookie is valid.
736
+ *
737
+ * Used internally. Shouldn't be necessary
738
+ * to call this directly.
739
+ *
740
+ * @param event the request event
741
+ * @param headers headers the token will be added to, as well as
742
+ * adding it to locals
743
+ * @returns the string CSRF token for inclusion in forms
744
+ */
745
+ async csrfToken(event, headers) {
746
+ let token = undefined;
747
+ // first try token in header
748
+ if (event.request.headers && event.request.headers.has(CSRFHEADER.toLowerCase())) {
749
+ const header = event.request.headers.get(CSRFHEADER.toLowerCase());
750
+ if (Array.isArray(header))
751
+ token = header[0];
752
+ else if (header)
753
+ token = header;
754
+ }
755
+ // if not in header, try in body
756
+ if (!token) {
757
+ if (!event.request?.body) {
758
+ CrossauthLogger.logger.warn(j({ msg: "Received CSRF header but not token", ip: event.request.referrerPolicy, hashedCsrfCookie: this.getHashOfCsrfCookie(event) }));
759
+ return;
760
+ }
761
+ const contentType = event.request.headers.get("content-type");
762
+ if (contentType == "application/json") {
763
+ const body = await event.request?.clone()?.json();
764
+ token = body.csrfToken;
765
+ }
766
+ else if (contentType == "application/x-www-form-urlencoded" || contentType == "multipart/form-data") {
767
+ const body = await event.request.clone().formData();
768
+ const formValue = body.get("csrfToken");
769
+ if (formValue && typeof formValue == "string")
770
+ token = formValue;
771
+ }
772
+ }
773
+ if (token) {
774
+ try {
775
+ this.sessionManager.validateDoubleSubmitCsrfToken(this.getCsrfCookieValue(event), token);
776
+ event.locals.csrfToken = token;
777
+ //resp.headers.set(CSRFHEADER, token);
778
+ this.setHeader(CSRFHEADER, token, headers);
779
+ }
780
+ catch (e) {
781
+ CrossauthLogger.logger.warn(j({ msg: "Invalid CSRF token", hashedCsrfCookie: this.getHashOfCsrfCookie(event) }));
782
+ this.clearCookie(this.sessionManager.csrfCookieName, this.sessionManager.csrfCookiePath, event);
783
+ event.locals.csrfToken = undefined;
784
+ }
785
+ }
786
+ else {
787
+ event.locals.csrfToken = undefined;
788
+ }
789
+ return token;
790
+ }
791
+ /**
792
+ * Used internally to update an existing Sveltekit request object with
793
+ * a new body and headers.
794
+ *
795
+ * Used when restoring a request that was interrupted for 2FA
796
+ *
797
+ * @param event the request event
798
+ * @param params JSON params to add to the new body
799
+ * @param contentType the new content type
800
+ * @returns the updated request event
801
+ */
802
+ static updateRequest(event, params, contentType) {
803
+ //const contentType = event.headers.get('content-type');
804
+ //const newContentType = contentType == 'application/json' ? 'application/json' : 'application/x-www-form-urlencoded';
805
+ let body;
806
+ if (contentType == 'application/json') {
807
+ body = JSON.stringify(params);
808
+ }
809
+ else {
810
+ body = "";
811
+ for (let name in params) {
812
+ const value = params[name];
813
+ if (body.length > 0)
814
+ body += "&";
815
+ body += encodeURIComponent(name) + "=" + encodeURIComponent(value);
816
+ }
817
+ }
818
+ event.request = new Request(event.request.url, {
819
+ method: "POST",
820
+ headers: event.request.headers,
821
+ body: body
822
+ });
823
+ return event;
824
+ }
825
+ /**
826
+ * Returns a hash of the session ID. Used for logging (for security,
827
+ * the actual session ID is not logged)
828
+ * @param event the Sveltekit request event
829
+ * @returns hash of the session ID
830
+ */
831
+ getHashOfSessionId(event) {
832
+ if (!event.locals.sessionId)
833
+ return "";
834
+ try {
835
+ return Crypto.hash(event.locals.sessionId);
836
+ }
837
+ catch (e) { }
838
+ return "";
839
+ }
840
+ /**
841
+ * Returns whether or not 2FA authentication was initiated as a result
842
+ * of visiting a page protected by it
843
+ * @param event the request event
844
+ * @returns true or false
845
+ */
846
+ async factor2PageVisitStarted(event) {
847
+ try {
848
+ const pre2fa = this.getSessionData(event, "pre2fa");
849
+ return pre2fa != undefined;
850
+ }
851
+ catch (e) {
852
+ const ce = CrossauthError.asCrossauthError(e);
853
+ CrossauthLogger.logger.debug(j({ err: ce }));
854
+ CrossauthLogger.logger.error(j({ cerr: ce, msg: "Couldn't get pre2fa data from session" }));
855
+ return false;
856
+ }
857
+ }
858
+ /////////////////////////////////////////////////////////////
859
+ // login protected URLs
860
+ /**
861
+ * Returns whether a page being visited as part of a request event is
862
+ * configured to be protected by login.
863
+ *
864
+ * See {@link SvelteKitSessionServerOptions.loginProtectedPageEndpoints} and
865
+ * {@link SvelteKitSessionServerOptions.loginProtectedExceptionPageEndpoints}.
866
+ *
867
+ * @param event the request event
868
+ * @returns true or false
869
+ */
870
+ isLoginPageProtected(event) {
871
+ const url = new URL(typeof event == "string" ? event : event.request.url);
872
+ // login page is never protected
873
+ if (url.pathname == this.loginUrl)
874
+ return false;
875
+ // return false for loginProtectedExceptionPageEndpoints
876
+ let isNotProtected = false;
877
+ isNotProtected = this.loginProtectedExceptionPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
878
+ if (isNotProtected)
879
+ return false;
880
+ // set protected to true for any pages that are in loginProtectedPageEndpoints
881
+ let isProtected = false;
882
+ return this.loginProtectedPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isProtected);
883
+ }
884
+ /**
885
+ * Returns whether an API call is being visited as part of a request event is
886
+ * configured to be protected by login.
887
+ *
888
+ * See {@link SvelteKitSessionServerOptions.loginProtectedApiEndpoints}.
889
+ *
890
+ * @param event the request event
891
+ * @returns true or false
892
+ */
893
+ isLoginApiProtected(event) {
894
+ const url = new URL(typeof event == "string" ? event : event.request.url);
895
+ // login page is never protected
896
+ if (url.pathname == this.loginUrl)
897
+ return false;
898
+ // return false for loginProtectedExceptionApiEndpoints
899
+ let isNotProtected = false;
900
+ isNotProtected = this.loginProtectedExceptionApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
901
+ if (isNotProtected)
902
+ return false;
903
+ let isProtected = false;
904
+ return this.loginProtectedApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isProtected);
905
+ }
906
+ /**
907
+ * Returns whether a page being visited as part of a request event is
908
+ * configured to be protected by 2FA.
909
+ *
910
+ * See {@link SvelteKitSessionServerOptions.factor2ProtectedPageEndpoints}.
911
+ *
912
+ * @param event the request event
913
+ * @returns true or false
914
+ */
915
+ isFactor2PageProtected(event) {
916
+ const url = new URL(typeof event == "string" ? event : event.request.url);
917
+ let isProtected = false;
918
+ return this.factor2ProtectedPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isProtected);
919
+ //return (this.loginProtectedPageEndpoints.includes(url.pathname));
920
+ }
921
+ /**
922
+ * Returns whether an API call is being visited as part of a request event is
923
+ * configured to be protected by 2FA.
924
+ *
925
+ * See {@link SvelteKitSessionServerOptions.factor2ProtectedApiEndpoints}.
926
+ *
927
+ * @param event the request event
928
+ * @returns true or false
929
+ */
930
+ isFactor2ApiProtected(event) {
931
+ const url = new URL(typeof event == "string" ? event : event.request.url);
932
+ //return (this.loginProtectedApiEndpoints.includes(url.pathname));
933
+ let isProtected = false;
934
+ return this.factor2ProtectedApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isProtected);
935
+ }
936
+ /**
937
+ * Returns whether a page being visited as part of a request event is
938
+ * configured to be protected as admin only.
939
+ *
940
+ * See {@link SvelteKitSessionServerOptions.adminPageEndpoints}.
941
+ *
942
+ * @param event the request event
943
+ * @returns true or false
944
+ */
945
+ isAdminPageEndpoint(event) {
946
+ const url = new URL(typeof event == "string" ? event : event.request.url);
947
+ //return (this.adminEndpoints.includes(url.pathname));
948
+ // return false for adminProtectedExceptionPageEndpoints
949
+ let isNotProtected = false;
950
+ isNotProtected = this.adminProtectedExceptionPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
951
+ if (isNotProtected)
952
+ return false;
953
+ isNotProtected = this.loginProtectedExceptionPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
954
+ if (isNotProtected)
955
+ return false;
956
+ let isAdmin = false;
957
+ return this.adminPageEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isAdmin);
958
+ }
959
+ /**
960
+ * Returns whether an AP call being visited as part of a request event is
961
+ * configured to be protected as admin only.
962
+ *
963
+ * See {@link SvelteKitSessionServerOptions.adminApiEndpoints}.
964
+ *
965
+ * @param event the request event
966
+ * @returns true or false
967
+ */
968
+ isAdminApiEndpoint(event) {
969
+ const url = new URL(typeof event == "string" ? event : event.request.url);
970
+ //return (this.adminEndpoints.includes(url.pathname));
971
+ // return false for adminProtectedExceptionApiEndpoints
972
+ let isNotProtected = false;
973
+ isNotProtected = this.adminProtectedExceptionApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
974
+ if (isNotProtected)
975
+ return false;
976
+ isNotProtected = this.loginProtectedExceptionApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isNotProtected);
977
+ if (isNotProtected)
978
+ return false;
979
+ let isAdmin = false;
980
+ return this.adminApiEndpoints.reduce((accumulator, currentValue) => accumulator || minimatch(url.pathname, currentValue), isAdmin);
981
+ }
982
+ /**
983
+ * Creates an anonymous session, setting the `Set-Cookue` headers
984
+ * in the reply.
985
+ *
986
+ * An anonymous sessiin is a session cookie that is not associated
987
+ * with a user (`userid` is undefined). It can be used to persist
988
+ * data between sessions just like a regular user session ID.
989
+ *
990
+ * @param event the SvelteKit reqzest event
991
+ * @param data session data to save
992
+ * @returns the session cookie value
993
+ */
994
+ async createAnonymousSession(event, data) {
995
+ CrossauthLogger.logger.debug(j({ msg: "Creating anonympous session ID " }));
996
+ // get custom fields from implentor-provided function
997
+ const formData = new JsonOrFormData();
998
+ await formData.loadData(event);
999
+ let extraFields = this.addToSession ? this.addToSession(event, formData.toObject()) : {};
1000
+ if (data)
1001
+ extraFields.data = JSON.stringify(data);
1002
+ // create session, setting the session cookie, CSRF cookie and CSRF token
1003
+ let { sessionCookie, csrfCookie, csrfFormOrHeaderValue } = await this.sessionManager.createAnonymousSession(extraFields);
1004
+ event.cookies.set(sessionCookie.name, sessionCookie.value, toCookieSerializeOptions(sessionCookie.options));
1005
+ if (this.enableCsrfProtection) {
1006
+ event.locals.csrfToken = csrfFormOrHeaderValue;
1007
+ event.cookies.set(csrfCookie.name, csrfCookie.value, toCookieSerializeOptions(csrfCookie.options));
1008
+ }
1009
+ event.locals.user = undefined;
1010
+ const sessionId = this.sessionManager.getSessionId(sessionCookie.value);
1011
+ event.locals.sessionId = sessionId;
1012
+ return sessionCookie.value;
1013
+ }
1014
+ ;
1015
+ /**
1016
+ * Sets locals based on session and CSRF cookies.
1017
+ *
1018
+ * Sets things like `locals.user`. You can call this if you need them
1019
+ * updated based on cookie settings and a page load hasn't been done
1020
+ * (ie the hooks haven't run).
1021
+ *
1022
+ * @param event the Sveltekit request event.
1023
+ */
1024
+ async refreshLocals(event) {
1025
+ try {
1026
+ const sessionCookieValue = this.getSessionCookieValue(event);
1027
+ if (sessionCookieValue) {
1028
+ const sessionId = this.sessionManager.getSessionId(sessionCookieValue);
1029
+ event.locals.sessionId = sessionId;
1030
+ const resp = await this.sessionManager.userForSessionId(sessionId);
1031
+ event.locals.user = resp.user;
1032
+ }
1033
+ else {
1034
+ event.locals.sessionId = undefined;
1035
+ event.locals.user = undefined;
1036
+ }
1037
+ }
1038
+ catch (e) {
1039
+ CrossauthLogger.logger.error(j({ errr: e }));
1040
+ }
1041
+ }
1042
+ ////////////////////////////////////////////////////////////////
1043
+ // SessionAdapter interface
1044
+ csrfProtectionEnabled() {
1045
+ return this.enableCsrfProtection;
1046
+ }
1047
+ getCsrfToken(event) {
1048
+ return event.locals.csrfToken;
1049
+ }
1050
+ getUser(event) {
1051
+ return event.locals.user;
1052
+ }
1053
+ /**
1054
+ * Returns the data stored along with the session server-side, with the
1055
+ * given name
1056
+ * @param event the Sveltekit request event
1057
+ * @param name tjhe data name to return
1058
+ * @returns an object or undefined.
1059
+ */
1060
+ async getSessionData(event, name) {
1061
+ try {
1062
+ const data = event.locals.sessionId ?
1063
+ await this.sessionManager.dataForSessionId(event.locals.sessionId) :
1064
+ undefined;
1065
+ if (data && name in data)
1066
+ return data[name];
1067
+ }
1068
+ catch (e) {
1069
+ CrossauthLogger.logger.error(j({
1070
+ msg: "Couldn't get " + name + "from session",
1071
+ cerr: e
1072
+ }));
1073
+ CrossauthLogger.logger.debug(j({ err: e }));
1074
+ }
1075
+ return undefined;
1076
+ }
1077
+ /**
1078
+ * Updates or sets the given field in the session `data` field.
1079
+ *
1080
+ * The `data` field in the session record is assumed to be JSON
1081
+ *
1082
+ * @param event the Sveltekit request event
1083
+ * @param name the name of the field to set
1084
+ * @param value the value to set it to.
1085
+ */
1086
+ async updateSessionData(event, name, value) {
1087
+ if (!event.locals.sessionId)
1088
+ throw new CrossauthError(ErrorCode.Unauthorized, "No session present");
1089
+ await this.sessionManager.updateSessionData(event.locals.sessionId, name, value);
1090
+ }
1091
+ async updateManySessionData(event, dataArray) {
1092
+ if (!event.locals.sessionId)
1093
+ throw new CrossauthError(ErrorCode.Unauthorized, "No session present");
1094
+ await this.sessionManager.updateManySessionData(event.locals.sessionId, dataArray);
1095
+ }
1096
+ /**
1097
+ * Deletes the given field from the session `data` field.
1098
+ *
1099
+ * The `data` field in the session record is assumed to be JSON
1100
+ *
1101
+ * @param event the Sveltekit request event
1102
+ * @param name the name of the field to set
1103
+ */
1104
+ async deleteSessionData(event, name) {
1105
+ if (!event.locals.sessionId) {
1106
+ CrossauthLogger.logger.debug(j({ msg: `Attempting to delete session data ${name} when no session is present` }));
1107
+ }
1108
+ else {
1109
+ await this.sessionManager.deleteSessionData(event.locals.sessionId, name);
1110
+ }
1111
+ }
1112
+ }