@inrupt/solid-client-authn-browser 1.17.5 → 2.1.0

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 (49) hide show
  1. package/dist/ClientAuthentication.d.ts +1 -0
  2. package/dist/Session.d.ts +4 -67
  3. package/dist/defaultSession.d.ts +0 -28
  4. package/dist/dependencies.d.ts +1 -2
  5. package/dist/index.d.ts +2 -3
  6. package/dist/index.js +40 -153
  7. package/dist/index.js.map +1 -1
  8. package/dist/index.mjs +34 -143
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/login/oidc/__mocks__/IOidcOptions.d.ts +2 -0
  11. package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +4 -0
  12. package/dist/solid-client-authn.bundle.js +19 -19
  13. package/dist/solid-client-authn.bundle.js.map +1 -1
  14. package/package.json +7 -8
  15. package/rollup.config.mjs +28 -14
  16. package/src/ClientAuthentication.spec.ts +1 -13
  17. package/src/Session.spec.ts +11 -178
  18. package/src/Session.ts +6 -98
  19. package/src/defaultSession.spec.ts +0 -18
  20. package/src/defaultSession.ts +0 -40
  21. package/src/dependencies.ts +1 -2
  22. package/src/index.spec.ts +1 -2
  23. package/src/index.ts +9 -3
  24. package/src/login/oidc/ClientRegistrar.spec.ts +9 -9
  25. package/src/login/oidc/ClientRegistrar.ts +15 -8
  26. package/src/login/oidc/IssuerConfigFetcher.spec.ts +17 -32
  27. package/src/login/oidc/IssuerConfigFetcher.ts +1 -5
  28. package/src/login/oidc/OidcLoginHandler.spec.ts +6 -3
  29. package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.spec.ts +28 -42
  30. package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.ts +18 -34
  31. package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.spec.ts +0 -11
  32. package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.spec.ts +1 -12
  33. package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.spec.ts +1 -0
  34. package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.ts +1 -2
  35. package/.prettierignore +0 -3
  36. package/sampleFlows/SolidAuthClientArchitecture.png +0 -0
  37. package/sampleFlows/authorization-code-with-pkce.png +0 -0
  38. package/sampleFlows/authorization-code-with-pkce.seqdiag +0 -46
  39. package/sampleFlows/authorization-code.png +0 -0
  40. package/sampleFlows/authorization-code.seqdiag +0 -50
  41. package/sampleFlows/client-credentials.png +0 -0
  42. package/sampleFlows/client-credentials.seqdiag +0 -38
  43. package/sampleFlows/legacy-implicit-flow.png +0 -0
  44. package/sampleFlows/legacy-implicit-flow.seqdiag +0 -43
  45. package/sampleFlows/self-signed.png +0 -0
  46. package/sampleFlows/self-signed.seqdiag +0 -49
  47. package/sampleFlows/signatory-access-control.seqdiag +0 -36
  48. package/sampleFlows/signatory-access-control.svg +0 -1
  49. package/sonar-project.properties +0 -15
@@ -24,7 +24,6 @@ import {
24
24
  mockStorageUtility,
25
25
  } from "@inrupt/solid-client-authn-core/mocks";
26
26
  import { jest, it, describe, expect } from "@jest/globals";
27
- import { Response as NodeResponse } from "@inrupt/universal-fetch";
28
27
  import ClientRegistrar from "./ClientRegistrar";
29
28
  import { IssuerConfigFetcherFetchConfigResponse } from "./__mocks__/IssuerConfigFetcher";
30
29
 
@@ -47,14 +46,14 @@ describe("ClientRegistrar", () => {
47
46
  // actual OIDC provider response.
48
47
  const mockFetch = (jest.fn() as any).mockResolvedValueOnce(
49
48
  /* eslint-disable camelcase */
50
- new NodeResponse(
49
+ new Response(
51
50
  JSON.stringify({
52
51
  client_id: "abcd",
53
52
  client_secret: "1234",
54
53
  redirect_uris: ["https://example.com"],
55
54
  id_token_signed_response_alg: "RS256",
56
55
  }),
57
- ) as unknown as Response,
56
+ ),
58
57
  /* eslint-enable camelcase */
59
58
  );
60
59
  global.fetch = mockFetch;
@@ -98,12 +97,12 @@ describe("ClientRegistrar", () => {
98
97
  it("can register a public client without secret", async () => {
99
98
  const mockFetch = (jest.fn() as any).mockResolvedValueOnce(
100
99
  /* eslint-disable camelcase */
101
- new NodeResponse(
100
+ new Response(
102
101
  JSON.stringify({
103
102
  client_id: "abcd",
104
103
  redirect_uris: ["https://example.com"],
105
104
  }),
106
- ) as unknown as Response,
105
+ ),
107
106
  /* eslint-enable camelcase */
108
107
  );
109
108
  global.fetch = mockFetch;
@@ -144,9 +143,9 @@ describe("ClientRegistrar", () => {
144
143
  it("handles a failure to dynamically register elegantly", async () => {
145
144
  const mockFetch = (jest.fn() as any).mockResolvedValueOnce(
146
145
  /* eslint-disable camelcase */
147
- new NodeResponse('{"error":"bad stuff that\'s an error"}', {
146
+ new Response('{"error":"bad stuff that\'s an error"}', {
148
147
  status: 400,
149
- }) as unknown as Response,
148
+ }),
150
149
  /* eslint-enable camelcase */
151
150
  );
152
151
  global.fetch = mockFetch;
@@ -177,6 +176,7 @@ describe("ClientRegistrar", () => {
177
176
  "solidClientAuthenticationUser:mySession": {
178
177
  clientId: "an id",
179
178
  clientSecret: "a secret",
179
+ clientType: "dynamic",
180
180
  },
181
181
  },
182
182
  false,
@@ -198,13 +198,13 @@ describe("ClientRegistrar", () => {
198
198
  it("saves dynamic registration information", async () => {
199
199
  const mockFetch = (jest.fn() as any).mockResolvedValueOnce(
200
200
  /* eslint-disable camelcase */
201
- new NodeResponse(
201
+ new Response(
202
202
  JSON.stringify({
203
203
  client_id: "some id",
204
204
  client_secret: "some secret",
205
205
  redirect_uris: ["https://example.com"],
206
206
  }),
207
- ) as unknown as Response,
207
+ ),
208
208
  /* eslint-enable camelcase */
209
209
  );
210
210
  global.fetch = mockFetch;
@@ -31,6 +31,7 @@ import type {
31
31
  IClient,
32
32
  IClientRegistrarOptions,
33
33
  } from "@inrupt/solid-client-authn-core";
34
+ import { isKnownClientType } from "@inrupt/solid-client-authn-core";
34
35
  import { registerClient } from "@inrupt/oidc-client-ext";
35
36
 
36
37
  /**
@@ -49,7 +50,8 @@ export default class ClientRegistrar implements IClientRegistrar {
49
50
  const [
50
51
  storedClientId,
51
52
  storedClientSecret,
52
- // storedClientName,
53
+ storedClientName,
54
+ storedClientType,
53
55
  ] = await Promise.all([
54
56
  this.storageUtility.getForUser(options.sessionId, "clientId", {
55
57
  secure: false,
@@ -57,17 +59,21 @@ export default class ClientRegistrar implements IClientRegistrar {
57
59
  this.storageUtility.getForUser(options.sessionId, "clientSecret", {
58
60
  secure: false,
59
61
  }),
60
- // this.storageUtility.getForUser(options.sessionId, "clientName", {
61
- // // FIXME: figure out how to persist secure storage at reload
62
- // secure: false,
63
- // }),
62
+ this.storageUtility.getForUser(options.sessionId, "clientName", {
63
+ secure: false,
64
+ }),
65
+ this.storageUtility.getForUser(options.sessionId, "clientType", {
66
+ secure: false,
67
+ }),
64
68
  ]);
65
- if (storedClientId) {
69
+ if (storedClientId && isKnownClientType(storedClientType)) {
66
70
  return {
67
71
  clientId: storedClientId,
68
72
  clientSecret: storedClientSecret,
69
- clientType: "dynamic",
70
- };
73
+ clientName: storedClientName,
74
+ // Note: static clients are not applicable in a browser context.
75
+ clientType: storedClientType,
76
+ } as IClient;
71
77
  }
72
78
 
73
79
  try {
@@ -75,6 +81,7 @@ export default class ClientRegistrar implements IClientRegistrar {
75
81
  // Save info
76
82
  const infoToSave: Record<string, string> = {
77
83
  clientId: registeredClient.clientId,
84
+ clientType: "dynamic",
78
85
  };
79
86
  if (registeredClient.clientSecret) {
80
87
  infoToSave.clientSecret = registeredClient.clientSecret;
@@ -21,24 +21,22 @@
21
21
 
22
22
  import { jest, it, describe, expect } from "@jest/globals";
23
23
  import { mockStorageUtility } from "@inrupt/solid-client-authn-core";
24
- import { Response as NodeResponse } from "@inrupt/universal-fetch";
25
- import type * as UniversalFetch from "@inrupt/universal-fetch";
26
24
  import IssuerConfigFetcher from "./IssuerConfigFetcher";
27
25
 
28
- jest.mock("@inrupt/universal-fetch", () => {
29
- const fetchModule = jest.requireActual(
30
- "@inrupt/universal-fetch",
31
- ) as typeof UniversalFetch;
32
- return {
33
- ...fetchModule,
34
- fetch: jest.fn<(typeof UniversalFetch)["fetch"]>(),
35
- };
36
- });
37
-
38
26
  /**
39
27
  * Test for IssuerConfigFetcher
40
28
  */
41
29
  describe("IssuerConfigFetcher", () => {
30
+ let mockFetch: jest.SpiedFunction<typeof fetch>;
31
+
32
+ beforeEach(() => {
33
+ mockFetch = jest.spyOn(globalThis, "fetch");
34
+ });
35
+
36
+ afterEach(() => {
37
+ jest.restoreAllMocks();
38
+ });
39
+
42
40
  const defaultMocks = {
43
41
  storageUtility: mockStorageUtility({}),
44
42
  };
@@ -55,11 +53,9 @@ describe("IssuerConfigFetcher", () => {
55
53
  const configFetcher = getIssuerConfigFetcher({
56
54
  storageUtility: mockStorageUtility({}),
57
55
  });
58
- const { fetch: mockFetch } = jest.requireMock(
59
- "@inrupt/universal-fetch",
60
- ) as jest.Mocked<typeof UniversalFetch>;
56
+
61
57
  mockFetch.mockResolvedValueOnce(
62
- new NodeResponse(
58
+ new Response(
63
59
  JSON.stringify({
64
60
  issuer: "https://example.com",
65
61
  // eslint-disable-next-line camelcase
@@ -83,10 +79,7 @@ describe("IssuerConfigFetcher", () => {
83
79
  });
84
80
 
85
81
  it("should throw an error if the fetched config could not be converted to JSON", async () => {
86
- const { fetch: mockFetch } = jest.requireMock(
87
- "@inrupt/universal-fetch",
88
- ) as jest.Mocked<typeof UniversalFetch>;
89
- mockFetch.mockResolvedValueOnce(new NodeResponse("Not JSON."));
82
+ mockFetch.mockResolvedValueOnce(new Response("Not JSON."));
90
83
  const configFetcher = new IssuerConfigFetcher(mockStorageUtility({}));
91
84
 
92
85
  await expect(configFetcher.fetchConfig("https://some.url")).rejects.toThrow(
@@ -95,11 +88,8 @@ describe("IssuerConfigFetcher", () => {
95
88
  });
96
89
 
97
90
  it("should return a config including the support for solid-oidc if present in the discovery profile", async () => {
98
- const { fetch: mockFetch } = jest.requireMock(
99
- "@inrupt/universal-fetch",
100
- ) as jest.Mocked<typeof UniversalFetch>;
101
91
  mockFetch.mockResolvedValueOnce(
102
- new NodeResponse(
92
+ new Response(
103
93
  JSON.stringify({
104
94
  issuer: "https://example.com",
105
95
  // eslint-disable-next-line camelcase
@@ -120,11 +110,8 @@ describe("IssuerConfigFetcher", () => {
120
110
  });
121
111
 
122
112
  it("should return a default value for the supported scopes if not advertized by the OpenID provider", async () => {
123
- const { fetch: mockFetch } = jest.requireMock(
124
- "@inrupt/universal-fetch",
125
- ) as jest.Mocked<typeof UniversalFetch>;
126
113
  mockFetch.mockResolvedValueOnce(
127
- new NodeResponse(
114
+ new Response(
128
115
  JSON.stringify({
129
116
  issuer: "https://example.com",
130
117
  // eslint-disable-next-line camelcase
@@ -143,12 +130,10 @@ describe("IssuerConfigFetcher", () => {
143
130
 
144
131
  it("should append the .well-known/openid-configuration path at the end of the issuer URL", async () => {
145
132
  // The response value is irrelevant to this test.
146
- const { fetch: mockFetch } = jest.requireMock(
147
- "@inrupt/universal-fetch",
148
- ) as jest.Mocked<typeof UniversalFetch>;
133
+
149
134
  mockFetch.mockImplementation(
150
135
  async () =>
151
- new NodeResponse(
136
+ new Response(
152
137
  JSON.stringify({
153
138
  issuer: "https://example.com",
154
139
  // eslint-disable-next-line camelcase
@@ -33,7 +33,6 @@ import type {
33
33
  IStorageUtility,
34
34
  } from "@inrupt/solid-client-authn-core";
35
35
  import { ConfigurationError } from "@inrupt/solid-client-authn-core";
36
- import { fetch as uniFetch } from "@inrupt/universal-fetch";
37
36
 
38
37
  export const WELL_KNOWN_OPENID_CONFIG = ".well-known/openid-configuration";
39
38
 
@@ -172,10 +171,7 @@ export default class IssuerConfigFetcher implements IIssuerConfigFetcher {
172
171
  // includes the full issuer path. See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig.
173
172
  issuer.endsWith("/") ? issuer : `${issuer}/`,
174
173
  ).href;
175
- const issuerConfigRequestBody = await uniFetch.call(
176
- globalThis,
177
- openIdConfigUrl,
178
- );
174
+ const issuerConfigRequestBody = await fetch(openIdConfigUrl);
179
175
  // Check the validity of the fetched config
180
176
  try {
181
177
  issuerConfig = processConfig(await issuerConfigRequestBody.json());
@@ -77,9 +77,10 @@ describe("OidcLoginHandler", () => {
77
77
  mockStorage({}),
78
78
  mockStorage({
79
79
  "solidClientAuthenticationUser:mySession": {
80
- // The value of the client ID doesn't matter here, and it could be a WebID.
80
+ // The value of the client ID doesn't matter.
81
81
  // This checks it gets passed through from storage to the handler.
82
- clientId: "some client ID",
82
+ clientId: "https://example.org/some-client-id",
83
+ clientType: "solid-oidc",
83
84
  },
84
85
  }),
85
86
  );
@@ -99,7 +100,9 @@ describe("OidcLoginHandler", () => {
99
100
  expect(actualHandler.handle.mock.calls).toHaveLength(1);
100
101
 
101
102
  const calledWith = actualHandler.handle.mock.calls[0][0];
102
- expect(calledWith.client.clientId).toBe("some client ID");
103
+ expect(calledWith.client.clientId).toBe(
104
+ "https://example.org/some-client-id",
105
+ );
103
106
  });
104
107
 
105
108
  it("should lookup client ID if not provided, if not found do DCR", async () => {
@@ -33,10 +33,7 @@ import {
33
33
  mockStorageUtility,
34
34
  } from "@inrupt/solid-client-authn-core/mocks";
35
35
  import { jest, it, describe, expect } from "@jest/globals";
36
- import { getBearerToken } from "@inrupt/oidc-client-ext";
37
36
  import type * as OidcClientExt from "@inrupt/oidc-client-ext";
38
- import { fetch, Response, Headers } from "@inrupt/universal-fetch";
39
- import type * as UniversalFetch from "@inrupt/universal-fetch";
40
37
  import type { JWK } from "jose";
41
38
  import { importJWK } from "jose";
42
39
  import type { KeyObject } from "crypto";
@@ -48,15 +45,7 @@ import {
48
45
  mockTokenRefresher,
49
46
  } from "../refresh/__mocks__/TokenRefresher";
50
47
 
51
- jest.mock("@inrupt/universal-fetch", () => {
52
- const fetchModule = jest.requireActual(
53
- "@inrupt/universal-fetch",
54
- ) as typeof UniversalFetch;
55
- return {
56
- ...fetchModule,
57
- fetch: jest.fn<typeof fetch>(),
58
- };
59
- });
48
+ const mockedFetch = jest.spyOn(globalThis, "fetch");
60
49
 
61
50
  const mockJwk = (): JWK => {
62
51
  return {
@@ -160,16 +149,22 @@ jest.mock("@inrupt/oidc-client-ext");
160
149
 
161
150
  jest.useFakeTimers();
162
151
 
163
- function mockOidcClient(): typeof OidcClientExt {
152
+ function mockOidcClient(
153
+ tokenType: "dpop" | "bearer" = "dpop",
154
+ ): typeof OidcClientExt {
164
155
  const mockedOidcClient = jest.requireMock<typeof OidcClientExt>(
165
156
  "@inrupt/oidc-client-ext",
166
157
  );
167
- mockedOidcClient.getDpopToken = jest
168
- .fn<typeof mockedOidcClient.getDpopToken>()
169
- .mockImplementationOnce(mockTokenEndpointDpopResponse);
170
- mockedOidcClient.getBearerToken = jest
171
- .fn<typeof mockedOidcClient.getBearerToken>()
172
- .mockResolvedValue(mockTokenEndpointBearerResponse());
158
+ mockedOidcClient.getTokens = jest
159
+ .fn<typeof mockedOidcClient.getTokens>()
160
+ .mockImplementationOnce(
161
+ tokenType === "dpop"
162
+ ? mockTokenEndpointDpopResponse
163
+ : () =>
164
+ new Promise<OidcClientExt.CodeExchangeResult>((resolve) => {
165
+ resolve(mockTokenEndpointBearerResponse());
166
+ }),
167
+ );
173
168
  mockedOidcClient.refresh = jest
174
169
  .fn<typeof mockedOidcClient.refresh>()
175
170
  .mockResolvedValueOnce({
@@ -202,9 +197,6 @@ function mockClientRegistrar(client: IClient): IClientRegistrar {
202
197
  }
203
198
 
204
199
  const mockFetch = (response: Response) => {
205
- const { fetch: mockedFetch } = jest.requireMock(
206
- "@inrupt/universal-fetch",
207
- ) as jest.Mocked<typeof UniversalFetch>;
208
200
  mockedFetch.mockResolvedValueOnce(response);
209
201
  return mockedFetch;
210
202
  };
@@ -331,13 +323,14 @@ describe("AuthCodeRedirectHandler", () => {
331
323
 
332
324
  await authCodeRedirectHandler.handle(mockRedirectUrl());
333
325
 
334
- expect(mockedOidcClient.getDpopToken).toHaveBeenCalledWith(
326
+ expect(mockedOidcClient.getTokens).toHaveBeenCalledWith(
335
327
  expect.anything(),
336
328
  expect.anything(),
337
329
  expect.objectContaining({
338
330
  codeVerifier: DEFAULT_CODE_VERIFIER,
339
331
  redirectUrl: DEFAULT_REDIRECT_URL,
340
332
  }),
333
+ expect.anything(),
341
334
  );
342
335
  });
343
336
 
@@ -415,8 +408,7 @@ describe("AuthCodeRedirectHandler", () => {
415
408
  });
416
409
 
417
410
  it("returns an authenticated bearer fetch if requested", async () => {
418
- mockOidcClient();
419
- const mockedFetch = jest.mocked<typeof fetch>(fetch);
411
+ mockOidcClient("bearer");
420
412
  mockedFetch.mockResolvedValueOnce(
421
413
  new Response("", {
422
414
  status: 200,
@@ -427,9 +419,8 @@ describe("AuthCodeRedirectHandler", () => {
427
419
  storageUtility: mockDefaultStorageUtility({ dpop: false }),
428
420
  });
429
421
 
430
- const redirectInfo = await authCodeRedirectHandler.handle(
431
- mockRedirectUrl(),
432
- );
422
+ const redirectInfo =
423
+ await authCodeRedirectHandler.handle(mockRedirectUrl());
433
424
 
434
425
  // This will call the oidc-client-ext module, which is mocked at
435
426
  // the top of this file. The purpose of this test is to check that, if
@@ -444,7 +435,6 @@ describe("AuthCodeRedirectHandler", () => {
444
435
 
445
436
  it("returns an authenticated DPoP fetch if requested", async () => {
446
437
  mockOidcClient();
447
- const mockedFetch = jest.mocked<typeof fetch>(fetch);
448
438
  mockedFetch.mockResolvedValueOnce(
449
439
  new Response("", {
450
440
  status: 200,
@@ -454,9 +444,8 @@ describe("AuthCodeRedirectHandler", () => {
454
444
  const authCodeRedirectHandler = getAuthCodeRedirectHandler({
455
445
  storageUtility: mockDefaultStorageUtility({ dpop: true }),
456
446
  });
457
- const redirectInfo = await authCodeRedirectHandler.handle(
458
- mockRedirectUrl(),
459
- );
447
+ const redirectInfo =
448
+ await authCodeRedirectHandler.handle(mockRedirectUrl());
460
449
  await redirectInfo.fetch("https://some.other.url");
461
450
 
462
451
  const header = new Headers(mockedFetch.mock.calls[0][1]?.headers);
@@ -525,9 +514,8 @@ describe("AuthCodeRedirectHandler", () => {
525
514
  storageUtility: mockDefaultStorageUtility(),
526
515
  });
527
516
 
528
- const sessionInfo = await authCodeRedirectHandler.handle(
529
- mockRedirectUrl(),
530
- );
517
+ const sessionInfo =
518
+ await authCodeRedirectHandler.handle(mockRedirectUrl());
531
519
 
532
520
  expect(sessionInfo.expirationDate).toBe(
533
521
  MOCK_TIMESTAMP + MOCK_EXPIRE_TIME * 1000,
@@ -535,10 +523,10 @@ describe("AuthCodeRedirectHandler", () => {
535
523
  });
536
524
 
537
525
  it("returns null for the expiration time if none was provided", async () => {
538
- const mockedOidcClient = mockOidcClient();
526
+ const mockedOidcClient = mockOidcClient("bearer");
539
527
  mockFetch(new Response());
540
- mockedOidcClient.getBearerToken = jest
541
- .fn(getBearerToken)
528
+ mockedOidcClient.getTokens = jest
529
+ .fn<(typeof OidcClientExt)["getTokens"]>()
542
530
  .mockResolvedValueOnce({
543
531
  accessToken: mockAccessTokenBearer(),
544
532
  idToken: mockIdToken(),
@@ -552,9 +540,8 @@ describe("AuthCodeRedirectHandler", () => {
552
540
  storageUtility: mockedStorage,
553
541
  });
554
542
 
555
- const sessionInfo = await authCodeRedirectHandler.handle(
556
- mockRedirectUrl(),
557
- );
543
+ const sessionInfo =
544
+ await authCodeRedirectHandler.handle(mockRedirectUrl());
558
545
 
559
546
  expect(sessionInfo.expirationDate).toBeUndefined();
560
547
  });
@@ -598,7 +585,6 @@ describe("AuthCodeRedirectHandler", () => {
598
585
  await authCodeRedirectHandler.handle(mockRedirectUrl());
599
586
 
600
587
  expect(mockAuthenticatedFetchBuild).toHaveBeenCalledWith(
601
- expect.anything(),
602
588
  expect.anything(),
603
589
  expect.objectContaining({
604
590
  refreshOptions: {
@@ -23,12 +23,6 @@
23
23
  * @hidden
24
24
  * @packageDocumentation
25
25
  */
26
- // FIXME: The following doesn't work in the browser, it results in all the fetches
27
- // being unauthenticated. This should be looked into when migrating to universal-fetch.
28
- // import { fetch } from "cross-fetch";
29
-
30
- import { fetch as uniFetch } from "@inrupt/universal-fetch";
31
-
32
26
  import type {
33
27
  IClient,
34
28
  IClientRegistrar,
@@ -45,13 +39,9 @@ import {
45
39
  loadOidcContextFromStorage,
46
40
  maybeBuildRpInitiatedLogout,
47
41
  } from "@inrupt/solid-client-authn-core";
48
- import type { CodeExchangeResult } from "@inrupt/oidc-client-ext";
49
- import { getDpopToken, getBearerToken } from "@inrupt/oidc-client-ext";
42
+ import { getTokens } from "@inrupt/oidc-client-ext";
50
43
  import type { EventEmitter } from "events";
51
44
 
52
- const globalFetch: typeof uniFetch = (...args) =>
53
- uniFetch.call(globalThis, ...args);
54
-
55
45
  /**
56
46
  * @hidden
57
47
  */
@@ -141,26 +131,24 @@ export class AuthCodeRedirectHandler implements IIncomingRedirectHandler {
141
131
  issuerConfig,
142
132
  );
143
133
 
144
- let tokens: CodeExchangeResult;
145
134
  const tokenCreatedAt = Date.now();
146
-
147
- if (isDpop) {
148
- tokens = await getDpopToken(issuerConfig, client, {
135
+ const tokens = await getTokens(
136
+ issuerConfig,
137
+ client,
138
+ {
149
139
  grantType: "authorization_code",
150
140
  // We rely on our 'canHandle' function checking that the OAuth 'code'
151
141
  // parameter is present in our query string.
152
142
  code: url.searchParams.get("code") as string,
153
143
  codeVerifier,
154
144
  redirectUrl: storedRedirectIri,
155
- });
156
-
157
- // Delete oidc-client-specific session information from storage. This is
158
- // done automatically when retrieving a bearer token, but since the DPoP
159
- // binding uses our custom code, this needs to be done manually.
160
- window.localStorage.removeItem(`oidc.${oauthState}`);
161
- } else {
162
- tokens = await getBearerToken(url.toString());
163
- }
145
+ },
146
+ isDpop,
147
+ );
148
+
149
+ // Delete oidc-client-specific session information from storage. oidc-client
150
+ // is no longer used for the token exchange, so it doesn't perform this automatically.
151
+ window.localStorage.removeItem(`oidc.${oauthState}`);
164
152
 
165
153
  let refreshOptions: RefreshOptions | undefined;
166
154
  if (tokens.refreshToken !== undefined) {
@@ -171,16 +159,12 @@ export class AuthCodeRedirectHandler implements IIncomingRedirectHandler {
171
159
  };
172
160
  }
173
161
 
174
- const authFetch = await buildAuthenticatedFetch(
175
- globalFetch,
176
- tokens.accessToken,
177
- {
178
- dpopKey: tokens.dpopKey,
179
- refreshOptions,
180
- eventEmitter,
181
- expiresIn: tokens.expiresIn,
182
- },
183
- );
162
+ const authFetch = await buildAuthenticatedFetch(tokens.accessToken, {
163
+ dpopKey: tokens.dpopKey,
164
+ refreshOptions,
165
+ eventEmitter,
166
+ expiresIn: tokens.expiresIn,
167
+ });
184
168
 
185
169
  await this.storageUtility.setForUser(
186
170
  storedSessionId,
@@ -22,19 +22,8 @@
22
22
  import { jest, it, describe, expect } from "@jest/globals";
23
23
  import { EventEmitter } from "events";
24
24
  import { EVENTS } from "@inrupt/solid-client-authn-core";
25
- import type * as UniversalFetch from "@inrupt/universal-fetch";
26
25
  import { ErrorOidcHandler } from "./ErrorOidcHandler";
27
26
 
28
- jest.mock("@inrupt/universal-fetch", () => {
29
- const fetchModule = jest.requireActual(
30
- "@inrupt/universal-fetch",
31
- ) as typeof UniversalFetch;
32
- return {
33
- ...fetchModule,
34
- fetch: jest.fn<(typeof UniversalFetch)["fetch"]>(),
35
- };
36
- });
37
-
38
27
  describe("ErrorOidcHandler", () => {
39
28
  describe("canHandle", () => {
40
29
  it("Does not handle IRIs without an error parameter", async () => {
@@ -19,20 +19,9 @@
19
19
  // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
20
  //
21
21
 
22
- import { jest, it, describe, expect } from "@jest/globals";
23
- import type * as UniversalFetch from "@inrupt/universal-fetch";
22
+ import { it, describe, expect } from "@jest/globals";
24
23
  import { FallbackRedirectHandler } from "./FallbackRedirectHandler";
25
24
 
26
- jest.mock("@inrupt/universal-fetch", () => {
27
- const fetchModule = jest.requireActual(
28
- "@inrupt/universal-fetch",
29
- ) as typeof UniversalFetch;
30
- return {
31
- ...fetchModule,
32
- fetch: jest.fn<(typeof UniversalFetch)["fetch"]>(),
33
- };
34
- });
35
-
36
25
  describe("FallbackRedirectHandler", () => {
37
26
  describe("canHandle", () => {
38
27
  it("always accept the given IRI", async () => {
@@ -262,6 +262,7 @@ describe("AuthorizationCodeWithPkceOidcHandler", () => {
262
262
  ...standardOidcOptions,
263
263
  client: {
264
264
  ...standardOidcOptions.client,
265
+ clientType: "dynamic",
265
266
  clientSecret: "I can't cook because I only drink Soylent",
266
267
  },
267
268
  issuerConfiguration: {
@@ -53,8 +53,7 @@ export default class AuthorizationCodeWithPkceOidcHandler
53
53
  authority: oidcLoginOptions.issuer.toString(),
54
54
  client_id: oidcLoginOptions.client.clientId,
55
55
  client_secret: oidcLoginOptions.client.clientSecret,
56
- redirect_uri: oidcLoginOptions.redirectUrl.toString(),
57
- post_logout_redirect_uri: oidcLoginOptions.redirectUrl.toString(),
56
+ redirect_uri: oidcLoginOptions.redirectUrl,
58
57
  response_type: "code",
59
58
  scope: DEFAULT_SCOPES,
60
59
  filterProtocolClaims: true,
package/.prettierignore DELETED
@@ -1,3 +0,0 @@
1
- # TODO: figure out why the root .prettierignore file isn't detected:
2
- docs/**/*.md
3
- examples/**/*.md
@@ -1,46 +0,0 @@
1
- title: Authorization Code Flow with PKCE
2
-
3
- note left of RP: AUTHORIZATION
4
- note over RP: 1.Alice uses the decentphotos app
5
- note over RP: 2. Alice selets her OP or WebID
6
- RP->Alice's Pod (RS): 2.1 Retrieve Profile
7
- Alice's Pod (RS)->RP:
8
- RP->Alice's OP: 3. Requests OP configuration
9
- Alice's OP->RP: 4. Returns OP configuration
10
- RP->Alice's OP: 5. Requests OP JWKS
11
- Alice's OP->RP: 6. Returns OP JWKS
12
- note over RP: 7. Generates Private/Public key pair
13
- note over RP: 8. Generates PKCE nonce
14
- note over RP: 9. Saves both to local storage
15
- RP->Alice's OP: 10. Authorization Request
16
- Alice's OP->Decentphotos WebID: 11. Retrieves WebID
17
- Decentphotos WebID->Alice's OP:
18
- note over Alice's OP: 11. Validates redirect protocol with WebID
19
- note over Alice's OP: 12. Gets Alice's consent
20
- note over Alice's OP: 13. Generates an id_vc
21
- Alice's OP->RP: 12. Sends code to redirect protocol
22
- RP->Alice's OP: 13. Token requst with code and nonce proof
23
- note over Alice's OP: 14. Validates nonce proof
24
- Alice's OP->RP: 15. Sends id_vc inside id_token
25
-
26
-
27
- note left of RP: SENDING REQUEST
28
- note over RP: 1. Creates a pop_token
29
- RP->Bob's Pod (RS): 2. Request sent
30
- note over Bob's Pod (RS): 3. Checks pop_token Audience
31
- note over Bob's Pod (RS): 4. Checks client signature
32
- Bob's Pod (RS)->Alice's Pod (RS): 5. Retrieves Profile
33
- Alice's Pod (RS)->Bob's Pod (RS):
34
- note over Bob's Pod (RS): 6. Checks Issuer
35
- Bob's Pod (RS)->Alice's OP: 7. Retrieves OP configuration
36
- Alice's OP->Bob's Pod (RS):
37
- Bob's Pod (RS)->Alice's OP: 8. Requests JWKS
38
- Alice's OP->Bob's Pod (RS):
39
- note over Bob's Pod (RS): 9. Performs Authentication
40
- note over Bob's Pod (RS): 10. Performs Authorization
41
- Bob's Pod (RS)->RP: 11. Returns Result
42
-
43
- note left of RP: REFRESH TOKEN
44
- Bob's Pod (RS)->RP: 1. 401: token expired
45
- RP->Alice's OP: 2. Refresh token auth request
46
- Alice's OP->RP: 3. Return updated token
Binary file