@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
@@ -1,6 +1,5 @@
1
- import { RequestEvent } from '@sveltejs/kit';
2
- import { User } from '@crossauth/common';
3
-
1
+ import type { RequestEvent } from '@sveltejs/kit';
2
+ import type { User } from '@crossauth/common';
4
3
  export declare abstract class SvelteKitSessionAdapter {
5
4
  abstract csrfProtectionEnabled(): boolean;
6
5
  abstract getCsrfToken(event: RequestEvent): string | undefined;
@@ -0,0 +1,2 @@
1
+ export class SvelteKitSessionAdapter {
2
+ }
@@ -1,8 +1,9 @@
1
- import { SvelteKitSessionServer, SvelteKitSessionServerOptions } from './sveltekitsession';
2
- import { OAuthClientManager, OAuthClientStorage } from '@crossauth/backend';
3
- import { OAuthClient } from '@crossauth/common';
4
- import { RequestEvent } from '@sveltejs/kit';
5
-
1
+ import { SvelteKitSessionServer } from './sveltekitsession';
2
+ import type { SvelteKitSessionServerOptions } from './sveltekitsession';
3
+ import { OAuthClientManager } from '@crossauth/backend';
4
+ import type { OAuthClientStorage } from '@crossauth/backend';
5
+ import type { OAuthClient } from '@crossauth/common';
6
+ import type { RequestEvent } from '@sveltejs/kit';
6
7
  /**
7
8
  * Return type for {@link SvelteKitUserClientEndpoints.searchClients}
8
9
  * {@link SvelteKitAdminClientEndpoints.searchClients} load.
@@ -290,7 +291,7 @@ export declare class SvelteKitSharedClientEndpoints {
290
291
  * - `csrfToken` the CSRF token if using
291
292
  */
292
293
  baseEndpoint(event: RequestEvent): {
293
- user: import('@crossauth/common').User | undefined;
294
+ user: import("@crossauth/common").User | undefined;
294
295
  csrfToken: string | undefined;
295
296
  };
296
297
  }
@@ -0,0 +1,630 @@
1
+ // Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
2
+ import { SvelteKitServer } from './sveltekitserver';
3
+ import { SvelteKitSessionServer } from './sveltekitsession';
4
+ import { setParameter, ParamType, OAuthClientManager, } from '@crossauth/backend';
5
+ import { CrossauthError, CrossauthLogger, j, ErrorCode, OAuthFlows } from '@crossauth/common';
6
+ import { JsonOrFormData } from './utils';
7
+ import { error, redirect } from '@sveltejs/kit';
8
+ //////////////////////////////////////////////////////////////////////
9
+ // Default functions
10
+ /**
11
+ * The `selectclient` and `admin/selectclient` endpoints have a customisable
12
+ * function for searching for a client. This is the default
13
+ * @param searchTerm the search term passed in the query string
14
+ * @param clientStorage the client storage to search
15
+ * @param userid the user id to se3arch for, or null for clients not owned
16
+ * by a user
17
+ * @returns An array of matching {@link @crossauth/common!OAuthClient} objects,
18
+ */
19
+ export async function defaultClientSearchFn(searchTerm, clientStorage, skip, _take, userid) {
20
+ let clients = [];
21
+ if (skip > 0)
22
+ return [];
23
+ try {
24
+ const client = await clientStorage.getClientById(searchTerm);
25
+ clients.push(client);
26
+ }
27
+ catch (e1) {
28
+ const ce1 = CrossauthError.asCrossauthError(e1);
29
+ if (ce1.code != ErrorCode.InvalidClientId) {
30
+ CrossauthLogger.logger.debug(j({ err: ce1 }));
31
+ throw ce1;
32
+ }
33
+ try {
34
+ clients =
35
+ await clientStorage.getClientByName(searchTerm, userid);
36
+ }
37
+ catch (e2) {
38
+ const ce2 = CrossauthError.asCrossauthError(e2);
39
+ if (ce2.code != ErrorCode.UserNotExist) {
40
+ CrossauthLogger.logger.debug(j({ err: ce2 }));
41
+ throw ce1;
42
+ }
43
+ }
44
+ }
45
+ return clients;
46
+ }
47
+ //////////////////////////////////////////////////////////////////////
48
+ // Class
49
+ /**
50
+ * Base class for user and admin endpoints that manipulate the OAuth
51
+ * clients table
52
+ */
53
+ export class SvelteKitSharedClientEndpoints {
54
+ /**
55
+ * The session server that instantiated this.
56
+ *
57
+ * Set in the constructor
58
+ */
59
+ sessionServer;
60
+ /**
61
+ * The login URL taken from the {@link SvelteKitSessionServerOptions}
62
+ * in the constructor.
63
+ */
64
+ loginUrl = "/login";
65
+ /**
66
+ * Function for searching the client table. Default is to make
67
+ * an exact match search on `client_name`.
68
+ */
69
+ clientSearchFn = defaultClientSearchFn;
70
+ /**
71
+ * The redirect function taken from the {@link SvelteKitSessionServerOptions}
72
+ * in the constructor.
73
+ */
74
+ redirect;
75
+ /**
76
+ * The error function taken from the {@link SvelteKitSessionServerOptions}
77
+ * in the constructor.
78
+ */
79
+ error;
80
+ /**
81
+ * Taken from the {@link SvelteKitSessionServerOptions}
82
+ * in the constructor.
83
+ */
84
+ validFlows = ["all"];
85
+ /**
86
+ * Friendly names for `validFlows`
87
+ */
88
+ valid_flowNames;
89
+ /**
90
+ * The OAuth client manager instantiated during construction
91
+ */
92
+ clientManager;
93
+ /**
94
+ * Taken from the {@link SvelteKitSessionServerOptions}
95
+ * in the constructor.
96
+ */
97
+ clientStorage;
98
+ /**
99
+ * Constructor
100
+ *
101
+ * @param sessionServer the session server to add these endpoints to
102
+ * @param options See {@link SvelteKitSessionServerOptions}
103
+ */
104
+ constructor(sessionServer, options) {
105
+ this.sessionServer = sessionServer;
106
+ setParameter("loginUrl", ParamType.JsonArray, this, options, "LOGIN_URL");
107
+ if (options.clientSearchFn)
108
+ this.clientSearchFn = options.clientSearchFn;
109
+ this.redirect = options.redirect ?? redirect;
110
+ this.error = options.error ?? error;
111
+ setParameter("validFlows", ParamType.JsonArray, this, options, "OAUTH_validFlows");
112
+ if (this.validFlows.length == 1 &&
113
+ this.validFlows[0] == OAuthFlows.All) {
114
+ this.validFlows = OAuthFlows.allFlows();
115
+ }
116
+ this.valid_flowNames = OAuthFlows.flowNames(this.validFlows);
117
+ if (options.clientStorage)
118
+ this.clientManager = new OAuthClientManager(options);
119
+ this.clientStorage = options.clientStorage;
120
+ }
121
+ ///////////////////////////////////////////////////////////////////
122
+ // Functions callable from apps
123
+ /**
124
+ * Returns either a list of all clients for the user matching a search term.
125
+ *
126
+ * The returned list is pagenaed using the `skip` and `take` parameters.
127
+ *
128
+ * The searching is done with `clientSearchFn` that was passed in the
129
+ * options (see {@link SvelteKitSessionServerOptions }). THe default
130
+ * is an exact username match.
131
+ *
132
+ * By default, the searh and pagination parameters are taken from
133
+ * the query parameters in the request but can be overridden.
134
+ *
135
+ * @param event the Sveltekit request event. The following query parameters
136
+ * are read:
137
+ * - `search` the search term which is ignored if it is undefined, null
138
+ * or the empty string.
139
+ * - `skip` the number to start returning from. 0 if not defined
140
+ * - `take` the maximum number to return. 10 if not defined.
141
+ * @param searchTerm overrides the search term from the query.
142
+ * @param skip overrides the skip term from the query
143
+ * @param take overrides the take term from the query
144
+ *
145
+ * @return an object with the following members:
146
+ * - `ok` true or false depending on whether there was an error
147
+ * - `clients` the matching array of clients
148
+ * - `error` error message if `ok` is false
149
+ * - `exception` a {@link @crossauth/common!CrossauthError} if there was
150
+ * an error.
151
+ * - `search` the search term that was used
152
+ * - `skip` the skip term that was used
153
+ * - `take` the take term that was used
154
+ * - `hasNext` whether there are still more results after the ones that
155
+ * were returned
156
+ * - `hasPrevious` whether there are more results before the ones that
157
+ * were returned.
158
+ */
159
+ async searchClients_internal(event, searchTerm, skip, take, userid) {
160
+ try {
161
+ if (!this.sessionServer.userStorage)
162
+ throw new CrossauthError(ErrorCode.Configuration, "Must provide user storage to use this function");
163
+ if (!this.sessionServer.clientStorage)
164
+ throw new CrossauthError(ErrorCode.Configuration, "Must provide client storage to use this function");
165
+ // can only call this if logged in
166
+ if (!event.locals.user)
167
+ throw this.redirect(302, this.loginUrl + "?next=" + encodeURIComponent(event.request.url));
168
+ let clients = [];
169
+ let prevClients = [];
170
+ let nextClients = [];
171
+ if (!skip) {
172
+ try {
173
+ const str = event.url.searchParams.get("skip");
174
+ if (str)
175
+ skip = parseInt(str);
176
+ }
177
+ catch (e) {
178
+ CrossauthLogger.logger.warn(j({ cerr: e, msg: "skip parameter is not an integer" }));
179
+ }
180
+ }
181
+ if (!skip)
182
+ skip = 0;
183
+ if (!take) {
184
+ try {
185
+ const str = event.url.searchParams.get("take");
186
+ if (str)
187
+ take = parseInt(str);
188
+ }
189
+ catch (e) {
190
+ CrossauthLogger.logger.warn(j({ cerr: e, msg: "take parameter is not an integer" }));
191
+ }
192
+ }
193
+ if (!take)
194
+ take = 10;
195
+ const searchFromUrl = event.url.searchParams.get("search");
196
+ if (!searchTerm && searchFromUrl != null && searchFromUrl != "")
197
+ searchTerm = searchFromUrl;
198
+ if (!searchTerm)
199
+ searchTerm = "";
200
+ if (searchTerm.length == 0)
201
+ searchTerm = undefined;
202
+ if (searchTerm) {
203
+ clients = await this.clientSearchFn(searchTerm, this.sessionServer.clientStorage, skip, take);
204
+ if (skip > 0) {
205
+ prevClients = await this.clientSearchFn(searchTerm, this.sessionServer.clientStorage, skip - 1, 1, userid);
206
+ }
207
+ }
208
+ else {
209
+ clients =
210
+ await this.sessionServer.clientStorage.getClients(skip, take, userid);
211
+ if (clients.length == take) {
212
+ nextClients =
213
+ await this.sessionServer.clientStorage.getClients(skip + take, 1, userid);
214
+ }
215
+ }
216
+ return {
217
+ ok: true,
218
+ clients,
219
+ skip,
220
+ take,
221
+ hasPrevious: prevClients.length > 0,
222
+ hasNext: nextClients.length > 0,
223
+ search: searchTerm,
224
+ clientUserId: userid,
225
+ };
226
+ }
227
+ catch (e) {
228
+ if (SvelteKitServer.isSvelteKitRedirect(e) ||
229
+ SvelteKitServer.isSvelteKitRedirect(e))
230
+ throw e;
231
+ const ce = CrossauthError.asCrossauthError(e);
232
+ CrossauthLogger.logger.debug(j({ err: ce }));
233
+ CrossauthLogger.logger.error(j({ cerr: ce }));
234
+ return {
235
+ ok: false,
236
+ error: ce.message,
237
+ errorCode: ce.code,
238
+ errorCodeName: ce.codeName,
239
+ hasPrevious: false,
240
+ hasNext: false,
241
+ skip: skip ?? 0,
242
+ take: take ?? 10,
243
+ search: searchTerm,
244
+ clientUserId: userid,
245
+ };
246
+ }
247
+ }
248
+ /**
249
+ * The base class of the load function for updating an OAuth client.
250
+ *
251
+ * @param event the Sveltekit request event. The following are taken:
252
+ * - `client_id` from the URL path parameters
253
+ * @returns {@link UpdateClientPageData}
254
+ */
255
+ async loadClient_internal(event) {
256
+ const client_id = event.params.client_id;
257
+ try {
258
+ if (!client_id)
259
+ throw new CrossauthError(ErrorCode.BadRequest, "No client ID specified");
260
+ if (!this.clientStorage)
261
+ throw new CrossauthError(ErrorCode.Configuration, "No client storage specified");
262
+ const client = await this.clientStorage.getClientById(client_id);
263
+ const userResp = client.userid == undefined ? undefined : await this.sessionServer?.userStorage?.getUserById(client.userid);
264
+ const clientUsername = userResp?.user?.username;
265
+ return {
266
+ ok: true,
267
+ client: client,
268
+ validFlows: this.validFlows,
269
+ valid_flowNames: this.valid_flowNames,
270
+ client_id,
271
+ clientUsername,
272
+ };
273
+ }
274
+ catch (e) {
275
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't load client");
276
+ CrossauthLogger.logger.debug(j({ err: ce }));
277
+ CrossauthLogger.logger.error(j({ cerr: ce }));
278
+ return {
279
+ error: ce.message,
280
+ errorCode: ce.code,
281
+ errorCodeName: ce.codeName,
282
+ ok: false,
283
+ validFlows: this.validFlows,
284
+ valid_flowNames: this.valid_flowNames,
285
+ client_id,
286
+ };
287
+ }
288
+ }
289
+ /**
290
+ * The base class of the actions function for updating an OAuth client.
291
+ *
292
+ * @param event the Sveltekit request event. The following are taken:
293
+ * - `client_id` from the URL path parameters
294
+ * - `client_name` from the body form data
295
+ * - `redirect_uri` from the body form data (space-separated)
296
+ * - `confidential` from the body form data: 1, `on`, `yes` or `true` are true
297
+ * _ `resetSecret` if true (1, `on`, `yes` or `true`), create and return a new secret. Ignored if not confidential
298
+ * - Flow names from {@link @crossauth/common!OAuthFlows} taken from the body form data. 1, `on`, `yes` or `true` are true
299
+ * @returns {@link UpdateClientFormData}. If a new secret was created, it will be placed as plaintext in the client that is returned.
300
+ */
301
+ async updateClient_internal(event, isAdmin) {
302
+ let formData = undefined;
303
+ try {
304
+ const client_id = event.params.client_id;
305
+ if (!client_id)
306
+ throw new CrossauthError(ErrorCode.BadRequest, "No client ID given");
307
+ // get form data
308
+ var data = new JsonOrFormData();
309
+ await data.loadData(event);
310
+ formData = data.toObject();
311
+ // get client
312
+ //const client = await this.clientStorage?.getClientById(client_id);
313
+ //if (!client) throw new CrossauthError(ErrorCode.InvalidClientId, "Client does not exist");
314
+ // throw an error if the CSRF token is invalid
315
+ if (this.sessionServer.enableCsrfProtection && event.locals.authType == "cookie" && !event.locals.csrfToken) {
316
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
317
+ }
318
+ const redirect_uri = !formData.redirect_uri || formData.redirect_uri.trim().length == 0 ?
319
+ [] : formData.redirect_uri.trim().split(/[, ][ \t\n]*/);
320
+ // validate redirect uris
321
+ let redirect_uriErrors = [];
322
+ for (let uri of redirect_uri) {
323
+ try {
324
+ OAuthClientManager.validateUri(uri);
325
+ }
326
+ catch (e) {
327
+ CrossauthLogger.logger.error(j({ err: e }));
328
+ redirect_uriErrors.push("[" + uri + "]");
329
+ }
330
+ }
331
+ if (redirect_uriErrors.length > 0) {
332
+ throw new CrossauthError(ErrorCode.BadRequest, "The following redirect URIs are invalid: "
333
+ + redirect_uriErrors.join(" "));
334
+ }
335
+ // get flows from booleans in body
336
+ let validFlows = [];
337
+ for (let flow of this.validFlows) {
338
+ if (flow in formData) {
339
+ validFlows.push(flow);
340
+ }
341
+ }
342
+ const clientUpdate = {};
343
+ clientUpdate.client_name = formData.client_name;
344
+ clientUpdate.confidential = data.getAsBoolean("confidential") ?? false;
345
+ clientUpdate.valid_flow = validFlows;
346
+ clientUpdate.redirect_uri = redirect_uri;
347
+ if (isAdmin) {
348
+ let userid = formData.userid ?? undefined;
349
+ if (userid && this.sessionServer?.userStorage) {
350
+ const { user } = await this.sessionServer?.userStorage.getUserById(userid);
351
+ userid = user.id;
352
+ }
353
+ clientUpdate.userid = formData.userid ? Number(formData.userid) : null;
354
+ }
355
+ const resetSecret = data.getAsBoolean("resetSecret");
356
+ if (!this.clientManager)
357
+ throw new CrossauthError(ErrorCode.Configuration, "Cannot call this endpoint as you did not provide a clientStorage");
358
+ const { client: newClient, newSecret } = await this.clientManager.updateClient(client_id, clientUpdate, resetSecret);
359
+ return {
360
+ ok: true,
361
+ client: newClient,
362
+ formData: formData,
363
+ //plaintextSecret: resetSecret ? formData.client_secret : undefined,
364
+ plaintextSecret: newSecret && newClient.client_secret ? newClient.client_secret : undefined,
365
+ };
366
+ }
367
+ catch (e) {
368
+ if (SvelteKitServer.isSvelteKitRedirect(e) || SvelteKitServer.isSvelteKitError(e))
369
+ throw e;
370
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't update client");
371
+ CrossauthLogger.logger.debug(j({ err: ce }));
372
+ CrossauthLogger.logger.error(j({ cerr: ce }));
373
+ return {
374
+ error: ce.message,
375
+ errorCode: ce.code,
376
+ errorCodeName: ce.codeName,
377
+ ok: false,
378
+ formData,
379
+ };
380
+ }
381
+ }
382
+ /**
383
+ * The base class of the load function for creating an OAuth client.
384
+ *
385
+ * @param event the Sveltekit request event. The following are taken:
386
+ * - `userid` from the body parameters parameters. Ignored if `isAdmin` is false. Can be undefined
387
+ * -
388
+ * @returns {@link CreateClientPageData}.
389
+ */
390
+ async emptyClient_internal(event, isAdmin) {
391
+ try {
392
+ // get client user id and username
393
+ var data = new JsonOrFormData();
394
+ await data.loadData(event);
395
+ let clientUserId = undefined;
396
+ if (isAdmin) {
397
+ const clientUserIdString = event.url.searchParams.get("userid");
398
+ if (clientUserIdString && this.sessionServer?.userStorage) {
399
+ const { user } = await this.sessionServer?.userStorage.getUserById(clientUserIdString);
400
+ clientUserId = user.id;
401
+ }
402
+ const formClientUserId = data.get("userid");
403
+ if (formClientUserId && this.sessionServer?.userStorage) {
404
+ const { user } = await this.sessionServer?.userStorage.getUserById(formClientUserId);
405
+ clientUserId = user.id;
406
+ }
407
+ }
408
+ else {
409
+ if (!event.locals.user)
410
+ throw new CrossauthError(ErrorCode.Unauthorized);
411
+ clientUserId = event.locals.user.id;
412
+ }
413
+ if (!this.clientStorage)
414
+ throw new CrossauthError(ErrorCode.Configuration, "No client storage specified");
415
+ const userResp = clientUserId == undefined ? undefined : await this.sessionServer?.userStorage?.getUserById(clientUserId);
416
+ const clientUsername = userResp?.user?.username;
417
+ return {
418
+ ok: true,
419
+ validFlows: this.validFlows,
420
+ valid_flowNames: this.valid_flowNames,
421
+ clientUserId,
422
+ clientUsername,
423
+ };
424
+ }
425
+ catch (e) {
426
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't initialize new client");
427
+ CrossauthLogger.logger.debug(j({ err: ce }));
428
+ CrossauthLogger.logger.error(j({ cerr: ce }));
429
+ return {
430
+ error: ce.message,
431
+ errorCode: ce.code,
432
+ errorCodeName: ce.codeName,
433
+ ok: false,
434
+ validFlows: this.validFlows,
435
+ valid_flowNames: this.valid_flowNames,
436
+ };
437
+ }
438
+ }
439
+ /**
440
+ * The base class of the actions function for creating an OAuth client.
441
+ *
442
+ * @param event the Sveltekit request event. The following are taken:
443
+ * - `userid` from the URL query parameters. Ignored if `isAdmin` is false. Can be undefined
444
+ * - `client_name` from the body form data
445
+ * - `redirect_uri` from the body form data (space-separated)
446
+ * - `confidential` from the body form data: 1, `on`, `yes` or `true` are true
447
+ * - Flow names from {@link @crossauth/common!OAuthFlows} taken from the body form data. 1, `on`, `yes` or `true` are true
448
+ * @returns {@link UpdateClientFormData}. If a secret was created, it will be placed as plaintext in the client that is returned. A random `client_id` is created.
449
+ */
450
+ async createClient_internal(event, isAdmin) {
451
+ let formData = undefined;
452
+ try {
453
+ // get form data
454
+ var data = new JsonOrFormData();
455
+ await data.loadData(event);
456
+ formData = data.toObject();
457
+ // get client user id
458
+ let clientUserId = undefined;
459
+ if (isAdmin) {
460
+ const clientUserIdString = data.get("userid");
461
+ if (clientUserIdString && this.sessionServer?.userStorage) {
462
+ const { user } = await this.sessionServer?.userStorage.getUserById(clientUserIdString);
463
+ clientUserId = user.id;
464
+ }
465
+ }
466
+ else {
467
+ if (!event.locals.user)
468
+ throw new CrossauthError(ErrorCode.Unauthorized);
469
+ clientUserId = event.locals.user.id;
470
+ }
471
+ if (!this.clientStorage)
472
+ throw new CrossauthError(ErrorCode.Configuration, "No client storage specified");
473
+ if (clientUserId)
474
+ await this.sessionServer?.userStorage?.getUserById(clientUserId); // just to make it throw an exception if user doesn't exist
475
+ // throw an error if the CSRF token is invalid
476
+ if (this.sessionServer.enableCsrfProtection && event.locals.authType == "cookie" && !event.locals.csrfToken) {
477
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
478
+ }
479
+ const redirect_uri = !formData.redirect_uri || formData.redirect_uri.trim().length == 0 ?
480
+ [] : formData.redirect_uri.trim().split(/[, ][ \t\n]*/);
481
+ // validate redirect uris
482
+ let redirect_uriErrors = [];
483
+ for (let uri of redirect_uri) {
484
+ try {
485
+ OAuthClientManager.validateUri(uri);
486
+ }
487
+ catch (e) {
488
+ CrossauthLogger.logger.error(j({ err: e }));
489
+ redirect_uriErrors.push("[" + uri + "]");
490
+ }
491
+ }
492
+ if (redirect_uriErrors.length > 0) {
493
+ throw new CrossauthError(ErrorCode.BadRequest, "The following redirect URIs are invalid: "
494
+ + redirect_uriErrors.join(" "));
495
+ }
496
+ // get flows from booleans in body
497
+ let validFlows = [];
498
+ for (let flow of this.validFlows) {
499
+ if (flow in formData) {
500
+ validFlows.push(flow);
501
+ }
502
+ }
503
+ const clientUpdate = {};
504
+ clientUpdate.client_name = formData.client_name;
505
+ clientUpdate.confidential = data.getAsBoolean("confidential");
506
+ clientUpdate.valid_flow = validFlows;
507
+ clientUpdate.redirect_uri = redirect_uri;
508
+ if (isAdmin) {
509
+ clientUpdate.userid = formData.userid ? Number(formData.userid) : null;
510
+ }
511
+ if (!this.clientManager)
512
+ throw new CrossauthError(ErrorCode.Configuration, "Cannot call this endpoint as you did not provide a clientStorage");
513
+ const newClient = await this.clientManager.createClient(formData.client_name, redirect_uri, validFlows, data.getAsBoolean("confidential") ?? false, clientUserId);
514
+ return {
515
+ ok: true,
516
+ client: newClient,
517
+ formData: formData,
518
+ };
519
+ }
520
+ catch (e) {
521
+ if (SvelteKitServer.isSvelteKitRedirect(e) || SvelteKitServer.isSvelteKitError(e))
522
+ throw e;
523
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't create client");
524
+ CrossauthLogger.logger.debug(j({ err: ce }));
525
+ CrossauthLogger.logger.error(j({ cerr: ce }));
526
+ return {
527
+ error: ce.message,
528
+ errorCode: ce.code,
529
+ errorCodeName: ce.codeName,
530
+ ok: false,
531
+ formData,
532
+ };
533
+ }
534
+ }
535
+ /**
536
+ * The base class of the load function for deleting an OAuth client.
537
+ *
538
+ * @param event the Sveltekit request event. The following are taken:
539
+ * - `client_id` from the URL path parameters
540
+ * @returns {@link DeleteClientPageData}
541
+ */
542
+ async loadDeleteClient_internal(event) {
543
+ const client_id = event.params.client_id;
544
+ try {
545
+ if (!client_id)
546
+ throw new CrossauthError(ErrorCode.BadRequest, "No client ID specified");
547
+ if (!this.clientStorage)
548
+ throw new CrossauthError(ErrorCode.Configuration, "No client storage specified");
549
+ const client = await this.clientStorage.getClientById(client_id);
550
+ const userResp = client.userid == undefined ? undefined : await this.sessionServer?.userStorage?.getUserById(client.userid);
551
+ const clientUsername = userResp?.user?.username;
552
+ return {
553
+ ok: true,
554
+ client: client,
555
+ client_id,
556
+ clientUsername,
557
+ };
558
+ }
559
+ catch (e) {
560
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't load client");
561
+ CrossauthLogger.logger.debug(j({ err: ce }));
562
+ CrossauthLogger.logger.error(j({ cerr: ce }));
563
+ return {
564
+ error: ce.message,
565
+ errorCode: ce.code,
566
+ errorCodeName: ce.codeName,
567
+ ok: false,
568
+ client_id,
569
+ };
570
+ }
571
+ }
572
+ /**
573
+ * The base class of the actions function for deleting an OAuth client.
574
+ *
575
+ * @param event the Sveltekit request event. The following are taken:
576
+ * - `client_id` from the URL path parameters
577
+ * @returns {@link DeleteClientFormData}
578
+ */
579
+ async deleteClient_internal(event, isAdmin) {
580
+ try {
581
+ // throw an error if the CSRF token is invalid
582
+ if (this.sessionServer.enableCsrfProtection && event.locals.authType == "cookie" && !event.locals.csrfToken) {
583
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
584
+ }
585
+ const client_id = event.params.client_id;
586
+ if (!client_id)
587
+ throw new CrossauthError(ErrorCode.BadRequest, "No client ID given");
588
+ if (!this.clientStorage)
589
+ throw new CrossauthError(ErrorCode.Configuration, "No client storage specified");
590
+ const client = await this.clientStorage?.getClientById(client_id);
591
+ if (!isAdmin) {
592
+ if (client.userid != event.locals.user?.id)
593
+ throw this.error(401, "Unauthorized");
594
+ }
595
+ await this.clientStorage.deleteClient(client_id);
596
+ return {
597
+ ok: true,
598
+ };
599
+ }
600
+ catch (e) {
601
+ if (SvelteKitServer.isSvelteKitRedirect(e) || SvelteKitServer.isSvelteKitError(e))
602
+ throw e;
603
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't delete client");
604
+ CrossauthLogger.logger.debug(j({ err: ce }));
605
+ CrossauthLogger.logger.error(j({ cerr: ce }));
606
+ return {
607
+ error: ce.message,
608
+ errorCode: ce.code,
609
+ errorCodeName: ce.codeName,
610
+ ok: false,
611
+ };
612
+ }
613
+ }
614
+ /////////////////////////////////////////////////////////////////
615
+ // Endpoints
616
+ /**
617
+ * Returned by all endpoitns
618
+ * @param event the sveltekit request event
619
+ * @returns object with
620
+ * - `user` - the logged in user
621
+ * - `csrfToken` the CSRF token if using
622
+ */
623
+ baseEndpoint(event) {
624
+ return {
625
+ user: event.locals.user,
626
+ csrfToken: event.locals.csrfToken,
627
+ };
628
+ }
629
+ }
630
+ ;