@crossauth/sveltekit 1.0.1 → 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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +16 -6181
  4. package/dist/sveltekitadminclientendpoints.d.ts +13 -12
  5. package/dist/sveltekitadminclientendpoints.js +187 -0
  6. package/dist/sveltekitadminendpoints.d.ts +5 -4
  7. package/dist/sveltekitadminendpoints.js +766 -0
  8. package/dist/sveltekitapikey.d.ts +4 -3
  9. package/dist/sveltekitapikey.js +81 -0
  10. package/dist/sveltekitoauthclient.d.ts +6 -4
  11. package/dist/sveltekitoauthclient.js +2309 -0
  12. package/dist/sveltekitoauthserver.d.ts +4 -4
  13. package/dist/sveltekitoauthserver.js +1350 -0
  14. package/dist/sveltekitresserver.d.ts +6 -4
  15. package/dist/sveltekitresserver.js +286 -0
  16. package/dist/sveltekitserver.d.ts +11 -9
  17. package/dist/sveltekitserver.js +393 -0
  18. package/dist/sveltekitsession.d.ts +6 -5
  19. package/dist/sveltekitsession.js +1112 -0
  20. package/dist/sveltekitsessionadapter.d.ts +2 -3
  21. package/dist/sveltekitsessionadapter.js +2 -0
  22. package/dist/sveltekitsharedclientendpoints.d.ts +7 -6
  23. package/dist/sveltekitsharedclientendpoints.js +630 -0
  24. package/dist/sveltekituserclientendpoints.d.ts +13 -12
  25. package/dist/sveltekituserclientendpoints.js +270 -0
  26. package/dist/sveltekituserendpoints.d.ts +6 -5
  27. package/dist/sveltekituserendpoints.js +1813 -0
  28. package/dist/tests/sveltekitadminclientendpoints.test.js +330 -0
  29. package/dist/tests/sveltekitadminendpoints.test.js +242 -0
  30. package/dist/tests/sveltekitapikeyserver.test.js +44 -0
  31. package/dist/tests/sveltekitoauthclient.test.d.ts +5 -5
  32. package/dist/tests/sveltekitoauthclient.test.js +1016 -0
  33. package/dist/tests/sveltekitoauthresserver.test.d.ts +4 -4
  34. package/dist/tests/sveltekitoauthresserver.test.js +185 -0
  35. package/dist/tests/sveltekitoauthserver.test.js +673 -0
  36. package/dist/tests/sveltekituserclientendpoints.test.js +244 -0
  37. package/dist/tests/sveltekituserendpoints.test.js +152 -0
  38. package/dist/tests/sveltemock.test.js +36 -0
  39. package/dist/tests/sveltemocks.d.ts +22 -8
  40. package/dist/tests/sveltemocks.js +114 -0
  41. package/dist/tests/sveltesessionhooks.test.js +224 -0
  42. package/dist/tests/testshared.d.ts +8 -8
  43. package/dist/tests/testshared.js +344 -0
  44. package/dist/utils.d.ts +1 -2
  45. package/dist/utils.js +123 -0
  46. package/package.json +23 -15
  47. package/dist/index.cjs +0 -1
@@ -0,0 +1,2309 @@
1
+ // Copyright (c) 2026 Matthew Baker. All rights reserved. Licenced under the Apache Licence 2.0. See LICENSE file
2
+ import { jwtDecode } from "jwt-decode";
3
+ import QRCode from 'qrcode';
4
+ import { CrossauthError, ErrorCode, CrossauthLogger, OAuthFlows, j, } from '@crossauth/common';
5
+ import { setParameter, ParamType, Crypto, OAuthClientBackend } from '@crossauth/backend';
6
+ import { SvelteKitServer } from './sveltekitserver';
7
+ import { json } from '@sveltejs/kit';
8
+ import { JsonOrFormData } from './utils';
9
+ import {} from './tests/sveltemocks';
10
+ ////////////////////////////////////////////////////////////////////////////
11
+ // DEFAULT FUNCTIONS
12
+ async function jsonError(_server, _event, ce) {
13
+ CrossauthLogger.logger.debug(j({ err: ce }));
14
+ CrossauthLogger.logger.error(j({ cerr: ce }));
15
+ return json({
16
+ ok: false,
17
+ status: ce.httpStatus,
18
+ errorMessage: ce.message,
19
+ errorMessages: ce.messages,
20
+ errorCode: ce.code,
21
+ errorCodeName: ce.codeName
22
+ }, { status: ce.httpStatus });
23
+ }
24
+ async function svelteKitError(server, _event, ce) {
25
+ throw server.oAuthClient?.error(ce.httpStatus, ce.message);
26
+ }
27
+ function decodePayload(token) {
28
+ let payload = undefined;
29
+ if (token) {
30
+ try {
31
+ payload = JSON.parse(Crypto.base64Decode(token.split(".")[1]));
32
+ }
33
+ catch (e) {
34
+ const ce = CrossauthError.asCrossauthError(e);
35
+ CrossauthLogger.logger.debug(j({ err: ce }));
36
+ CrossauthLogger.logger.error(j({ msg: "Couldn't decode token", cerr: ce }));
37
+ }
38
+ }
39
+ return payload;
40
+ }
41
+ async function sendJson(oauthResponse, client, _event, _silent, _setUserFn) {
42
+ let resp = { ok: true, ...oauthResponse };
43
+ if (client.jwtTokens.includes("id")) {
44
+ resp["id_payload"] = oauthResponse.id_payload ?? decodePayload(oauthResponse.id_token);
45
+ }
46
+ return json(resp);
47
+ }
48
+ function logTokens(oauthResponse, jwtTokens) {
49
+ if (oauthResponse.access_token) {
50
+ try {
51
+ if (oauthResponse.access_token && jwtTokens.includes("access")) {
52
+ const decoded = jwtDecode(oauthResponse.access_token);
53
+ const jti = decoded.jti ? decoded.jti : (decoded.sid ? decoded.sid : "");
54
+ const hash = jti ? Crypto.hash(jti) : undefined;
55
+ CrossauthLogger.logger.debug(j({ msg: "Got access token",
56
+ accessTokenHash: hash }));
57
+ }
58
+ }
59
+ catch (e) {
60
+ CrossauthLogger.logger.debug(j({ err: e }));
61
+ }
62
+ }
63
+ if (oauthResponse.id_token) {
64
+ try {
65
+ if (oauthResponse.id_token && jwtTokens.includes("id")) {
66
+ const decoded = oauthResponse.id_payload ?? jwtDecode(oauthResponse.id_token);
67
+ if (decoded) {
68
+ const jti = decoded.jti ? decoded.jti : (decoded.sid ? decoded.sid : "");
69
+ const hash = jti ? Crypto.hash(jti) : undefined;
70
+ CrossauthLogger.logger.debug(j({ msg: "Got id token",
71
+ idTokenHash: hash }));
72
+ }
73
+ }
74
+ }
75
+ catch (e) {
76
+ CrossauthLogger.logger.debug(j({ err: e }));
77
+ }
78
+ }
79
+ if (oauthResponse.refresh_token && jwtTokens.includes("refresh")) {
80
+ try {
81
+ if (oauthResponse.refresh_token) {
82
+ const jti = jwtDecode(oauthResponse.refresh_token)?.jti;
83
+ const hash = jti ? Crypto.hash(jti) : undefined;
84
+ CrossauthLogger.logger.debug(j({ msg: "Got refresh token",
85
+ refreshTokenHash: hash }));
86
+ }
87
+ }
88
+ catch (e) {
89
+ CrossauthLogger.logger.debug(j({ err: e }));
90
+ }
91
+ }
92
+ }
93
+ async function updateSessionData(oauthResponse, client, event) {
94
+ if (!client.server.sessionAdapter) {
95
+ throw new CrossauthError(ErrorCode.Configuration, "Cannot update session data if not using sessions");
96
+ }
97
+ let expires_in = oauthResponse.expires_in;
98
+ if (!expires_in && oauthResponse.access_token && client.jwtTokens.includes("access")) {
99
+ const payload = jwtDecode(oauthResponse.access_token);
100
+ if (payload.exp)
101
+ expires_in = payload.exp;
102
+ }
103
+ if (!expires_in) {
104
+ throw new CrossauthError(ErrorCode.BadRequest, "OAuth server did not return an expiry for the access token");
105
+ }
106
+ const expires_at = Date.now() + (expires_in) * 1000;
107
+ let sessionData = { ...oauthResponse, expires_at };
108
+ if ("id_token" in oauthResponse) {
109
+ let payload = oauthResponse.id_payload ?? decodePayload(oauthResponse["id_token"]);
110
+ if (payload)
111
+ sessionData["id_payload"] = payload;
112
+ }
113
+ await client.storeSessionData(event, sessionData);
114
+ }
115
+ async function saveInSessionAndRedirect(oauthResponse, client, event, silent, setUserFn) {
116
+ if (oauthResponse.error) {
117
+ const ce = CrossauthError.fromOAuthError(oauthResponse.error, oauthResponse.error_description);
118
+ return client.errorFn(client.server, event, ce);
119
+ }
120
+ logTokens(oauthResponse, client.jwtTokens);
121
+ try {
122
+ if (oauthResponse.access_token || oauthResponse.id_token || oauthResponse.refresh_token) {
123
+ await updateSessionData(oauthResponse, client, event);
124
+ const payload = oauthResponse.id_payload ?? decodePayload(oauthResponse.id_token);
125
+ if (payload)
126
+ await setUserFn(event, payload);
127
+ }
128
+ if (!silent)
129
+ return client.redirect(302, client.authorizedUrl);
130
+ }
131
+ catch (e) {
132
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
133
+ throw e;
134
+ const ce = CrossauthError.asCrossauthError(e);
135
+ CrossauthLogger.logger.debug(j({ err: ce }));
136
+ CrossauthLogger.logger.debug(j({ cerr: ce, msg: "Error receiving tokens" }));
137
+ return client.errorFn(client.server, event, ce);
138
+ }
139
+ }
140
+ async function saveInSessionAndReturn(oauthResponse, client, event, silent, setUserFn) {
141
+ if (oauthResponse.error) {
142
+ const ce = CrossauthError.fromOAuthError(oauthResponse.error, oauthResponse.error_description);
143
+ return client.errorFn(client.server, event, ce);
144
+ }
145
+ logTokens(oauthResponse, client.jwtTokens);
146
+ try {
147
+ if (oauthResponse.access_token || oauthResponse.id_token || oauthResponse.refresh_token) {
148
+ await updateSessionData(oauthResponse, client, event);
149
+ const payload = oauthResponse.id_payload ?? decodePayload(oauthResponse.id_token);
150
+ if (payload)
151
+ await setUserFn(event, payload);
152
+ }
153
+ return json({ ok: true, ...oauthResponse });
154
+ if (!silent)
155
+ return client.redirect(302, client.authorizedUrl);
156
+ }
157
+ catch (e) {
158
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
159
+ throw e;
160
+ const ce = CrossauthError.asCrossauthError(e);
161
+ CrossauthLogger.logger.debug(j({ err: ce }));
162
+ CrossauthLogger.logger.debug(j({ cerr: ce, msg: "Error receiving tokens" }));
163
+ return client.errorFn(client.server, event, ce);
164
+ }
165
+ }
166
+ async function saveInSessionAndLoad(oauthResponse, client, event, _silent, setUserFn) {
167
+ if (oauthResponse.error) {
168
+ return {
169
+ ok: false,
170
+ error: oauthResponse.error,
171
+ error_description: oauthResponse.error_description
172
+ };
173
+ }
174
+ logTokens(oauthResponse, client.jwtTokens);
175
+ try {
176
+ if (oauthResponse.access_token || oauthResponse.id_token || oauthResponse.refresh_token) {
177
+ await updateSessionData(oauthResponse, client, event);
178
+ }
179
+ let resp = {
180
+ ok: true,
181
+ ...oauthResponse,
182
+ };
183
+ if (client.jwtTokens.includes("id")) {
184
+ resp["id_payload"] = oauthResponse.id_payload ?? decodePayload(oauthResponse.id_token);
185
+ }
186
+ if (resp["id_payload"])
187
+ await setUserFn(event, resp["id_payload"]);
188
+ return resp;
189
+ }
190
+ catch (e) {
191
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
192
+ throw e;
193
+ const ce = CrossauthError.asCrossauthError(e);
194
+ CrossauthLogger.logger.debug(j({ err: ce }));
195
+ CrossauthLogger.logger.debug(j({ cerr: ce, msg: "Error receiving tokens" }));
196
+ return {
197
+ ok: false,
198
+ error: ce.oauthErrorCode,
199
+ error_description: ce.message,
200
+ };
201
+ }
202
+ }
203
+ async function sendInPage(oauthResponse, client, _event, _silent) {
204
+ if (oauthResponse.error) {
205
+ return {
206
+ ok: false,
207
+ error: oauthResponse.error,
208
+ error_description: oauthResponse.error_description
209
+ };
210
+ }
211
+ logTokens(oauthResponse, client.jwtTokens);
212
+ try {
213
+ let resp = {
214
+ ok: true,
215
+ ...oauthResponse
216
+ };
217
+ if (client.jwtTokens.includes("id")) {
218
+ resp["id_payload"] = oauthResponse.id_payload ?? decodePayload(oauthResponse.id_token);
219
+ }
220
+ return resp;
221
+ }
222
+ catch (e) {
223
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
224
+ throw e;
225
+ const ce = CrossauthError.asCrossauthError(e);
226
+ CrossauthLogger.logger.debug(j({ err: ce }));
227
+ CrossauthLogger.logger.debug(j({ cerr: ce, msg: "Error receiving tokens" }));
228
+ return {
229
+ ok: false,
230
+ error: ce.oauthErrorCode,
231
+ error_description: ce.message,
232
+ };
233
+ }
234
+ }
235
+ ///////////////////////////////////////////////////////////////////////////////
236
+ // CLASSES
237
+ /**
238
+ * The SvelteKit version of the OAuth client.
239
+ *
240
+ * Makes requests to an authorization server, using a configurable set
241
+ * of flows, which sends back errors or tokens,
242
+ *
243
+ * When constructing this class, you define what happens with tokens that
244
+ * are returned, or errors that are returned. You do this with the
245
+ * configuration options {@link SvelteKitOAuthClientOptions.tokenResponseType}
246
+ * and {@link SvelteKitOAuthClientOptions.errorResponseType}.
247
+ *
248
+ * **{@link SvelteKitOAuthClientOptions.tokenResponseType}**
249
+ *
250
+ * - `sendJson` the token response is sent as-is as a JSON Response.
251
+ * In addition to the `token` endpoint response fields,
252
+ * `ok: true` and `id_payload` with the decoded
253
+ * payload of the ID token are retruned.
254
+ * This method should be used
255
+ * with `get`/ `post` endpoints, not `load`/`actions`.
256
+ * - `saveInSessionAndLoad` the response fields are saved in the `data`
257
+ * field of the session ID in key storage. In addition, `expires_at` is
258
+ * set to the number of seconds since Epoch that the access token expires
259
+ * at. When using this method, you should define a SvelteKit page
260
+ * in your routes and put the the `load` (GET methods) or `actions`
261
+ * (POST methods) function for each endpoint
262
+ * in the route's `+page.server.ts`.
263
+ * A consequence is the query parameters passed to the
264
+ * redirect Uri are displayed in the address bar, as the response
265
+ * is to the redirect to the redirect Uri.
266
+ * - saveInSessionAndRedirect` same as `saveInSessionAndLoad` except that
267
+ * a redirect is done to the `authorizedUrl`. As an alternative to using `load`
268
+ * or `actions` method in a `+page.server.ts`, you can use the `get`
269
+ * or `post` method in a `+server.ts`.
270
+ * - saveInSessionAndReturn` same as `saveInSessionAndLoad` except that
271
+ * a JSON response is returned`. Instead of using the `load`
272
+ * or `actions` method in a `+page.server.ts`, you should use the `get`
273
+ * or `post` method in a `+server.ts`.
274
+ * - `sendInPage` same as `saveinSessionAndLoad` except the tokens are
275
+ * not saved in the session. Use the `load`/`actions` function in your
276
+ * `+page.server.ts`.
277
+ * - `custom` the function in
278
+ * {@link SvelteKitOAuthClientOptions.receiveTokenFn} is called. If
279
+ * using `get` or `post` methods, your functiin should return
280
+ * a Response. If using `load` and `actions` ir shouls ewruen
281
+ * an object for passing in `data` or `form` exports.
282
+ *
283
+ * **{@link SvelteKitOAuthClientOptions.errorResponseType}**
284
+ *
285
+ * - `sendJson` a JSON response is sent with fields
286
+ * `status`, `errorMessage`,
287
+ * `errorMessages` and `errorCodeName`.
288
+ * - `svelteKitError` calls the SvelteKit `error` function (the one
289
+ * provided in the options to {@link SvelteKitServe}).
290
+ * - `custom` {@link SvelteKitOAuthClientOptions.errorFn} is called.
291
+ *
292
+ * Note that this parameter is only used when you are using the `get`/`post`
293
+ * endpoints, not the `load`/ `actions` ones. The latter return the error in
294
+ * the PageData from the load.
295
+ *
296
+ * **Backend-for-Frontend (BFF)**
297
+ *
298
+ * This class supports the backend-for-frontend (BFF) model.
299
+ * This pattern avoids you having to store the access token in the frontend.
300
+
301
+ * For this to work
302
+ * you should set @link SvelteKitOAuthClientOptions.tokenResponseType} to
303
+ * `saveInSessionAndLoad` or `saveInSessionAndRedirect`. Then to call
304
+ * your resource server functions, you call then on a URL on this client
305
+ * rather than the resource server directly. The client backend will
306
+ * attach the access token, and also refresh the token automatically if
307
+ * expired.
308
+ *
309
+ * You need to provide the following options:
310
+ * - `bffBaseUrl` - the resource server URL, eg `http://resserver.com`
311
+ * - `bffEndpointName` - the prefix for BFF endpoints on this server.
312
+ * Eg if your BFF URL on this server is in `routes/bff` then
313
+ * set `bffEndpointName` to `/bff`.
314
+ *
315
+ * You may optionally also se `bffEndpoints`.
316
+ *
317
+ * To sue BFF, first set `tokenResponseType` to
318
+ * `saveInSessionAndLoad` or `saveInSessionAndRedirect` and set `bffBaseUrl`
319
+ * and `bffEndpointName`. THen create a route in your `routes` called
320
+ * *bffEndpointName*`/`*someMethod* with a `+server.ts`. In that `+server.ts`,
321
+ * create a `GET` and/or `POST` endpoint with
322
+ * `bffEndpoint.get` or `bffEndpoint.post`. The request will be forwarded
323
+ * to *bffBaseUrl*`/`*someMethod* with the the body and query parameters
324
+ * taken from your query and with the access token attached as the
325
+ * `Authorization` header. The resulting JSON and HTTP status will be returned.
326
+ *
327
+ * If you have a lot of endpoints, you may instead prefer to create a single
328
+ * one, eg as `routes/[...method]` and use `allBffEndpoint.get` or `.post` .
329
+ * Put all valid BFF endpoints in the `bffEndpoints` option. If, for one
330
+ * of these endpoints, eg `method`, you set `matchSubUrls` to true, then
331
+ * `method/XXX`, `method/YYY` will match as well as `method`.
332
+ *
333
+ * **Middleware**
334
+ *
335
+ * This class provides middleware that works with the BFF method.
336
+ *
337
+ * If an ID token is saved in the session and it is valid, the following
338
+ * state attributes are set in the request object:
339
+ *
340
+ * - `idPayload` the payload from the ID token
341
+ * - `user` a :class:`crossauth_backend.User` object created from the ID
342
+ * token
343
+ * - `authType` set to `oidc`
344
+ *
345
+ * **Endpoints provided by this class**
346
+ *
347
+ * | 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 |
348
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
349
+ * | authorizationCodeFlowEndpoint | Starts the authorization code flow. | None - redirects to `redirect_uri` | *Not provided* | - `scope` |
350
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
351
+ * | authorizationCodeFlowWithPKCEEndpoint | Starts the authorization code flow with PKCE. | None - redirects to `redirect_uri` | *Not provided* | - `scope` |
352
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
353
+ * | redirectUriEndpoint | Redirect Uri for authorization code flows | See {@link OAuthTokenResponse} | *Not provided* | As per OAuth Authorization Code Flow spec |
354
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
355
+ * | clientCredentialsFlowEndpoint | Executes the client credentials flow | *Not provided* | See {@link OAuthTokenResponse} | As per OAuth Client Credentials Flow spec |
356
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
357
+ * | refreshTokenFlowEndpoint | Executes the refresh token flow | *Not provided* | See {@link OAuthTokenResponse} | As per OAuth Refresh Token Flow spec |
358
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
359
+ * | refreshTokensIfExpiredEndpoint | Executes the refresh token flow only if access token expired | *Not provided* | See {@link OAuthTokenResponse} | As per OAuth Refresh Token Flow spec or nothing |
360
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
361
+ * | autoRefreshTokensIfExpiredEndpoint | Same as refreshTokensIfExpiredEndpoint but only returns an object, no redirect | *Not provided* | See {@link OAuthTokenResponse} | As per OAuth Refresh Token Flow spec or nothing |
362
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
363
+ * | refreshTokensIfExpiredEndpoint | Same as refreshTokenFlowEndpoint but only returns an object, no redirect | *Not provided* | See {@link OAuthTokenResponse} | As per OAuth Refresh Token Flow spec or nothing |
364
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
365
+ * | passwordFlowEndpoint | | *Not provided* | `password` | |
366
+ * | | Executes the password flow only with out without MFA | | - See {@link OAuthTokenResponse}. Returns password flow response if no MFA, MFA challenge response if user has 2FA | See OAuth password flow or Auth0 Password with MFA password flow specs |
367
+ * | | | | `passwordOtp` | |
368
+ * | | Pass OTP for Password MFA flow | | - See {@link OAuthTokenResponse}. Returns Password MFA challenge response if user has 2FA | See Auth0 Password with MFA password flow specs |
369
+ * | | | | `passwordOob` | |
370
+ * | | Pass OOB for Password MFA flow | | - See {@link OAuthTokenResponse}. Returns Password MFA challenge response if user has 2FA | See Auth0 Password with MFA password flow specs |
371
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
372
+ * | passwordOtp Endpoint | `post` is same as `passwordOtp` action above | *Not provided* | See {@link OAuthTokenResponse}. Returns MFA challenge response if user has 2FA | See OAuth password flow or Auth0 Password with MFA password flow specs |
373
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
374
+ * | passwordOob Endpoint | `post` is same as `passwordOob` action above | *Not provided* | See {@link OAuthTokenResponse}. Returns MFA challenge response if user has 2FA | See OAuth password flow or Auth0 Password with MFA password flow specs |
375
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
376
+ * | bffEndpoint | BFF resource server request. See class documentation | As per the corresponding resource server endpoint | As per the correspoinding resource server endpoint | As per the corresponding resource server endpoint |
377
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
378
+ * | allBffEndpoint | BFF resource server request. See class documentation | As per the corresponding resource server endpoint | As per the correspoinding resource server endpoint | As per the corresponding resource server endpoint |
379
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
380
+ * | accessTokenEndpoint | For BFF only, return the access token payload or error | JSON of the access token payload | *Not provided* | `decode`, default `true` |
381
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
382
+ * | refreshTokenEndpoint | For BFF only, return the refresh token payload or error | JSON of the refresh token payload | *Not provided* | `decode`, default `true` |
383
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
384
+ * | idTokenEndpoint | For BFF only, return the id token payload or error | POST: JSON of the id token payload | *Not provided* | `decode`, default `true` |
385
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
386
+ * | havAeccessTokenEndpoint | For BFF only, return whether access token present | POST: `ok` of false or true | *Not provided* | |
387
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
388
+ * | haveRefreshTokenEndpoint | For BFF only, return whether refresh token present | POST: `ok` of false or true | *Not provided* | |
389
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
390
+ * | haveIdTokenEndpoint | For BFF only, return whether id token present | POST: `ok` of false or true | *Not provided* | |
391
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
392
+ * | tokensEndpoint | For BFF only, return a JSON object of all of the above | POST: All of the above, keyed on `access_token`, `have_access_token`, etc. | *Not provided* | `decode`, default `true` |
393
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
394
+ * | deleteTokensEndpoint | For BFF only, deletes tokens saved for session | POST: `ok` of false or true | `default`: `ok` of false or true | *None* |
395
+ * | ------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------- | --------------------------------------------------------------- |
396
+ */
397
+ export class SvelteKitOAuthClient extends OAuthClientBackend {
398
+ server;
399
+ sessionDataName = "oauth";
400
+ receiveTokenFn = sendJson;
401
+ errorFn = jsonError;
402
+ loginUrl = "/login";
403
+ validFlows = [OAuthFlows.All];
404
+ authorizedUrl = "";
405
+ autoRefreshActive = {};
406
+ redirect;
407
+ error;
408
+ /**
409
+ * See {@link SvelteKitOAuthClientOptions}
410
+ */
411
+ loginProtectedFlows = [];
412
+ tokenResponseType = "sendJson";
413
+ errorResponseType = "sendJson";
414
+ bffEndpoints = [];
415
+ bffEndpointName = "bff";
416
+ bffBaseUrl;
417
+ tokenEndpoints = [];
418
+ bffMaxTries = 1;
419
+ bffSleepMilliseconds = 500;
420
+ jwtTokens = ["access", "id", "refresh"];
421
+ hook;
422
+ testMiddleware = false;
423
+ // @ts-ignore
424
+ testEvent = undefined;
425
+ /**
426
+ * Constructor
427
+ * @param server the {@link SvelteKitServer} instance
428
+ * @param authServerBaseUrl the `iss` claim in the access token must match this value
429
+ * @param options See {@link SvelteKitOAuthClientOptions}
430
+ */
431
+ constructor(server, authServerBaseUrl, options) {
432
+ super(authServerBaseUrl, options);
433
+ this.server = server;
434
+ setParameter("sessionDataName", ParamType.String, this, options, "OAUTH_SESSION_DATA_NAME");
435
+ setParameter("tokenResponseType", ParamType.String, this, options, "OAUTH_TOKEN_RESPONSE_TYPE");
436
+ setParameter("errorResponseType", ParamType.String, this, options, "OAUTH_ERROR_RESPONSE_TYPE");
437
+ setParameter("loginUrl", ParamType.String, this, options, "LOGIN_URL");
438
+ setParameter("bffEndpointName", ParamType.String, this, options, "OAUTH_BFF_ENDPOINT_NAME");
439
+ setParameter("bffBaseUrl", ParamType.String, this, options, "OAUTH_BFF_BASEURL");
440
+ setParameter("redirect_uri", ParamType.String, this, options, "OAUTH_REDIRECTURI", true);
441
+ setParameter("authorizedUrl", ParamType.String, this, options, "AUTHORIZED_URL", false);
442
+ setParameter("validFlows", ParamType.JsonArray, this, options, "OAUTH_validFlows");
443
+ setParameter("bffMaxTries", ParamType.Number, this, options, "OAUTH_BFF_MAX_RETRIES");
444
+ setParameter("bffSleepMilliseconds", ParamType.Number, this, options, "OAUTH_BFF_SLEEP_MILLISECONDS");
445
+ setParameter("jwtTokens", ParamType.JsonArray, this, options, "OAUTH_JWT_TOKENS");
446
+ if (this.bffEndpointName && !this.bffEndpointName.startsWith("/"))
447
+ this.bffEndpointName = "/" + this.bffEndpointName;
448
+ if (this.bffEndpointName && this.bffEndpointName.endsWith("/"))
449
+ this.bffEndpointName = this.bffEndpointName.substring(0, this.bffEndpointName.length - 1);
450
+ if (this.bffBaseUrl && this.bffBaseUrl.endsWith("/"))
451
+ this.bffBaseUrl = this.bffBaseUrl.substring(0, this.bffBaseUrl.length - 1);
452
+ if (options.redirect)
453
+ this.redirect = options.redirect;
454
+ if (options.error)
455
+ this.error = options.error;
456
+ if (this.validFlows.length == 1 && this.validFlows[0] == OAuthFlows.All) {
457
+ this.validFlows = OAuthFlows.allFlows();
458
+ }
459
+ else {
460
+ if (!OAuthFlows.areAllValidFlows(this.validFlows)) {
461
+ throw new CrossauthError(ErrorCode.Configuration, "Invalid flows specificied in " + this.validFlows.join(","));
462
+ }
463
+ }
464
+ try {
465
+ new URL(this.redirect_uri ?? "");
466
+ }
467
+ catch (e) {
468
+ throw new CrossauthError(ErrorCode.Configuration, "Invalid redirect Uri " + this.redirect_uri);
469
+ }
470
+ if (options.tokenEndpoints)
471
+ this.tokenEndpoints = options.tokenEndpoints;
472
+ if (this.bffEndpointName.endsWith("/"))
473
+ this.bffEndpointName = this.bffEndpointName.substring(0, this.bffEndpointName.length - 1);
474
+ if (options.bffEndpoints)
475
+ this.bffEndpoints = options.bffEndpoints.map((ep) => {
476
+ return { ...ep, methodsString: ep.methods.map((m) => m) };
477
+ });
478
+ if (this.bffEndpoints) {
479
+ for (let endpoint of this.bffEndpoints) {
480
+ if (!endpoint.url.startsWith("/"))
481
+ endpoint.url = "/" + endpoint.url;
482
+ }
483
+ }
484
+ if (this.loginProtectedFlows.length == 1 &&
485
+ this.loginProtectedFlows[0] == OAuthFlows.All) {
486
+ this.loginProtectedFlows = this.validFlows;
487
+ }
488
+ else {
489
+ if (!OAuthFlows.areAllValidFlows(this.loginProtectedFlows)) {
490
+ throw new CrossauthError(ErrorCode.Configuration, "Invalid flows specificied in " + this.loginProtectedFlows.join(","));
491
+ }
492
+ }
493
+ // receive token fn
494
+ if (this.tokenResponseType == "custom" && !options.receiveTokenFn) {
495
+ throw new CrossauthError(ErrorCode.Configuration, "Token response type of custom selected but receiveTokenFn not defined");
496
+ }
497
+ if (this.tokenResponseType == "custom" && options.receiveTokenFn) {
498
+ this.receiveTokenFn = options.receiveTokenFn;
499
+ }
500
+ else if (this.tokenResponseType == "sendJson") {
501
+ this.receiveTokenFn = sendJson;
502
+ }
503
+ else if (this.tokenResponseType == "sendInPage") {
504
+ this.receiveTokenFn = sendInPage;
505
+ }
506
+ else if (this.tokenResponseType == "saveInSessionAndLoad") {
507
+ this.receiveTokenFn = saveInSessionAndLoad;
508
+ }
509
+ else if (this.tokenResponseType == "saveInSessionAndRedirect") {
510
+ this.receiveTokenFn = saveInSessionAndRedirect;
511
+ }
512
+ else if (this.tokenResponseType == "saveInSessionAndReturn") {
513
+ this.receiveTokenFn = saveInSessionAndReturn;
514
+ }
515
+ if ((this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "saveInSessionAndRedirect") &&
516
+ this.authorizedUrl == "") {
517
+ throw new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is" + this.tokenResponseType + ", must provide authorizedUrl");
518
+ }
519
+ if ((this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "saveInSessionAndRedirect") &&
520
+ this.server.sessionAdapter == undefined) {
521
+ throw new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is" + this.tokenResponseType + ", must activate the session server");
522
+ }
523
+ // errorFn
524
+ if (this.errorResponseType == "custom" && !options.errorFn) {
525
+ throw new CrossauthError(ErrorCode.Configuration, "Error response type of custom selected but errorFn not defined");
526
+ }
527
+ if (this.errorResponseType == "custom" && options.errorFn) {
528
+ this.errorFn = options.errorFn;
529
+ }
530
+ else if (this.errorResponseType == "sendJson") {
531
+ this.errorFn = jsonError;
532
+ }
533
+ else if (this.errorResponseType == "svelteKitError") {
534
+ this.errorFn = svelteKitError;
535
+ }
536
+ if (!options.redirect)
537
+ throw new CrossauthError(ErrorCode.Configuration, "Must provide the SvelteKit redirect function");
538
+ if (!options.error && this.errorResponseType == "svelteKitError")
539
+ throw new CrossauthError(ErrorCode.Configuration, "Must provide the SvelteKit error function");
540
+ if (this, this.loginProtectedFlows.length > 0 && this.loginUrl == "") {
541
+ throw new CrossauthError(ErrorCode.Configuration, "loginUrl must be set if protecting oauth endpoints");
542
+ }
543
+ this.hook = async ({ event }) => {
544
+ CrossauthLogger.logger.debug(j({ msg: "OAuth hook, user " + event.locals.user }));
545
+ if (event.locals.user)
546
+ return undefined;
547
+ if (!server.sessionAdapter)
548
+ return undefined;
549
+ let sessionData = await server.sessionAdapter.getSessionData(event, this.sessionDataName);
550
+ let validIdToken = false;
551
+ //CrossauthLogger.logger.debug(j({msg:"Session data " + (sessionData && sessionData["id_payload"]) ? JSON.stringify(sessionData?.id_payload) : "none)"}));
552
+ if (sessionData && sessionData["id_payload"]) {
553
+ let expiry = sessionData["expires_at"];
554
+ if (expiry && expiry > Date.now() && sessionData["id_payload"].sub) {
555
+ CrossauthLogger.logger.debug(j({ msg: "ID token is valid" }));
556
+ await this.setEventLocalsUser(event, sessionData["id_payload"]);
557
+ if (event.locals.user)
558
+ validIdToken = true;
559
+ }
560
+ }
561
+ if (!validIdToken && sessionData && sessionData["refresh_token"]) {
562
+ CrossauthLogger.logger.debug(j({ msg: "No ID token found but refresh token found - attemping refresh flow" }));
563
+ const resp = await this.refreshTokens(event, "silent", false);
564
+ if (!resp?.ok) {
565
+ const error = resp instanceof Response || resp == undefined ? "server_error" : (resp.error ?? "server_error");
566
+ const error_description = resp instanceof Response || resp == undefined ? "Unknown error" : (resp.error_description ?? "Unknown error");
567
+ const ce = CrossauthError.fromOAuthError(error, error_description);
568
+ CrossauthLogger.logger.debug(j({ err: ce }));
569
+ CrossauthLogger.logger.warn(j({ msg: "Error refreshing token", cerr: ce }));
570
+ }
571
+ else {
572
+ await this.setEventLocalsUser(event, sessionData["id_payload"]);
573
+ }
574
+ }
575
+ if (this.testMiddleware)
576
+ this.testEvent = event;
577
+ return undefined;
578
+ };
579
+ }
580
+ /**
581
+ * If you implement your own function to receive tokens and you use BFF,
582
+ * use this function to set `event.locals.user`.
583
+ * @param event the Sveltekit request event
584
+ * @param token the ID token
585
+ */
586
+ async setEventLocalsUser(event, token) {
587
+ let user = undefined;
588
+ event.locals.idTokenPayload = token;
589
+ try {
590
+ user = await this.userCreationFn(token, this.userStorage, this.userMatchField, this.idTokenMatchField);
591
+ event.locals.user = user;
592
+ event.locals.authType = user ? "oidc" : undefined;
593
+ CrossauthLogger.logger.debug(j({ msg: "Set locals.user to " + (user ? user?.username : "undefined") }));
594
+ }
595
+ catch (e) {
596
+ CrossauthLogger.logger.error(j({ cerr: e }));
597
+ event.locals.user = undefined;
598
+ event.locals.authType = undefined;
599
+ }
600
+ }
601
+ async passwordPost(event, formData) {
602
+ try {
603
+ let resp = await this.passwordFlow(formData.username, formData.password, formData.scope);
604
+ if (resp.error == "mfa_required" &&
605
+ resp.mfa_token &&
606
+ this.validFlows.includes(OAuthFlows.PasswordMfa)) {
607
+ const mfa_token = resp.mfa_token;
608
+ let scope = formData.scope;
609
+ if (scope == "")
610
+ scope = undefined;
611
+ resp = this.errorIfIdTokenInvalid(await this.passwordMfa(mfa_token, scope, event));
612
+ if (resp.error) {
613
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
614
+ throw ce;
615
+ }
616
+ //return await this.receiveTokenFn(resp, this, event);
617
+ return resp;
618
+ }
619
+ else if (resp.error) {
620
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
621
+ throw ce;
622
+ }
623
+ //return await this.receiveTokenFn(resp, this, event);
624
+ return resp;
625
+ }
626
+ catch (e) {
627
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
628
+ throw e;
629
+ const ce = CrossauthError.asCrossauthError(e);
630
+ CrossauthLogger.logger.error(j({
631
+ msg: "Error receiving token",
632
+ cerr: ce,
633
+ user: this.server.sessionAdapter?.getUser(event)
634
+ }));
635
+ CrossauthLogger.logger.debug(j({ err: e }));
636
+ return {
637
+ error: ce.oauthErrorCode,
638
+ error_description: ce.message,
639
+ };
640
+ }
641
+ }
642
+ async passwordMfa(mfa_token, scope, _event) {
643
+ const authenticatorsResponse = await this.mfaAuthenticators(mfa_token);
644
+ if (authenticatorsResponse.error ||
645
+ !authenticatorsResponse.authenticators ||
646
+ !Array.isArray(authenticatorsResponse.authenticators) ||
647
+ authenticatorsResponse.authenticators.length == 0 ||
648
+ (authenticatorsResponse.authenticators.length > 1 &&
649
+ !authenticatorsResponse.authenticators[0].active)) {
650
+ return {
651
+ error: authenticatorsResponse.error ?? "server_error",
652
+ error_description: authenticatorsResponse.error_description ?? "Unexpected error getting MFA authenticators",
653
+ };
654
+ }
655
+ const auth = authenticatorsResponse.authenticators[0];
656
+ if (auth.authenticator_type == "otp") {
657
+ const resp = await this.mfaOtpRequest(mfa_token, auth.id);
658
+ if (resp.error || resp.challenge_type != "otp") {
659
+ const ce = CrossauthError.fromOAuthError(resp.error ?? "server_error", resp.error_description ?? "Invalid response from MFA OTP challenge");
660
+ CrossauthLogger.logger.debug({ err: ce });
661
+ CrossauthLogger.logger.error({ cerr: ce });
662
+ return {
663
+ error: ce.oauthErrorCode,
664
+ error_description: ce.message,
665
+ };
666
+ }
667
+ return {
668
+ scope: scope,
669
+ mfa_token: mfa_token,
670
+ challenge_type: resp.challenge_type,
671
+ };
672
+ }
673
+ else if (auth.authenticator_type == "oob") {
674
+ const resp = await this.mfaOobRequest(mfa_token, auth.id);
675
+ if (resp.error || resp.challenge_type != "oob" || !resp.oob_code ||
676
+ resp.binding_method != "prompt") {
677
+ const ce = CrossauthError.fromOAuthError(resp.error ?? "server_error", resp.error_description ?? "Invalid response from MFA OOB challenge");
678
+ CrossauthLogger.logger.debug({ err: ce });
679
+ CrossauthLogger.logger.error({ cerr: ce });
680
+ return {
681
+ error: ce.oauthErrorCode,
682
+ error_description: ce.message,
683
+ };
684
+ }
685
+ return {
686
+ scope: scope,
687
+ mfa_token: mfa_token,
688
+ oob_channel: auth.oob_channel,
689
+ challenge_type: resp.challenge_type,
690
+ binding_method: resp.binding_method,
691
+ oob_code: resp.oob_code,
692
+ name: auth.name,
693
+ };
694
+ }
695
+ const ce = new CrossauthError(ErrorCode.UnknownError, "Unsupported MFA type " + auth.authenticator_type + " returned");
696
+ return {
697
+ error: ce.oauthErrorCode,
698
+ error_description: ce.message,
699
+ };
700
+ }
701
+ async passwordOtp(_event, formData) {
702
+ let scope = formData.scope;
703
+ if (scope == "")
704
+ scope = undefined;
705
+ const resp = this.errorIfIdTokenInvalid(await this.mfaOtpComplete(formData.mfa_token, formData.otp, scope));
706
+ if (resp.error) {
707
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description ?? "Error completing MFA");
708
+ return {
709
+ error: ce.oauthErrorCode,
710
+ error_description: ce.message,
711
+ };
712
+ }
713
+ //return await this.receiveTokenFn(resp, this, event);
714
+ return resp;
715
+ }
716
+ async passwordOob(event, formData) {
717
+ let scope = formData.scope;
718
+ if (scope == "")
719
+ scope = undefined;
720
+ const resp = this.errorIfIdTokenInvalid(await this.mfaOobComplete(formData.mfa_token, formData.oob_code, formData.binding_code, scope));
721
+ if (resp.error) {
722
+ CrossauthLogger.logger.warn(j({
723
+ msg: "Error completing MFA",
724
+ user: this.server.sessionAdapter?.getUser(event),
725
+ hashedMfaToken: formData.mfa_token ? Crypto.hash(formData.mfa_token) : undefined,
726
+ }));
727
+ return {
728
+ error: resp.error,
729
+ error_description: resp.error_description,
730
+ };
731
+ }
732
+ //return await this.receiveTokenFn(resp, this, event);
733
+ return resp;
734
+ }
735
+ async refresh(mode, event, onlyIfExpired, refreshToken, expiresAt) {
736
+ if (!expiresAt || !refreshToken) {
737
+ if (mode != "silent") {
738
+ return await this.receiveTokenFn({}, this, event, true, this.setEventLocalsUser);
739
+ }
740
+ return undefined;
741
+ }
742
+ if (!onlyIfExpired || expiresAt <= Date.now()) {
743
+ if (event.locals.sessionId && this.autoRefreshActive[event.locals.sessionId])
744
+ return undefined;
745
+ try {
746
+ if (event.locals.sessionId)
747
+ this.autoRefreshActive[event.locals.sessionId] = true;
748
+ const resp = this.errorIfIdTokenInvalid(await this.refreshTokenFlow(refreshToken));
749
+ if (!resp.error && !resp.access_token) {
750
+ resp.error = "server_error";
751
+ resp.error_description = "Unexpectedly did not receive error or access token";
752
+ }
753
+ if (!resp.error) {
754
+ const resp1 = await this.receiveTokenFn(resp, this, event, mode == "silent", this.setEventLocalsUser);
755
+ if (mode != "silent")
756
+ return resp1;
757
+ }
758
+ if (mode != "silent") {
759
+ const ce = CrossauthError.fromOAuthError(resp.error ?? "server_error", resp.error_description);
760
+ if (mode == "page")
761
+ return this.errorFn(this.server, event, ce);
762
+ return {
763
+ error: ce.oauthErrorCode,
764
+ error_description: ce.message,
765
+ };
766
+ }
767
+ let expires_in = resp.expires_in;
768
+ if (!expires_in && resp.access_token) {
769
+ const payload = jwtDecode(resp.access_token);
770
+ if (payload.exp)
771
+ expires_in = payload.exp;
772
+ }
773
+ if (!expires_in) {
774
+ throw new CrossauthError(ErrorCode.BadRequest, "OAuth server did not return an expiry for the access token");
775
+ }
776
+ const expires_at = (new Date().getTime() + (expires_in * 1000));
777
+ return {
778
+ access_token: resp.access_token,
779
+ refresh_token: resp.refresh_token,
780
+ expires_in: resp.expires_in,
781
+ expires_at: expires_at,
782
+ error: resp.error,
783
+ error_description: resp.error_description
784
+ };
785
+ }
786
+ catch (e) {
787
+ if (SvelteKitServer.isSvelteKitRedirect(e))
788
+ throw e;
789
+ if (SvelteKitServer.isSvelteKitError(e))
790
+ throw e;
791
+ CrossauthLogger.logger.debug(j({ err: e }));
792
+ CrossauthLogger.logger.error(j({
793
+ cerr: e,
794
+ msg: "Failed refreshing access token"
795
+ }));
796
+ if (mode != "silent") {
797
+ const ce = CrossauthError.asCrossauthError(e);
798
+ if (mode == "page")
799
+ return this.errorFn(this.server, event, ce);
800
+ return {
801
+ error: ce.oauthErrorCode,
802
+ error_description: ce.message,
803
+ };
804
+ }
805
+ return {
806
+ error: "server_error",
807
+ error_description: "Failed refreshing access token",
808
+ };
809
+ }
810
+ finally {
811
+ if (event.locals.sessionId && event.locals.sessionId in this.autoRefreshActive)
812
+ delete this.autoRefreshActive[event.locals.sessionId];
813
+ }
814
+ }
815
+ return undefined;
816
+ }
817
+ async refreshTokens(event, mode, onlyIfExpired) {
818
+ try {
819
+ if (!this.server.sessionAdapter) {
820
+ return {
821
+ ok: false,
822
+ error: "server_error",
823
+ error_description: "Refresh tokens if expired or silent refresh only available if sessions are enabled",
824
+ };
825
+ }
826
+ if (this.server.sessionAdapter.csrfProtectionEnabled() && !this.server.sessionAdapter.getCsrfToken(event)) {
827
+ return {
828
+ ok: false,
829
+ error: "access_denied",
830
+ error_description: "No CSRF token found"
831
+ };
832
+ }
833
+ const oauthData = await this.server.sessionAdapter.getSessionData(event, this.sessionDataName);
834
+ if (!oauthData?.refresh_token) {
835
+ if (mode == "silent") {
836
+ return new Response(null, { status: 204 });
837
+ }
838
+ else {
839
+ const ce = new CrossauthError(ErrorCode.InvalidSession, "No tokens found in session");
840
+ throw ce;
841
+ }
842
+ }
843
+ let resp = await this.refresh(mode, event, onlyIfExpired, oauthData.refresh_token, oauthData.expires_at);
844
+ if (resp && "id_token" in resp) {
845
+ resp = this.errorIfIdTokenInvalid(resp);
846
+ }
847
+ if (mode == "silent") {
848
+ if (resp instanceof Response) {
849
+ throw new CrossauthError(ErrorCode.Configuration, "Unexpected error: refresh: mode is silent but didn't receive an object");
850
+ }
851
+ return { ok: true, expires_at: resp?.expires_at };
852
+ }
853
+ else if (mode == "post") {
854
+ if (resp == undefined)
855
+ return this.receiveTokenFn({}, this, event, false, this.setEventLocalsUser);
856
+ if (resp != undefined) {
857
+ if (resp instanceof Response)
858
+ return resp;
859
+ throw new CrossauthError(ErrorCode.Configuration, "refreshTokenFn for post should return Response not object");
860
+ }
861
+ }
862
+ }
863
+ catch (e) {
864
+ if (SvelteKitServer.isSvelteKitRedirect(e))
865
+ throw e;
866
+ if (SvelteKitServer.isSvelteKitError(e))
867
+ throw e;
868
+ const ce = CrossauthError.asCrossauthError(e);
869
+ CrossauthLogger.logger.debug({ err: ce });
870
+ CrossauthLogger.logger.error({ cerr: ce });
871
+ if (mode == "page")
872
+ return this.errorFn(this.server, event, ce);
873
+ else
874
+ return {
875
+ ok: false,
876
+ error: ce.oauthErrorCode,
877
+ error_description: ce.message,
878
+ };
879
+ }
880
+ }
881
+ ;
882
+ async passwordFlow_post(event, passwordFn) {
883
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
884
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
885
+ return this.errorFn(this.server, event, ce);
886
+ }
887
+ let formData = undefined;
888
+ try {
889
+ if (!(this.validFlows.includes(OAuthFlows.Password) ||
890
+ this.validFlows.includes(OAuthFlows.PasswordMfa))) {
891
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Password flow is not supported");
892
+ return this.errorFn(this.server, event, ce);
893
+ }
894
+ var data = new JsonOrFormData();
895
+ await data.loadData(event);
896
+ formData = data.toObject();
897
+ /*if (!event.locals.user &&
898
+ (this.loginProtectedFlows.includes(OAuthFlows.Password) ||
899
+ this.loginProtectedFlows.includes(OAuthFlows.PasswordMfa))) {
900
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Must log in to use password flow");
901
+
902
+ return this.errorFn(this.server, event, ce);
903
+ }*/
904
+ // if the session server and CSRF protection enabled, require a valid CSRF token
905
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
906
+ try {
907
+ //const cookieValue = this.server.sessionAdapter.getCsrfCookieValue(event);
908
+ //if (cookieValue) this.server.sessionAdapter.sessionManager.validateCsrfCookie(cookieValue);
909
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
910
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
911
+ }
912
+ }
913
+ catch (e) {
914
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
915
+ throw e;
916
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
917
+ return this.errorFn(this.server, event, ce);
918
+ }
919
+ }
920
+ const resp = this.errorIfIdTokenInvalid(await passwordFn(event, formData));
921
+ if (!resp)
922
+ throw new CrossauthError(ErrorCode.UnknownError, "Password flow returned no data");
923
+ if (resp.error)
924
+ return {
925
+ ok: false,
926
+ ...resp,
927
+ };
928
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
929
+ if (resp && resp2 instanceof Response)
930
+ return resp2;
931
+ throw new CrossauthError(ErrorCode.UnknownError, "Receive token function did not return a Response");
932
+ }
933
+ catch (e) {
934
+ if (SvelteKitServer.isSvelteKitRedirect(e))
935
+ throw e;
936
+ if (SvelteKitServer.isSvelteKitError(e))
937
+ throw e;
938
+ const ce = CrossauthError.asCrossauthError(e);
939
+ CrossauthLogger.logger.debug({ err: e });
940
+ CrossauthLogger.logger.error({ cerr: e });
941
+ //throw this.error(ce.httpStatus, ce.message);
942
+ return this.errorFn(this.server, event, ce);
943
+ }
944
+ }
945
+ async passwordFlow_action(event, passwordFn) {
946
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
947
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
948
+ throw ce;
949
+ }
950
+ let formData = undefined;
951
+ try {
952
+ if (!(this.validFlows.includes(OAuthFlows.Password) ||
953
+ this.validFlows.includes(OAuthFlows.PasswordMfa))) {
954
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Password and Password MFA flows are not supported");
955
+ return this.errorFn(this.server, event, ce);
956
+ }
957
+ var data = new JsonOrFormData();
958
+ await data.loadData(event);
959
+ formData = data.toObject();
960
+ /*if (!event.locals.user &&
961
+ (this.loginProtectedFlows.includes(OAuthFlows.Password) ||
962
+ this.loginProtectedFlows.includes(OAuthFlows.PasswordMfa))) {
963
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Must log in to use refresh token");
964
+ throw ce;
965
+ } */
966
+ // if the session server and CSRF protection enabled, require a valid CSRF token
967
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
968
+ try {
969
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
970
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
971
+ }
972
+ }
973
+ catch (e) {
974
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
975
+ throw e;
976
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
977
+ throw ce;
978
+ }
979
+ }
980
+ const resp = await passwordFn(event, formData);
981
+ if (!resp)
982
+ throw new CrossauthError(ErrorCode.UnknownError, "Password flow returned no data");
983
+ if (resp.error) {
984
+ return {
985
+ ok: false,
986
+ ...resp,
987
+ };
988
+ }
989
+ if (resp.challenge_type) {
990
+ if (!(this.validFlows.includes(OAuthFlows.PasswordMfa))) {
991
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Password MFA flow is not supported");
992
+ return this.errorFn(this.server, event, ce);
993
+ }
994
+ return resp;
995
+ }
996
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser) ?? {};
997
+ if (resp2 instanceof Response)
998
+ throw new CrossauthError(ErrorCode.Configuration, "Refresh token flow should return an object not Response");
999
+ return resp2;
1000
+ }
1001
+ catch (e) {
1002
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1003
+ throw e;
1004
+ if (SvelteKitServer.isSvelteKitError(e))
1005
+ throw e;
1006
+ const ce = CrossauthError.asCrossauthError(e);
1007
+ CrossauthLogger.logger.debug({ err: e });
1008
+ CrossauthLogger.logger.error({ cerr: e });
1009
+ //throw this.error(ce.httpStatus, ce.message);
1010
+ return {
1011
+ ok: false,
1012
+ error: ce.oauthErrorCode,
1013
+ error_description: ce.message
1014
+ };
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Call a resource on the resource server, passing in the access token
1019
+ * along with the body from the event and, unless overridden, the URL.
1020
+ *
1021
+ * It is probably easier to use `bffEndpoint` instead of this method.
1022
+ * However you can use this if you need to pass custom headers or want
1023
+ * to specify the URL manually.
1024
+ *
1025
+ * @param event the Sveltekit request event
1026
+ * @param opts additional data to put in resource server request. You can also override the URL here
1027
+ * @returns resource server response
1028
+ */
1029
+ async bff(event, opts = {}) {
1030
+ try {
1031
+ if (!this.server.sessionAdapter)
1032
+ throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1033
+ if (!this.server.oAuthClient)
1034
+ throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1035
+ if (!this.bffBaseUrl)
1036
+ throw new CrossauthError(ErrorCode.Configuration, "Must set bffBaseUrl to use bff()");
1037
+ if (!this.bffEndpointName)
1038
+ throw new CrossauthError(ErrorCode.Configuration, "Must set bffEndpointName to use bff()");
1039
+ let url = opts.url;
1040
+ if (!url) {
1041
+ if (!event.url.pathname.startsWith(this.bffEndpointName))
1042
+ throw new CrossauthError(ErrorCode.Unauthorized, "Attempt to call BFF url with the wrong prefix");
1043
+ const path = event.url.pathname.substring(this.bffEndpointName.length);
1044
+ let query = event.url.searchParams?.toString() ?? undefined;
1045
+ if (query && query != "")
1046
+ query = "?" + query;
1047
+ url = new URL(this.bffBaseUrl + path + query);
1048
+ }
1049
+ if (!opts.headers) {
1050
+ opts.headers = new Headers();
1051
+ }
1052
+ for (let i = 0; i < this.bffMaxTries; ++i) {
1053
+ if (i > 0)
1054
+ await new Promise(r => setTimeout(r, this.bffSleepMilliseconds));
1055
+ const oauthData = await this.server.sessionAdapter.getSessionData(event, this.sessionDataName);
1056
+ if (!oauthData) {
1057
+ if (i == this.bffMaxTries) {
1058
+ throw new CrossauthError(ErrorCode.Unauthorized, "No access token found");
1059
+ }
1060
+ else {
1061
+ continue;
1062
+ }
1063
+ }
1064
+ let access_token = oauthData.access_token;
1065
+ if (oauthData && oauthData.access_token) {
1066
+ const resp = await this.refresh("silent", event, true, oauthData.refresh_token, oauthData.expires_at);
1067
+ // following shouldn't happen but TS doesn't know that
1068
+ if (resp instanceof Response)
1069
+ throw new CrossauthError(ErrorCode.Configuration, "Expected object when refreshing tokens, not Response");
1070
+ if (resp?.access_token) {
1071
+ access_token = resp.access_token;
1072
+ }
1073
+ else if (resp?.error) {
1074
+ continue; // try again
1075
+ }
1076
+ }
1077
+ opts.headers.set("accept", "application/json");
1078
+ opts.headers.set("content-type", "application/json");
1079
+ if (access_token)
1080
+ opts.headers.set("authorization", "Bearer " + access_token);
1081
+ let resp;
1082
+ let body = undefined;
1083
+ if (event.request.body) {
1084
+ var data = new JsonOrFormData();
1085
+ await data.loadData(event);
1086
+ body = data.toObject();
1087
+ }
1088
+ CrossauthLogger.logger.debug(j({ msg: "Calling BFF URL", url: url, method: event.request.method }));
1089
+ if (body) {
1090
+ resp = await fetch(url, {
1091
+ headers: opts.headers,
1092
+ method: opts.method ?? event.request.method,
1093
+ body: JSON.stringify(body ?? "{}"),
1094
+ });
1095
+ }
1096
+ else {
1097
+ resp = await fetch(url, {
1098
+ headers: opts.headers,
1099
+ method: opts.method ?? event.request.method,
1100
+ });
1101
+ }
1102
+ if (resp.status == 401) {
1103
+ if (i < this.bffMaxTries - 1) {
1104
+ continue;
1105
+ }
1106
+ else {
1107
+ return resp;
1108
+ }
1109
+ }
1110
+ else {
1111
+ return resp;
1112
+ }
1113
+ }
1114
+ return new Response(null, { status: 401 }); // not reached but to ensure TS return type is correct
1115
+ }
1116
+ catch (e) {
1117
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1118
+ throw e;
1119
+ const ce = CrossauthError.asCrossauthError(e);
1120
+ CrossauthLogger.logger.debug({ err: ce });
1121
+ CrossauthLogger.logger.error({ cerr: ce });
1122
+ return json({
1123
+ error: ce.oauthErrorCode,
1124
+ error_description: ce.message,
1125
+ }, { status: ce.httpStatus });
1126
+ }
1127
+ }
1128
+ async unpack(resp) {
1129
+ if (resp.status == 204) {
1130
+ return { status: 204, body: {} };
1131
+ }
1132
+ else {
1133
+ try {
1134
+ return { status: resp.status, body: await resp.json() };
1135
+ }
1136
+ catch (e) {
1137
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1138
+ throw e;
1139
+ const ce = CrossauthError.asCrossauthError(e);
1140
+ CrossauthLogger.logger.debug({ err: ce });
1141
+ CrossauthLogger.logger.error({ cerr: ce });
1142
+ return { status: resp.status, body: {}, error: ce.oauthErrorCode, error_description: ce.message };
1143
+ }
1144
+ }
1145
+ }
1146
+ pack(ret) {
1147
+ if (ret instanceof Response)
1148
+ return ret;
1149
+ let status = 200;
1150
+ if (ret?.error == "access_denied")
1151
+ status = 401;
1152
+ else if (ret?.error)
1153
+ status = 500;
1154
+ else if (!ret)
1155
+ status = 204;
1156
+ return json(ret ?? null, { status });
1157
+ }
1158
+ /**
1159
+ * Ordinarily you would not call this directly but use `allBffEndpoint`.
1160
+ *
1161
+ * However you can use this if you need to pass custom headers.
1162
+ * @param event the Sveltekit request event
1163
+ * @param opts additional data to put in resource server request
1164
+ * @returns resource server response
1165
+ */
1166
+ async allBff(event, opts = {}) {
1167
+ try {
1168
+ CrossauthLogger.logger.debug(j({ msg: "Called allBff", url: event.url.toString() }));
1169
+ if (!this.server.sessionAdapter)
1170
+ throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1171
+ if (!this.server.oAuthClient)
1172
+ throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1173
+ if (!this.bffBaseUrl)
1174
+ throw new CrossauthError(ErrorCode.Configuration, "Must set bffBaseUrl to use bff()");
1175
+ if (!this.bffEndpointName)
1176
+ throw new CrossauthError(ErrorCode.Configuration, "Must set bffEndpointName to use bff()");
1177
+ if (!this.bffEndpoints || this.bffEndpoints.length == 0)
1178
+ throw new CrossauthError(ErrorCode.Unauthorized, "Invalid BFF endpoint");
1179
+ if (!event.url.pathname.startsWith(this.bffEndpointName))
1180
+ throw new CrossauthError(ErrorCode.Unauthorized, "Attempt to call BFF url with the wrong prefix");
1181
+ const path = event.url.pathname.substring(this.bffEndpointName.length);
1182
+ let idx = undefined;
1183
+ for (let i = 0; i < this.bffEndpoints.length; ++i) {
1184
+ let endpoint = this.bffEndpoints[i];
1185
+ if (endpoint.matchSubUrls) {
1186
+ let url = endpoint.url;
1187
+ let urlWithSlash = endpoint.url;
1188
+ if (!urlWithSlash.endsWith("/"))
1189
+ urlWithSlash += "/";
1190
+ if (endpoint.methodsString.includes(event.request.method) && (path.startsWith(urlWithSlash) || path == url)) {
1191
+ idx = i;
1192
+ break;
1193
+ }
1194
+ }
1195
+ else {
1196
+ let url = endpoint.url;
1197
+ if (endpoint.methodsString.includes(event.request.method) && (path == url)) {
1198
+ idx = i;
1199
+ break;
1200
+ }
1201
+ }
1202
+ }
1203
+ if (idx != undefined)
1204
+ return await this.bff(event, opts);
1205
+ else {
1206
+ throw new CrossauthError(ErrorCode.Unauthorized, "Illegal BFF URL called " + event.url.toString());
1207
+ }
1208
+ }
1209
+ catch (e) {
1210
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1211
+ throw e;
1212
+ const ce = CrossauthError.asCrossauthError(e);
1213
+ CrossauthLogger.logger.debug({ err: ce });
1214
+ CrossauthLogger.logger.error({ cerr: ce });
1215
+ return json({
1216
+ error: ce.oauthErrorCode,
1217
+ error_description: ce.message,
1218
+ }, { status: ce.httpStatus });
1219
+ }
1220
+ }
1221
+ tokenPayload(token, oauthData, isHave, decodeToken) {
1222
+ if (!(token in oauthData)) {
1223
+ return isHave ? { ok: false } : undefined;
1224
+ }
1225
+ if (isHave)
1226
+ return { ok: true };
1227
+ if (!decodeToken)
1228
+ return oauthData[token];
1229
+ if (token + "_payload" in oauthData)
1230
+ return oauthData[token + "_payload"];
1231
+ const payload = decodePayload(oauthData[token]);
1232
+ return payload;
1233
+ }
1234
+ async tokens(event, token) {
1235
+ try {
1236
+ let data = new JsonOrFormData(true);
1237
+ await data.loadData(event);
1238
+ const decode = data.getAsBoolean("decode") ?? true;
1239
+ if (!this.server.sessionAdapter)
1240
+ throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1241
+ if (!this.server.oAuthClient)
1242
+ throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1243
+ if (!this.tokenEndpoints || this.tokenEndpoints.length == 0)
1244
+ throw new CrossauthError(ErrorCode.Unauthorized, "No tokens have been made available");
1245
+ let tokens = Array.isArray(token) ? token : [token];
1246
+ const oauthData = await this.server.sessionAdapter.getSessionData(event, this.sessionDataName);
1247
+ if (!oauthData) {
1248
+ throw new CrossauthError(ErrorCode.Unauthorized, "No access token found");
1249
+ }
1250
+ let tokensReturning = {};
1251
+ let lastTokenPayload = undefined;
1252
+ let isHave = false;
1253
+ for (let t of tokens) {
1254
+ if (!this.tokenEndpoints.includes(t))
1255
+ throw new CrossauthError(ErrorCode.Unauthorized, "Token type " + t + " may not be returned");
1256
+ isHave = false;
1257
+ let tokenName = t;
1258
+ if (t.startsWith("have_")) {
1259
+ tokenName = t.replace("have_", "");
1260
+ isHave = true;
1261
+ }
1262
+ const tokenName1 = tokenName.replace("_token", "");
1263
+ const decodeToken = decode && this.jwtTokens.includes(tokenName1);
1264
+ let payload = this.tokenPayload(tokenName, oauthData, isHave, decodeToken);
1265
+ if (isHave) {
1266
+ // @ts-ignore because t is a string
1267
+ tokensReturning[t] = payload.ok;
1268
+ }
1269
+ else if (payload) {
1270
+ // @ts-ignore because t is a string
1271
+ tokensReturning[t] = payload;
1272
+ }
1273
+ // @ts-ignore because t is a string
1274
+ lastTokenPayload = tokensReturning[t];
1275
+ }
1276
+ if (!Array.isArray(token)) {
1277
+ if (!lastTokenPayload) {
1278
+ if (token.startsWith("have_"))
1279
+ return { status: 200, body: { ok: false } };
1280
+ else
1281
+ return { status: 204 };
1282
+ }
1283
+ if (isHave)
1284
+ return { status: 200, body: typeof (lastTokenPayload) == "boolean" ? { ok: lastTokenPayload } : lastTokenPayload };
1285
+ return { status: 200, body: lastTokenPayload };
1286
+ }
1287
+ else {
1288
+ return { status: 200, body: tokensReturning };
1289
+ }
1290
+ }
1291
+ catch (e) {
1292
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1293
+ throw e;
1294
+ const ce = CrossauthError.asCrossauthError(e);
1295
+ CrossauthLogger.logger.debug({ err: ce });
1296
+ CrossauthLogger.logger.error({ cerr: ce });
1297
+ return { status: ce.httpStatus, body: {
1298
+ error: ce.oauthErrorCode,
1299
+ error_description: ce.message,
1300
+ } };
1301
+ }
1302
+ }
1303
+ async tokensResponse(event, token) {
1304
+ const resp = await this.tokens(event, token);
1305
+ if (resp.body)
1306
+ return json(resp.body, { status: resp.status });
1307
+ return json(null, { status: resp.status });
1308
+ }
1309
+ async startDeviceCodeFlow_internal(event) {
1310
+ let formData = undefined;
1311
+ try {
1312
+ if (!(this.validFlows.includes(OAuthFlows.DeviceCode))) {
1313
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Device code flow is not supported");
1314
+ throw ce;
1315
+ }
1316
+ var data = new JsonOrFormData();
1317
+ await data.loadData(event);
1318
+ formData = data.toObject();
1319
+ // if the session server and CSRF protection enabled, require a valid CSRF token
1320
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
1321
+ try {
1322
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
1323
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
1324
+ }
1325
+ }
1326
+ catch (e) {
1327
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1328
+ throw e;
1329
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
1330
+ throw ce;
1331
+ }
1332
+ }
1333
+ // get scopes from body
1334
+ let scope = formData.scope;
1335
+ if (scope == "")
1336
+ scope = undefined;
1337
+ let url = this.authServerBaseUrl;
1338
+ if (!(url.endsWith("/")))
1339
+ url += "/";
1340
+ url += this.deviceAuthorizationUrl;
1341
+ const resp = await this.startDeviceCodeFlow(url, scope);
1342
+ let qrUrl = undefined;
1343
+ if (resp.verification_uri_complete) {
1344
+ await QRCode.toDataURL(resp.verification_uri_complete)
1345
+ .then((url) => {
1346
+ qrUrl = url;
1347
+ })
1348
+ .catch((err) => {
1349
+ CrossauthLogger.logger.debug(j({ err: err }));
1350
+ CrossauthLogger.logger.warn(j({ msg: "Couldn't generate verification URL QR Code" }));
1351
+ });
1352
+ }
1353
+ if (qrUrl)
1354
+ return { verification_uri_qrdata: qrUrl, ...resp };
1355
+ return resp;
1356
+ }
1357
+ catch (e) {
1358
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1359
+ throw e;
1360
+ if (SvelteKitServer.isSvelteKitError(e))
1361
+ throw e;
1362
+ const ce = CrossauthError.asCrossauthError(e);
1363
+ CrossauthLogger.logger.debug({ err: e });
1364
+ CrossauthLogger.logger.error({ cerr: e });
1365
+ //throw this.error(ce.httpStatus, ce.message);
1366
+ return {
1367
+ error: ce.oauthErrorCode,
1368
+ error_description: ce.message
1369
+ };
1370
+ }
1371
+ }
1372
+ async pollDeviceCodeFlow_internal(event) {
1373
+ let formData = undefined;
1374
+ try {
1375
+ if (!(this.validFlows.includes(OAuthFlows.DeviceCode))) {
1376
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Device code flow is not supported");
1377
+ throw ce;
1378
+ }
1379
+ var data = new JsonOrFormData();
1380
+ await data.loadData(event);
1381
+ formData = data.toObject();
1382
+ // if the session server and CSRF protection enabled, require a valid CSRF token
1383
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
1384
+ try {
1385
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
1386
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
1387
+ }
1388
+ }
1389
+ catch (e) {
1390
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1391
+ throw e;
1392
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
1393
+ throw ce;
1394
+ }
1395
+ }
1396
+ // get device code from body
1397
+ let deviceCode = formData.device_code;
1398
+ if (!deviceCode)
1399
+ throw new CrossauthError(ErrorCode.BadRequest, "No device code given when polling for user authorization");
1400
+ const resp = this.errorIfIdTokenInvalid(await this.pollDeviceCodeFlow(deviceCode));
1401
+ if (resp.access_token && !resp.error) {
1402
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1403
+ return resp2;
1404
+ }
1405
+ else {
1406
+ if (resp.error == "authorization_pending")
1407
+ return { ok: true, ...resp };
1408
+ let error = resp.error ?? "server_error";
1409
+ let error_description = resp.error_description ?? "Didn't receive an access token";
1410
+ const ce = CrossauthError.fromOAuthError(error, error_description);
1411
+ return this.errorFn(this.server, event, ce);
1412
+ }
1413
+ }
1414
+ catch (e) {
1415
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1416
+ throw e;
1417
+ if (SvelteKitServer.isSvelteKitError(e))
1418
+ throw e;
1419
+ const ce = CrossauthError.asCrossauthError(e);
1420
+ CrossauthLogger.logger.debug({ err: e });
1421
+ CrossauthLogger.logger.error({ cerr: e });
1422
+ //throw this.error(ce.httpStatus, ce.message);
1423
+ return this.errorFn(this.server, event, ce);
1424
+ }
1425
+ }
1426
+ async deleteSessionData(event) {
1427
+ // if the session server and CSRF protection enabled, require a valid CSRF token
1428
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
1429
+ try {
1430
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
1431
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
1432
+ }
1433
+ }
1434
+ catch (e) {
1435
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1436
+ throw e;
1437
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
1438
+ return this.errorFn(this.server, event, ce);
1439
+ }
1440
+ }
1441
+ await this.server.sessionAdapter?.deleteSessionData(event, this.sessionDataName);
1442
+ }
1443
+ async storeSessionData(event, sessionData) {
1444
+ // we need a session to save the state
1445
+ if (this.server.sessionServer) {
1446
+ // if we are using Crossauth's session server and there is nop
1447
+ // session, we can create an anonymous one.
1448
+ // For other session adapters, we assume the session was already
1449
+ // creeated or that the session adapter can create one automaticall
1450
+ // when updateSessionData is called
1451
+ let sessionCookieValue = this.server.sessionServer?.getSessionCookieValue(event);
1452
+ if (!sessionCookieValue) {
1453
+ sessionCookieValue =
1454
+ await this.server.sessionServer?.createAnonymousSession(event, { [this.sessionDataName]: sessionData });
1455
+ }
1456
+ else {
1457
+ await this.server.sessionAdapter?.updateSessionData(event, this.sessionDataName, sessionData);
1458
+ }
1459
+ }
1460
+ else {
1461
+ await this.server.sessionAdapter?.updateSessionData(event, this.sessionDataName, sessionData);
1462
+ }
1463
+ }
1464
+ ////////////////////////////////////////////////////////////////
1465
+ // Endpoints
1466
+ /////
1467
+ // Authorization code flows
1468
+ authorizationCodeFlowEndpoint = {
1469
+ get: async (event) => {
1470
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1471
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use load not get");
1472
+ return this.errorFn(this.server, event, ce);
1473
+ }
1474
+ try {
1475
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode))) {
1476
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
1477
+ return this.errorFn(this.server, event, ce);
1478
+ }
1479
+ if (!this.server.sessionAdapter) {
1480
+ throw new CrossauthError(ErrorCode.Configuration, "Need session server or adapter for authorization code flow");
1481
+ }
1482
+ let scope = event.url.searchParams.get("scope") ?? undefined;
1483
+ if (scope == "")
1484
+ scope = undefined;
1485
+ const state = this.randomValue(this.stateLength);
1486
+ const sessionData = { scope, state };
1487
+ // we need a session to save the state
1488
+ await this.storeSessionData(event, sessionData);
1489
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope);
1490
+ if (error || !url) {
1491
+ const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1492
+ return await this.errorFn(this.server, event, ce);
1493
+ }
1494
+ if (this.oauthLogFetch) {
1495
+ CrossauthLogger.logger.debug(j({ msg: "OAuth redirect", url: url }));
1496
+ }
1497
+ else {
1498
+ CrossauthLogger.logger.debug(j({
1499
+ msg: `OAuth redirect`,
1500
+ }));
1501
+ }
1502
+ throw this.redirect(302, url);
1503
+ }
1504
+ catch (e) {
1505
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1506
+ throw e;
1507
+ if (SvelteKitServer.isSvelteKitError(e))
1508
+ throw e;
1509
+ const ce = CrossauthError.asCrossauthError(e);
1510
+ CrossauthLogger.logger.debug({ err: e });
1511
+ CrossauthLogger.logger.error({ cerr: e });
1512
+ //throw this.error(ce.httpStatus, ce.message);
1513
+ return this.errorFn(this.server, event, ce);
1514
+ }
1515
+ },
1516
+ load: async (event) => {
1517
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
1518
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
1519
+ return {
1520
+ ok: false,
1521
+ error: ce.oauthErrorCode,
1522
+ error_description: ce.message,
1523
+ };
1524
+ }
1525
+ try {
1526
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode))) {
1527
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
1528
+ return {
1529
+ ok: false,
1530
+ error: ce.oauthErrorCode,
1531
+ error_description: ce.message,
1532
+ };
1533
+ }
1534
+ if (!this.server.sessionAdapter) {
1535
+ throw new CrossauthError(ErrorCode.Configuration, "Need session server or adapter for authorization code flow");
1536
+ }
1537
+ let scope = event.url.searchParams.get("scope") ?? undefined;
1538
+ if (scope == "")
1539
+ scope = undefined;
1540
+ const state = this.randomValue(this.stateLength);
1541
+ const sessionData = { scope, state };
1542
+ // we need a session to save the state
1543
+ await this.storeSessionData(event, sessionData);
1544
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope);
1545
+ if (error || !url) {
1546
+ const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1547
+ return {
1548
+ ok: false,
1549
+ error: ce.oauthErrorCode,
1550
+ error_description: ce.message,
1551
+ };
1552
+ }
1553
+ if (this.oauthLogFetch) {
1554
+ CrossauthLogger.logger.debug(j({ msg: "OAuth redirect", url: url }));
1555
+ }
1556
+ else {
1557
+ CrossauthLogger.logger.debug(j({
1558
+ msg: `OAuth redirect`,
1559
+ }));
1560
+ }
1561
+ throw this.redirect(302, url);
1562
+ }
1563
+ catch (e) {
1564
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1565
+ throw e;
1566
+ if (SvelteKitServer.isSvelteKitError(e))
1567
+ throw e;
1568
+ const ce = CrossauthError.asCrossauthError(e);
1569
+ CrossauthLogger.logger.debug({ err: e });
1570
+ CrossauthLogger.logger.error({ cerr: e });
1571
+ //throw this.error(ce.httpStatus, ce.message);
1572
+ return {
1573
+ ok: false,
1574
+ error: ce.oauthErrorCode,
1575
+ error_description: ce.message,
1576
+ };
1577
+ }
1578
+ },
1579
+ };
1580
+ authorizationCodeFlowWithPKCEEndpoint = {
1581
+ get: async (event) => {
1582
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1583
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use load not get");
1584
+ return this.errorFn(this.server, event, ce);
1585
+ }
1586
+ try {
1587
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE))) {
1588
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
1589
+ return this.errorFn(this.server, event, ce);
1590
+ }
1591
+ if (!this.server.sessionAdapter) {
1592
+ throw new CrossauthError(ErrorCode.Configuration, "Need session server or adapter for authorization code flow");
1593
+ }
1594
+ let scope = event.url.searchParams.get("scope") ?? undefined;
1595
+ if (scope == "")
1596
+ scope = undefined;
1597
+ const state = this.randomValue(this.stateLength);
1598
+ const { codeChallenge, codeVerifier } = await this.codeChallengeAndVerifier();
1599
+ const sessionData = { scope, state, codeChallenge, codeVerifier };
1600
+ // we need a session to save the state
1601
+ await this.storeSessionData(event, sessionData);
1602
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope, codeChallenge, true);
1603
+ if (error || !url) {
1604
+ const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1605
+ return await this.errorFn(this.server, event, ce);
1606
+ }
1607
+ if (this.oauthLogFetch) {
1608
+ CrossauthLogger.logger.debug(j({ msg: "OAuth redirect", url: url }));
1609
+ }
1610
+ else {
1611
+ CrossauthLogger.logger.debug(j({
1612
+ msg: `OAuth redirect`,
1613
+ }));
1614
+ }
1615
+ throw this.redirect(302, url);
1616
+ }
1617
+ catch (e) {
1618
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1619
+ throw e;
1620
+ if (SvelteKitServer.isSvelteKitError(e))
1621
+ throw e;
1622
+ const ce = CrossauthError.asCrossauthError(e);
1623
+ CrossauthLogger.logger.debug({ err: e });
1624
+ CrossauthLogger.logger.error({ cerr: e });
1625
+ //throw this.error(ce.httpStatus, ce.message);
1626
+ return json({
1627
+ error: ce.oauthErrorCode,
1628
+ error_description: ce.message
1629
+ }, { status: ce.httpStatus });
1630
+ }
1631
+ },
1632
+ load: async (event) => {
1633
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
1634
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use get not load");
1635
+ return {
1636
+ ok: false,
1637
+ error: ce.oauthErrorCode,
1638
+ error_description: ce.message,
1639
+ };
1640
+ }
1641
+ try {
1642
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE))) {
1643
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
1644
+ return {
1645
+ ok: false,
1646
+ error: ce.oauthErrorCode,
1647
+ error_description: ce.message,
1648
+ };
1649
+ }
1650
+ if (!this.server.sessionAdapter) {
1651
+ throw new CrossauthError(ErrorCode.Configuration, "Need session server or adapter for authorization code flow");
1652
+ }
1653
+ let scope = event.url.searchParams.get("scope") ?? undefined;
1654
+ if (scope == "")
1655
+ scope = undefined;
1656
+ const state = this.randomValue(this.stateLength);
1657
+ const { codeChallenge, codeVerifier } = await this.codeChallengeAndVerifier();
1658
+ const sessionData = { scope, state, codeChallenge, codeVerifier };
1659
+ // we need a session to save the state
1660
+ await this.storeSessionData(event, sessionData);
1661
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope, codeChallenge, true);
1662
+ if (error || !url) {
1663
+ const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1664
+ return {
1665
+ ok: false,
1666
+ error: ce.oauthErrorCode,
1667
+ error_description: ce.message,
1668
+ };
1669
+ }
1670
+ if (this.oauthLogFetch) {
1671
+ CrossauthLogger.logger.debug(j({ msg: "OAuth redirect", url: url }));
1672
+ }
1673
+ else {
1674
+ CrossauthLogger.logger.debug(j({
1675
+ msg: `OAuth redirect`,
1676
+ }));
1677
+ }
1678
+ throw this.redirect(302, url);
1679
+ }
1680
+ catch (e) {
1681
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1682
+ throw e;
1683
+ if (SvelteKitServer.isSvelteKitError(e))
1684
+ throw e;
1685
+ const ce = CrossauthError.asCrossauthError(e);
1686
+ CrossauthLogger.logger.debug({ err: e });
1687
+ CrossauthLogger.logger.error({ cerr: e });
1688
+ //throw this.error(ce.httpStatus, ce.message);
1689
+ return {
1690
+ ok: false,
1691
+ error: ce.oauthErrorCode,
1692
+ error_description: ce.message,
1693
+ };
1694
+ }
1695
+ },
1696
+ };
1697
+ redirectUriEndpoint = {
1698
+ get: async (event) => {
1699
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1700
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use load not get");
1701
+ return this.errorFn(this.server, event, ce);
1702
+ }
1703
+ try {
1704
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode) ||
1705
+ this.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE) ||
1706
+ this.validFlows.includes(OAuthFlows.OidcAuthorizationCode))) {
1707
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flows are not supported");
1708
+ return this.errorFn(this.server, event, ce);
1709
+ }
1710
+ CrossauthLogger.logger.debug(j({ msg: "redirectUriEndpoint, token response type " + this.tokenResponseType }));
1711
+ const code = event.url.searchParams.get("code") ?? "";
1712
+ const state = event.url.searchParams.get("state") ?? undefined;
1713
+ const error = event.url.searchParams.get("error") ?? undefined;
1714
+ const error_description = event.url.searchParams.get("error") ?? undefined;
1715
+ const oauthData = await this.server.sessionAdapter?.getSessionData(event, this.sessionDataName);
1716
+ if (oauthData?.state != state) {
1717
+ throw new CrossauthError(ErrorCode.Unauthorized, "State does not match");
1718
+ }
1719
+ const resp = this.errorIfIdTokenInvalid(await this.redirectEndpoint(code, oauthData?.scope, oauthData?.codeVerifier, error, error_description));
1720
+ if (resp.error)
1721
+ return this.errorFn(this.server, event, CrossauthError.fromOAuthError(resp.error, resp.error_description));
1722
+ if (resp.error) {
1723
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1724
+ return await this.errorFn(this.server, event, ce);
1725
+ }
1726
+ return await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1727
+ }
1728
+ catch (e) {
1729
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1730
+ throw e;
1731
+ if (SvelteKitServer.isSvelteKitError(e))
1732
+ throw e;
1733
+ const ce = CrossauthError.asCrossauthError(e);
1734
+ CrossauthLogger.logger.debug({ err: e });
1735
+ CrossauthLogger.logger.error({ cerr: e });
1736
+ //throw this.error(ce.httpStatus, ce.message);
1737
+ return this.errorFn(this.server, event, ce);
1738
+ }
1739
+ },
1740
+ load: async (event) => {
1741
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
1742
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use get not load");
1743
+ return {
1744
+ ok: false,
1745
+ error: ce.oauthErrorCode,
1746
+ error_description: ce.message,
1747
+ };
1748
+ }
1749
+ try {
1750
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode) ||
1751
+ this.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE) ||
1752
+ this.validFlows.includes(OAuthFlows.OidcAuthorizationCode))) {
1753
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flows are not supported");
1754
+ return {
1755
+ ok: false,
1756
+ error: ce.oauthErrorCode,
1757
+ error_description: ce.message,
1758
+ };
1759
+ }
1760
+ const code = event.url.searchParams.get("code") ?? "";
1761
+ const state = event.url.searchParams.get("state") ?? undefined;
1762
+ const error = event.url.searchParams.get("error") ?? undefined;
1763
+ const error_description = event.url.searchParams.get("error") ?? undefined;
1764
+ const oauthData = await this.server.sessionAdapter?.getSessionData(event, this.sessionDataName);
1765
+ if (oauthData?.state != state) {
1766
+ throw new CrossauthError(ErrorCode.Unauthorized, "State does not match");
1767
+ }
1768
+ const resp = this.errorIfIdTokenInvalid(await this.redirectEndpoint(code, oauthData?.scope, oauthData?.codeVerifier, error, error_description));
1769
+ if (resp.error)
1770
+ return {
1771
+ ok: false,
1772
+ error: resp.error,
1773
+ error_description: resp.error_description,
1774
+ };
1775
+ if (resp.error) {
1776
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1777
+ return {
1778
+ ok: false,
1779
+ error: ce.oauthErrorCode,
1780
+ error_description: ce.message,
1781
+ };
1782
+ }
1783
+ const receiveTokenResp = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1784
+ if (receiveTokenResp instanceof Response)
1785
+ return {
1786
+ ok: false,
1787
+ error: "server_error",
1788
+ error_description: "When using load, receiveTokenFn should return an object not a Response",
1789
+ };
1790
+ if (receiveTokenResp == undefined)
1791
+ return {
1792
+ ok: false,
1793
+ error: "server_error",
1794
+ error_description: "No response received from receiveTokenFn",
1795
+ };
1796
+ if (receiveTokenResp.error)
1797
+ return {
1798
+ ok: false,
1799
+ error: receiveTokenResp.error,
1800
+ error_description: receiveTokenResp.error_description,
1801
+ };
1802
+ return {
1803
+ ...receiveTokenResp,
1804
+ };
1805
+ }
1806
+ catch (e) {
1807
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1808
+ throw e;
1809
+ if (SvelteKitServer.isSvelteKitError(e))
1810
+ throw e;
1811
+ const ce = CrossauthError.asCrossauthError(e);
1812
+ CrossauthLogger.logger.debug({ err: e });
1813
+ CrossauthLogger.logger.error({ cerr: e });
1814
+ //throw this.error(ce.httpStatus, ce.message);
1815
+ return {
1816
+ ok: false,
1817
+ error: ce.oauthErrorCode,
1818
+ error_description: ce.message,
1819
+ };
1820
+ }
1821
+ },
1822
+ };
1823
+ /////
1824
+ // Client credentials flow
1825
+ clientCredentialsFlowEndpoint = {
1826
+ post: async (event) => {
1827
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1828
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
1829
+ return this.errorFn(this.server, event, ce);
1830
+ }
1831
+ let formData = undefined;
1832
+ try {
1833
+ if (!(this.validFlows.includes(OAuthFlows.ClientCredentials))) {
1834
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Client credentials flow is not supported");
1835
+ return this.errorFn(this.server, event, ce);
1836
+ }
1837
+ var data = new JsonOrFormData();
1838
+ await data.loadData(event);
1839
+ formData = data.toObject();
1840
+ /*if (!event.locals.user &&
1841
+ (this.loginProtectedFlows.includes(OAuthFlows.ClientCredentials))) {
1842
+ return this.error(401, "Must log in to use client credentials");
1843
+ } */
1844
+ const resp = this.errorIfIdTokenInvalid(await this.clientCredentialsFlow(formData?.scope));
1845
+ if (resp.error) {
1846
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1847
+ return await this.errorFn(this.server, event, ce);
1848
+ }
1849
+ const resp1 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1850
+ if (resp1 instanceof Response)
1851
+ return resp1;
1852
+ return this.pack(resp1);
1853
+ }
1854
+ catch (e) {
1855
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1856
+ throw e;
1857
+ if (SvelteKitServer.isSvelteKitError(e))
1858
+ throw e;
1859
+ const ce = CrossauthError.asCrossauthError(e);
1860
+ CrossauthLogger.logger.debug({ err: e });
1861
+ CrossauthLogger.logger.error({ cerr: e });
1862
+ //throw this.error(ce.httpStatus, ce.message);
1863
+ return this.errorFn(this.server, event, ce);
1864
+ }
1865
+ },
1866
+ actions: {
1867
+ default: async (event) => {
1868
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
1869
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
1870
+ throw ce;
1871
+ }
1872
+ let formData = undefined;
1873
+ try {
1874
+ if (!(this.validFlows.includes(OAuthFlows.ClientCredentials))) {
1875
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Client credentials flow is not supported");
1876
+ throw ce;
1877
+ }
1878
+ var data = new JsonOrFormData();
1879
+ await data.loadData(event);
1880
+ formData = data.toObject();
1881
+ /*if (!event.locals.user &&
1882
+ (this.loginProtectedFlows.includes(OAuthFlows.ClientCredentials))) {
1883
+ return {
1884
+ ok: false,
1885
+ error: "access_denied",
1886
+ error_description: "Must log in to use client credentials flow" ,
1887
+ }
1888
+ } */
1889
+ const resp = this.errorIfIdTokenInvalid(await this.clientCredentialsFlow(formData?.scope));
1890
+ if (resp.error) {
1891
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1892
+ throw ce;
1893
+ }
1894
+ return await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser) ?? {};
1895
+ }
1896
+ catch (e) {
1897
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1898
+ throw e;
1899
+ if (SvelteKitServer.isSvelteKitError(e))
1900
+ throw e;
1901
+ const ce = CrossauthError.asCrossauthError(e);
1902
+ CrossauthLogger.logger.debug({ err: e });
1903
+ CrossauthLogger.logger.error({ cerr: e });
1904
+ //throw this.error(ce.httpStatus, ce.message);
1905
+ return {
1906
+ ok: false,
1907
+ error: ce.oauthErrorCode,
1908
+ error_description: ce.message
1909
+ };
1910
+ }
1911
+ }
1912
+ }
1913
+ };
1914
+ /////
1915
+ // Refresh token flows
1916
+ refreshTokenFlowEndpoint = {
1917
+ post: async (event) => {
1918
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1919
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
1920
+ return this.errorFn(this.server, event, ce);
1921
+ }
1922
+ let formData = undefined;
1923
+ try {
1924
+ if (!(this.validFlows.includes(OAuthFlows.RefreshToken))) {
1925
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Refresh token flow is not supported");
1926
+ return this.errorFn(this.server, event, ce);
1927
+ }
1928
+ var data = new JsonOrFormData();
1929
+ await data.loadData(event);
1930
+ formData = data.toObject();
1931
+ /*if (!event.locals.user &&
1932
+ (this.loginProtectedFlows.includes(OAuthFlows.RefreshToken))) {
1933
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Must log in to use refresh token flow");
1934
+
1935
+ return this.errorFn(this.server, event, ce);
1936
+ } */
1937
+ // if the session server and CSRF protection enabled, require a valid CSRF token
1938
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
1939
+ try {
1940
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
1941
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
1942
+ }
1943
+ }
1944
+ catch (e) {
1945
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
1946
+ throw e;
1947
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
1948
+ return this.errorFn(this.server, event, ce);
1949
+ }
1950
+ }
1951
+ // get refresh token from body if present, otherwise
1952
+ // try to find in session
1953
+ let refreshToken = formData.refresh_token;
1954
+ if (!refreshToken && this.server.sessionAdapter) {
1955
+ const oauthData = await this.server.sessionAdapter.getSessionData(event, this.sessionDataName);
1956
+ if (!oauthData?.refresh_token) {
1957
+ const ce = new CrossauthError(ErrorCode.BadRequest, "No refresh token in session or in parameters");
1958
+ return this.errorFn(this.server, event, ce);
1959
+ }
1960
+ refreshToken = oauthData.refresh_token;
1961
+ }
1962
+ if (!refreshToken) {
1963
+ // TODO: refresh token cookie - call with no refresh token?
1964
+ const ce = new CrossauthError(ErrorCode.BadRequest, "No refresh token supplied");
1965
+ return this.errorFn(this.server, event, ce);
1966
+ }
1967
+ const resp = this.errorIfIdTokenInvalid(await this.refreshTokenFlow(refreshToken));
1968
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1969
+ if (resp && resp2 instanceof Response)
1970
+ return resp2;
1971
+ throw new CrossauthError(ErrorCode.UnknownError, "Receive token function did not return a Response");
1972
+ }
1973
+ catch (e) {
1974
+ if (SvelteKitServer.isSvelteKitRedirect(e))
1975
+ throw e;
1976
+ if (SvelteKitServer.isSvelteKitError(e))
1977
+ throw e;
1978
+ const ce = CrossauthError.asCrossauthError(e);
1979
+ CrossauthLogger.logger.debug({ err: e });
1980
+ CrossauthLogger.logger.error({ cerr: e });
1981
+ //throw this.error(ce.httpStatus, ce.message);
1982
+ return this.errorFn(this.server, event, ce);
1983
+ }
1984
+ },
1985
+ actions: {
1986
+ default: async (event) => {
1987
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
1988
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
1989
+ throw ce;
1990
+ }
1991
+ let formData = undefined;
1992
+ try {
1993
+ if (!(this.validFlows.includes(OAuthFlows.RefreshToken))) {
1994
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Refresh token flow is not supported");
1995
+ return this.errorFn(this.server, event, ce);
1996
+ }
1997
+ var data = new JsonOrFormData();
1998
+ await data.loadData(event);
1999
+ formData = data.toObject();
2000
+ /*if (!event.locals.user &&
2001
+ (this.loginProtectedFlows.includes(OAuthFlows.RefreshToken))) {
2002
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Must log in to use refresh token");
2003
+ throw ce;
2004
+ } */
2005
+ // if the session server and CSRF protection enabled, require a valid CSRF token
2006
+ if (this.server.sessionAdapter && this.server.sessionAdapter.csrfProtectionEnabled()) {
2007
+ try {
2008
+ if (!this.server.sessionAdapter.getCsrfToken(event)) {
2009
+ throw new CrossauthError(ErrorCode.InvalidCsrf);
2010
+ }
2011
+ }
2012
+ catch (e) {
2013
+ if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
2014
+ throw e;
2015
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "CSRF token not present");
2016
+ throw ce;
2017
+ }
2018
+ }
2019
+ // get refresh token from body if present, otherwise
2020
+ // try to find in session
2021
+ let refreshToken = formData.refresh_token;
2022
+ if (!refreshToken && this.server.sessionAdapter) {
2023
+ const oauthData = await this.server.sessionAdapter.getSessionData(event, this.sessionDataName);
2024
+ if (!oauthData?.refresh_token) {
2025
+ const ce = new CrossauthError(ErrorCode.BadRequest, "No refresh token in session or in parameters");
2026
+ throw ce;
2027
+ }
2028
+ refreshToken = oauthData.refresh_token;
2029
+ }
2030
+ if (!refreshToken) {
2031
+ // TODO: refresh token cookie - call with no refresh token?
2032
+ const ce = new CrossauthError(ErrorCode.BadRequest, "No refresh token supplied");
2033
+ throw ce;
2034
+ }
2035
+ const resp = this.errorIfIdTokenInvalid(await this.refreshTokenFlow(refreshToken));
2036
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser) ?? {};
2037
+ if (resp2 instanceof Response)
2038
+ throw new CrossauthError(ErrorCode.Configuration, "Refresh token flow should return an object not Response");
2039
+ return resp2;
2040
+ }
2041
+ catch (e) {
2042
+ if (SvelteKitServer.isSvelteKitRedirect(e))
2043
+ throw e;
2044
+ if (SvelteKitServer.isSvelteKitError(e))
2045
+ throw e;
2046
+ const ce = CrossauthError.asCrossauthError(e);
2047
+ CrossauthLogger.logger.debug({ err: e });
2048
+ CrossauthLogger.logger.error({ cerr: e });
2049
+ //throw this.error(ce.httpStatus, ce.message);
2050
+ return {
2051
+ ok: false,
2052
+ error: ce.oauthErrorCode,
2053
+ error_description: ce.message
2054
+ };
2055
+ }
2056
+ }
2057
+ }
2058
+ };
2059
+ refreshTokensIfExpiredEndpoint = {
2060
+ post: async (event) => {
2061
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
2062
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
2063
+ return this.errorFn(this.server, event, ce);
2064
+ }
2065
+ return this.pack(await this.refreshTokens(event, "post", true));
2066
+ },
2067
+ actions: {
2068
+ default: async (event) => {
2069
+ if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
2070
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
2071
+ throw ce;
2072
+ }
2073
+ return this.refreshTokens(event, "page", true);
2074
+ }
2075
+ },
2076
+ };
2077
+ autoRefreshTokensIfExpiredEndpoint = {
2078
+ post: async (event) => {
2079
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
2080
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
2081
+ return this.errorFn(this.server, event, ce);
2082
+ }
2083
+ return this.pack(await this.refreshTokens(event, "silent", true));
2084
+ },
2085
+ };
2086
+ autoRefreshTokensEndpoint = {
2087
+ post: async (event) => {
2088
+ if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
2089
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
2090
+ return this.errorFn(this.server, event, ce);
2091
+ }
2092
+ return this.pack(await this.refreshTokens(event, "silent", false));
2093
+ },
2094
+ };
2095
+ /////
2096
+ // Device code flow
2097
+ startDeviceCodeFlowEndpoint = {
2098
+ actions: {
2099
+ default: async (event) => {
2100
+ return await this.startDeviceCodeFlow_internal(event);
2101
+ },
2102
+ },
2103
+ post: async (event) => {
2104
+ const resp = await this.startDeviceCodeFlow_internal(event);
2105
+ if (resp.error) {
2106
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
2107
+ return json(resp, { status: ce.httpStatus });
2108
+ }
2109
+ return json(resp);
2110
+ }
2111
+ };
2112
+ pollDeviceCodeFlowEndpoint = {
2113
+ actions: {
2114
+ default: async (event) => {
2115
+ /*if (this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
2116
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
2117
+ throw ce;
2118
+ }*/
2119
+ const resp = await this.pollDeviceCodeFlow_internal(event);
2120
+ if (resp instanceof (Response))
2121
+ return this.unpack(resp);
2122
+ if (resp == undefined)
2123
+ return {};
2124
+ return resp;
2125
+ },
2126
+ },
2127
+ post: async (event) => {
2128
+ /*if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
2129
+ const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
2130
+ return this.errorFn(this.server, event, ce);
2131
+ }*/
2132
+ const resp = await this.pollDeviceCodeFlow_internal(event);
2133
+ if (resp instanceof Response)
2134
+ return resp;
2135
+ if (resp == undefined)
2136
+ return new Response(null, { status: 204 });
2137
+ if (resp.error) {
2138
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
2139
+ return json(resp, { status: ce.httpStatus });
2140
+ }
2141
+ return json(resp);
2142
+ }
2143
+ };
2144
+ /////
2145
+ // Password and Password MFA flows
2146
+ passwordFlowEndpoint = {
2147
+ post: async (event) => await this.passwordFlow_post(event, (e, formData) => this.passwordPost(e, formData)),
2148
+ actions: {
2149
+ password: async (event) => await this.passwordFlow_action(event, (e, formData) => this.passwordPost(e, formData)),
2150
+ passwordOtp: async (event) => await this.passwordFlow_action(event, (e, formData) => this.passwordOtp(e, formData)),
2151
+ passwordOob: async (event) => await this.passwordFlow_action(event, (e, formData) => this.passwordOob(e, formData)),
2152
+ }
2153
+ };
2154
+ passwordOtpEndpoint = {
2155
+ post: async (event) => await this.passwordFlow_post(event, (e, formData) => this.passwordOtp(e, formData)),
2156
+ actions: {
2157
+ default: async (event) => await this.passwordFlow_action(event, (e, formData) => this.passwordOtp(e, formData)),
2158
+ }
2159
+ };
2160
+ passwordOobEndpoint = {
2161
+ post: async (event) => await this.passwordFlow_post(event, (e, formData) => this.passwordOob(e, formData)),
2162
+ actions: {
2163
+ default: async (event) => await this.passwordFlow_action(event, (e, formData) => this.passwordOob(e, formData)),
2164
+ }
2165
+ };
2166
+ /////
2167
+ // Delete tokens
2168
+ deleteTokensEndpoint = {
2169
+ post: async (event) => {
2170
+ try {
2171
+ await this.deleteSessionData(event);
2172
+ return json({ ok: true });
2173
+ }
2174
+ catch (e) {
2175
+ if (SvelteKitServer.isSvelteKitRedirect(e))
2176
+ throw e;
2177
+ if (SvelteKitServer.isSvelteKitError(e))
2178
+ throw e;
2179
+ const ce = CrossauthError.asCrossauthError(e);
2180
+ CrossauthLogger.logger.debug({ err: ce });
2181
+ CrossauthLogger.logger.error({ cerr: ce });
2182
+ //throw this.error(ce.httpStatus, ce.message);
2183
+ return json({
2184
+ ok: false,
2185
+ user: this.server.sessionAdapter?.getUser(event),
2186
+ csrfToken: this.server.sessionAdapter?.getCsrfToken(event),
2187
+ errorCode: ce.code,
2188
+ errorCodeName: ce.codeName,
2189
+ errorMessage: ce.message,
2190
+ }, { status: ce.httpStatus });
2191
+ }
2192
+ },
2193
+ actions: {
2194
+ default: async (event) => {
2195
+ try {
2196
+ await this.deleteSessionData(event);
2197
+ return { ok: true };
2198
+ }
2199
+ catch (e) {
2200
+ if (SvelteKitServer.isSvelteKitRedirect(e))
2201
+ throw e;
2202
+ if (SvelteKitServer.isSvelteKitError(e))
2203
+ throw e;
2204
+ const ce = CrossauthError.asCrossauthError(e);
2205
+ CrossauthLogger.logger.debug({ err: ce });
2206
+ CrossauthLogger.logger.error({ cerr: ce });
2207
+ //throw this.error(ce.httpStatus, ce.message);
2208
+ return {
2209
+ ok: false,
2210
+ user: this.server.sessionAdapter?.getUser(event),
2211
+ csrfToken: this.server.sessionAdapter?.getCsrfToken(event),
2212
+ errorCode: ce.code,
2213
+ errorCodeName: ce.codeName,
2214
+ errorMessage: ce.message,
2215
+ };
2216
+ }
2217
+ }
2218
+ }
2219
+ };
2220
+ /////
2221
+ // BFF endpoints
2222
+ bffEndpoint = {
2223
+ post: async (event) => await this.bff(event),
2224
+ get: async (event) => await this.bff(event),
2225
+ put: async (event) => await this.bff(event),
2226
+ head: async (event) => await this.bff(event),
2227
+ options: async (event) => await this.bff(event),
2228
+ delete: async (event) => await this.bff(event),
2229
+ patch: async (event) => await this.bff(event),
2230
+ actions: {
2231
+ get: async (event) => await this.unpack(await this.bff(event)),
2232
+ post: async (event) => await this.unpack(await this.bff(event)),
2233
+ },
2234
+ };
2235
+ allBffEndpoint = {
2236
+ post: async (event) => await this.allBff(event),
2237
+ get: async (event) => await this.allBff(event),
2238
+ put: async (event) => await this.allBff(event),
2239
+ head: async (event) => await this.allBff(event),
2240
+ options: async (event) => await this.allBff(event),
2241
+ delete: async (event) => await this.allBff(event),
2242
+ patch: async (event) => await this.allBff(event),
2243
+ actions: {
2244
+ get: async (event) => await this.unpack(await this.allBff(event, { method: "GET" })),
2245
+ gpostet: async (event) => await this.unpack(await this.allBff(event, { method: "POST" })),
2246
+ put: async (event) => await this.unpack(await this.allBff(event, { method: "PUT" })),
2247
+ options: async (event) => await this.unpack(await this.allBff(event, { method: "OPTIONS" })),
2248
+ delete: async (event) => await this.unpack(await this.allBff(event, { method: "DELETE" })),
2249
+ patch: async (event) => await this.unpack(await this.allBff(event, { method: "PATCH" })),
2250
+ },
2251
+ };
2252
+ /////
2253
+ // Endpoints for getting BFF tokens
2254
+ accessTokenEndpoint = {
2255
+ post: async (event) => await this.tokens(event, "access_token"),
2256
+ actions: {
2257
+ default: async (event) => await this.tokens(event, "access_token"),
2258
+ },
2259
+ };
2260
+ haveAccessTokenEndpoint = {
2261
+ post: async (event) => await this.tokensResponse(event, "have_access_token"),
2262
+ actions: {
2263
+ default: async (event) => await this.tokens(event, "have_access_token"),
2264
+ },
2265
+ };
2266
+ refreshTokenEndpoint = {
2267
+ post: async (event) => await this.tokensResponse(event, "refresh_token"),
2268
+ actions: {
2269
+ default: async (event) => await this.tokens(event, "refresh_token"),
2270
+ },
2271
+ };
2272
+ haveRefreshTokenEndpoint = {
2273
+ post: async (event) => await this.tokensResponse(event, "have_refresh_token"),
2274
+ actions: {
2275
+ default: async (event) => await this.tokens(event, "have_refresh_token"),
2276
+ },
2277
+ };
2278
+ idTokenEndpoint = {
2279
+ post: async (event) => await this.tokensResponse(event, "id_token"),
2280
+ actions: {
2281
+ default: async (event) => await this.tokens(event, "id_token"),
2282
+ },
2283
+ };
2284
+ haveIdTokenEndpoint = {
2285
+ post: async (event) => await this.tokensResponse(event, "have_id_token"),
2286
+ actions: {
2287
+ default: async (event) => await this.tokens(event, "have_id_token"),
2288
+ },
2289
+ };
2290
+ tokensEndpoint = {
2291
+ post: async (event) => await this.tokensResponse(event, this.tokenEndpoints),
2292
+ actions: {
2293
+ default: async (event) => await this.tokens(event, this.tokenEndpoints),
2294
+ },
2295
+ };
2296
+ errorIfIdTokenInvalid(oauthResponse) {
2297
+ if (oauthResponse["id_token"] && this.jwtTokens.includes("id")) {
2298
+ /*const payload = this.validateIdToken(oauthResponse["id_token"]);
2299
+ if (payload == undefined) {
2300
+ return {
2301
+ error: "access_denied",
2302
+ error_description: "Invalid ID token received"
2303
+ }
2304
+ }*/
2305
+ return oauthResponse;
2306
+ }
2307
+ return oauthResponse;
2308
+ }
2309
+ }