@crossauth/sveltekit 1.1.2 → 1.1.4

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.
@@ -201,7 +201,7 @@ export interface SearchUsersActionData {
201
201
  * | baseEndpoint | This PageData is returned by all endpoints' load function. | - `user` logged in {@link @crossauth/common!User} | *Not provided* | | |
202
202
  * | | | - `csrfToken` CSRF token if enabled | | | | | loginPage |
203
203
  * | -------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- | --------- |
204
- * | searchUsersEndpoint | Returns a paginated set of users or those matchign search | See return of {@link SvelteKitAdminEndpoints.searchUsers} | *Not provided* | | |
204
+ * | searchUsersEndpoint | Returns a paginated set of users or those matchign search | See return of searchUsers | *Not provided* | | |
205
205
  * | -------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- | --------- |
206
206
  * | updateUserEndpoint | Update a user's details | - `allowedFactor2` see {@link SvelteKitAdminEndpoints}.`signupEndpoint` | `default`: | `default`: | `id` |
207
207
  * | | | - `editUser` the {@link @crossauth/common!User} being edited | - see {@link SvelteKitAdminEndpoints.updateUser} return | - see {@link SvelteKitAdminEndpoints.updateUser} event | |
@@ -86,7 +86,7 @@ async function defaultUserSearchFn(searchTerm, userStorage, skip = 0, _take = 10
86
86
  * | baseEndpoint | This PageData is returned by all endpoints' load function. | - `user` logged in {@link @crossauth/common!User} | *Not provided* | | |
87
87
  * | | | - `csrfToken` CSRF token if enabled | | | | | loginPage |
88
88
  * | -------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- | --------- |
89
- * | searchUsersEndpoint | Returns a paginated set of users or those matchign search | See return of {@link SvelteKitAdminEndpoints.searchUsers} | *Not provided* | | |
89
+ * | searchUsersEndpoint | Returns a paginated set of users or those matchign search | See return of searchUsers | *Not provided* | | |
90
90
  * | -------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ----------------------------------------------------------- | --------- |
91
91
  * | updateUserEndpoint | Update a user's details | - `allowedFactor2` see {@link SvelteKitAdminEndpoints}.`signupEndpoint` | `default`: | `default`: | `id` |
92
92
  * | | | - `editUser` the {@link @crossauth/common!User} being edited | - see {@link SvelteKitAdminEndpoints.updateUser} return | - see {@link SvelteKitAdminEndpoints.updateUser} event | |
@@ -62,7 +62,7 @@ export interface SvelteKitOAuthClientOptions extends OAuthClientOptions {
62
62
  */
63
63
  receiveTokenFn?: (oauthResponse: OAuthTokenResponse, client: SvelteKitOAuthClient, event: RequestEvent, silent: boolean, setUserFn: (event: RequestEvent, token: {
64
64
  [key: string]: any;
65
- }) => Promise<void>) => Promise<Response | TokenReturn | undefined>;
65
+ }) => Promise<void>) => Promise<Response | TokenReturn>;
66
66
  /**
67
67
  * The function to call when there is an OAuth error and
68
68
  * {@link SvelteKitOAuthClientOptions.errorResponseType}
@@ -440,19 +440,19 @@ export declare class SvelteKitOAuthClient extends OAuthClientBackend {
440
440
  load: (event: RequestEvent) => Promise<AuthorizationCodeFlowReturn>;
441
441
  };
442
442
  readonly redirectUriEndpoint: {
443
- get: (event: RequestEvent) => Promise<Response | TokenReturn | undefined>;
443
+ get: (event: RequestEvent) => Promise<Response>;
444
444
  load: (event: RequestEvent) => Promise<RedirectUriReturn>;
445
445
  };
446
446
  readonly clientCredentialsFlowEndpoint: {
447
447
  post: (event: RequestEvent) => Promise<Response>;
448
448
  actions: {
449
- default: (event: RequestEvent) => Promise<{}>;
449
+ default: (event: RequestEvent) => Promise<Response | TokenReturn>;
450
450
  };
451
451
  };
452
452
  readonly refreshTokenFlowEndpoint: {
453
453
  post: (event: RequestEvent) => Promise<Response>;
454
454
  actions: {
455
- default: (event: RequestEvent) => Promise<{}>;
455
+ default: (event: RequestEvent) => Promise<Response | TokenReturn>;
456
456
  };
457
457
  };
458
458
  readonly refreshTokensIfExpiredEndpoint: {
@@ -505,9 +505,9 @@ export declare class SvelteKitOAuthClient extends OAuthClientBackend {
505
505
  ok: boolean;
506
506
  }>;
507
507
  actions: {
508
- password: (event: RequestEvent) => Promise<{}>;
509
- passwordOtp: (event: RequestEvent) => Promise<{}>;
510
- passwordOob: (event: RequestEvent) => Promise<{}>;
508
+ password: (event: RequestEvent) => Promise<Response | OAuthTokenResponse>;
509
+ passwordOtp: (event: RequestEvent) => Promise<Response | OAuthTokenResponse>;
510
+ passwordOob: (event: RequestEvent) => Promise<Response | OAuthTokenResponse>;
511
511
  };
512
512
  };
513
513
  readonly passwordOtpEndpoint: {
@@ -532,7 +532,7 @@ export declare class SvelteKitOAuthClient extends OAuthClientBackend {
532
532
  ok: boolean;
533
533
  }>;
534
534
  actions: {
535
- default: (event: RequestEvent) => Promise<{}>;
535
+ default: (event: RequestEvent) => Promise<Response | OAuthTokenResponse>;
536
536
  };
537
537
  };
538
538
  readonly passwordOobEndpoint: {
@@ -557,7 +557,7 @@ export declare class SvelteKitOAuthClient extends OAuthClientBackend {
557
557
  ok: boolean;
558
558
  }>;
559
559
  actions: {
560
- default: (event: RequestEvent) => Promise<{}>;
560
+ default: (event: RequestEvent) => Promise<Response | OAuthTokenResponse>;
561
561
  };
562
562
  };
563
563
  readonly deleteTokensEndpoint: {
@@ -7,6 +7,7 @@ import { SvelteKitServer } from './sveltekitserver';
7
7
  import { json } from '@sveltejs/kit';
8
8
  import { JsonOrFormData } from './utils';
9
9
  import {} from './tests/sveltemocks';
10
+ import {} from './sveltekitoauthserver';
10
11
  ////////////////////////////////////////////////////////////////////////////
11
12
  // DEFAULT FUNCTIONS
12
13
  async function jsonError(_server, _event, ce) {
@@ -125,8 +126,31 @@ async function saveInSessionAndRedirect(oauthResponse, client, event, silent, se
125
126
  if (payload)
126
127
  await setUserFn(event, payload);
127
128
  }
129
+ let url = client.authorizedUrl;
130
+ var data = new JsonOrFormData();
131
+ await data.loadData(event);
132
+ let formData = data.toObject();
133
+ let msg = formData.msg ?? event.url.searchParams.get("msg");
134
+ let error = formData.error ?? event.url.searchParams.get("error");
135
+ if (msg) {
136
+ if (url.includes("?")) {
137
+ url += "&msg=" + encodeURIComponent(msg);
138
+ }
139
+ else {
140
+ url += "?msg=" + encodeURIComponent(msg);
141
+ }
142
+ }
143
+ if (error) {
144
+ if (url.includes("?")) {
145
+ url += "&error=" + encodeURIComponent(error);
146
+ }
147
+ else {
148
+ url += "?error=" + encodeURIComponent(error);
149
+ }
150
+ }
128
151
  if (!silent)
129
- return client.redirect(302, client.authorizedUrl);
152
+ return client.redirect(302, url);
153
+ return json({});
130
154
  }
131
155
  catch (e) {
132
156
  if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
@@ -151,8 +175,6 @@ async function saveInSessionAndReturn(oauthResponse, client, event, silent, setU
151
175
  await setUserFn(event, payload);
152
176
  }
153
177
  return json({ ok: true, ...oauthResponse });
154
- if (!silent)
155
- return client.redirect(302, client.authorizedUrl);
156
178
  }
157
179
  catch (e) {
158
180
  if (SvelteKitServer.isSvelteKitError(e) || SvelteKitServer.isSvelteKitRedirect(e))
@@ -540,40 +562,49 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
540
562
  if (this, this.loginProtectedFlows.length > 0 && this.loginUrl == "") {
541
563
  throw new CrossauthError(ErrorCode.Configuration, "loginUrl must be set if protecting oauth endpoints");
542
564
  }
565
+ // this doesn't work as it relies on data stored in the session, which cannot be done
566
+ // as tokens are fetched from a backend POST, thus no cookies
543
567
  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 }));
568
+ CrossauthLogger.logger.debug(j({ msg: "OAuth hook, user " + event.locals.user?.username }));
569
+ try {
570
+ if (event.locals.user)
571
+ return undefined;
572
+ if (!server.sessionAdapter)
573
+ return undefined;
574
+ let sessionData = await server.sessionAdapter.getSessionData(event, this.sessionDataName);
575
+ let validIdToken = false;
576
+ //CrossauthLogger.logger.debug(j({msg:"Session data " + (sessionData && sessionData["id_payload"]) ? JSON.stringify(sessionData?.id_payload) : "none)"}));
577
+ if (sessionData && sessionData["id_payload"]) {
578
+ let expiry = sessionData["expires_at"];
579
+ if (expiry && expiry > Date.now() && sessionData["id_payload"].sub) {
580
+ CrossauthLogger.logger.debug(j({ msg: "ID token is valid" }));
581
+ await this.setEventLocalsUser(event, sessionData["id_payload"]);
582
+ if (event.locals.user)
583
+ validIdToken = true;
584
+ }
570
585
  }
571
- else {
572
- await this.setEventLocalsUser(event, sessionData["id_payload"]);
586
+ if (!validIdToken && sessionData && sessionData["refresh_token"]) {
587
+ CrossauthLogger.logger.debug(j({ msg: "No ID token found but refresh token found - attemping refresh flow" }));
588
+ const resp = await this.refreshTokens(event, "silent", false);
589
+ if (!resp?.ok) {
590
+ const error = resp instanceof Response || resp == undefined ? "server_error" : (resp.error ?? "server_error");
591
+ const error_description = resp instanceof Response || resp == undefined ? "Unknown error" : (resp.error_description ?? "Unknown error");
592
+ const ce = CrossauthError.fromOAuthError(error, error_description);
593
+ CrossauthLogger.logger.debug(j({ err: ce }));
594
+ CrossauthLogger.logger.warn(j({ msg: "Error refreshing token", cerr: ce }));
595
+ }
596
+ else {
597
+ await this.setEventLocalsUser(event, sessionData["id_payload"]);
598
+ }
573
599
  }
600
+ if (this.testMiddleware)
601
+ this.testEvent = event;
602
+ }
603
+ catch (e) {
604
+ let ce = CrossauthError.asCrossauthError(e);
605
+ CrossauthLogger.logger.debug(j({ err: ce }));
606
+ CrossauthLogger.logger.error(j({ msg: "Error in oauth client hook", cerr: ce }));
574
607
  }
575
- if (this.testMiddleware)
576
- this.testEvent = event;
577
608
  return undefined;
578
609
  };
579
610
  }
@@ -1030,7 +1061,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1030
1061
  try {
1031
1062
  if (!this.server.sessionAdapter)
1032
1063
  throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1033
- if (!this.server.oAuthClient)
1064
+ if (!this.server.oAuthClient && !this.server.oAuthClients)
1034
1065
  throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1035
1066
  if (!this.bffBaseUrl)
1036
1067
  throw new CrossauthError(ErrorCode.Configuration, "Must set bffBaseUrl to use bff()");
@@ -1168,7 +1199,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1168
1199
  CrossauthLogger.logger.debug(j({ msg: "Called allBff", url: event.url.toString() }));
1169
1200
  if (!this.server.sessionAdapter)
1170
1201
  throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1171
- if (!this.server.oAuthClient)
1202
+ if (!this.server.oAuthClient && !this.server.oAuthClients)
1172
1203
  throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1173
1204
  if (!this.bffBaseUrl)
1174
1205
  throw new CrossauthError(ErrorCode.Configuration, "Must set bffBaseUrl to use bff()");
@@ -1238,7 +1269,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1238
1269
  const decode = data.getAsBoolean("decode") ?? true;
1239
1270
  if (!this.server.sessionAdapter)
1240
1271
  throw new CrossauthError(ErrorCode.Configuration, "Session server must be instantiated to use bff()");
1241
- if (!this.server.oAuthClient)
1272
+ if (!this.server.oAuthClient && !this.server.oAuthClients)
1242
1273
  throw new CrossauthError(ErrorCode.Configuration, "OAuth Client not found"); // pathological but prevents TS errors
1243
1274
  if (!this.tokenEndpoints || this.tokenEndpoints.length == 0)
1244
1275
  throw new CrossauthError(ErrorCode.Unauthorized, "No tokens have been made available");
@@ -1304,7 +1335,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1304
1335
  const resp = await this.tokens(event, token);
1305
1336
  if (resp.body)
1306
1337
  return json(resp.body, { status: resp.status });
1307
- return json(null, { status: resp.status });
1338
+ return json({ error: "null body" }, { status: resp.status });
1308
1339
  }
1309
1340
  async startDeviceCodeFlow_internal(event) {
1310
1341
  let formData = undefined;
@@ -1469,8 +1500,10 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1469
1500
  get: async (event) => {
1470
1501
  if (this.tokenResponseType == "saveInSessionAndLoad" || this.tokenResponseType == "sendInPage") {
1471
1502
  const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use load not get");
1472
- return this.errorFn(this.server, event, ce);
1503
+ CrossauthLogger.logger.debug(j({ err: ce }));
1504
+ return await this.errorFn(this.server, event, ce);
1473
1505
  }
1506
+ let redirectUrl = undefined;
1474
1507
  try {
1475
1508
  if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode))) {
1476
1509
  const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flow is not supported");
@@ -1480,13 +1513,22 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1480
1513
  throw new CrossauthError(ErrorCode.Configuration, "Need session server or adapter for authorization code flow");
1481
1514
  }
1482
1515
  let scope = event.url.searchParams.get("scope") ?? undefined;
1516
+ let upstream = event.url.searchParams.get("upstream") ?? undefined;
1517
+ let next = event.url.searchParams.get("next") ?? undefined;
1483
1518
  if (scope == "")
1484
1519
  scope = undefined;
1485
1520
  const state = this.randomValue(this.stateLength);
1486
1521
  const sessionData = { scope, state };
1487
1522
  // we need a session to save the state
1488
1523
  await this.storeSessionData(event, sessionData);
1489
- const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope);
1524
+ // if we have an upstream client, this call will save the original /authorize call and redirect to
1525
+ // the upstream auth server's /authorize endpoint
1526
+ if ((upstream || this.server.oAuthAuthServer?.authServer.upstreamClient) && next && this.server.oAuthAuthServer) {
1527
+ await this.server.oAuthAuthServer.saveDownstreamAuthzCodeFlow(event, new URL(next, event.url), upstream);
1528
+ }
1529
+ // the following call returns a constructed url for the
1530
+ // auth server's /authorize endpoint
1531
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, { scope, upstream });
1490
1532
  if (error || !url) {
1491
1533
  const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1492
1534
  return await this.errorFn(this.server, event, ce);
@@ -1499,7 +1541,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1499
1541
  msg: `OAuth redirect`,
1500
1542
  }));
1501
1543
  }
1502
- throw this.redirect(302, url);
1544
+ redirectUrl = url;
1503
1545
  }
1504
1546
  catch (e) {
1505
1547
  if (SvelteKitServer.isSvelteKitRedirect(e))
@@ -1510,8 +1552,12 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1510
1552
  CrossauthLogger.logger.debug({ err: e });
1511
1553
  CrossauthLogger.logger.error({ cerr: e });
1512
1554
  //throw this.error(ce.httpStatus, ce.message);
1513
- return this.errorFn(this.server, event, ce);
1555
+ return jsonError(this.server, event, ce);
1556
+ //return await this.errorFn(this.server, event, ce);
1514
1557
  }
1558
+ if (!redirectUrl)
1559
+ jsonError(this.server, event, new CrossauthError(ErrorCode.UnknownError, "Unexpectedly got redirect URL"));
1560
+ throw this.redirect(302, redirectUrl); // to auth server's authorize endpoint
1515
1561
  },
1516
1562
  load: async (event) => {
1517
1563
  if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
@@ -1537,11 +1583,20 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1537
1583
  let scope = event.url.searchParams.get("scope") ?? undefined;
1538
1584
  if (scope == "")
1539
1585
  scope = undefined;
1586
+ let upstream = event.url.searchParams.get("upstream") ?? undefined;
1587
+ let next = event.url.searchParams.get("next") ?? undefined;
1540
1588
  const state = this.randomValue(this.stateLength);
1541
1589
  const sessionData = { scope, state };
1542
1590
  // we need a session to save the state
1543
1591
  await this.storeSessionData(event, sessionData);
1544
- const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope);
1592
+ // if we have an upstream client, this call will save the original /authorize call and redirect to
1593
+ // the upstream auth server's /authorize endpoint
1594
+ if ((upstream || this.server.oAuthAuthServer?.authServer.upstreamClient) && next && this.server.oAuthAuthServer) {
1595
+ await this.server.oAuthAuthServer.saveDownstreamAuthzCodeFlow(event, new URL(next), upstream);
1596
+ }
1597
+ // the following call returns a constructed url for the
1598
+ // auth server's /authorize endpoint
1599
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, { scope, upstream });
1545
1600
  if (error || !url) {
1546
1601
  const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1547
1602
  return {
@@ -1558,7 +1613,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1558
1613
  msg: `OAuth redirect`,
1559
1614
  }));
1560
1615
  }
1561
- throw this.redirect(302, url);
1616
+ throw this.redirect(302, url); // to auth server's authorize endpoint
1562
1617
  }
1563
1618
  catch (e) {
1564
1619
  if (SvelteKitServer.isSvelteKitRedirect(e))
@@ -1594,12 +1649,21 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1594
1649
  let scope = event.url.searchParams.get("scope") ?? undefined;
1595
1650
  if (scope == "")
1596
1651
  scope = undefined;
1652
+ let upstream = event.url.searchParams.get("upstream") ?? undefined;
1653
+ let next = event.url.searchParams.get("next") ?? undefined;
1597
1654
  const state = this.randomValue(this.stateLength);
1598
1655
  const { codeChallenge, codeVerifier } = await this.codeChallengeAndVerifier();
1599
1656
  const sessionData = { scope, state, codeChallenge, codeVerifier };
1600
1657
  // we need a session to save the state
1601
1658
  await this.storeSessionData(event, sessionData);
1602
- const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope, codeChallenge, true);
1659
+ // if we have an upstream client, this call will save the original /authorize call and redirect to
1660
+ // the upstream auth server's /authorize endpoint
1661
+ if ((upstream || this.server.oAuthAuthServer?.authServer.upstreamClient) && next && this.server.oAuthAuthServer) {
1662
+ await this.server.oAuthAuthServer.saveDownstreamAuthzCodeFlow(event, new URL(next), upstream);
1663
+ }
1664
+ // the following call returns a constructed url for the
1665
+ // auth server's /authorize endpoint
1666
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, { scope, codeChallenge, pkce: true, upstream });
1603
1667
  if (error || !url) {
1604
1668
  const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1605
1669
  return await this.errorFn(this.server, event, ce);
@@ -1612,7 +1676,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1612
1676
  msg: `OAuth redirect`,
1613
1677
  }));
1614
1678
  }
1615
- throw this.redirect(302, url);
1679
+ throw this.redirect(302, url); // to auth server's authorize endpoint
1616
1680
  }
1617
1681
  catch (e) {
1618
1682
  if (SvelteKitServer.isSvelteKitRedirect(e))
@@ -1653,12 +1717,21 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1653
1717
  let scope = event.url.searchParams.get("scope") ?? undefined;
1654
1718
  if (scope == "")
1655
1719
  scope = undefined;
1720
+ let upstream = event.url.searchParams.get("upstream") ?? undefined;
1721
+ let next = event.url.searchParams.get("next") ?? undefined;
1656
1722
  const state = this.randomValue(this.stateLength);
1657
1723
  const { codeChallenge, codeVerifier } = await this.codeChallengeAndVerifier();
1658
1724
  const sessionData = { scope, state, codeChallenge, codeVerifier };
1659
1725
  // we need a session to save the state
1660
1726
  await this.storeSessionData(event, sessionData);
1661
- const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, scope, codeChallenge, true);
1727
+ // if we have an upstream client, this call will save the original /authorize call and redirect to
1728
+ // the upstream auth server's /authorize endpoint
1729
+ if ((upstream || this.server.oAuthAuthServer?.authServer.upstreamClient) && next && this.server.oAuthAuthServer) {
1730
+ await this.server.oAuthAuthServer.saveDownstreamAuthzCodeFlow(event, new URL(next), upstream);
1731
+ }
1732
+ // the following call returns a constructed url for the
1733
+ // auth server's /authorize endpoint
1734
+ const { url, error, error_description } = await this.startAuthorizationCodeFlow(state, { scope, codeChallenge, pkce: true, upstream });
1662
1735
  if (error || !url) {
1663
1736
  const ce = CrossauthError.fromOAuthError(error ?? "server_error", error_description);
1664
1737
  return {
@@ -1675,7 +1748,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1675
1748
  msg: `OAuth redirect`,
1676
1749
  }));
1677
1750
  }
1678
- throw this.redirect(302, url);
1751
+ throw this.redirect(302, url); // to auth server's authorize endpoint
1679
1752
  }
1680
1753
  catch (e) {
1681
1754
  if (SvelteKitServer.isSvelteKitRedirect(e))
@@ -1700,42 +1773,39 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1700
1773
  const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use load not get");
1701
1774
  return this.errorFn(this.server, event, ce);
1702
1775
  }
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);
1776
+ if (!(this.validFlows.includes(OAuthFlows.AuthorizationCode) ||
1777
+ this.validFlows.includes(OAuthFlows.AuthorizationCodeWithPKCE) ||
1778
+ this.validFlows.includes(OAuthFlows.OidcAuthorizationCode))) {
1779
+ const ce = new CrossauthError(ErrorCode.Unauthorized, "Authorization flows are not supported");
1737
1780
  return this.errorFn(this.server, event, ce);
1738
1781
  }
1782
+ CrossauthLogger.logger.debug(j({ msg: "redirectUriEndpoint, token response type " + this.tokenResponseType }));
1783
+ const code = event.url.searchParams.get("code") ?? "";
1784
+ const state = event.url.searchParams.get("state") ?? undefined;
1785
+ const error = event.url.searchParams.get("error") ?? undefined;
1786
+ const error_description = event.url.searchParams.get("error") ?? undefined;
1787
+ const oauthData = await this.server.sessionAdapter?.getSessionData(event, this.sessionDataName);
1788
+ if (oauthData?.state != state) {
1789
+ return json({
1790
+ error: "access_denied",
1791
+ error_description: "state does not match"
1792
+ }, { status: 403 });
1793
+ }
1794
+ // call the auth server's token endpoint to exchange the code for new tokens
1795
+ const resp = this.errorIfIdTokenInvalid(await this.redirectEndpoint({
1796
+ code,
1797
+ scope: oauthData?.scope,
1798
+ codeVerifier: oauthData?.codeVerifier,
1799
+ error,
1800
+ errorDescription: error_description
1801
+ }));
1802
+ if (resp.error)
1803
+ return this.errorFn(this.server, event, CrossauthError.fromOAuthError(resp.error, resp.error_description));
1804
+ if (resp.error) {
1805
+ const ce = CrossauthError.fromOAuthError(resp.error, resp.error_description);
1806
+ return await this.errorFn(this.server, event, ce);
1807
+ }
1808
+ return await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
1739
1809
  },
1740
1810
  load: async (event) => {
1741
1811
  if ( /*this.tokenResponseType == "saveInSessionAndRedirect" ||*/this.tokenResponseType == "sendJson" || this.tokenResponseType == "saveInSessionAndLoad") {
@@ -1765,7 +1835,13 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1765
1835
  if (oauthData?.state != state) {
1766
1836
  throw new CrossauthError(ErrorCode.Unauthorized, "State does not match");
1767
1837
  }
1768
- const resp = this.errorIfIdTokenInvalid(await this.redirectEndpoint(code, oauthData?.scope, oauthData?.codeVerifier, error, error_description));
1838
+ const resp = this.errorIfIdTokenInvalid(await this.redirectEndpoint({
1839
+ code,
1840
+ scope: oauthData?.scope,
1841
+ codeVerifier: oauthData?.codeVerifier,
1842
+ error,
1843
+ errorDescription: error_description
1844
+ }));
1769
1845
  if (resp.error)
1770
1846
  return {
1771
1847
  ok: false,
@@ -1919,6 +1995,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1919
1995
  const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use actions not post");
1920
1996
  return this.errorFn(this.server, event, ce);
1921
1997
  }
1998
+ CrossauthLogger.logger.debug(j({ msg: "Refresh token flow started" }));
1922
1999
  let formData = undefined;
1923
2000
  try {
1924
2001
  if (!(this.validFlows.includes(OAuthFlows.RefreshToken))) {
@@ -1988,6 +2065,7 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
1988
2065
  const ce = new CrossauthError(ErrorCode.Configuration, "If tokenResponseType is " + this.tokenResponseType + ", use post not load");
1989
2066
  throw ce;
1990
2067
  }
2068
+ CrossauthLogger.logger.debug(j({ msg: "Refresh token flow started" }));
1991
2069
  let formData = undefined;
1992
2070
  try {
1993
2071
  if (!(this.validFlows.includes(OAuthFlows.RefreshToken))) {
@@ -2033,7 +2111,12 @@ export class SvelteKitOAuthClient extends OAuthClientBackend {
2033
2111
  throw ce;
2034
2112
  }
2035
2113
  const resp = this.errorIfIdTokenInvalid(await this.refreshTokenFlow(refreshToken));
2036
- const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser) ?? {};
2114
+ const resp2 = await this.receiveTokenFn(resp, this, event, false, this.setEventLocalsUser);
2115
+ if (!resp2) {
2116
+ return {
2117
+ ok: true,
2118
+ };
2119
+ }
2037
2120
  if (resp2 instanceof Response)
2038
2121
  throw new CrossauthError(ErrorCode.Configuration, "Refresh token flow should return an object not Response");
2039
2122
  return resp2;
@@ -14,6 +14,7 @@ export interface AuthorizeQueryType {
14
14
  state: string;
15
15
  code_challenge?: string;
16
16
  code_challenge_method?: string;
17
+ next?: string;
17
18
  }
18
19
  export interface ReturnBase {
19
20
  ok: boolean;
@@ -177,6 +178,11 @@ export interface SvelteKitAuthorizationServerOptions extends OAuthAuthorizationS
177
178
  redirect?: any;
178
179
  /** Pass the Sveltekit error function */
179
180
  error?: any;
181
+ /**
182
+ * This is used when there is an upstream client to save the tokens.
183
+ * Default `oauth`.
184
+ */
185
+ sessionDataName?: string;
180
186
  }
181
187
  /**
182
188
  * This class implements an OAuth authorization server, serving endpoints
@@ -263,6 +269,7 @@ export declare class SvelteKitAuthorizationServer {
263
269
  private jwksEndpointUrl;
264
270
  readonly redirect: any;
265
271
  readonly error: any;
272
+ private sessionDataName;
266
273
  /**
267
274
  * Constructor
268
275
  * @param svelteKitServer the SvelteKit server this belongs to
@@ -297,7 +304,10 @@ export declare class SvelteKitAuthorizationServer {
297
304
  private setRefreshTokenCookie;
298
305
  private requireGetParam;
299
306
  private requireBodyParam;
300
- private getAuthorizeQuery;
307
+ getAuthorizeQuery(url: URL): {
308
+ query?: AuthorizeQueryType;
309
+ error: ReturnBase;
310
+ };
301
311
  private getMfaChallengeQuery;
302
312
  private mfaAuthenticators;
303
313
  private mfaChallenge;
@@ -345,6 +355,14 @@ export declare class SvelteKitAuthorizationServer {
345
355
  [key: string]: any;
346
356
  }, sessionDataName: string): Promise<void>;
347
357
  private redirectError;
358
+ /**
359
+ * Called from the client side of an auth server with an upstream auth server
360
+ * @param event Sveltekit event
361
+ * @param url URL the originating client originally made to our auth server's /authorize endpoint
362
+ * @param upstream the label of the upstream client if we have seeral
363
+ * @returns
364
+ */
365
+ saveDownstreamAuthzCodeFlow(event: RequestEvent, url: URL, upstream?: string): Promise<ReturnBase | undefined>;
348
366
  /**
349
367
  * `load` and `actions` functions for the authorize endpoint.
350
368
  *
@@ -366,7 +384,7 @@ export declare class SvelteKitAuthorizationServer {
366
384
  post: (event: RequestEvent) => Promise<Response>;
367
385
  };
368
386
  readonly upstreamRedirectUriEndpoint: {
369
- get: (event: RequestEvent) => Promise<void | Response>;
387
+ get: (event: RequestEvent, upstream?: string) => Promise<void | Response>;
370
388
  };
371
389
  /**
372
390
  * `get` and `post` functions for the mfa/authenticators endpoint.