@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,1350 @@
1
+ // Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
2
+ import { OAuthClientStorage, KeyStorage, OAuthAuthorizationServer, setParameter, ParamType, Authenticator, Crypto, OAuthClientManager, DoubleSubmitCsrfToken, toCookieSerializeOptions, } from '@crossauth/backend';
3
+ import { SvelteKitServer } from './sveltekitserver';
4
+ import { CrossauthError, CrossauthLogger, j, OAuthFlows, ErrorCode } from '@crossauth/common';
5
+ import { json } from '@sveltejs/kit';
6
+ import { JsonOrFormData } from './utils';
7
+ import {} from 'cookie';
8
+ const DEFAULT_UPSTREAM_SESSION_DATA_NAME = "upstreamoauth";
9
+ ;
10
+ ;
11
+ ;
12
+ ///////////////////////////////////////////////////////////////////////////////
13
+ // CLASS
14
+ /**
15
+ * This class implements an OAuth authorization server, serving endpoints
16
+ * with SvelteKit.
17
+ *
18
+ * You shouldn't have to instantiate this directly. It is instantiated
19
+ * by {@link SvelteKitServer} if you enable the authorization server there.
20
+ *
21
+ * **Endpoints**
22
+ *
23
+ * | Name | Description | PageData (returned by load) or JSON returned by get/post | ActionData (return by actions) | Form fields expected by actions or post/get input data |
24
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
25
+ * | baseEndpoint | This PageData is returned by all endpoints' load function. | - `user` logged in {@link @crossauth/common!User} | *Not provided* | |
26
+ * | | | - `csrfToken` CSRF token if enabled | | | | loginPage |
27
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
28
+ * | oidcConfigurationEndpoint | Use this as your `.well-known/openid-configuration`. | `get`: | | |
29
+ * | | | - see {@link @crossauth/backend!OAuthAuthorizationServer.oidcConfiguration} | | |
30
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
31
+ * | jwksGetEndpoint | Use this as your `jwks` endpoint. | `get`: | | |
32
+ * | | | - see {@link @crossauth/backend!OAuthAuthorizationServer.jwks} | | |
33
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
34
+ * | getCsrfTokenEndpoint | Sends a CSRF token for use when refresh token is a cookie | `get`: | | |
35
+ * | | | - `ok` (true or false) | | |
36
+ * | | | - `csrfToken` | | |
37
+ * | | | - `error` | | |
38
+ * | | | - `error_description` | | |
39
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
40
+ * | authorizeEndpoint | The OAuth `authorize` endpoint | `load`: | `default`: | `authorized` true if the user clicks authorized |
41
+ * | | | - `succcess` (true or false) | - `ok`: true or false | `response_type` OAuth response type (take from PageData) |
42
+ * | | | - `authorizationNeeded`: | - `formData`: fata submitted from in the form | `client_id` take from PageData |
43
+ * | | | - `user`: the user object | - `error` an OAuth error type | `redirect_uri` take from PageData |
44
+ * | | | - `response_type`: OAuth response type | - `error_description` text error description | `scope` take from PageData |
45
+ * | | | - `client_id`: | | `state` take from PageData |
46
+ * | | | - `client_name`: | | `code_challenge` take from PageData |
47
+ * | | | - `redirect_uri`: | | `code_challenge_method` take from PageData |
48
+ * | | | - `scope`: as a string | | |
49
+ * | | | - `scopes`: as an array | | |
50
+ * | | | - `state`: | | |
51
+ * | | | - `code_challenge`: | | |
52
+ * | | | - `code_challenge_method`: | | |
53
+ * | | | - `csrfToken`: | | |
54
+ * | | | - `error` | | |
55
+ * | | | - `error_description` | | |
56
+ * | | | See OAuth definition for more detials. | | |
57
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
58
+ * | tokenEndpoint | The OAuth `token` endpoint | `post`: | | See OAuth definition of `token` endpoint |
59
+ * | | | - See OAuth definition of `token` endpoint | | |
60
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
61
+ * | mfaAuthenticatorsEndpoint | For the Auth0 Password MFA authenticators | `get`: | | See OAuth definition of `token` endpoint |
62
+ * | | | - See Auth0 Password MFA documentation | | |
63
+ * | | | `post`: | | |
64
+ * | | | - See Auth0 Password MFA documentation | | See OAuth definition of `token` endpoint |
65
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
66
+ * | mfaChallengeEndpoint | For the Auth0 Password MFA challenge | `post`: | | See OAuth definition of `token` endpoint |
67
+ * | | | - See Auth0 Password MFA documentation | | |
68
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
69
+ * | deviceAuthorizationEndpoint | Starts the device flow (for the device) | `post`: | | `client_id` client ID |
70
+ * | | | - `ok` true or false | | `client_secret` if the client is confidential |
71
+ * | | | - `error` if there was an error | | `scope` optional |
72
+ * | | | - `error_description` if there was an error | | |
73
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
74
+ * | deviceEndpoint | Device flow - authorization endpoint on other device | `load`: | `authorize`: to authorize scopes (call after `userCode`) | `user_code` query param for GET, form field for POST. Optional |
75
+ * | | | See {@link DevicePageData} | See {@link DeviceFormData} | If not provided, user will be prompted |
76
+ * | | | | `userCode` to submit the user code | `authorize`: `client_id`, `user_code`, `authorized` |
77
+ * | --------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
78
+ *
79
+ */
80
+ export class SvelteKitAuthorizationServer {
81
+ /** The underlying framework-independent authorization server */
82
+ authServer;
83
+ svelteKitServer;
84
+ loginUrl = "/login";
85
+ clientStorage;
86
+ // Refresh token cookie functionality
87
+ refreshTokenType = "json";
88
+ refreshTokenCookieName = "CROSSAUTH_REFRESH_TOKEN";
89
+ refreshTokenCookieDomain = undefined;
90
+ refreshTokenCookieHttpOnly = false;
91
+ refreshTokenCookiePath = "/";
92
+ refreshTokenCookieSecure = true;
93
+ refreshTokenCookieSameSite = "strict";
94
+ csrfTokens;
95
+ authorizeEndpointUrl = "/oauth/authorize";
96
+ tokenEndpointUrl = "/oauth/token";
97
+ jwksEndpointUrl = "/oauth/jwks";
98
+ redirect;
99
+ error;
100
+ /**
101
+ * Constructor
102
+ * @param svelteKitServer the SvelteKit server this belongs to
103
+ * @param clientStorage where OAuth clients are stored
104
+ * @param keyStorage where refresh tokens, authorization cods, etc are temporarily stored
105
+ * @param authenticators The authenticators (factor1 and factor2) to enable
106
+ * for the password flow
107
+ * @param options see {@link SvelteKitAuthorizationServerOptions}
108
+ */
109
+ constructor(svelteKitServer, clientStorage, keyStorage, authenticators, options = {}) {
110
+ this.svelteKitServer = svelteKitServer;
111
+ this.clientStorage = clientStorage;
112
+ if (options.redirect)
113
+ this.redirect = options.redirect;
114
+ if (options.error)
115
+ this.error = options.error;
116
+ this.authServer =
117
+ new OAuthAuthorizationServer(this.clientStorage, keyStorage, authenticators, options);
118
+ setParameter("loginUrl", ParamType.String, this, options, "LOGIN_URL");
119
+ setParameter("refreshTokenType", ParamType.String, this, options, "OAUTH_REFRESH_TOKEN_TYPE");
120
+ setParameter("refreshTokenCookieName", ParamType.String, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_NAME");
121
+ setParameter("refreshTokenCookieDomain", ParamType.String, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_DOMAIN");
122
+ setParameter("refreshTokenCookieHttpOnly", ParamType.Boolean, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_HTTPONLY");
123
+ setParameter("refreshTokenCookiePath", ParamType.String, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_PATH");
124
+ setParameter("refreshTokenCookieSecure", ParamType.Boolean, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_SECURE");
125
+ setParameter("refreshTokenCookieSameSite", ParamType.String, this, options, "OAUTH_REFRESH_TOKEN_COOKIE_SAMESITE");
126
+ setParameter("authorizeEndpointUrl", ParamType.String, this, options, "OAUTH_AUTHORIZE_ENDPOINT");
127
+ setParameter("tokenEndpointUrl", ParamType.String, this, options, "OAUTH_TOKEN_ENDPOINT");
128
+ setParameter("jwksEndpointUrl", ParamType.String, this, options, "OAUTH_JWKS_ENDPOINT");
129
+ if (this.refreshTokenType != "json") {
130
+ if (this.svelteKitServer.sessionServer?.enableCsrfProtection == true) {
131
+ this.csrfTokens = this.svelteKitServer.sessionServer.sessionManager.csrfTokens;
132
+ }
133
+ else {
134
+ this.csrfTokens = new DoubleSubmitCsrfToken(options.doubleSubmitCookieOptions);
135
+ }
136
+ }
137
+ }
138
+ /**
139
+ * Returns this server's OIDC configuration. Just wraps
140
+ * {@link @crossauth/backend!OAuthAuthorizationServer.oidcConfiguration}
141
+ * @returns An {@link @crossauth/common!OpenIdConfiguration} object
142
+ */
143
+ oidcConfiguration() {
144
+ return this.authServer.oidcConfiguration({
145
+ authorizeEndpoint: this.authorizeEndpointUrl,
146
+ tokenEndpoint: this.tokenEndpointUrl,
147
+ jwksUri: this.jwksEndpointUrl,
148
+ additionalClaims: []
149
+ });
150
+ }
151
+ ;
152
+ /**
153
+ * Either returns an error or redirects with throw
154
+ */
155
+ async authorize(event, authorized, { responseType, client_id, redirect_uri, scope, state, codeChallenge, codeChallengeMethod, }) {
156
+ let error;
157
+ let errorDescription;
158
+ let code;
159
+ // Create an authorizatin code
160
+ if (authorized) {
161
+ const resp = await this.authServer.authorizeGetEndpoint({
162
+ responseType,
163
+ client_id,
164
+ redirect_uri,
165
+ scope,
166
+ state,
167
+ codeChallenge,
168
+ codeChallengeMethod,
169
+ user: event.locals.user,
170
+ });
171
+ code = resp.code;
172
+ error = resp.error;
173
+ errorDescription = resp.error_description;
174
+ // couldn't create an authorization code
175
+ if (error || !code) {
176
+ const ce = CrossauthError.fromOAuthError(error ?? "server_error", errorDescription ?? "Neither code nor error received");
177
+ CrossauthLogger.logger.error(j({ cerr: ce }));
178
+ return {
179
+ ok: false,
180
+ error,
181
+ error_description: errorDescription,
182
+ };
183
+ }
184
+ throw this.redirect(302, this.authServer.redirect_uri(redirect_uri, code, state));
185
+ }
186
+ else {
187
+ // resource owner did not grant access
188
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "You have not granted access");
189
+ CrossauthLogger.logger.debug(j({ err: ce }));
190
+ CrossauthLogger.logger.error(j({ cerr: ce }));
191
+ CrossauthLogger.logger.error(j({
192
+ msg: errorDescription,
193
+ errorCode: ce.code,
194
+ errorCodeName: ce.codeName
195
+ }));
196
+ try {
197
+ OAuthClientManager.validateUri(redirect_uri);
198
+ throw this.redirect(302, redirect_uri + "?error=access_denied&error_description=" + encodeURIComponent("Access was not granted"));
199
+ }
200
+ catch (e) {
201
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
202
+ throw e;
203
+ CrossauthLogger.logger.error(j({
204
+ msg: `Couldn't send error message ${ce.codeName} to ${redirect_uri}}`
205
+ }));
206
+ return {
207
+ ok: false,
208
+ error: "server_error",
209
+ error_description: "Redirect Uri is not valid"
210
+ };
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Creates and returns a signed CSRF token based on the session ID
216
+ *
217
+ * This is for the situation when the refresh token is sent as a cookie.
218
+ * When this happens, we need CSRF protection on it.
219
+ *
220
+ * @returns a CSRF cookie and value to put in the form or CSRF header
221
+ */
222
+ async createCsrfToken() {
223
+ if (!this.csrfTokens)
224
+ throw new CrossauthError(ErrorCode.Configuration, "CSRF tokens not enabled");
225
+ this.csrfTokens.makeCsrfCookie(this.csrfTokens.createCsrfToken());
226
+ const csrfToken = this.csrfTokens.createCsrfToken();
227
+ const csrfFormOrHeaderValue = this.csrfTokens.makeCsrfFormOrHeaderToken(csrfToken);
228
+ const csrfCookie = this.csrfTokens.makeCsrfCookie(csrfToken);
229
+ return {
230
+ csrfCookie,
231
+ csrfFormOrHeaderValue,
232
+ };
233
+ }
234
+ setRefreshTokenCookie(event, token, expiresIn) {
235
+ if (!this.refreshTokenCookieName)
236
+ return;
237
+ let expiresAt = expiresIn ? new Date(Date.now() + expiresIn * 1000).toUTCString() : undefined;
238
+ let cookieParams = {
239
+ path: this.refreshTokenCookiePath ?? "/",
240
+ };
241
+ if (expiresAt)
242
+ cookieParams.expires = new Date(expiresAt);
243
+ if (this.refreshTokenCookieSameSite)
244
+ cookieParams.sameSite = this.refreshTokenCookieSameSite;
245
+ if (this.refreshTokenCookieDomain)
246
+ cookieParams.domain = this.refreshTokenCookieDomain;
247
+ if (this.refreshTokenCookieHttpOnly == true)
248
+ cookieParams.httpOnly = true;
249
+ if (this.refreshTokenCookieSecure == true)
250
+ cookieParams.secure = true;
251
+ event.cookies.set(this.refreshTokenCookieName, token, cookieParams);
252
+ }
253
+ requireGetParam(event, name) {
254
+ const val = event.url.searchParams.get(name);
255
+ if (!val)
256
+ return {
257
+ ok: false,
258
+ error: "invalid_request",
259
+ error_description: name + " is required"
260
+ };
261
+ return undefined;
262
+ }
263
+ requireBodyParam(formData, name) {
264
+ if (!(name in formData))
265
+ return {
266
+ ok: false,
267
+ error: "invalid_request",
268
+ error_description: name + " is required"
269
+ };
270
+ return undefined;
271
+ }
272
+ getAuthorizeQuery(event) {
273
+ let error = this.requireGetParam(event, "response_type");
274
+ if (error)
275
+ return { error };
276
+ error = this.requireGetParam(event, "client_id");
277
+ if (error)
278
+ return { error };
279
+ error = this.requireGetParam(event, "redirect_uri");
280
+ if (error)
281
+ return { error };
282
+ error = this.requireGetParam(event, "state");
283
+ if (error)
284
+ return { error };
285
+ const response_type = event.url.searchParams.get("response_type") ?? "";
286
+ const client_id = event.url.searchParams.get("client_id") ?? "";
287
+ const redirect_uri = event.url.searchParams.get("redirect_uri") ?? "";
288
+ const scope = event.url.searchParams.get("scope") ?? undefined;
289
+ const state = event.url.searchParams.get("state") ?? "";
290
+ const code_challenge = event.url.searchParams.get("code_challenge") ?? undefined;
291
+ const code_challenge_method = event.url.searchParams.get("code_challenge_method") ?? undefined;
292
+ let query = {
293
+ response_type,
294
+ client_id,
295
+ redirect_uri,
296
+ scope,
297
+ state,
298
+ code_challenge,
299
+ code_challenge_method,
300
+ };
301
+ return { query, error: { error: "Unknown error", error_description: "Unknown error", ok: true } };
302
+ }
303
+ async getMfaChallengeQuery(event) {
304
+ let form = new JsonOrFormData();
305
+ await form.loadData(event);
306
+ const formData = form.toObject();
307
+ let error = this.requireBodyParam(formData, "client_id");
308
+ if (error)
309
+ return { error };
310
+ error = this.requireBodyParam(formData, "challenge_type");
311
+ if (error)
312
+ return { error };
313
+ error = this.requireBodyParam(formData, "mfa_token");
314
+ if (error)
315
+ return { error };
316
+ error = this.requireBodyParam(formData, "authenticator_id");
317
+ if (error)
318
+ return { error };
319
+ const client_id = formData.client_id ?? "";
320
+ const challenge_type = formData.challenge_type ?? "";
321
+ const mfa_token = formData.mfa_token ?? "";
322
+ const authenticator_id = formData.authenticator_id ?? "";
323
+ const client_secret = formData.client_secret ?? undefined;
324
+ let query = {
325
+ client_id,
326
+ client_secret,
327
+ challenge_type,
328
+ mfa_token,
329
+ authenticator_id,
330
+ };
331
+ return { query, error: { error: "Unknown error", error_description: "Unknown error", ok: true } };
332
+ }
333
+ async mfaAuthenticators(event) {
334
+ const authHeader = event.request.headers.get('authorization')?.split(" ");
335
+ if (!authHeader || authHeader.length != 2) {
336
+ return {
337
+ error: "access_denied",
338
+ error_description: "Invalid authorization header"
339
+ };
340
+ }
341
+ const mfa_token = authHeader[1];
342
+ const resp = await this.authServer.mfaAuthenticatorsEndpoint(mfa_token);
343
+ if (resp.authenticators) {
344
+ return resp.authenticators;
345
+ }
346
+ const ce = CrossauthError.fromOAuthError(resp.error ?? "server_error");
347
+ return {
348
+ error: ce.oauthErrorCode,
349
+ error_description: ce.message,
350
+ };
351
+ }
352
+ async mfaChallenge(event) {
353
+ let qresp = await this.getMfaChallengeQuery(event);
354
+ if (!qresp.query)
355
+ return qresp.error;
356
+ let query = qresp.query;
357
+ const resp = await this.authServer.mfaChallengeEndpoint(query.mfa_token, query.client_id, query.client_secret, query.challenge_type, query.authenticator_id);
358
+ return resp;
359
+ }
360
+ getClientIdAndSecret(formData, event) {
361
+ // OAuth spec says we may take client credentials from
362
+ // authorization header
363
+ let client_id = formData.client_id;
364
+ let client_secret = formData.client_secret;
365
+ const authorizationHeader = event.request.headers.get("authorization");
366
+ if (authorizationHeader) {
367
+ let client_id1;
368
+ let client_secret1;
369
+ const parts = authorizationHeader.split(" ");
370
+ if (parts.length == 2 &&
371
+ parts[0].toLocaleLowerCase() == "basic") {
372
+ const decoded = Crypto.base64Decode(parts[1]);
373
+ const parts2 = decoded.split(":", 2);
374
+ if (parts2.length == 2) {
375
+ client_id1 = parts2[0];
376
+ client_secret1 = parts2[1];
377
+ }
378
+ }
379
+ if (client_id1 == undefined || client_secret1 == undefined) {
380
+ CrossauthLogger.logger.warn(j({
381
+ msg: "Ignoring malform authenization header " +
382
+ authorizationHeader
383
+ }));
384
+ }
385
+ else {
386
+ client_id = client_id1;
387
+ client_secret = client_secret1;
388
+ }
389
+ }
390
+ return { client_id, client_secret };
391
+ }
392
+ async applyUserCode(userCode, event, user) {
393
+ // if there is a user code, apply it. Otherwise we will show the form
394
+ // and it will be processed by the action
395
+ try {
396
+ const ret = await this.authServer.deviceEndpoint({ userCode, user });
397
+ if (ret.error) {
398
+ return {
399
+ ok: false,
400
+ completed: false,
401
+ retryAllowed: false,
402
+ error: ret.error,
403
+ error_description: ret.error_description,
404
+ };
405
+ }
406
+ if (!ret.client_id) {
407
+ CrossauthLogger.logger.error(j({ msg: "No client id found for user code", userCodeHash: Crypto.hash(userCode), ip: event.request.referrer, username: event.locals.user?.username }));
408
+ return {
409
+ ok: false,
410
+ completed: false,
411
+ retryAllowed: false,
412
+ error: "server_error",
413
+ error_description: "No client id found for user code",
414
+ };
415
+ }
416
+ if (ret.error == "access_denied") {
417
+ CrossauthLogger.logger.error(j({ msg: "Incorrect user code given", userCodeHash: Crypto.hash(userCode), ip: event.request.referrer, username: event.locals.user?.username }));
418
+ if (this.authServer.userCodeThrottle > 0) {
419
+ let wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
420
+ await wait(this.authServer.userCodeThrottle);
421
+ }
422
+ return {
423
+ ok: false,
424
+ completed: false,
425
+ retryAllowed: true,
426
+ error: ret.error,
427
+ error_description: ret.error_description,
428
+ };
429
+ }
430
+ else if (ret.error == "expired_token") {
431
+ CrossauthLogger.logger.error(j({ msg: "Expired user code", userCodeHash: Crypto.hash(userCode), ip: event.request.referrer, username: event.locals.user?.username }));
432
+ return {
433
+ ok: false,
434
+ completed: false,
435
+ retryAllowed: false,
436
+ error: ret.error,
437
+ error_description: ret.error_description,
438
+ };
439
+ }
440
+ const client = await this.clientStorage.getClientById(ret.client_id);
441
+ // if the user needs to authorize scopes, tell the caller this
442
+ // - user code will have not been set to ok in the above call yet
443
+ if (ret.scopeAuthorizationNeeded) {
444
+ return {
445
+ ok: true,
446
+ completed: false,
447
+ retryAllowed: true,
448
+ authorizationNeeded: {
449
+ user,
450
+ client_id: ret.client_id,
451
+ client_name: client.client_name,
452
+ scope: ret.scope,
453
+ scopes: ret.scope ? ret.scope.split(" ") : [],
454
+ csrfToken: event.locals.csrfToken
455
+ },
456
+ user: event.locals.user,
457
+ csrfToken: event.locals.csrfToken,
458
+ user_code: userCode,
459
+ };
460
+ }
461
+ else {
462
+ // all scopes were authorized - this completes the flow
463
+ return {
464
+ ok: true,
465
+ completed: true,
466
+ retryAllowed: false,
467
+ user: event.locals.user,
468
+ csrfToken: event.locals.csrfToken,
469
+ };
470
+ }
471
+ }
472
+ catch (e) {
473
+ const ce = CrossauthError.asCrossauthError(e);
474
+ CrossauthLogger.logger.debug(j({ err: ce }));
475
+ CrossauthLogger.logger.error(j({ msg: ce.message, cerr: ce }));
476
+ return {
477
+ ok: false,
478
+ completed: false,
479
+ retryAllowed: true,
480
+ error: ce.oauthErrorCode,
481
+ error_description: ce.message,
482
+ };
483
+ }
484
+ }
485
+ ////////////////////////////////////////////////////////////////
486
+ // Sveltekit user endpoints
487
+ /**
488
+ * Fields that are returned by all endpoint load methods
489
+ *
490
+ * See class description
491
+ * @param event the SvelteKit event
492
+ * @returns an object with:
493
+ * - `user` the `User` object from `event.locals.user`
494
+ * - `csrfToken` the CSRF token from `event.locals.csrfToken`
495
+ */
496
+ baseEndpoint(event) {
497
+ return {
498
+ user: event.locals.user,
499
+ csrfToken: event.locals.csrfToken,
500
+ };
501
+ }
502
+ /**
503
+ * `get` function for the oidcConfiguration endpoint.
504
+ *
505
+ * See class description for details.
506
+ */
507
+ oidcConfigurationEndpoint = {
508
+ get: async (_event) => {
509
+ return json(this.authServer.oidcConfiguration({
510
+ authorizeEndpoint: this.authorizeEndpointUrl,
511
+ tokenEndpoint: this.tokenEndpointUrl,
512
+ jwksUri: this.jwksEndpointUrl,
513
+ additionalClaims: []
514
+ }));
515
+ },
516
+ };
517
+ /**
518
+ * `get` function for the jwks endpoint.
519
+ *
520
+ * See class description for details.
521
+ */
522
+ jwksGetEndpoint = {
523
+ get: async (_event) => {
524
+ try {
525
+ return json(this.authServer.jwks());
526
+ }
527
+ catch (e) {
528
+ const ce = CrossauthError.asCrossauthError(e);
529
+ CrossauthLogger.logger.debug({ err: e });
530
+ CrossauthLogger.logger.error({ cerr: e });
531
+ return json({
532
+ error: ce.oauthErrorCode,
533
+ error_description: ce.message
534
+ });
535
+ }
536
+ },
537
+ };
538
+ /**
539
+ * `get` function for the csrfToken endpoint.
540
+ *
541
+ * See class description for details. Not needed if you disable CSRF
542
+ * protection to rely on Sveltekit's.
543
+ */
544
+ getCsrfTokenEndpoint = {
545
+ get: async (event) => {
546
+ if (!this.csrfTokens)
547
+ return json({
548
+ ok: false,
549
+ error: "invalid_request",
550
+ error_description: "No CSRF token",
551
+ });
552
+ let csrfCookieValue = "";
553
+ try {
554
+ const { csrfCookie, csrfFormOrHeaderValue } = await this.createCsrfToken();
555
+ csrfCookieValue = csrfCookie.value;
556
+ event.cookies.set(csrfCookie.name, csrfCookie.value, toCookieSerializeOptions(csrfCookie.options));
557
+ return json({ ok: true, csrfToken: csrfFormOrHeaderValue });
558
+ }
559
+ catch (e) {
560
+ const ce = CrossauthError.asCrossauthError(e);
561
+ CrossauthLogger.logger.error(j({
562
+ msg: "getcsrftoken failure",
563
+ user: event.locals.user?.username,
564
+ hashedCsrfCookie: Crypto.hash(csrfCookieValue.split(".")[0]),
565
+ error: ce.code,
566
+ errorCodeName: ce.codeName
567
+ }));
568
+ CrossauthLogger.logger.debug(j({ err: e }));
569
+ CrossauthLogger.logger.error({ cerr: e });
570
+ return json({
571
+ ok: false,
572
+ error: ce.oauthErrorCode,
573
+ error_description: ce.message,
574
+ });
575
+ }
576
+ },
577
+ };
578
+ async storeSessionData(event, sessionData, sessionDataName) {
579
+ // we need a session to save the state
580
+ if (this.svelteKitServer.sessionServer) {
581
+ // if we are using Crossauth's session server and there is nop
582
+ // session, we can create an anonymous one.
583
+ // For other session adapters, we assume the session was already
584
+ // creeated or that the session adapter can create one automaticall
585
+ // when updateSessionData is called
586
+ let sessionCookieValue = this.svelteKitServer.sessionServer?.getSessionCookieValue(event);
587
+ if (!sessionCookieValue) {
588
+ sessionCookieValue =
589
+ await this.svelteKitServer.sessionServer?.createAnonymousSession(event, { [sessionDataName]: sessionData });
590
+ }
591
+ else {
592
+ await this.svelteKitServer.sessionAdapter?.updateSessionData(event, sessionDataName, sessionData);
593
+ }
594
+ }
595
+ else {
596
+ await this.svelteKitServer.sessionAdapter?.updateSessionData(event, sessionDataName, sessionData);
597
+ }
598
+ }
599
+ async redirectError(redirect_uri, error, error_description) {
600
+ if (redirect_uri) {
601
+ throw this.redirect(302, redirect_uri + "?error=" + encodeURIComponent(error) + "&error_description=" + encodeURIComponent(error_description));
602
+ }
603
+ else {
604
+ throw this.error(500, error_description);
605
+ }
606
+ }
607
+ /**
608
+ * `load` and `actions` functions for the authorize endpoint.
609
+ *
610
+ * See class description for details.
611
+ */
612
+ authorizeEndpoint = {
613
+ load: async (event) => {
614
+ if (!(this.authServer.validFlows.includes(OAuthFlows.AuthorizationCode) ||
615
+ this.authServer.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE) ||
616
+ this.authServer.validFlows.includes(OAuthFlows.OidcAuthorizationCode))) {
617
+ throw this.error(401, "authorize cannot be called because the authorization code flows are not supported");
618
+ }
619
+ // handle the case where we have an upstream authz server
620
+ // either throw redirect or return error
621
+ if (this.authServer.upstreamClient && this.authServer.upstreamClientOptions) {
622
+ const state = Crypto.randomValue(32);
623
+ let resp = this.getAuthorizeQuery(event);
624
+ if (!resp.query)
625
+ return resp.error;
626
+ let query = resp.query;
627
+ if (query.response_type == "code") {
628
+ // validate client
629
+ let client;
630
+ try {
631
+ client = await this.clientStorage.getClientById(query.client_id);
632
+ }
633
+ catch (e) {
634
+ CrossauthLogger.logger.debug(j({ err: e }));
635
+ return { ok: false, error: "unauthorized_client",
636
+ error_description: "Client is not authorized" };
637
+ }
638
+ let scopes = undefined;
639
+ if (query.scope)
640
+ scopes = decodeURIComponent(query.scope).split(" ");
641
+ scopes = scopes?.filter((a) => (a.length > 0));
642
+ const resp = await this.authServer.upstreamClient.startAuthorizationCodeFlow(state, query.scope, query.code_challenge, query.code_challenge != undefined);
643
+ if (resp.error) {
644
+ return {
645
+ ok: false,
646
+ error: resp.error,
647
+ error_description: resp.error_description,
648
+ };
649
+ }
650
+ else {
651
+ const codeResp = await this.authServer.getAuthorizationCode(client, query.redirect_uri, scopes, state, query.code_challenge, query.code_challenge_method);
652
+ if (!codeResp.code) {
653
+ return {
654
+ ok: false,
655
+ error: "server_error",
656
+ error_description: "Couldn't create authorization code",
657
+ };
658
+ }
659
+ const sessionData = {
660
+ scope: query.scope,
661
+ state,
662
+ code: codeResp.code,
663
+ orig_client_id: query.client_id,
664
+ orig_redirect_uri: query.redirect_uri,
665
+ orig_state: query.state
666
+ };
667
+ if (!this.authServer.upstreamClientOptions.options.redirect_uri) {
668
+ return {
669
+ ok: false,
670
+ error: "server_error",
671
+ error_description: "redirect uri not given for upstream client",
672
+ };
673
+ }
674
+ // we need a session to save the state
675
+ const sessionDataName = this.authServer.upstreamClientOptions.sessionDataName ?? DEFAULT_UPSTREAM_SESSION_DATA_NAME;
676
+ await this.storeSessionData(event, sessionData, sessionDataName);
677
+ //await this.authServer.setAuthorizationCodeData(codeResp.code, sessionData);
678
+ /*let url = this.authServer.upstreamClientOptions.authServerBaseUrl +
679
+ "?response_type=code" +
680
+ "&redirect_uri=" + encodeURIComponent(this.authServer.upstreamClientOptions.options.redirect_uri)
681
+ "&client_id=" + encodeURIComponent(this.authServer.upstreamClient.client_id);
682
+ if (this.authServer.upstreamClient.client_secret) url += "&client_secret=" + encodeURIComponent(this.authServer.upstreamClient.client_secret);
683
+ if (query.state) url += "&state=" + encodeURIComponent(query.state);
684
+ if (this.authServer.upstreamClientOptions.scopes) url += encodeURIComponent(this.authServer.upstreamClientOptions.scopes.join(" "));*/
685
+ let url = resp.url;
686
+ CrossauthLogger.logger.debug(j({ msg: "upstream url " + url }));
687
+ //CrossauthLogger.logger.debug(j({msg: "upstream base url " + this.authServer.upstreamAuthServerBaseUrl}))
688
+ throw this.redirect(302, url);
689
+ }
690
+ }
691
+ else {
692
+ return {
693
+ ok: false,
694
+ error: "invalid_request",
695
+ error_description: "authorize can only be called with response_type code",
696
+ };
697
+ }
698
+ }
699
+ // we don't have an upstream server
700
+ if (!event.locals.user)
701
+ return this.redirect(302, this.loginUrl + "?next=" + encodeURIComponent(event.request.url));
702
+ let resp = this.getAuthorizeQuery(event);
703
+ if (!resp.query)
704
+ return resp.error;
705
+ let query = resp.query;
706
+ // this just checks they are valid strings and not empty if required,
707
+ // to avoid XSR vulnerabilities
708
+ CrossauthLogger.logger.debug(j({ msg: "validating authorize parameters" }));
709
+ let { error_description } = this.authServer.validateAuthorizeParameters(query);
710
+ let ce = undefined;
711
+ if (error_description) {
712
+ ce = new CrossauthError(ErrorCode.BadRequest, error_description);
713
+ CrossauthLogger.logger.error(j({
714
+ msg: "authorize parameter invalid",
715
+ cerr: ce,
716
+ user: event.locals.user?.username
717
+ }));
718
+ }
719
+ else {
720
+ CrossauthLogger.logger.error(j({
721
+ msg: "authorize parameter valid",
722
+ user: event.locals.user?.username
723
+ }));
724
+ }
725
+ if (ce) {
726
+ return {
727
+ ok: false,
728
+ error: ce.oauthErrorCode,
729
+ error_description: ce.message
730
+ };
731
+ //throw this.error(ce.httpStatus, ce.message);
732
+ }
733
+ let hasAllScopes = false;
734
+ CrossauthLogger.logger.debug(j({
735
+ msg: `Checking scopes have been authorized`,
736
+ scope: query.scope
737
+ }));
738
+ if (query.scope) {
739
+ hasAllScopes = await this.authServer.hasAllScopes(query.client_id, event.locals.user, query.scope.split(" "));
740
+ }
741
+ else {
742
+ hasAllScopes = await this.authServer.hasAllScopes(query.client_id, event.locals.user, [null]);
743
+ }
744
+ if (hasAllScopes) {
745
+ CrossauthLogger.logger.debug(j({
746
+ msg: `All scopes authorized`,
747
+ scope: query.scope
748
+ }));
749
+ // all scopes have been previously authorized
750
+ // - create an authorization code
751
+ const resp = await this.authorize(event, true, {
752
+ responseType: query.response_type,
753
+ client_id: query.client_id,
754
+ redirect_uri: query.redirect_uri,
755
+ scope: query.scope,
756
+ state: query.state,
757
+ codeChallenge: query.code_challenge,
758
+ codeChallengeMethod: query.code_challenge_method,
759
+ });
760
+ // the above either throws a redirect or returns with an error
761
+ return {
762
+ ok: false,
763
+ error: resp.error ?? "server_error",
764
+ error_description: resp.error_description ?? "An unexpected error occurred",
765
+ };
766
+ }
767
+ else {
768
+ // requesting new scopes - signal caller to show authorization
769
+ // page to user
770
+ CrossauthLogger.logger.debug(j({
771
+ msg: `Not all scopes authorized`,
772
+ scope: query.scope
773
+ }));
774
+ try {
775
+ const client = await this.clientStorage.getClientById(query.client_id);
776
+ return {
777
+ ok: true,
778
+ authorizationNeeded: {
779
+ user: event.locals.user,
780
+ response_type: query.response_type,
781
+ client_id: query.client_id,
782
+ client_name: client.client_name,
783
+ redirect_uri: query.redirect_uri,
784
+ scope: query.scope,
785
+ scopes: query.scope ? query.scope.split(" ") : undefined,
786
+ state: query.state,
787
+ code_challenge: query.code_challenge,
788
+ code_challenge_method: query.code_challenge_method,
789
+ csrfToken: event.locals.csrfToken,
790
+ },
791
+ ...this.baseEndpoint,
792
+ };
793
+ }
794
+ catch (e) {
795
+ const ce = e;
796
+ CrossauthLogger.logger.debug(j({ err: ce }));
797
+ return {
798
+ ok: false,
799
+ error: "unauthorized_client",
800
+ error_description: "Not a valid client",
801
+ };
802
+ }
803
+ }
804
+ }, // load
805
+ actions: {
806
+ default: async (event) => {
807
+ let formData = undefined;
808
+ try {
809
+ // get form data
810
+ var data = new JsonOrFormData();
811
+ await data.loadData(event);
812
+ formData = data.toObject();
813
+ const authorized = data.getAsBoolean('authorized');
814
+ const response_type = formData.response_type;
815
+ const client_id = formData.client_id;
816
+ const redirect_uri = formData.redirect_uri;
817
+ const scope = formData.scope;
818
+ const state = formData.state;
819
+ const code_challenge = formData.code_challenge;
820
+ const code_challenge_method = formData.code_challenge_method;
821
+ let missing = undefined;
822
+ if (authorized == undefined)
823
+ missing = "authorized";
824
+ if (!response_type)
825
+ missing = "response_type";
826
+ else if (!client_id)
827
+ missing = "client_id";
828
+ else if (!redirect_uri)
829
+ missing = "redirect_uri";
830
+ else if (!state)
831
+ missing = "state";
832
+ if (missing) {
833
+ return {
834
+ ok: false,
835
+ error: "invalid_request",
836
+ error_description: "Invalid form: does not contain " + missing + " parameter"
837
+ };
838
+ }
839
+ // this should not be called if a user is not logged in
840
+ if (!event.locals.user)
841
+ return this.redirect(302, this.loginUrl + "?next=" + encodeURIComponent(event.request.url));
842
+ if (this.svelteKitServer.sessionServer?.enableCsrfProtection && !event.locals.csrfToken)
843
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
844
+ // The following will either return an error or will throw a redirect
845
+ const resp = await this.authorize(event, authorized ?? false, {
846
+ responseType: response_type,
847
+ client_id: client_id,
848
+ redirect_uri: redirect_uri,
849
+ scope: scope,
850
+ state: state,
851
+ codeChallenge: code_challenge,
852
+ codeChallengeMethod: code_challenge_method,
853
+ });
854
+ // the above either throws a redirect or returns with an error
855
+ return {
856
+ ok: false,
857
+ error: resp.error ?? "server_error",
858
+ error_description: resp.error_description ?? "An unexpected error occurred",
859
+ };
860
+ }
861
+ catch (e) {
862
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
863
+ throw e;
864
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't process authorization code");
865
+ return {
866
+ error: ce.oauthErrorCode,
867
+ error_description: ce.message,
868
+ ok: false,
869
+ formData,
870
+ };
871
+ }
872
+ }
873
+ }
874
+ };
875
+ /**
876
+ * `post` function for the token endpoint.
877
+ *
878
+ * See class description for details. Not needed if you disable CSRF
879
+ * protection to rely on Sveltekit's.
880
+ */
881
+ tokenEndpoint = {
882
+ post: async (event) => {
883
+ let formData = undefined;
884
+ try {
885
+ if (!(this.authServer.validFlows.includes(OAuthFlows.AuthorizationCode) ||
886
+ this.authServer.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE) ||
887
+ this.authServer.validFlows.includes(OAuthFlows.OidcAuthorizationCode) ||
888
+ this.authServer.validFlows.includes(OAuthFlows.ClientCredentials) ||
889
+ this.authServer.validFlows.includes(OAuthFlows.RefreshToken) ||
890
+ this.authServer.validFlows.includes(OAuthFlows.Password) ||
891
+ this.authServer.validFlows.includes(OAuthFlows.PasswordMfa ||
892
+ this.authServer.validFlows.includes(OAuthFlows.DeviceCode)))) {
893
+ return json({
894
+ ok: false,
895
+ error: "invalid_request",
896
+ error_description: "Token endpoint cannot be called as the supported OAuth flow types don't require it",
897
+ }, { status: 500 });
898
+ }
899
+ // get form data
900
+ var data = new JsonOrFormData();
901
+ await data.loadData(event);
902
+ formData = data.toObject();
903
+ const { client_id, client_secret } = this.getClientIdAndSecret(formData, event);
904
+ // if the grant type is authorization_code and we have an upstream client
905
+ // defined, we should already have tokens stored with the authoriazion code
906
+ if (formData.grant_type == "authorization_code" &&
907
+ this.authServer.upstreamClient && this.authServer.upstreamClientOptions) {
908
+ const codeData = await this.authServer.getAuthorizationCodeData(formData.code);
909
+ if (codeData == undefined) {
910
+ return json({
911
+ error: "access_denied",
912
+ error_description: "Invalid or expired authorization code",
913
+ });
914
+ }
915
+ if (!codeData.access_token) {
916
+ return json({ error: "access_denied", error_description: "No access token was issued" }, { status: 401 });
917
+ }
918
+ await this.authServer.deleteAuthorizationCodeData(formData.code);
919
+ return json({
920
+ access_token: codeData.access_token,
921
+ id_token: codeData.id_token,
922
+ id_payload: codeData.id_payload,
923
+ refresh_token: codeData.refresh_token,
924
+ expires_in: codeData.expires_in,
925
+ });
926
+ }
927
+ // if refreshTokenType is not "json", check if there
928
+ // is a refresh token in the cookie.
929
+ // there must also be a valid CSRF token
930
+ let refreshToken = formData.refresh_token;
931
+ let refreshTokenCookie = event.cookies.get(this.refreshTokenCookieName);
932
+ if (((this.refreshTokenType == "cookie" && refreshTokenCookie) ||
933
+ (this.refreshTokenType == "both" && refreshTokenCookie &&
934
+ refreshToken == undefined)) &&
935
+ this.csrfTokens /* this part is just for typescript checker */) {
936
+ const csrfCookie = event.cookies.get(this.csrfTokens.cookieName);
937
+ let csrfHeader = event.request.headers.get(this.csrfTokens.headerName.toLowerCase());
938
+ if (Array.isArray(csrfHeader))
939
+ csrfHeader = csrfHeader[0];
940
+ if (!csrfCookie || !csrfHeader) {
941
+ return json({
942
+ ok: false,
943
+ error: "access_denied",
944
+ error_description: "Invalid csrf token",
945
+ }, { status: 401 });
946
+ }
947
+ try {
948
+ this.csrfTokens.validateDoubleSubmitCsrfToken(csrfCookie, csrfHeader);
949
+ }
950
+ catch (e) {
951
+ CrossauthLogger.logger.debug(j({ err: e }));
952
+ CrossauthLogger.logger.warn(j({ cerr: e, msg: "Invalid csrf token", client_id: formData.client_id }));
953
+ return json({
954
+ ok: false,
955
+ error: "access_denied",
956
+ error_description: "Invalid csrf token",
957
+ }, { status: 401 });
958
+ }
959
+ refreshToken = refreshTokenCookie;
960
+ }
961
+ const resp = await this.authServer.tokenEndpoint({
962
+ grantType: formData.grant_type,
963
+ client_id: client_id,
964
+ client_secret: client_secret,
965
+ scope: formData.scope,
966
+ codeVerifier: formData.code_verifier,
967
+ code: formData.code,
968
+ username: formData.username,
969
+ password: formData.password,
970
+ mfaToken: formData.mfa_token,
971
+ oobCode: formData.oob_code,
972
+ bindingCode: formData.binding_code,
973
+ otp: formData.otp,
974
+ refreshToken: refreshToken,
975
+ deviceCode: formData.device_code,
976
+ });
977
+ if (resp.refresh_token && this.refreshTokenType != "json") {
978
+ this.setRefreshTokenCookie(event, resp.refresh_token, resp.expires_in);
979
+ }
980
+ if (resp.error == "authorization_pending") {
981
+ return json(resp);
982
+ }
983
+ if (resp.error || !resp.access_token) {
984
+ let error = "server_error";
985
+ let errorDescription = "Neither code nor error received when requestoing authorization";
986
+ if (resp.error)
987
+ error = resp.error;
988
+ if (resp.error_description)
989
+ errorDescription = resp.error_description;
990
+ const ce = CrossauthError.fromOAuthError(error, errorDescription);
991
+ CrossauthLogger.logger.error(j({ cerr: ce }));
992
+ return json(resp, { status: ce.httpStatus });
993
+ }
994
+ return json(resp);
995
+ }
996
+ catch (e) {
997
+ const ce = CrossauthError.asCrossauthError(e);
998
+ CrossauthLogger.logger.debug({ err: e });
999
+ CrossauthLogger.logger.error({ cerr: e });
1000
+ return json({
1001
+ error: ce.oauthErrorCode,
1002
+ error_description: ce.message
1003
+ }, { status: ce.httpStatus });
1004
+ }
1005
+ },
1006
+ };
1007
+ upstreamRedirectUriEndpoint = {
1008
+ get: async (event) => {
1009
+ let oauthData = {};
1010
+ try {
1011
+ if (!this.authServer.upstreamClientOptions) {
1012
+ CrossauthLogger.logger.error(j({ msg: "Cannot call upstreamRedirectUriEndpoint if upstreamClient not defined" }));
1013
+ return json({
1014
+ ok: false,
1015
+ error: "server_error",
1016
+ error_description: "Cannot call upstreamRedirectUriEndpoint if upstreamClient not defined"
1017
+ });
1018
+ }
1019
+ CrossauthLogger.logger.debug(j({ msg: "upstreamRedirectUriEndpoint" }));
1020
+ if (!this.authServer.upstreamClient || !this.authServer.upstreamClientOptions.tokenMergeFn) {
1021
+ CrossauthLogger.logger.error(j({ msg: "upstreamRedirectUri endpoint called but no upstreamClient or merge function set" }));
1022
+ return this.redirectError(oauthData.orig_redirect_uri, "server_error", "upstreamRedirectUri endpoint called but no upstreamClient or merge function set");
1023
+ }
1024
+ const code = event.url.searchParams.get("code") ?? "";
1025
+ const state = event.url.searchParams.get("state") ?? undefined;
1026
+ const error = event.url.searchParams.get("error") ?? undefined;
1027
+ const error_description = event.url.searchParams.get("error") ?? undefined;
1028
+ const sessionDataName = this.authServer.upstreamClientOptions.sessionDataName ?? DEFAULT_UPSTREAM_SESSION_DATA_NAME;
1029
+ oauthData = await this.svelteKitServer.sessionAdapter?.getSessionData(event, sessionDataName) ?? {};
1030
+ if (oauthData?.state != state) {
1031
+ CrossauthLogger.logger.error(j({ msg: "State does not match" }));
1032
+ throw new CrossauthError(ErrorCode.Unauthorized, "State does not match");
1033
+ }
1034
+ const resp = await this.authServer.upstreamClient.redirectEndpoint(code, oauthData?.scope, oauthData?.codeVerifier, error, error_description);
1035
+ if (resp.error) {
1036
+ CrossauthLogger.logger.error(j({ msg: resp.error_description }));
1037
+ return this.redirectError(oauthData.orig_redirect_uri, resp.error, resp.error_description ?? "unknown error");
1038
+ }
1039
+ let access_token = resp.access_token;
1040
+ if (resp.access_token && this.authServer.upstreamClientOptions.accessTokenIsJwt) {
1041
+ const resp1 = await this.authServer.upstreamClient.getAccessPayload(resp.access_token, false);
1042
+ if (resp1.error)
1043
+ return json(resp1);
1044
+ else if (resp1.payload)
1045
+ access_token = resp1.payload;
1046
+ else {
1047
+ CrossauthLogger.logger.error(j({ msg: "No error or access payload received when querying access token" }));
1048
+ return this.redirectError(oauthData.orig_redirect_uri, "server_error", "No error or access payload received when querying access token");
1049
+ }
1050
+ }
1051
+ const mergeResponse = await this.authServer.upstreamClientOptions.tokenMergeFn(access_token ?? "", resp.id_payload, this.authServer.userStorage);
1052
+ if (mergeResponse.authorized) {
1053
+ const ret = await this.authServer.createTokensFromPayload(oauthData.orig_client_id, mergeResponse.access_payload, mergeResponse.id_payload);
1054
+ oauthData = {
1055
+ ...oauthData,
1056
+ access_token: ret.access_token,
1057
+ id_token: ret.id_token,
1058
+ id_payload: ret.id_payload,
1059
+ expires_in: ret.expires_in,
1060
+ refresh_token: resp.refresh_token,
1061
+ };
1062
+ let authzData = await this.authServer.getAuthorizationCodeData(oauthData.code);
1063
+ authzData = {
1064
+ ...authzData,
1065
+ access_token: ret.access_token,
1066
+ id_token: ret.id_token,
1067
+ id_payload: ret.id_token,
1068
+ refresh_token: resp.refresh_token,
1069
+ expires_in: ret.expires_in
1070
+ };
1071
+ await this.authServer.setAuthorizationCodeData(oauthData.code, authzData);
1072
+ throw this.redirect(302, this.authServer.redirect_uri(oauthData.orig_redirect_uri, oauthData.code, oauthData.orig_state));
1073
+ }
1074
+ else {
1075
+ CrossauthLogger.logger.error(j({ msg: mergeResponse.error_description ?? "Error merging tokens" }));
1076
+ return this.redirectError(oauthData.orig_redirect_uri, mergeResponse.error ?? "server_error", mergeResponse.error_description ?? "Error merging tokens");
1077
+ }
1078
+ }
1079
+ catch (e) {
1080
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1081
+ throw e;
1082
+ if (SvelteKitServer.isSvelteKitError(e))
1083
+ throw e;
1084
+ const ce = CrossauthError.asCrossauthError(e);
1085
+ CrossauthLogger.logger.debug({ err: e });
1086
+ CrossauthLogger.logger.error({ cerr: e });
1087
+ //throw this.error(ce.httpStatus, ce.message);
1088
+ return this.redirectError(oauthData.orig_redirect_uri, ce.oauthErrorCode, ce.message);
1089
+ }
1090
+ },
1091
+ };
1092
+ /**
1093
+ * `get` and `post` functions for the mfa/authenticators endpoint.
1094
+ *
1095
+ * See class description for details. Not needed if you disable CSRF
1096
+ * protection to rely on Sveltekit's.
1097
+ */
1098
+ mfaAuthenticatorsEndpoint = {
1099
+ get: async (event) => {
1100
+ try {
1101
+ // get form data
1102
+ var data = new JsonOrFormData();
1103
+ await data.loadData(event);
1104
+ return json(await this.mfaAuthenticators(event));
1105
+ }
1106
+ catch (e) {
1107
+ const ce = CrossauthError.asCrossauthError(e);
1108
+ CrossauthLogger.logger.debug({ err: e });
1109
+ CrossauthLogger.logger.error({ cerr: e });
1110
+ //throw this.error(ce.httpStatus, ce.message);
1111
+ return json({
1112
+ error: ce.oauthErrorCode,
1113
+ error_description: ce.message
1114
+ });
1115
+ }
1116
+ },
1117
+ post: async (event) => {
1118
+ try {
1119
+ // get form data
1120
+ var data = new JsonOrFormData();
1121
+ await data.loadData(event);
1122
+ let resp = await this.mfaAuthenticators(event);
1123
+ let status = 200;
1124
+ if (!Array.isArray(resp) && resp.error == "access_denied")
1125
+ status = 401;
1126
+ else if (!Array.isArray(resp) && resp.error)
1127
+ status = 500;
1128
+ return json(resp, { status });
1129
+ }
1130
+ catch (e) {
1131
+ const ce = CrossauthError.asCrossauthError(e);
1132
+ CrossauthLogger.logger.debug({ err: e });
1133
+ CrossauthLogger.logger.error({ cerr: e });
1134
+ return json({
1135
+ error: ce.oauthErrorCode,
1136
+ error_description: ce.message
1137
+ }, { status: ce.httpStatus });
1138
+ }
1139
+ },
1140
+ };
1141
+ /**
1142
+ * `post` function for the mfa/challenge endpoint.
1143
+ *
1144
+ * See class description for details. Not needed if you disable CSRF
1145
+ * protection to rely on Sveltekit's.
1146
+ */
1147
+ mfaChallengeEndpoint = {
1148
+ post: async (event) => {
1149
+ try {
1150
+ // get form data
1151
+ var data = new JsonOrFormData();
1152
+ await data.loadData(event);
1153
+ const resp = await this.mfaChallenge(event);
1154
+ let status = 200;
1155
+ if (resp.error == "access_denied")
1156
+ status = 401;
1157
+ else if (resp.error)
1158
+ status = 500;
1159
+ return json(resp, { status });
1160
+ }
1161
+ catch (e) {
1162
+ const ce = CrossauthError.asCrossauthError(e);
1163
+ CrossauthLogger.logger.debug({ err: e });
1164
+ CrossauthLogger.logger.error({ cerr: e });
1165
+ return json({
1166
+ error: ce.oauthErrorCode,
1167
+ error_description: ce.message
1168
+ }, { status: 500 });
1169
+ }
1170
+ },
1171
+ };
1172
+ /**
1173
+ * `post` function for the device_authorization endpoint.
1174
+ *
1175
+ * See class description for details. Not needed if you disable CSRF
1176
+ * protection to rely on Sveltekit's.
1177
+ */
1178
+ deviceAuthorizationEndpoint = {
1179
+ post: async (event) => {
1180
+ let formData = undefined;
1181
+ try {
1182
+ if (!(this.authServer.validFlows.includes(OAuthFlows.DeviceCode))) {
1183
+ return json({
1184
+ ok: false,
1185
+ error: "invalid_request",
1186
+ error_description: "Device authorization endpoint cannot be called as the supported OAuth flow types don't require it",
1187
+ });
1188
+ }
1189
+ // get form data
1190
+ var data = new JsonOrFormData();
1191
+ await data.loadData(event);
1192
+ formData = data.toObject();
1193
+ const { client_id, client_secret } = this.getClientIdAndSecret(formData, event);
1194
+ const resp = await this.authServer.deviceAuthorizationEndpoint({
1195
+ client_id: client_id,
1196
+ client_secret: client_secret,
1197
+ scope: formData.scope,
1198
+ });
1199
+ if (resp.error) {
1200
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1201
+ CrossauthLogger.logger.error(j({ cerr: ce }));
1202
+ return json(resp, { status: 500 });
1203
+ }
1204
+ if (!resp.device_code || !resp.user_code || !resp.verification_uri || !resp.verification_uri_complete || !resp.expires_in) {
1205
+ let error = "server_error";
1206
+ let errorDescription = "Device authorization result has missing data";
1207
+ const ce = new CrossauthError(ErrorCode.UnknownError, errorDescription);
1208
+ CrossauthLogger.logger.error(j({ cerr: ce }));
1209
+ return json({
1210
+ error,
1211
+ error_description: errorDescription,
1212
+ }, { status: 500 });
1213
+ }
1214
+ return json(resp);
1215
+ }
1216
+ catch (e) {
1217
+ const ce = CrossauthError.asCrossauthError(e);
1218
+ CrossauthLogger.logger.debug({ err: e });
1219
+ CrossauthLogger.logger.error({ cerr: e });
1220
+ return json({
1221
+ error: ce.oauthErrorCode,
1222
+ error_description: ce.message
1223
+ }, { status: 500 });
1224
+ }
1225
+ },
1226
+ };
1227
+ /**
1228
+ * `load` and `actions` functions for the authorize endpoint.
1229
+ *
1230
+ * See class description for details. Not needed if you disable CSRF
1231
+ * protection to rely on Sveltekit's.
1232
+ */
1233
+ deviceEndpoint = {
1234
+ load: async (event) => {
1235
+ if (!(this.authServer.validFlows.includes(OAuthFlows.DeviceCode))) {
1236
+ throw this.error(401, "device cannot be called because the device code flow is not supported");
1237
+ }
1238
+ if (!event.locals.user)
1239
+ return this.redirect(302, this.loginUrl + "?next=" + encodeURIComponent(event.request.url));
1240
+ let userCode = event.url.searchParams.get("user_code");
1241
+ // if there is a user code, apply it. Otherwise we will show the form
1242
+ // and it will be processed by the action
1243
+ if (userCode) {
1244
+ return await this.applyUserCode(userCode, event, event.locals.user);
1245
+ }
1246
+ else {
1247
+ // no user code given - prompt user for it
1248
+ return {
1249
+ ok: true,
1250
+ completed: false,
1251
+ retryAllowed: true,
1252
+ user: event.locals.user,
1253
+ csrfToken: event.locals.csrfToken,
1254
+ };
1255
+ }
1256
+ }, // load
1257
+ actions: {
1258
+ userCode: async (event) => {
1259
+ if (!event.locals.user)
1260
+ throw this.error(401, "Access Denied");
1261
+ try {
1262
+ // get form data
1263
+ var data = new JsonOrFormData();
1264
+ await data.loadData(event);
1265
+ const userCode = data.get('user_code');
1266
+ if (!userCode) {
1267
+ return {
1268
+ ok: false,
1269
+ completed: false,
1270
+ retryAllowed: true,
1271
+ error: "access_denied",
1272
+ error_description: "No user code given",
1273
+ };
1274
+ }
1275
+ return await this.applyUserCode(userCode, event, event.locals.user);
1276
+ }
1277
+ catch (e) {
1278
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1279
+ throw e;
1280
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't validate user code");
1281
+ return {
1282
+ ok: false,
1283
+ completed: false,
1284
+ retryAllowed: true,
1285
+ error: ce.oauthErrorCode,
1286
+ error_description: ce.message,
1287
+ };
1288
+ }
1289
+ },
1290
+ authorize: async (event) => {
1291
+ let formData = undefined;
1292
+ try {
1293
+ // get form data
1294
+ var data = new JsonOrFormData();
1295
+ await data.loadData(event);
1296
+ formData = data.toObject();
1297
+ const authorized = data.getAsBoolean('authorized');
1298
+ const scope = formData.scope;
1299
+ const client_id = formData.client_id;
1300
+ const userCode = formData.user_code;
1301
+ let missing = undefined;
1302
+ if (authorized == undefined)
1303
+ missing = "authorized";
1304
+ if (client_id == undefined)
1305
+ missing = "client_id";
1306
+ if (userCode == undefined)
1307
+ missing = "user_code";
1308
+ if (missing) {
1309
+ return {
1310
+ ok: false,
1311
+ completed: false,
1312
+ retryAllowed: false,
1313
+ error: "invalid_request",
1314
+ error_description: "Invalid form: does not contain " + missing + " parameter"
1315
+ };
1316
+ }
1317
+ // this should not be called if a user is not logged in
1318
+ if (!event.locals.user)
1319
+ return this.redirect(302, this.loginUrl + "?next=" + encodeURIComponent(event.request.url));
1320
+ if (this.svelteKitServer.sessionServer?.enableCsrfProtection && !event.locals.csrfToken)
1321
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
1322
+ const ret = await this.authServer.validateAndPersistScope(client_id, scope, event.locals.user);
1323
+ if (ret.error) {
1324
+ return {
1325
+ ok: false,
1326
+ completed: false,
1327
+ retryAllowed: false,
1328
+ error: "unauthorized_client",
1329
+ error_description: "You did not authorize access to your account"
1330
+ };
1331
+ }
1332
+ return await this.applyUserCode(userCode, event, event.locals.user);
1333
+ }
1334
+ catch (e) {
1335
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1336
+ throw e;
1337
+ let ce = CrossauthError.asCrossauthError(e, "Couldn't process authorization code");
1338
+ return {
1339
+ error: ce.oauthErrorCode,
1340
+ error_description: ce.message,
1341
+ ok: false,
1342
+ completed: false,
1343
+ retryAllowed: false,
1344
+ };
1345
+ }
1346
+ }
1347
+ }
1348
+ };
1349
+ }
1350
+ ;