@unwanted/matrix-sdk-mini 34.13.0 → 36.0.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 (95) hide show
  1. package/lib/@types/event.d.ts +25 -0
  2. package/lib/@types/event.d.ts.map +1 -1
  3. package/lib/@types/event.js +5 -0
  4. package/lib/@types/event.js.map +1 -1
  5. package/lib/client.d.ts +43 -8
  6. package/lib/client.d.ts.map +1 -1
  7. package/lib/client.js +147 -85
  8. package/lib/client.js.map +1 -1
  9. package/lib/embedded.d.ts +2 -1
  10. package/lib/embedded.d.ts.map +1 -1
  11. package/lib/embedded.js +57 -74
  12. package/lib/embedded.js.map +1 -1
  13. package/lib/feature.d.ts.map +1 -1
  14. package/lib/feature.js +2 -1
  15. package/lib/feature.js.map +1 -1
  16. package/lib/http-api/errors.d.ts +3 -3
  17. package/lib/http-api/errors.js +3 -3
  18. package/lib/http-api/errors.js.map +1 -1
  19. package/lib/http-api/utils.js +2 -2
  20. package/lib/http-api/utils.js.map +1 -1
  21. package/lib/models/event-timeline.d.ts.map +1 -1
  22. package/lib/models/event-timeline.js +1 -21
  23. package/lib/models/event-timeline.js.map +1 -1
  24. package/lib/models/event.d.ts +11 -1
  25. package/lib/models/event.d.ts.map +1 -1
  26. package/lib/models/event.js +48 -5
  27. package/lib/models/event.js.map +1 -1
  28. package/lib/models/invites-ignorer-types.d.ts +27 -0
  29. package/lib/models/invites-ignorer-types.d.ts.map +1 -0
  30. package/lib/models/invites-ignorer-types.js +36 -0
  31. package/lib/models/invites-ignorer-types.js.map +1 -0
  32. package/lib/models/invites-ignorer.d.ts +2 -26
  33. package/lib/models/invites-ignorer.d.ts.map +1 -1
  34. package/lib/models/invites-ignorer.js +2 -27
  35. package/lib/models/invites-ignorer.js.map +1 -1
  36. package/lib/models/room-member.d.ts +6 -1
  37. package/lib/models/room-member.d.ts.map +1 -1
  38. package/lib/models/room-member.js +7 -1
  39. package/lib/models/room-member.js.map +1 -1
  40. package/lib/models/room.d.ts +6 -1
  41. package/lib/models/room.d.ts.map +1 -1
  42. package/lib/models/room.js +7 -1
  43. package/lib/models/room.js.map +1 -1
  44. package/lib/oidc/authorize.d.ts +2 -2
  45. package/lib/oidc/authorize.d.ts.map +1 -1
  46. package/lib/oidc/authorize.js +5 -5
  47. package/lib/oidc/authorize.js.map +1 -1
  48. package/lib/oidc/discovery.d.ts +8 -0
  49. package/lib/oidc/discovery.d.ts.map +1 -1
  50. package/lib/oidc/discovery.js +22 -11
  51. package/lib/oidc/discovery.js.map +1 -1
  52. package/lib/oidc/index.d.ts +3 -4
  53. package/lib/oidc/index.d.ts.map +1 -1
  54. package/lib/oidc/index.js.map +1 -1
  55. package/lib/oidc/register.js +3 -3
  56. package/lib/oidc/register.js.map +1 -1
  57. package/lib/oidc/tokenRefresher.d.ts.map +1 -1
  58. package/lib/oidc/tokenRefresher.js +6 -5
  59. package/lib/oidc/tokenRefresher.js.map +1 -1
  60. package/lib/oidc/validate.d.ts +9 -23
  61. package/lib/oidc/validate.d.ts.map +1 -1
  62. package/lib/oidc/validate.js +13 -28
  63. package/lib/oidc/validate.js.map +1 -1
  64. package/lib/randomstring.d.ts +30 -3
  65. package/lib/randomstring.d.ts.map +1 -1
  66. package/lib/randomstring.js +68 -16
  67. package/lib/randomstring.js.map +1 -1
  68. package/lib/store/indexeddb-local-backend.js +4 -2
  69. package/lib/store/indexeddb-local-backend.js.map +1 -1
  70. package/lib/testing.d.ts +17 -0
  71. package/lib/testing.d.ts.map +1 -1
  72. package/lib/testing.js +42 -0
  73. package/lib/testing.js.map +1 -1
  74. package/package.json +5 -5
  75. package/src/@types/event.ts +17 -0
  76. package/src/client.ts +86 -28
  77. package/src/embedded.ts +39 -49
  78. package/src/feature.ts +1 -0
  79. package/src/http-api/errors.ts +3 -3
  80. package/src/http-api/utils.ts +2 -2
  81. package/src/models/event-timeline.ts +1 -21
  82. package/src/models/event.ts +46 -0
  83. package/src/models/invites-ignorer-types.ts +48 -0
  84. package/src/models/invites-ignorer.ts +7 -42
  85. package/src/models/room-member.ts +16 -1
  86. package/src/models/room.ts +16 -1
  87. package/src/oidc/authorize.ts +7 -7
  88. package/src/oidc/discovery.ts +16 -10
  89. package/src/oidc/index.ts +3 -4
  90. package/src/oidc/register.ts +3 -3
  91. package/src/oidc/tokenRefresher.ts +3 -2
  92. package/src/oidc/validate.ts +40 -63
  93. package/src/randomstring.ts +65 -19
  94. package/src/store/indexeddb-local-backend.ts +2 -2
  95. package/src/testing.ts +41 -0
@@ -42,6 +42,7 @@ import { IAnnotatedPushRule } from "../@types/PushRules.ts";
42
42
  import { Room } from "./room.ts";
43
43
  import { EventTimeline } from "./event-timeline.ts";
44
44
  import { Membership } from "../@types/membership.ts";
45
+ import { RoomState } from "./room-state.ts";
45
46
 
46
47
  export { EventStatus } from "./event-status.ts";
47
48
 
@@ -226,6 +227,7 @@ export enum MatrixEventEvent {
226
227
  Status = "Event.status",
227
228
  Replaced = "Event.replaced",
228
229
  RelationsCreated = "Event.relationsCreated",
230
+ SentinelUpdated = "Event.sentinelUpdated",
229
231
  }
230
232
 
231
233
  export type MatrixEventEmittedEvents = MatrixEventEvent | ThreadEvent.Update;
@@ -238,6 +240,7 @@ export type MatrixEventHandlerMap = {
238
240
  [MatrixEventEvent.Status]: (event: MatrixEvent, status: EventStatus | null) => void;
239
241
  [MatrixEventEvent.Replaced]: (event: MatrixEvent) => void;
240
242
  [MatrixEventEvent.RelationsCreated]: (relationType: string, eventType: string) => void;
243
+ [MatrixEventEvent.SentinelUpdated]: () => void;
241
244
  } & Pick<ThreadEventHandlerMap, ThreadEvent.Update>;
242
245
 
243
246
  export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, MatrixEventHandlerMap> {
@@ -308,6 +311,7 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
308
311
  * Should be read-only
309
312
  */
310
313
  public sender: RoomMember | null = null;
314
+
311
315
  /**
312
316
  * The room member who is the target of this event, e.g.
313
317
  * the invitee, the person being banned, etc.
@@ -315,6 +319,48 @@ export class MatrixEvent extends TypedEventEmitter<MatrixEventEmittedEvents, Mat
315
319
  * Should be read-only
316
320
  */
317
321
  public target: RoomMember | null = null;
322
+
323
+ /**
324
+ * Update the sentinels and forwardLooking flag for this event.
325
+ * @param stateContext - the room state to be queried
326
+ * @param toStartOfTimeline - if true the event's forwardLooking flag is set false
327
+ * @internal
328
+ */
329
+ public setMetadata(stateContext: RoomState, toStartOfTimeline: boolean): void {
330
+ // If an event is an m.room.member state event then we should set the sentinels again in case setEventMetadata
331
+ // was already called before the state was applied and thus the sentinel points at the member from before this event.
332
+ const affectsSelf =
333
+ this.isState() && this.getType() === EventType.RoomMember && this.getSender() === this.getStateKey();
334
+ let changed = false;
335
+ // When we try to generate a sentinel member before we have that member
336
+ // in the members object, we still generate a sentinel but it doesn't
337
+ // have a membership event, so test to see if events.member is set. We
338
+ // check this to avoid overriding non-sentinel members by sentinel ones
339
+ // when adding the event to a filtered timeline
340
+ if (affectsSelf || !this.sender?.events?.member) {
341
+ const newSender = stateContext.getSentinelMember(this.getSender()!);
342
+ if (newSender !== this.sender) changed = true;
343
+ this.sender = newSender;
344
+ }
345
+ if (affectsSelf || (!this.target?.events?.member && this.getType() === EventType.RoomMember)) {
346
+ const newTarget = stateContext.getSentinelMember(this.getStateKey()!);
347
+ if (newTarget !== this.target) changed = true;
348
+ this.target = newTarget;
349
+ }
350
+ if (this.isState()) {
351
+ // room state has no concept of 'old' or 'current', but we want the
352
+ // room state to regress back to previous values if toStartOfTimeline
353
+ // is set, which means inspecting prev_content if it exists. This
354
+ // is done by toggling the forwardLooking flag.
355
+ if (toStartOfTimeline) {
356
+ this.forwardLooking = false;
357
+ }
358
+ }
359
+ if (changed) {
360
+ this.emit(MatrixEventEvent.SentinelUpdated);
361
+ }
362
+ }
363
+
318
364
  /**
319
365
  * The sending status of the event.
320
366
  * @privateRemarks
@@ -0,0 +1,48 @@
1
+ /*
2
+ Copyright 2022 The Matrix.org Foundation C.I.C.
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
12
+ */
13
+ import { UnstableValue } from "matrix-events-sdk";
14
+ /// The event type storing the user's individual policies.
15
+ ///
16
+ /// Exported for testing purposes.
17
+ export const POLICIES_ACCOUNT_EVENT_TYPE = new UnstableValue("m.policies", "org.matrix.msc3847.policies");
18
+ /// The key within the user's individual policies storing the user's ignored invites.
19
+ ///
20
+ /// Exported for testing purposes.
21
+ export const IGNORE_INVITES_ACCOUNT_EVENT_KEY = new UnstableValue(
22
+ "m.ignore.invites",
23
+ "org.matrix.msc3847.ignore.invites",
24
+ );
25
+ /// The types of recommendations understood.
26
+ export enum PolicyRecommendation {
27
+ Ban = "m.ban",
28
+ }
29
+ /**
30
+ * The various scopes for policies.
31
+ */
32
+ export enum PolicyScope {
33
+ /**
34
+ * The policy deals with an individual user, e.g. reject invites
35
+ * from this user.
36
+ */
37
+ User = "m.policy.user",
38
+ /**
39
+ * The policy deals with a room, e.g. reject invites towards
40
+ * a specific room.
41
+ */
42
+ Room = "m.policy.room",
43
+ /**
44
+ * The policy deals with a server, e.g. reject invites from
45
+ * this server.
46
+ */
47
+ Server = "m.policy.server",
48
+ }
@@ -14,8 +14,6 @@ See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
15
  */
16
16
 
17
- import { UnstableValue } from "matrix-events-sdk";
18
-
19
17
  import { MatrixClient } from "../client.ts";
20
18
  import { IContent, MatrixEvent } from "./event.ts";
21
19
  import { EventTimeline } from "./event-timeline.ts";
@@ -23,47 +21,14 @@ import { Preset } from "../@types/partials.ts";
23
21
  import { globToRegexp } from "../utils.ts";
24
22
  import { Room } from "./room.ts";
25
23
  import { EventType, StateEvents } from "../@types/event.ts";
24
+ import {
25
+ IGNORE_INVITES_ACCOUNT_EVENT_KEY,
26
+ POLICIES_ACCOUNT_EVENT_TYPE,
27
+ PolicyRecommendation,
28
+ PolicyScope,
29
+ } from "./invites-ignorer-types.ts";
26
30
 
27
- /// The event type storing the user's individual policies.
28
- ///
29
- /// Exported for testing purposes.
30
- export const POLICIES_ACCOUNT_EVENT_TYPE = new UnstableValue("m.policies", "org.matrix.msc3847.policies");
31
-
32
- /// The key within the user's individual policies storing the user's ignored invites.
33
- ///
34
- /// Exported for testing purposes.
35
- export const IGNORE_INVITES_ACCOUNT_EVENT_KEY = new UnstableValue(
36
- "m.ignore.invites",
37
- "org.matrix.msc3847.ignore.invites",
38
- );
39
-
40
- /// The types of recommendations understood.
41
- export enum PolicyRecommendation {
42
- Ban = "m.ban",
43
- }
44
-
45
- /**
46
- * The various scopes for policies.
47
- */
48
- export enum PolicyScope {
49
- /**
50
- * The policy deals with an individual user, e.g. reject invites
51
- * from this user.
52
- */
53
- User = "m.policy.user",
54
-
55
- /**
56
- * The policy deals with a room, e.g. reject invites towards
57
- * a specific room.
58
- */
59
- Room = "m.policy.room",
60
-
61
- /**
62
- * The policy deals with a server, e.g. reject invites from
63
- * this server.
64
- */
65
- Server = "m.policy.server",
66
- }
31
+ export { IGNORE_INVITES_ACCOUNT_EVENT_KEY, POLICIES_ACCOUNT_EVENT_TYPE, PolicyRecommendation, PolicyScope };
67
32
 
68
33
  const scopeToEventTypeMap: Record<PolicyScope, keyof StateEvents> = {
69
34
  [PolicyScope.User]: EventType.PolicyRuleUser,
@@ -368,6 +368,11 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
368
368
  * If false, any non-matrix content URLs will be ignored. Setting this option to
369
369
  * true will expose URLs that, if fetched, will leak information about the user
370
370
  * to anyone who they share a room with.
371
+ * @param useAuthentication - (optional) If true, the caller supports authenticated
372
+ * media and wants an authentication-required URL. Note that server support for
373
+ * authenticated media will not be checked - it is the caller's responsibility
374
+ * to do so before calling this function. Note also that useAuthentication
375
+ * implies allowRedirects. Defaults to false (unauthenticated endpoints).
371
376
  * @returns the avatar URL or null.
372
377
  */
373
378
  public getAvatarUrl(
@@ -377,13 +382,23 @@ export class RoomMember extends TypedEventEmitter<RoomMemberEvent, RoomMemberEve
377
382
  resizeMethod: string,
378
383
  allowDefault = true,
379
384
  allowDirectLinks: boolean,
385
+ useAuthentication: boolean = false,
380
386
  ): string | null {
381
387
  const rawUrl = this.getMxcAvatarUrl();
382
388
 
383
389
  if (!rawUrl && !allowDefault) {
384
390
  return null;
385
391
  }
386
- const httpUrl = getHttpUriForMxc(baseUrl, rawUrl, width, height, resizeMethod, allowDirectLinks);
392
+ const httpUrl = getHttpUriForMxc(
393
+ baseUrl,
394
+ rawUrl,
395
+ width,
396
+ height,
397
+ resizeMethod,
398
+ allowDirectLinks,
399
+ undefined,
400
+ useAuthentication,
401
+ );
387
402
  if (httpUrl) {
388
403
  return httpUrl;
389
404
  }
@@ -1614,6 +1614,11 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1614
1614
  * "crop" or "scale".
1615
1615
  * @param allowDefault - True to allow an identicon for this room if an
1616
1616
  * avatar URL wasn't explicitly set. Default: true. (Deprecated)
1617
+ * @param useAuthentication - (optional) If true, the caller supports authenticated
1618
+ * media and wants an authentication-required URL. Note that server support for
1619
+ * authenticated media will not be checked - it is the caller's responsibility
1620
+ * to do so before calling this function. Note also that useAuthentication
1621
+ * implies allowRedirects. Defaults to false (unauthenticated endpoints).
1617
1622
  * @returns the avatar URL or null.
1618
1623
  */
1619
1624
  public getAvatarUrl(
@@ -1622,6 +1627,7 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1622
1627
  height: number,
1623
1628
  resizeMethod: ResizeMethod,
1624
1629
  allowDefault = true,
1630
+ useAuthentication: boolean = false,
1625
1631
  ): string | null {
1626
1632
  const roomAvatarEvent = this.currentState.getStateEvents(EventType.RoomAvatar, "");
1627
1633
  if (!roomAvatarEvent && !allowDefault) {
@@ -1630,7 +1636,16 @@ export class Room extends ReadReceipt<RoomEmittedEvents, RoomEventHandlerMap> {
1630
1636
 
1631
1637
  const mainUrl = roomAvatarEvent ? roomAvatarEvent.getContent().url : null;
1632
1638
  if (mainUrl) {
1633
- return getHttpUriForMxc(baseUrl, mainUrl, width, height, resizeMethod);
1639
+ return getHttpUriForMxc(
1640
+ baseUrl,
1641
+ mainUrl,
1642
+ width,
1643
+ height,
1644
+ resizeMethod,
1645
+ undefined,
1646
+ undefined,
1647
+ useAuthentication,
1648
+ );
1634
1649
  }
1635
1650
 
1636
1651
  return null;
@@ -17,13 +17,13 @@ limitations under the License.
17
17
  import { IdTokenClaims, Log, OidcClient, SigninResponse, SigninState, WebStorageStateStore } from "oidc-client-ts";
18
18
 
19
19
  import { logger } from "../logger.ts";
20
- import { randomString } from "../randomstring.ts";
20
+ import { secureRandomString } from "../randomstring.ts";
21
21
  import { OidcError } from "./error.ts";
22
22
  import {
23
23
  BearerTokenResponse,
24
24
  UserState,
25
25
  validateBearerTokenResponse,
26
- ValidatedIssuerMetadata,
26
+ ValidatedAuthMetadata,
27
27
  validateIdToken,
28
28
  validateStoredUserState,
29
29
  } from "./validate.ts";
@@ -52,7 +52,7 @@ export type AuthorizationParams = {
52
52
  * @returns scope
53
53
  */
54
54
  export const generateScope = (deviceId?: string): string => {
55
- const safeDeviceId = deviceId ?? randomString(10);
55
+ const safeDeviceId = deviceId ?? secureRandomString(10);
56
56
  return `openid urn:matrix:org.matrix.msc2967.client:api:* urn:matrix:org.matrix.msc2967.client:device:${safeDeviceId}`;
57
57
  };
58
58
 
@@ -79,9 +79,9 @@ const generateCodeChallenge = async (codeVerifier: string): Promise<string> => {
79
79
  export const generateAuthorizationParams = ({ redirectUri }: { redirectUri: string }): AuthorizationParams => ({
80
80
  scope: generateScope(),
81
81
  redirectUri,
82
- state: randomString(8),
83
- nonce: randomString(8),
84
- codeVerifier: randomString(64), // https://tools.ietf.org/html/rfc7636#section-4.1 length needs to be 43-128 characters
82
+ state: secureRandomString(8),
83
+ nonce: secureRandomString(8),
84
+ codeVerifier: secureRandomString(64), // https://tools.ietf.org/html/rfc7636#section-4.1 length needs to be 43-128 characters
85
85
  });
86
86
 
87
87
  /**
@@ -138,7 +138,7 @@ export const generateOidcAuthorizationUrl = async ({
138
138
  urlState,
139
139
  }: {
140
140
  clientId: string;
141
- metadata: ValidatedIssuerMetadata;
141
+ metadata: ValidatedAuthMetadata;
142
142
  homeserverUrl: string;
143
143
  identityServerUrl?: string;
144
144
  redirectUri: string;
@@ -16,7 +16,7 @@ limitations under the License.
16
16
 
17
17
  import { MetadataService, OidcClientSettingsStore } from "oidc-client-ts";
18
18
 
19
- import { isValidatedIssuerMetadata, validateOIDCIssuerWellKnown } from "./validate.ts";
19
+ import { validateAuthMetadata } from "./validate.ts";
20
20
  import { Method, timeoutSignal } from "../http-api/index.ts";
21
21
  import { OidcClientConfig } from "./index.ts";
22
22
 
@@ -30,6 +30,7 @@ import { OidcClientConfig } from "./index.ts";
30
30
  * @param issuer - the OIDC issuer as returned by the /auth_issuer API
31
31
  * @returns validated authentication metadata and optionally signing keys
32
32
  * @throws when delegated auth config is invalid or unreachable
33
+ * @deprecated in favour of {@link MatrixClient#getAuthMetadata}
33
34
  */
34
35
  export const discoverAndValidateOIDCIssuerWellKnown = async (issuer: string): Promise<OidcClientConfig> => {
35
36
  const issuerOpenIdConfigUrl = new URL(".well-known/openid-configuration", issuer);
@@ -38,23 +39,28 @@ export const discoverAndValidateOIDCIssuerWellKnown = async (issuer: string): Pr
38
39
  signal: timeoutSignal(5000),
39
40
  });
40
41
  const issuerWellKnown = await issuerWellKnownResponse.json();
41
- const validatedIssuerConfig = validateOIDCIssuerWellKnown(issuerWellKnown);
42
+ return validateAuthMetadataAndKeys(issuerWellKnown);
43
+ };
44
+ /**
45
+ * @experimental
46
+ * Validate the authentication metadata and fetch the signing keys from the jwks_uri in the metadata
47
+ * @param authMetadata - the authentication metadata to validate
48
+ * @returns validated authentication metadata and signing keys
49
+ */
50
+ export const validateAuthMetadataAndKeys = async (authMetadata: unknown): Promise<OidcClientConfig> => {
51
+ const validatedIssuerConfig = validateAuthMetadata(authMetadata);
42
52
 
43
53
  // create a temporary settings store, so we can use metadata service for discovery
44
54
  const settings = new OidcClientSettingsStore({
45
- authority: issuer,
55
+ authority: validatedIssuerConfig.issuer,
56
+ metadata: validatedIssuerConfig,
46
57
  redirect_uri: "", // Not known yet, this is here to make the type checker happy
47
58
  client_id: "", // Not known yet, this is here to make the type checker happy
48
59
  });
49
60
  const metadataService = new MetadataService(settings);
50
- const metadata = await metadataService.getMetadata();
51
- const signingKeys = (await metadataService.getSigningKeys()) ?? undefined;
52
-
53
- isValidatedIssuerMetadata(metadata);
54
-
61
+
55
62
  return {
56
63
  ...validatedIssuerConfig,
57
- metadata,
58
- signingKeys,
64
+ signingKeys: await metadataService.getSigningKeys(),
59
65
  };
60
66
  };
package/src/oidc/index.ts CHANGED
@@ -15,7 +15,7 @@ limitations under the License.
15
15
  */
16
16
 
17
17
  import type { SigningKey } from "oidc-client-ts";
18
- import { ValidatedIssuerConfig, ValidatedIssuerMetadata } from "./validate.ts";
18
+ import { ValidatedAuthMetadata } from "./validate.ts";
19
19
 
20
20
  export * from "./authorize.ts";
21
21
  export * from "./discovery.ts";
@@ -28,7 +28,6 @@ export * from "./validate.ts";
28
28
  * Validated config for native OIDC authentication, as returned by {@link discoverAndValidateOIDCIssuerWellKnown}.
29
29
  * Contains metadata and signing keys from the issuer's well-known (https://oidc-issuer.example.com/.well-known/openid-configuration).
30
30
  */
31
- export interface OidcClientConfig extends ValidatedIssuerConfig {
32
- metadata: ValidatedIssuerMetadata;
33
- signingKeys?: SigningKey[];
31
+ export interface OidcClientConfig extends ValidatedAuthMetadata {
32
+ signingKeys: SigningKey[] | null;
34
33
  }
@@ -65,12 +65,12 @@ export const registerOidcClient = async (
65
65
  delegatedAuthConfig: OidcClientConfig,
66
66
  clientMetadata: OidcRegistrationClientMetadata,
67
67
  ): Promise<string> => {
68
- if (!delegatedAuthConfig.registrationEndpoint) {
68
+ if (!delegatedAuthConfig.registration_endpoint) {
69
69
  throw new Error(OidcError.DynamicRegistrationNotSupported);
70
70
  }
71
71
 
72
72
  const grantTypes: NonEmptyArray<string> = ["authorization_code", "refresh_token"];
73
- if (grantTypes.some((scope) => !delegatedAuthConfig.metadata.grant_types_supported.includes(scope))) {
73
+ if (grantTypes.some((scope) => !delegatedAuthConfig.grant_types_supported.includes(scope))) {
74
74
  throw new Error(OidcError.DynamicRegistrationNotSupported);
75
75
  }
76
76
 
@@ -95,7 +95,7 @@ export const registerOidcClient = async (
95
95
  };
96
96
 
97
97
  try {
98
- const response = await fetch(delegatedAuthConfig.registrationEndpoint, {
98
+ const response = await fetch(delegatedAuthConfig.registration_endpoint, {
99
99
  method: Method.Post,
100
100
  headers,
101
101
  body: JSON.stringify(metadata),
@@ -77,11 +77,12 @@ export class OidcTokenRefresher {
77
77
  const scope = generateScope(deviceId);
78
78
 
79
79
  this.oidcClient = new OidcClient({
80
- ...config.metadata,
80
+ metadata: config,
81
+ signingKeys: config.signingKeys ?? undefined,
81
82
  client_id: clientId,
82
83
  scope,
83
84
  redirect_uri: redirectUri,
84
- authority: config.metadata.issuer,
85
+ authority: config.issuer,
85
86
  stateStore: new WebStorageStateStore({ prefix: "mx_oidc_", store: window.sessionStorage }),
86
87
  });
87
88
  } catch (error) {
@@ -20,13 +20,28 @@ import { IdTokenClaims, OidcMetadata, SigninResponse } from "oidc-client-ts";
20
20
  import { logger } from "../logger.ts";
21
21
  import { OidcError } from "./error.ts";
22
22
 
23
- export type ValidatedIssuerConfig = {
24
- authorizationEndpoint: string;
25
- tokenEndpoint: string;
26
- registrationEndpoint?: string;
27
- accountManagementEndpoint?: string;
28
- accountManagementActionsSupported?: string[];
29
- };
23
+ /**
24
+ * Metadata from OIDC authority discovery
25
+ * With validated properties required in type
26
+ */
27
+ export type ValidatedAuthMetadata = Partial<OidcMetadata> &
28
+ Pick<
29
+ OidcMetadata,
30
+ | "issuer"
31
+ | "authorization_endpoint"
32
+ | "token_endpoint"
33
+ | "revocation_endpoint"
34
+ | "response_types_supported"
35
+ | "grant_types_supported"
36
+ | "code_challenge_methods_supported"
37
+ > & {
38
+ // MSC2965 extensions to the OIDC spec
39
+ account_management_uri?: string;
40
+ account_management_actions_supported?: string[];
41
+ // The OidcMetadata type from oidc-client-ts does not include `prompt_values_supported`
42
+ // even though it is part of the OIDC spec
43
+ prompt_values_supported?: string[];
44
+ };
30
45
 
31
46
  const isRecord = (value: unknown): value is Record<string, unknown> =>
32
47
  !!value && typeof value === "object" && !Array.isArray(value);
@@ -67,78 +82,39 @@ const requiredArrayValue = (wellKnown: Record<string, unknown>, key: string, val
67
82
  * Validates issuer `.well-known/openid-configuration`
68
83
  * As defined in RFC5785 https://openid.net/specs/openid-connect-discovery-1_0.html
69
84
  * validates that OP is compatible with Element's OIDC flow
70
- * @param wellKnown - json object
85
+ * @param authMetadata - json object
71
86
  * @returns valid issuer config
72
87
  * @throws Error - when issuer config is not found or is invalid
73
88
  */
74
- export const validateOIDCIssuerWellKnown = (wellKnown: unknown): ValidatedIssuerConfig => {
75
- if (!isRecord(wellKnown)) {
89
+ export const validateAuthMetadata = (authMetadata: unknown): ValidatedAuthMetadata => {
90
+ if (!isRecord(authMetadata)) {
76
91
  logger.error("Issuer configuration not found or malformed");
77
92
  throw new Error(OidcError.OpSupport);
78
93
  }
79
94
 
80
95
  const isInvalid = [
81
- requiredStringProperty(wellKnown, "authorization_endpoint"),
82
- requiredStringProperty(wellKnown, "token_endpoint"),
83
- requiredStringProperty(wellKnown, "revocation_endpoint"),
84
- optionalStringProperty(wellKnown, "registration_endpoint"),
85
- optionalStringProperty(wellKnown, "account_management_uri"),
86
- optionalStringProperty(wellKnown, "device_authorization_endpoint"),
87
- optionalStringArrayProperty(wellKnown, "account_management_actions_supported"),
88
- requiredArrayValue(wellKnown, "response_types_supported", "code"),
89
- requiredArrayValue(wellKnown, "grant_types_supported", "authorization_code"),
90
- requiredArrayValue(wellKnown, "code_challenge_methods_supported", "S256"),
96
+ requiredStringProperty(authMetadata, "issuer"),
97
+ requiredStringProperty(authMetadata, "authorization_endpoint"),
98
+ requiredStringProperty(authMetadata, "token_endpoint"),
99
+ requiredStringProperty(authMetadata, "revocation_endpoint"),
100
+ optionalStringProperty(authMetadata, "registration_endpoint"),
101
+ optionalStringProperty(authMetadata, "account_management_uri"),
102
+ optionalStringProperty(authMetadata, "device_authorization_endpoint"),
103
+ optionalStringArrayProperty(authMetadata, "account_management_actions_supported"),
104
+ requiredArrayValue(authMetadata, "response_types_supported", "code"),
105
+ requiredArrayValue(authMetadata, "grant_types_supported", "authorization_code"),
106
+ requiredArrayValue(authMetadata, "code_challenge_methods_supported", "S256"),
107
+ optionalStringArrayProperty(authMetadata, "prompt_values_supported"),
91
108
  ].some((isValid) => !isValid);
92
109
 
93
110
  if (!isInvalid) {
94
- return {
95
- authorizationEndpoint: <string>wellKnown["authorization_endpoint"],
96
- tokenEndpoint: <string>wellKnown["token_endpoint"],
97
- registrationEndpoint: <string>wellKnown["registration_endpoint"],
98
- accountManagementEndpoint: <string>wellKnown["account_management_uri"],
99
- accountManagementActionsSupported: <string[]>wellKnown["account_management_actions_supported"],
100
- };
111
+ return authMetadata as ValidatedAuthMetadata;
101
112
  }
102
113
 
103
114
  logger.error("Issuer configuration not valid");
104
115
  throw new Error(OidcError.OpSupport);
105
116
  };
106
117
 
107
- /**
108
- * Metadata from OIDC authority discovery
109
- * With validated properties required in type
110
- */
111
- export type ValidatedIssuerMetadata = Partial<OidcMetadata> &
112
- Pick<
113
- OidcMetadata,
114
- | "issuer"
115
- | "authorization_endpoint"
116
- | "token_endpoint"
117
- | "registration_endpoint"
118
- | "revocation_endpoint"
119
- | "response_types_supported"
120
- | "grant_types_supported"
121
- | "code_challenge_methods_supported"
122
- | "device_authorization_endpoint"
123
- > & {
124
- // MSC2965 extensions to the OIDC spec
125
- account_management_uri?: string;
126
- account_management_actions_supported?: string[];
127
- };
128
-
129
- /**
130
- * Wraps validateOIDCIssuerWellKnown in a type assertion
131
- * that asserts expected properties are present
132
- * (Typescript assertions cannot be arrow functions)
133
- * @param metadata - issuer openid-configuration response
134
- * @throws when metadata validation fails
135
- */
136
- export function isValidatedIssuerMetadata(
137
- metadata: Partial<OidcMetadata>,
138
- ): asserts metadata is ValidatedIssuerMetadata {
139
- validateOIDCIssuerWellKnown(metadata);
140
- }
141
-
142
118
  export const decodeIdToken = (token: string): IdTokenClaims => {
143
119
  try {
144
120
  return jwtDecode<IdTokenClaims>(token);
@@ -179,7 +155,8 @@ export const validateIdToken = (
179
155
  * The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience, or if it contains additional audiences not trusted by the Client.
180
156
  * EW: Don't accept tokens with other untrusted audiences
181
157
  * */
182
- if (claims.aud !== clientId) {
158
+ const sanitisedAuds = typeof claims.aud === "string" ? [claims.aud] : claims.aud;
159
+ if (!sanitisedAuds.includes(clientId)) {
183
160
  throw new Error("Invalid audience");
184
161
  }
185
162