@inrupt/solid-client-authn-browser 1.17.1 → 1.17.3
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/dist/ClientAuthentication.d.ts +7 -0
- package/dist/Session.d.ts +174 -0
- package/dist/constant.d.ts +2 -2
- package/dist/defaultSession.d.ts +70 -0
- package/dist/dependencies.d.ts +12 -0
- package/dist/index.js +833 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +833 -34
- package/dist/index.mjs.map +1 -1
- package/dist/login/oidc/AggregateOidcHandler.d.ts +10 -0
- package/dist/login/oidc/AggregateRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/ClientRegistrar.d.ts +7 -0
- package/dist/login/oidc/IssuerConfigFetcher.d.ts +10 -0
- package/dist/login/oidc/OidcLoginHandler.d.ts +10 -0
- package/dist/login/oidc/Redirector.d.ts +7 -0
- package/dist/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.d.ts +3 -0
- package/dist/login/oidc/incomingRedirectHandler/ErrorOidcHandler.d.ts +10 -0
- package/dist/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.d.ts +10 -0
- package/dist/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.d.ts +12 -0
- package/dist/login/oidc/refresh/TokenRefresher.d.ts +7 -0
- package/dist/logout/buildRpInitiatedLogout.d.ts +7 -0
- package/dist/sessionInfo/SessionInfoManager.d.ts +18 -0
- package/dist/solid-client-authn.bundle.js +7 -7
- package/dist/solid-client-authn.bundle.js.map +1 -1
- package/dist/storage/BrowserStorage.d.ts +7 -0
- package/dist/storage/StorageUtility.d.ts +14 -0
- package/dist/util/UuidGenerator.d.ts +6 -0
- package/package.json +8 -8
- package/rollup.config.mjs +1 -1
- package/src/ClientAuthentication.spec.ts +106 -57
- package/src/ClientAuthentication.ts +18 -7
- package/src/Session.spec.ts +51 -54
- package/src/Session.ts +24 -18
- package/src/__mocks__/ClientAuthentication.ts +2 -2
- package/src/defaultSession.ts +4 -3
- package/src/dependencies.ts +5 -5
- package/src/login/oidc/ClientRegistrar.spec.ts +22 -22
- package/src/login/oidc/ClientRegistrar.ts +4 -2
- package/src/login/oidc/IssuerConfigFetcher.spec.ts +25 -25
- package/src/login/oidc/IssuerConfigFetcher.ts +8 -6
- package/src/login/oidc/OidcLoginHandler.spec.ts +15 -15
- package/src/login/oidc/OidcLoginHandler.ts +14 -9
- package/src/login/oidc/Redirector.spec.ts +1 -1
- package/src/login/oidc/__mocks__/ClientRegistrar.ts +3 -3
- package/src/login/oidc/__mocks__/IssuerConfigFetcher.ts +3 -3
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.spec.ts +42 -42
- package/src/login/oidc/incomingRedirectHandler/AuthCodeRedirectHandler.ts +19 -13
- package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.spec.ts +12 -12
- package/src/login/oidc/incomingRedirectHandler/ErrorOidcHandler.ts +2 -2
- package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.spec.ts +7 -7
- package/src/login/oidc/incomingRedirectHandler/FallbackRedirectHandler.ts +2 -2
- package/src/login/oidc/oidcHandlers/AuthorizationCodeWithPkceOidcHandler.spec.ts +17 -17
- package/src/login/oidc/refresh/TokenRefresher.spec.ts +14 -14
- package/src/login/oidc/refresh/TokenRefresher.ts +12 -8
- package/src/login/oidc/refresh/__mocks__/TokenRefresher.ts +1 -1
- package/src/sessionInfo/SessionInfoManager.spec.ts +37 -15
- package/src/sessionInfo/SessionInfoManager.ts +46 -50
- package/src/sessionInfo/__mocks__/SessionInfoManager.ts +3 -3
|
@@ -36,7 +36,7 @@ describe("ClientRegistrar", () => {
|
|
|
36
36
|
storage: StorageUtilityMock,
|
|
37
37
|
};
|
|
38
38
|
function getClientRegistrar(
|
|
39
|
-
mocks: Partial<typeof defaultMocks> = defaultMocks
|
|
39
|
+
mocks: Partial<typeof defaultMocks> = defaultMocks,
|
|
40
40
|
): ClientRegistrar {
|
|
41
41
|
return new ClientRegistrar(mocks.storage ?? defaultMocks.storage);
|
|
42
42
|
}
|
|
@@ -53,8 +53,8 @@ describe("ClientRegistrar", () => {
|
|
|
53
53
|
client_secret: "1234",
|
|
54
54
|
redirect_uris: ["https://example.com"],
|
|
55
55
|
id_token_signed_response_alg: "RS256",
|
|
56
|
-
})
|
|
57
|
-
) as unknown as Response
|
|
56
|
+
}),
|
|
57
|
+
) as unknown as Response,
|
|
58
58
|
/* eslint-enable camelcase */
|
|
59
59
|
);
|
|
60
60
|
global.fetch = mockFetch;
|
|
@@ -71,8 +71,8 @@ describe("ClientRegistrar", () => {
|
|
|
71
71
|
{
|
|
72
72
|
...IssuerConfigFetcherFetchConfigResponse,
|
|
73
73
|
registrationEndpoint: registrationUrl,
|
|
74
|
-
}
|
|
75
|
-
)
|
|
74
|
+
},
|
|
75
|
+
),
|
|
76
76
|
).toMatchObject({
|
|
77
77
|
clientId: "abcd",
|
|
78
78
|
clientSecret: "1234",
|
|
@@ -102,8 +102,8 @@ describe("ClientRegistrar", () => {
|
|
|
102
102
|
JSON.stringify({
|
|
103
103
|
client_id: "abcd",
|
|
104
104
|
redirect_uris: ["https://example.com"],
|
|
105
|
-
})
|
|
106
|
-
) as unknown as Response
|
|
105
|
+
}),
|
|
106
|
+
) as unknown as Response,
|
|
107
107
|
/* eslint-enable camelcase */
|
|
108
108
|
);
|
|
109
109
|
global.fetch = mockFetch;
|
|
@@ -119,7 +119,7 @@ describe("ClientRegistrar", () => {
|
|
|
119
119
|
{
|
|
120
120
|
...IssuerConfigFetcherFetchConfigResponse,
|
|
121
121
|
registrationEndpoint: registrationUrl,
|
|
122
|
-
}
|
|
122
|
+
},
|
|
123
123
|
);
|
|
124
124
|
expect(registeredClient.clientSecret).toBeUndefined();
|
|
125
125
|
});
|
|
@@ -134,10 +134,10 @@ describe("ClientRegistrar", () => {
|
|
|
134
134
|
sessionId: "mySession",
|
|
135
135
|
redirectUrl: "https://example.com",
|
|
136
136
|
},
|
|
137
|
-
IssuerConfigFetcherFetchConfigResponse
|
|
138
|
-
)
|
|
137
|
+
IssuerConfigFetcherFetchConfigResponse,
|
|
138
|
+
),
|
|
139
139
|
).rejects.toThrow(
|
|
140
|
-
"Dynamic Registration could not be completed because the issuer has no registration endpoint."
|
|
140
|
+
"Dynamic Registration could not be completed because the issuer has no registration endpoint.",
|
|
141
141
|
);
|
|
142
142
|
});
|
|
143
143
|
|
|
@@ -146,7 +146,7 @@ describe("ClientRegistrar", () => {
|
|
|
146
146
|
/* eslint-disable camelcase */
|
|
147
147
|
new NodeResponse('{"error":"bad stuff that\'s an error"}', {
|
|
148
148
|
status: 400,
|
|
149
|
-
}) as unknown as Response
|
|
149
|
+
}) as unknown as Response,
|
|
150
150
|
/* eslint-enable camelcase */
|
|
151
151
|
);
|
|
152
152
|
global.fetch = mockFetch;
|
|
@@ -163,10 +163,10 @@ describe("ClientRegistrar", () => {
|
|
|
163
163
|
{
|
|
164
164
|
...IssuerConfigFetcherFetchConfigResponse,
|
|
165
165
|
registrationEndpoint: registrationUrl,
|
|
166
|
-
}
|
|
167
|
-
)
|
|
166
|
+
},
|
|
167
|
+
),
|
|
168
168
|
).rejects.toThrow(
|
|
169
|
-
"Client registration failed: [Error: Dynamic client registration failed: bad stuff that's an error - ]"
|
|
169
|
+
"Client registration failed: [Error: Dynamic client registration failed: bad stuff that's an error - ]",
|
|
170
170
|
);
|
|
171
171
|
});
|
|
172
172
|
|
|
@@ -179,7 +179,7 @@ describe("ClientRegistrar", () => {
|
|
|
179
179
|
clientSecret: "a secret",
|
|
180
180
|
},
|
|
181
181
|
},
|
|
182
|
-
false
|
|
182
|
+
false,
|
|
183
183
|
),
|
|
184
184
|
});
|
|
185
185
|
const client = await clientRegistrar.getClient(
|
|
@@ -189,7 +189,7 @@ describe("ClientRegistrar", () => {
|
|
|
189
189
|
},
|
|
190
190
|
{
|
|
191
191
|
...IssuerConfigFetcherFetchConfigResponse,
|
|
192
|
-
}
|
|
192
|
+
},
|
|
193
193
|
);
|
|
194
194
|
expect(client.clientId).toBe("an id");
|
|
195
195
|
expect(client.clientSecret).toBe("a secret");
|
|
@@ -203,8 +203,8 @@ describe("ClientRegistrar", () => {
|
|
|
203
203
|
client_id: "some id",
|
|
204
204
|
client_secret: "some secret",
|
|
205
205
|
redirect_uris: ["https://example.com"],
|
|
206
|
-
})
|
|
207
|
-
) as unknown as Response
|
|
206
|
+
}),
|
|
207
|
+
) as unknown as Response,
|
|
208
208
|
/* eslint-enable camelcase */
|
|
209
209
|
);
|
|
210
210
|
global.fetch = mockFetch;
|
|
@@ -222,14 +222,14 @@ describe("ClientRegistrar", () => {
|
|
|
222
222
|
{
|
|
223
223
|
...IssuerConfigFetcherFetchConfigResponse,
|
|
224
224
|
registrationEndpoint: registrationUrl,
|
|
225
|
-
}
|
|
225
|
+
},
|
|
226
226
|
);
|
|
227
227
|
|
|
228
228
|
await expect(
|
|
229
|
-
myStorage.getForUser("mySession", "clientId", { secure: false })
|
|
229
|
+
myStorage.getForUser("mySession", "clientId", { secure: false }),
|
|
230
230
|
).resolves.toBe("some id");
|
|
231
231
|
await expect(
|
|
232
|
-
myStorage.getForUser("mySession", "clientSecret", { secure: false })
|
|
232
|
+
myStorage.getForUser("mySession", "clientSecret", { secure: false }),
|
|
233
233
|
).resolves.toBe("some secret");
|
|
234
234
|
});
|
|
235
235
|
});
|
|
@@ -37,11 +37,13 @@ import { registerClient } from "@inrupt/oidc-client-ext";
|
|
|
37
37
|
* @hidden
|
|
38
38
|
*/
|
|
39
39
|
export default class ClientRegistrar implements IClientRegistrar {
|
|
40
|
-
constructor(private storageUtility: IStorageUtility) {
|
|
40
|
+
constructor(private storageUtility: IStorageUtility) {
|
|
41
|
+
this.storageUtility = storageUtility;
|
|
42
|
+
}
|
|
41
43
|
|
|
42
44
|
async getClient(
|
|
43
45
|
options: IClientRegistrarOptions,
|
|
44
|
-
issuerConfig: IIssuerConfig
|
|
46
|
+
issuerConfig: IIssuerConfig,
|
|
45
47
|
): Promise<IClient> {
|
|
46
48
|
// If client secret and/or client id are stored in storage, use those.
|
|
47
49
|
const [
|
|
@@ -27,7 +27,7 @@ import IssuerConfigFetcher from "./IssuerConfigFetcher";
|
|
|
27
27
|
|
|
28
28
|
jest.mock("@inrupt/universal-fetch", () => {
|
|
29
29
|
const fetchModule = jest.requireActual(
|
|
30
|
-
"@inrupt/universal-fetch"
|
|
30
|
+
"@inrupt/universal-fetch",
|
|
31
31
|
) as typeof UniversalFetch;
|
|
32
32
|
return {
|
|
33
33
|
...fetchModule,
|
|
@@ -44,10 +44,10 @@ describe("IssuerConfigFetcher", () => {
|
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
function getIssuerConfigFetcher(
|
|
47
|
-
mocks: Partial<typeof defaultMocks> = defaultMocks
|
|
47
|
+
mocks: Partial<typeof defaultMocks> = defaultMocks,
|
|
48
48
|
): IssuerConfigFetcher {
|
|
49
49
|
return new IssuerConfigFetcher(
|
|
50
|
-
mocks.storageUtility ?? defaultMocks.storageUtility
|
|
50
|
+
mocks.storageUtility ?? defaultMocks.storageUtility,
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -56,7 +56,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
56
56
|
storageUtility: mockStorageUtility({}),
|
|
57
57
|
});
|
|
58
58
|
const { fetch: mockFetch } = jest.requireMock(
|
|
59
|
-
"@inrupt/universal-fetch"
|
|
59
|
+
"@inrupt/universal-fetch",
|
|
60
60
|
) as jest.Mocked<typeof UniversalFetch>;
|
|
61
61
|
mockFetch.mockResolvedValueOnce(
|
|
62
62
|
new NodeResponse(
|
|
@@ -66,37 +66,37 @@ describe("IssuerConfigFetcher", () => {
|
|
|
66
66
|
claim_types_supported: "oidc",
|
|
67
67
|
bleepBloop: "Meep Moop",
|
|
68
68
|
end_session_endpoint: "https://example.com/endSessionEndpoint",
|
|
69
|
-
})
|
|
70
|
-
)
|
|
69
|
+
}),
|
|
70
|
+
),
|
|
71
71
|
);
|
|
72
72
|
const fetchedConfig = await configFetcher.fetchConfig(
|
|
73
|
-
"https://arbitrary.url"
|
|
73
|
+
"https://arbitrary.url",
|
|
74
74
|
);
|
|
75
75
|
expect(fetchedConfig.issuer.startsWith("https:")).toBeTruthy();
|
|
76
76
|
expect(fetchedConfig.issuer).toBe("https://example.com");
|
|
77
77
|
expect((fetchedConfig as any).claim_types_supported).toBeUndefined();
|
|
78
78
|
expect(fetchedConfig.claimTypesSupported).toBe("oidc");
|
|
79
79
|
expect(fetchedConfig.endSessionEndpoint).toBe(
|
|
80
|
-
"https://example.com/endSessionEndpoint"
|
|
80
|
+
"https://example.com/endSessionEndpoint",
|
|
81
81
|
);
|
|
82
82
|
expect((fetchedConfig as any).bleepBloop).toBeUndefined();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
it("should throw an error if the fetched config could not be converted to JSON", async () => {
|
|
86
86
|
const { fetch: mockFetch } = jest.requireMock(
|
|
87
|
-
"@inrupt/universal-fetch"
|
|
87
|
+
"@inrupt/universal-fetch",
|
|
88
88
|
) as jest.Mocked<typeof UniversalFetch>;
|
|
89
89
|
mockFetch.mockResolvedValueOnce(new NodeResponse("Not JSON."));
|
|
90
90
|
const configFetcher = new IssuerConfigFetcher(mockStorageUtility({}));
|
|
91
91
|
|
|
92
92
|
await expect(configFetcher.fetchConfig("https://some.url")).rejects.toThrow(
|
|
93
|
-
"[https://some.url] has an invalid configuration:"
|
|
93
|
+
"[https://some.url] has an invalid configuration:",
|
|
94
94
|
);
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
it("should return a config including the support for solid-oidc if present in the discovery profile", async () => {
|
|
98
98
|
const { fetch: mockFetch } = jest.requireMock(
|
|
99
|
-
"@inrupt/universal-fetch"
|
|
99
|
+
"@inrupt/universal-fetch",
|
|
100
100
|
) as jest.Mocked<typeof UniversalFetch>;
|
|
101
101
|
mockFetch.mockResolvedValueOnce(
|
|
102
102
|
new NodeResponse(
|
|
@@ -105,14 +105,14 @@ describe("IssuerConfigFetcher", () => {
|
|
|
105
105
|
// eslint-disable-next-line camelcase
|
|
106
106
|
claim_types_supported: "oidc",
|
|
107
107
|
scopes_supported: ["openid", "offline_access", "webid"],
|
|
108
|
-
})
|
|
109
|
-
)
|
|
108
|
+
}),
|
|
109
|
+
),
|
|
110
110
|
);
|
|
111
111
|
const configFetcher = getIssuerConfigFetcher({
|
|
112
112
|
storageUtility: mockStorageUtility({}),
|
|
113
113
|
});
|
|
114
114
|
const fetchedConfig = await configFetcher.fetchConfig(
|
|
115
|
-
"https://arbitrary.url"
|
|
115
|
+
"https://arbitrary.url",
|
|
116
116
|
);
|
|
117
117
|
expect(fetchedConfig.scopesSupported).toContain("webid");
|
|
118
118
|
expect(fetchedConfig.scopesSupported).toContain("openid");
|
|
@@ -121,7 +121,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
121
121
|
|
|
122
122
|
it("should return a default value for the supported scopes if not advertized by the OpenID provider", async () => {
|
|
123
123
|
const { fetch: mockFetch } = jest.requireMock(
|
|
124
|
-
"@inrupt/universal-fetch"
|
|
124
|
+
"@inrupt/universal-fetch",
|
|
125
125
|
) as jest.Mocked<typeof UniversalFetch>;
|
|
126
126
|
mockFetch.mockResolvedValueOnce(
|
|
127
127
|
new NodeResponse(
|
|
@@ -129,14 +129,14 @@ describe("IssuerConfigFetcher", () => {
|
|
|
129
129
|
issuer: "https://example.com",
|
|
130
130
|
// eslint-disable-next-line camelcase
|
|
131
131
|
claim_types_supported: "oidc",
|
|
132
|
-
})
|
|
133
|
-
)
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
134
|
);
|
|
135
135
|
const configFetcher = getIssuerConfigFetcher({
|
|
136
136
|
storageUtility: mockStorageUtility({}),
|
|
137
137
|
});
|
|
138
138
|
const fetchedConfig = await configFetcher.fetchConfig(
|
|
139
|
-
"https://arbitrary.url"
|
|
139
|
+
"https://arbitrary.url",
|
|
140
140
|
);
|
|
141
141
|
expect(fetchedConfig.scopesSupported).toContain("openid");
|
|
142
142
|
});
|
|
@@ -144,7 +144,7 @@ describe("IssuerConfigFetcher", () => {
|
|
|
144
144
|
it("should append the .well-known/openid-configuration path at the end of the issuer URL", async () => {
|
|
145
145
|
// The response value is irrelevant to this test.
|
|
146
146
|
const { fetch: mockFetch } = jest.requireMock(
|
|
147
|
-
"@inrupt/universal-fetch"
|
|
147
|
+
"@inrupt/universal-fetch",
|
|
148
148
|
) as jest.Mocked<typeof UniversalFetch>;
|
|
149
149
|
mockFetch.mockImplementation(
|
|
150
150
|
async () =>
|
|
@@ -154,8 +154,8 @@ describe("IssuerConfigFetcher", () => {
|
|
|
154
154
|
// eslint-disable-next-line camelcase
|
|
155
155
|
claim_types_supported: "oidc",
|
|
156
156
|
scopes_supported: ["openid", "offline_access", "webid"],
|
|
157
|
-
})
|
|
158
|
-
)
|
|
157
|
+
}),
|
|
158
|
+
),
|
|
159
159
|
);
|
|
160
160
|
const configFetcher = getIssuerConfigFetcher({
|
|
161
161
|
storageUtility: mockStorageUtility({}),
|
|
@@ -163,22 +163,22 @@ describe("IssuerConfigFetcher", () => {
|
|
|
163
163
|
// No trailing slash
|
|
164
164
|
await configFetcher.fetchConfig("https://arbitrary.url");
|
|
165
165
|
expect(mockFetch).toHaveBeenLastCalledWith(
|
|
166
|
-
"https://arbitrary.url/.well-known/openid-configuration"
|
|
166
|
+
"https://arbitrary.url/.well-known/openid-configuration",
|
|
167
167
|
);
|
|
168
168
|
// A trailing slash
|
|
169
169
|
await configFetcher.fetchConfig("https://arbitrary.url/");
|
|
170
170
|
expect(mockFetch).toHaveBeenLastCalledWith(
|
|
171
|
-
"https://arbitrary.url/.well-known/openid-configuration"
|
|
171
|
+
"https://arbitrary.url/.well-known/openid-configuration",
|
|
172
172
|
);
|
|
173
173
|
// A path without a trailing slash
|
|
174
174
|
await configFetcher.fetchConfig("https://arbitrary.url/path");
|
|
175
175
|
expect(mockFetch).toHaveBeenLastCalledWith(
|
|
176
|
-
"https://arbitrary.url/path/.well-known/openid-configuration"
|
|
176
|
+
"https://arbitrary.url/path/.well-known/openid-configuration",
|
|
177
177
|
);
|
|
178
178
|
// A path with a trailing slash
|
|
179
179
|
await configFetcher.fetchConfig("https://arbitrary.url/path/");
|
|
180
180
|
expect(mockFetch).toHaveBeenLastCalledWith(
|
|
181
|
-
"https://arbitrary.url/path/.well-known/openid-configuration"
|
|
181
|
+
"https://arbitrary.url/path/.well-known/openid-configuration",
|
|
182
182
|
);
|
|
183
183
|
});
|
|
184
184
|
});
|
|
@@ -131,7 +131,7 @@ const issuerConfigKeyMap: Record<
|
|
|
131
131
|
/* eslint-enable camelcase */
|
|
132
132
|
|
|
133
133
|
function processConfig(
|
|
134
|
-
config: Record<string, string | string[]
|
|
134
|
+
config: Record<string, string | string[]>,
|
|
135
135
|
): IIssuerConfig {
|
|
136
136
|
const parsedConfig: Record<string, string | string[]> = {};
|
|
137
137
|
Object.keys(config).forEach((key) => {
|
|
@@ -153,7 +153,9 @@ function processConfig(
|
|
|
153
153
|
* @hidden
|
|
154
154
|
*/
|
|
155
155
|
export default class IssuerConfigFetcher implements IIssuerConfigFetcher {
|
|
156
|
-
constructor(private storageUtility: IStorageUtility) {
|
|
156
|
+
constructor(private storageUtility: IStorageUtility) {
|
|
157
|
+
this.storageUtility = storageUtility;
|
|
158
|
+
}
|
|
157
159
|
|
|
158
160
|
// This method needs no state (so can be static), and can be exposed to allow
|
|
159
161
|
// callers to know where this implementation puts state it needs.
|
|
@@ -168,11 +170,11 @@ export default class IssuerConfigFetcher implements IIssuerConfigFetcher {
|
|
|
168
170
|
WELL_KNOWN_OPENID_CONFIG,
|
|
169
171
|
// Make sure to append a slash at issuer URL, so that the .well-known URL
|
|
170
172
|
// includes the full issuer path. See https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfig.
|
|
171
|
-
issuer.endsWith("/") ? issuer : `${issuer}
|
|
173
|
+
issuer.endsWith("/") ? issuer : `${issuer}/`,
|
|
172
174
|
).href;
|
|
173
175
|
const issuerConfigRequestBody = await uniFetch.call(
|
|
174
176
|
globalThis,
|
|
175
|
-
openIdConfigUrl
|
|
177
|
+
openIdConfigUrl,
|
|
176
178
|
);
|
|
177
179
|
// Check the validity of the fetched config
|
|
178
180
|
try {
|
|
@@ -181,14 +183,14 @@ export default class IssuerConfigFetcher implements IIssuerConfigFetcher {
|
|
|
181
183
|
throw new ConfigurationError(
|
|
182
184
|
`[${issuer.toString()}] has an invalid configuration: ${
|
|
183
185
|
(err as { message: string }).message
|
|
184
|
-
}
|
|
186
|
+
}`,
|
|
185
187
|
);
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
// Update store with fetched config
|
|
189
191
|
await this.storageUtility.set(
|
|
190
192
|
IssuerConfigFetcher.getLocalStorageKey(issuer),
|
|
191
|
-
JSON.stringify(issuerConfig)
|
|
193
|
+
JSON.stringify(issuerConfig),
|
|
192
194
|
);
|
|
193
195
|
|
|
194
196
|
return issuerConfig;
|
|
@@ -47,13 +47,13 @@ describe("OidcLoginHandler", () => {
|
|
|
47
47
|
clientRegistrar: mockDefaultClientRegistrar(),
|
|
48
48
|
};
|
|
49
49
|
function getInitialisedHandler(
|
|
50
|
-
mocks: Partial<typeof defaultMocks> = defaultMocks
|
|
50
|
+
mocks: Partial<typeof defaultMocks> = defaultMocks,
|
|
51
51
|
): OidcLoginHandler {
|
|
52
52
|
return new OidcLoginHandler(
|
|
53
53
|
mocks.storageUtility ?? defaultMocks.storageUtility,
|
|
54
54
|
mocks.oidcHandler ?? defaultMocks.oidcHandler,
|
|
55
55
|
mocks.issuerConfigFetcher ?? defaultMocks.issuerConfigFetcher,
|
|
56
|
-
mocks.clientRegistrar ?? defaultMocks.clientRegistrar
|
|
56
|
+
mocks.clientRegistrar ?? defaultMocks.clientRegistrar,
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -81,7 +81,7 @@ describe("OidcLoginHandler", () => {
|
|
|
81
81
|
// This checks it gets passed through from storage to the handler.
|
|
82
82
|
clientId: "some client ID",
|
|
83
83
|
},
|
|
84
|
-
})
|
|
84
|
+
}),
|
|
85
85
|
);
|
|
86
86
|
const handler = getInitialisedHandler({
|
|
87
87
|
oidcHandler: actualHandler,
|
|
@@ -111,7 +111,7 @@ describe("OidcLoginHandler", () => {
|
|
|
111
111
|
|
|
112
112
|
const mockedEmptyStorage = new StorageUtility(
|
|
113
113
|
mockStorage({}),
|
|
114
|
-
mockStorage({})
|
|
114
|
+
mockStorage({}),
|
|
115
115
|
);
|
|
116
116
|
|
|
117
117
|
const actualHandler = defaultMocks.oidcHandler;
|
|
@@ -140,7 +140,7 @@ describe("OidcLoginHandler", () => {
|
|
|
140
140
|
|
|
141
141
|
const mockedEmptyStorage = new StorageUtility(
|
|
142
142
|
mockStorage({}),
|
|
143
|
-
mockStorage({})
|
|
143
|
+
mockStorage({}),
|
|
144
144
|
);
|
|
145
145
|
|
|
146
146
|
const actualHandler = defaultMocks.oidcHandler;
|
|
@@ -183,7 +183,7 @@ describe("OidcLoginHandler", () => {
|
|
|
183
183
|
|
|
184
184
|
const storedClientId = await actualStorage.getForUser(
|
|
185
185
|
"mySession",
|
|
186
|
-
"clientId"
|
|
186
|
+
"clientId",
|
|
187
187
|
);
|
|
188
188
|
expect(storedClientId).toEqual(inputClientId);
|
|
189
189
|
});
|
|
@@ -215,7 +215,7 @@ describe("OidcLoginHandler", () => {
|
|
|
215
215
|
|
|
216
216
|
const storedClientId = await mockedStorage.getForUser(
|
|
217
217
|
"mySession",
|
|
218
|
-
"clientId"
|
|
218
|
+
"clientId",
|
|
219
219
|
);
|
|
220
220
|
expect(storedClientId).toBe("https://my.app/registration#app");
|
|
221
221
|
});
|
|
@@ -241,15 +241,15 @@ describe("OidcLoginHandler", () => {
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
expect(await actualStorage.getForUser("mySession", "clientId")).toEqual(
|
|
244
|
-
inputClientId
|
|
244
|
+
inputClientId,
|
|
245
245
|
);
|
|
246
246
|
|
|
247
247
|
expect(await actualStorage.getForUser("mySession", "clientSecret")).toEqual(
|
|
248
|
-
inputClientSecret
|
|
248
|
+
inputClientSecret,
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
expect(await actualStorage.getForUser("mySession", "clientName")).toEqual(
|
|
252
|
-
inputClientName
|
|
252
|
+
inputClientName,
|
|
253
253
|
);
|
|
254
254
|
});
|
|
255
255
|
|
|
@@ -259,7 +259,7 @@ describe("OidcLoginHandler", () => {
|
|
|
259
259
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
260
260
|
// @ts-ignore
|
|
261
261
|
await expect(handler.handle({})).rejects.toThrow(
|
|
262
|
-
"OidcLoginHandler requires an OIDC issuer"
|
|
262
|
+
"OidcLoginHandler requires an OIDC issuer",
|
|
263
263
|
);
|
|
264
264
|
});
|
|
265
265
|
|
|
@@ -270,7 +270,7 @@ describe("OidcLoginHandler", () => {
|
|
|
270
270
|
sessionId: "doesn't matter",
|
|
271
271
|
tokenType: "DPoP",
|
|
272
272
|
oidcIssuer: "https://whatever.com",
|
|
273
|
-
})
|
|
273
|
+
}),
|
|
274
274
|
).rejects.toThrow("OidcLoginHandler requires a redirect URL");
|
|
275
275
|
});
|
|
276
276
|
|
|
@@ -284,7 +284,7 @@ describe("OidcLoginHandler", () => {
|
|
|
284
284
|
redirectUrl: "https://app.com/redirect",
|
|
285
285
|
clientId: "coolApp",
|
|
286
286
|
tokenType: "DPoP",
|
|
287
|
-
})
|
|
287
|
+
}),
|
|
288
288
|
).resolves.toBe(true);
|
|
289
289
|
});
|
|
290
290
|
|
|
@@ -299,7 +299,7 @@ describe("OidcLoginHandler", () => {
|
|
|
299
299
|
issuerConfig.issuer = "https://some.issuer/";
|
|
300
300
|
const handler = getInitialisedHandler({
|
|
301
301
|
issuerConfigFetcher: mockIssuerConfigFetcher(
|
|
302
|
-
issuerConfig
|
|
302
|
+
issuerConfig,
|
|
303
303
|
) as jest.Mocked<IIssuerConfigFetcher>,
|
|
304
304
|
oidcHandler: actualHandler,
|
|
305
305
|
});
|
|
@@ -314,7 +314,7 @@ describe("OidcLoginHandler", () => {
|
|
|
314
314
|
expect(actualHandler.handle).toHaveBeenCalledWith(
|
|
315
315
|
expect.objectContaining({
|
|
316
316
|
issuer: "https://some.issuer/",
|
|
317
|
-
})
|
|
317
|
+
}),
|
|
318
318
|
);
|
|
319
319
|
});
|
|
320
320
|
});
|
|
@@ -45,13 +45,13 @@ import {
|
|
|
45
45
|
} from "@inrupt/solid-client-authn-core";
|
|
46
46
|
|
|
47
47
|
function hasIssuer(
|
|
48
|
-
options: ILoginOptions
|
|
48
|
+
options: ILoginOptions,
|
|
49
49
|
): options is ILoginOptions & { oidcIssuer: string } {
|
|
50
50
|
return typeof options.oidcIssuer === "string";
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function hasRedirectUrl(
|
|
54
|
-
options: ILoginOptions
|
|
54
|
+
options: ILoginOptions,
|
|
55
55
|
): options is ILoginOptions & { redirectUrl: string } {
|
|
56
56
|
return typeof options.redirectUrl === "string";
|
|
57
57
|
}
|
|
@@ -64,8 +64,13 @@ export default class OidcLoginHandler implements ILoginHandler {
|
|
|
64
64
|
private storageUtility: IStorageUtility,
|
|
65
65
|
private oidcHandler: IOidcHandler,
|
|
66
66
|
private issuerConfigFetcher: IIssuerConfigFetcher,
|
|
67
|
-
private clientRegistrar: IClientRegistrar
|
|
68
|
-
) {
|
|
67
|
+
private clientRegistrar: IClientRegistrar,
|
|
68
|
+
) {
|
|
69
|
+
this.storageUtility = storageUtility;
|
|
70
|
+
this.oidcHandler = oidcHandler;
|
|
71
|
+
this.issuerConfigFetcher = issuerConfigFetcher;
|
|
72
|
+
this.clientRegistrar = clientRegistrar;
|
|
73
|
+
}
|
|
69
74
|
|
|
70
75
|
async canHandle(options: ILoginOptions): Promise<boolean> {
|
|
71
76
|
return hasIssuer(options) && hasRedirectUrl(options);
|
|
@@ -75,15 +80,15 @@ export default class OidcLoginHandler implements ILoginHandler {
|
|
|
75
80
|
if (!hasIssuer(options)) {
|
|
76
81
|
throw new ConfigurationError(
|
|
77
82
|
`OidcLoginHandler requires an OIDC issuer: missing property 'oidcIssuer' in ${JSON.stringify(
|
|
78
|
-
options
|
|
79
|
-
)}
|
|
83
|
+
options,
|
|
84
|
+
)}`,
|
|
80
85
|
);
|
|
81
86
|
}
|
|
82
87
|
if (!hasRedirectUrl(options)) {
|
|
83
88
|
throw new ConfigurationError(
|
|
84
89
|
`OidcLoginHandler requires a redirect URL: missing property 'redirectUrl' in ${JSON.stringify(
|
|
85
|
-
options
|
|
86
|
-
)}
|
|
90
|
+
options,
|
|
91
|
+
)}`,
|
|
87
92
|
);
|
|
88
93
|
}
|
|
89
94
|
|
|
@@ -95,7 +100,7 @@ export default class OidcLoginHandler implements ILoginHandler {
|
|
|
95
100
|
options,
|
|
96
101
|
issuerConfig,
|
|
97
102
|
this.storageUtility,
|
|
98
|
-
this.clientRegistrar
|
|
103
|
+
this.clientRegistrar,
|
|
99
104
|
);
|
|
100
105
|
|
|
101
106
|
// Construct OIDC Options
|
|
@@ -41,7 +41,7 @@ export const PublicClientRegistrarResponse: IClient = {
|
|
|
41
41
|
export const ClientRegistrarMock: jest.Mocked<IClientRegistrar> = {
|
|
42
42
|
getClient: jest.fn(
|
|
43
43
|
(_options: IClientRegistrarOptions, _issuerConfig: IIssuerConfig) =>
|
|
44
|
-
Promise.resolve(ClientRegistrarResponse)
|
|
44
|
+
Promise.resolve(ClientRegistrarResponse),
|
|
45
45
|
),
|
|
46
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
47
|
} as any;
|
|
@@ -49,7 +49,7 @@ export const ClientRegistrarMock: jest.Mocked<IClientRegistrar> = {
|
|
|
49
49
|
export const PublicClientRegistrarMock: jest.Mocked<IClientRegistrar> = {
|
|
50
50
|
getClient: jest.fn(
|
|
51
51
|
(_options: IClientRegistrarOptions, _issuerConfig: IIssuerConfig) =>
|
|
52
|
-
Promise.resolve(PublicClientRegistrarResponse)
|
|
52
|
+
Promise.resolve(PublicClientRegistrarResponse),
|
|
53
53
|
),
|
|
54
54
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
55
|
} as any;
|
|
@@ -58,7 +58,7 @@ export const mockDefaultClientRegistrar = (): IClientRegistrar => {
|
|
|
58
58
|
return {
|
|
59
59
|
getClient: jest.fn(
|
|
60
60
|
(_options: IClientRegistrarOptions, _issuerConfig: IIssuerConfig) =>
|
|
61
|
-
Promise.resolve(ClientRegistrarResponse)
|
|
61
|
+
Promise.resolve(ClientRegistrarResponse),
|
|
62
62
|
),
|
|
63
63
|
};
|
|
64
64
|
};
|
|
@@ -40,7 +40,7 @@ export const IssuerConfigFetcherFetchConfigResponse: IIssuerConfig = {
|
|
|
40
40
|
export const IssuerConfigFetcherMock: jest.Mocked<IIssuerConfigFetcher> = {
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
42
42
|
fetchConfig: jest.fn((_issuer: string) =>
|
|
43
|
-
Promise.resolve(IssuerConfigFetcherFetchConfigResponse)
|
|
43
|
+
Promise.resolve(IssuerConfigFetcherFetchConfigResponse),
|
|
44
44
|
),
|
|
45
45
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
46
|
} as any;
|
|
@@ -49,14 +49,14 @@ export const mockDefaultIssuerConfigFetcher = (): IIssuerConfigFetcher => {
|
|
|
49
49
|
return {
|
|
50
50
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
51
|
fetchConfig: (jest.fn() as any).mockResolvedValue(
|
|
52
|
-
IssuerConfigFetcherFetchConfigResponse
|
|
52
|
+
IssuerConfigFetcherFetchConfigResponse,
|
|
53
53
|
),
|
|
54
54
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
55
|
} as any;
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
export const mockIssuerConfigFetcher = (
|
|
59
|
-
config: IIssuerConfig
|
|
59
|
+
config: IIssuerConfig,
|
|
60
60
|
): IIssuerConfigFetcher => {
|
|
61
61
|
return {
|
|
62
62
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|