@inrupt/solid-client-authn-browser 2.0.0 → 2.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inrupt/solid-client-authn-browser",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "license": "MIT",
5
5
  "types": "dist/index.d.ts",
6
6
  "browser": "dist/index.js",
@@ -25,12 +25,12 @@
25
25
  "build-docs-preview-site": "npm run build-api-docs; cd docs/api; make html"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^20.10.3",
29
- "@types/uuid": "^9.0.4"
28
+ "@types/node": "^20.12.7",
29
+ "@types/uuid": "^9.0.8"
30
30
  },
31
31
  "dependencies": {
32
- "@inrupt/oidc-client-ext": "^2.0.0",
33
- "@inrupt/solid-client-authn-core": "^2.0.0",
32
+ "@inrupt/oidc-client-ext": "^2.2.0",
33
+ "@inrupt/solid-client-authn-core": "^2.2.0",
34
34
  "events": "^3.3.0",
35
35
  "jose": "^5.1.3",
36
36
  "uuid": "^9.0.1"
@@ -38,5 +38,5 @@
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "gitHead": "80d9c4b81829762cabbd5ffc1564636efe45000c"
41
+ "gitHead": "8c3bd6871831c4fa14b593021bbb830cf68e332f"
42
42
  }
@@ -53,7 +53,6 @@ jest.mock("@inrupt/solid-client-authn-core", () => {
53
53
  ) as typeof SolidClientAuthnCore;
54
54
  return {
55
55
  ...actualCoreModule,
56
- fetchJwks: jest.fn(),
57
56
  };
58
57
  });
59
58
 
@@ -425,6 +424,7 @@ describe("ClientAuthentication", () => {
425
424
  expect(defaultMocks.redirectHandler.handle).toHaveBeenCalledWith(
426
425
  url,
427
426
  mockEmitter,
427
+ undefined,
428
428
  );
429
429
 
430
430
  // Calling the redirect handler should have updated the fetch.
@@ -101,7 +101,11 @@ export default class ClientAuthentication extends ClientAuthenticationBase {
101
101
  eventEmitter: EventEmitter,
102
102
  ): Promise<ISessionInfo | undefined> => {
103
103
  try {
104
- const redirectInfo = await this.redirectHandler.handle(url, eventEmitter);
104
+ const redirectInfo = await this.redirectHandler.handle(
105
+ url,
106
+ eventEmitter,
107
+ undefined,
108
+ );
105
109
  // The `FallbackRedirectHandler` directly returns the global `fetch` for
106
110
  // his value, so we should ensure it's bound to `window` rather than to
107
111
  // ClientAuthentication, to avoid the following error:
package/src/Session.ts CHANGED
@@ -115,7 +115,7 @@ function isLoggedIn(
115
115
  }
116
116
 
117
117
  /**
118
- * A {@link Session} object represents a user's session on an application. The session holds state, as it stores information enabling acces to private resources after login for instance.
118
+ * A {@link Session} object represents a user's session on an application. The session holds state, as it stores information enabling access to private resources after login for instance.
119
119
  */
120
120
  export class Session implements IHasSessionEventListener {
121
121
  /**
@@ -176,6 +176,7 @@ describe("ClientRegistrar", () => {
176
176
  "solidClientAuthenticationUser:mySession": {
177
177
  clientId: "an id",
178
178
  clientSecret: "a secret",
179
+ clientType: "dynamic",
179
180
  },
180
181
  },
181
182
  false,
@@ -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;
@@ -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,7 +33,6 @@ 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
37
  import type { JWK } from "jose";
39
38
  import { importJWK } from "jose";
@@ -150,16 +149,22 @@ jest.mock("@inrupt/oidc-client-ext");
150
149
 
151
150
  jest.useFakeTimers();
152
151
 
153
- function mockOidcClient(): typeof OidcClientExt {
152
+ function mockOidcClient(
153
+ tokenType: "dpop" | "bearer" = "dpop",
154
+ ): typeof OidcClientExt {
154
155
  const mockedOidcClient = jest.requireMock<typeof OidcClientExt>(
155
156
  "@inrupt/oidc-client-ext",
156
157
  );
157
- mockedOidcClient.getDpopToken = jest
158
- .fn<typeof mockedOidcClient.getDpopToken>()
159
- .mockImplementationOnce(mockTokenEndpointDpopResponse);
160
- mockedOidcClient.getBearerToken = jest
161
- .fn<typeof mockedOidcClient.getBearerToken>()
162
- .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
+ );
163
168
  mockedOidcClient.refresh = jest
164
169
  .fn<typeof mockedOidcClient.refresh>()
165
170
  .mockResolvedValueOnce({
@@ -318,13 +323,14 @@ describe("AuthCodeRedirectHandler", () => {
318
323
 
319
324
  await authCodeRedirectHandler.handle(mockRedirectUrl());
320
325
 
321
- expect(mockedOidcClient.getDpopToken).toHaveBeenCalledWith(
326
+ expect(mockedOidcClient.getTokens).toHaveBeenCalledWith(
322
327
  expect.anything(),
323
328
  expect.anything(),
324
329
  expect.objectContaining({
325
330
  codeVerifier: DEFAULT_CODE_VERIFIER,
326
331
  redirectUrl: DEFAULT_REDIRECT_URL,
327
332
  }),
333
+ expect.anything(),
328
334
  );
329
335
  });
330
336
 
@@ -402,7 +408,7 @@ describe("AuthCodeRedirectHandler", () => {
402
408
  });
403
409
 
404
410
  it("returns an authenticated bearer fetch if requested", async () => {
405
- mockOidcClient();
411
+ mockOidcClient("bearer");
406
412
  mockedFetch.mockResolvedValueOnce(
407
413
  new Response("", {
408
414
  status: 200,
@@ -517,10 +523,10 @@ describe("AuthCodeRedirectHandler", () => {
517
523
  });
518
524
 
519
525
  it("returns null for the expiration time if none was provided", async () => {
520
- const mockedOidcClient = mockOidcClient();
526
+ const mockedOidcClient = mockOidcClient("bearer");
521
527
  mockFetch(new Response());
522
- mockedOidcClient.getBearerToken = jest
523
- .fn(getBearerToken)
528
+ mockedOidcClient.getTokens = jest
529
+ .fn<(typeof OidcClientExt)["getTokens"]>()
524
530
  .mockResolvedValueOnce({
525
531
  accessToken: mockAccessTokenBearer(),
526
532
  idToken: mockIdToken(),
@@ -39,8 +39,7 @@ import {
39
39
  loadOidcContextFromStorage,
40
40
  maybeBuildRpInitiatedLogout,
41
41
  } from "@inrupt/solid-client-authn-core";
42
- import type { CodeExchangeResult } from "@inrupt/oidc-client-ext";
43
- import { getDpopToken, getBearerToken } from "@inrupt/oidc-client-ext";
42
+ import { getTokens } from "@inrupt/oidc-client-ext";
44
43
  import type { EventEmitter } from "events";
45
44
 
46
45
  /**
@@ -132,26 +131,24 @@ export class AuthCodeRedirectHandler implements IIncomingRedirectHandler {
132
131
  issuerConfig,
133
132
  );
134
133
 
135
- let tokens: CodeExchangeResult;
136
134
  const tokenCreatedAt = Date.now();
137
-
138
- if (isDpop) {
139
- tokens = await getDpopToken(issuerConfig, client, {
135
+ const tokens = await getTokens(
136
+ issuerConfig,
137
+ client,
138
+ {
140
139
  grantType: "authorization_code",
141
140
  // We rely on our 'canHandle' function checking that the OAuth 'code'
142
141
  // parameter is present in our query string.
143
142
  code: url.searchParams.get("code") as string,
144
143
  codeVerifier,
145
144
  redirectUrl: storedRedirectIri,
146
- });
147
-
148
- // Delete oidc-client-specific session information from storage. This is
149
- // done automatically when retrieving a bearer token, but since the DPoP
150
- // binding uses our custom code, this needs to be done manually.
151
- window.localStorage.removeItem(`oidc.${oauthState}`);
152
- } else {
153
- tokens = await getBearerToken(url.toString());
154
- }
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}`);
155
152
 
156
153
  let refreshOptions: RefreshOptions | undefined;
157
154
  if (tokens.refreshToken !== undefined) {
@@ -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,