@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.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +16 -6181
- package/dist/sveltekitadminclientendpoints.d.ts +13 -12
- package/dist/sveltekitadminclientendpoints.js +187 -0
- package/dist/sveltekitadminendpoints.d.ts +5 -4
- package/dist/sveltekitadminendpoints.js +766 -0
- package/dist/sveltekitapikey.d.ts +4 -4
- package/dist/sveltekitapikey.js +81 -0
- package/dist/sveltekitoauthclient.d.ts +6 -5
- package/dist/sveltekitoauthclient.js +2309 -0
- package/dist/sveltekitoauthserver.d.ts +4 -4
- package/dist/sveltekitoauthserver.js +1350 -0
- package/dist/sveltekitresserver.d.ts +6 -5
- package/dist/sveltekitresserver.js +286 -0
- package/dist/sveltekitserver.d.ts +11 -10
- package/dist/sveltekitserver.js +393 -0
- package/dist/sveltekitsession.d.ts +5 -5
- package/dist/sveltekitsession.js +1112 -0
- package/dist/sveltekitsessionadapter.d.ts +2 -3
- package/dist/sveltekitsessionadapter.js +2 -0
- package/dist/sveltekitsharedclientendpoints.d.ts +7 -6
- package/dist/sveltekitsharedclientendpoints.js +630 -0
- package/dist/sveltekituserclientendpoints.d.ts +13 -12
- package/dist/sveltekituserclientendpoints.js +270 -0
- package/dist/sveltekituserendpoints.d.ts +6 -5
- package/dist/sveltekituserendpoints.js +1813 -0
- package/dist/tests/sveltekitadminclientendpoints.test.js +330 -0
- package/dist/tests/sveltekitadminendpoints.test.js +242 -0
- package/dist/tests/sveltekitapikeyserver.test.js +44 -0
- package/dist/tests/sveltekitoauthclient.test.d.ts +5 -5
- package/dist/tests/sveltekitoauthclient.test.js +1016 -0
- package/dist/tests/sveltekitoauthresserver.test.d.ts +4 -4
- package/dist/tests/sveltekitoauthresserver.test.js +185 -0
- package/dist/tests/sveltekitoauthserver.test.js +673 -0
- package/dist/tests/sveltekituserclientendpoints.test.js +244 -0
- package/dist/tests/sveltekituserendpoints.test.js +152 -0
- package/dist/tests/sveltemock.test.js +36 -0
- package/dist/tests/sveltemocks.d.ts +2 -3
- package/dist/tests/sveltemocks.js +114 -0
- package/dist/tests/sveltesessionhooks.test.js +224 -0
- package/dist/tests/testshared.d.ts +8 -8
- package/dist/tests/testshared.js +344 -0
- package/dist/utils.d.ts +1 -2
- package/dist/utils.js +123 -0
- package/package.json +6 -4
- 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;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { SvelteKitSessionServer
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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(
|
|
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
|
+
;
|