@medplum/core 1.0.6 → 2.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.
@@ -146,6 +146,7 @@ export interface BaseLoginRequest {
146
146
  readonly codeChallengeMethod?: string;
147
147
  readonly googleClientId?: string;
148
148
  readonly launch?: string;
149
+ readonly redirectUri?: string;
149
150
  }
150
151
  export interface EmailPasswordLoginRequest extends BaseLoginRequest {
151
152
  readonly email: string;
@@ -338,6 +339,12 @@ export declare class MedplumClient extends EventTarget {
338
339
  * @category Authentication
339
340
  */
340
341
  clear(): void;
342
+ /**
343
+ * Clears the active login from local storage.
344
+ * Does not clear all local storage (such as other logins).
345
+ * @category Authentication
346
+ */
347
+ clearActiveLogin(): void;
341
348
  /**
342
349
  * Invalidates any cached values or cached requests for the given URL.
343
350
  * @category Caching
@@ -467,10 +474,15 @@ export declare class MedplumClient extends EventTarget {
467
474
  * @returns Promise to the authentication response.
468
475
  */
469
476
  startGoogleLogin(loginRequest: GoogleLoginRequest): Promise<LoginAuthenticationResponse>;
470
- getCodeChallenge(loginRequest: BaseLoginRequest): {
471
- codeChallenge?: string;
472
- codeChallengeMethod?: string;
473
- };
477
+ /**
478
+ * Returns the PKCE code challenge and method.
479
+ * If the login request already includes a code challenge, it is returned.
480
+ * Otherwise, a new PKCE code challenge is generated.
481
+ * @category Authentication
482
+ * @param loginRequest The original login request.
483
+ * @returns The PKCE code challenge and method.
484
+ */
485
+ ensureCodeChallenge<T extends BaseLoginRequest>(loginRequest: T): Promise<T>;
474
486
  /**
475
487
  * Signs out locally.
476
488
  * Does not invalidate tokens with the server.
@@ -482,8 +494,9 @@ export declare class MedplumClient extends EventTarget {
482
494
  * Returns true if the user is signed in.
483
495
  * This may result in navigating away to the sign in page.
484
496
  * @category Authentication
497
+ * @param loginParams Optional login parameters.
485
498
  */
486
- signInWithRedirect(): Promise<ProfileResource | void>;
499
+ signInWithRedirect(loginParams?: Partial<BaseLoginRequest>): Promise<ProfileResource | void>;
487
500
  /**
488
501
  * Tries to sign out the user.
489
502
  * See: https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html
@@ -1154,7 +1167,10 @@ export declare class MedplumClient extends EventTarget {
1154
1167
  * Starts a new PKCE flow.
1155
1168
  * These PKCE values are stateful, and must survive redirects and page refreshes.
1156
1169
  */
1157
- startPkce(): Promise<void>;
1170
+ startPkce(): Promise<{
1171
+ codeChallengeMethod: string;
1172
+ codeChallenge: string;
1173
+ }>;
1158
1174
  /**
1159
1175
  * Processes an OAuth authorization code.
1160
1176
  * See: https://openid.net/specs/openid-connect-core-1_0.html#TokenRequest
@@ -721,6 +721,21 @@
721
721
  }
722
722
  return curr?.valueString;
723
723
  }
724
+ /**
725
+ * Returns an extension by extension URLs.
726
+ * @param resource The base resource.
727
+ * @param urls Array of extension URLs. Each entry represents a nested extension.
728
+ * @returns The extension object if found; undefined otherwise.
729
+ */
730
+ function getExtension(resource, ...urls) {
731
+ // Let curr be the current resource or extension. Extensions can be nested.
732
+ let curr = resource;
733
+ // For each of the urls, try to find a matching nested extension.
734
+ for (let i = 0; i < urls.length && curr; i++) {
735
+ curr = curr?.extension?.find((e) => e.url === urls[i]);
736
+ }
737
+ return curr;
738
+ }
724
739
  /**
725
740
  * FHIR JSON stringify.
726
741
  * Removes properties with empty string values.
@@ -6468,7 +6483,7 @@
6468
6483
  // PKCE auth based on:
6469
6484
  // https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
6470
6485
  var _MedplumClient_instances, _MedplumClient_fetch, _MedplumClient_createPdf, _MedplumClient_storage, _MedplumClient_requestCache, _MedplumClient_cacheTime, _MedplumClient_baseUrl, _MedplumClient_authorizeUrl, _MedplumClient_tokenUrl, _MedplumClient_logoutUrl, _MedplumClient_onUnauthenticated, _MedplumClient_clientId, _MedplumClient_clientSecret, _MedplumClient_accessToken, _MedplumClient_refreshToken, _MedplumClient_refreshPromise, _MedplumClient_profilePromise, _MedplumClient_profile, _MedplumClient_config, _MedplumClient_addLogin, _MedplumClient_refreshProfile, _MedplumClient_getCacheEntry, _MedplumClient_setCacheEntry, _MedplumClient_request, _MedplumClient_addFetchOptionsDefaults, _MedplumClient_setRequestContentType, _MedplumClient_setRequestBody, _MedplumClient_handleUnauthenticated, _MedplumClient_requestAuthorization, _MedplumClient_refresh, _MedplumClient_fetchTokens, _MedplumClient_verifyTokens, _MedplumClient_setupStorageListener;
6471
- const MEDPLUM_VERSION = "1.0.6-5860113c";
6486
+ const MEDPLUM_VERSION = "2.0.0-7fe99080";
6472
6487
  const DEFAULT_BASE_URL = 'https://api.medplum.com/';
6473
6488
  const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
6474
6489
  const DEFAULT_CACHE_TIME = 60000; // 60 seconds
@@ -6589,6 +6604,15 @@
6589
6604
  */
6590
6605
  clear() {
6591
6606
  __classPrivateFieldGet(this, _MedplumClient_storage, "f").clear();
6607
+ this.clearActiveLogin();
6608
+ }
6609
+ /**
6610
+ * Clears the active login from local storage.
6611
+ * Does not clear all local storage (such as other logins).
6612
+ * @category Authentication
6613
+ */
6614
+ clearActiveLogin() {
6615
+ __classPrivateFieldGet(this, _MedplumClient_storage, "f").setString('activeLogin', undefined);
6592
6616
  __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
6593
6617
  __classPrivateFieldSet(this, _MedplumClient_accessToken, undefined, "f");
6594
6618
  __classPrivateFieldSet(this, _MedplumClient_refreshToken, undefined, "f");
@@ -6740,11 +6764,11 @@
6740
6764
  * @returns Promise to the authentication response.
6741
6765
  */
6742
6766
  async startNewUser(newUserRequest) {
6743
- await this.startPkce();
6767
+ const { codeChallengeMethod, codeChallenge } = await this.startPkce();
6744
6768
  return this.post('auth/newuser', {
6745
6769
  ...newUserRequest,
6746
- codeChallengeMethod: 'S256',
6747
- codeChallenge: sessionStorage.getItem('codeChallenge'),
6770
+ codeChallengeMethod,
6771
+ codeChallenge,
6748
6772
  });
6749
6773
  }
6750
6774
  /**
@@ -6776,13 +6800,10 @@
6776
6800
  * @returns Promise to the authentication response.
6777
6801
  */
6778
6802
  async startLogin(loginRequest) {
6779
- const { codeChallenge, codeChallengeMethod } = this.getCodeChallenge(loginRequest);
6780
6803
  return this.post('auth/login', {
6781
- ...loginRequest,
6804
+ ...(await this.ensureCodeChallenge(loginRequest)),
6782
6805
  clientId: loginRequest.clientId ?? __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
6783
6806
  scope: loginRequest.scope,
6784
- codeChallengeMethod,
6785
- codeChallenge,
6786
6807
  });
6787
6808
  }
6788
6809
  /**
@@ -6794,30 +6815,25 @@
6794
6815
  * @returns Promise to the authentication response.
6795
6816
  */
6796
6817
  async startGoogleLogin(loginRequest) {
6797
- const { codeChallenge, codeChallengeMethod } = this.getCodeChallenge(loginRequest);
6798
6818
  return this.post('auth/google', {
6799
- ...loginRequest,
6819
+ ...(await this.ensureCodeChallenge(loginRequest)),
6800
6820
  clientId: loginRequest.clientId ?? __classPrivateFieldGet(this, _MedplumClient_clientId, "f"),
6801
6821
  scope: loginRequest.scope,
6802
- codeChallengeMethod,
6803
- codeChallenge,
6804
6822
  });
6805
6823
  }
6806
- getCodeChallenge(loginRequest) {
6824
+ /**
6825
+ * Returns the PKCE code challenge and method.
6826
+ * If the login request already includes a code challenge, it is returned.
6827
+ * Otherwise, a new PKCE code challenge is generated.
6828
+ * @category Authentication
6829
+ * @param loginRequest The original login request.
6830
+ * @returns The PKCE code challenge and method.
6831
+ */
6832
+ async ensureCodeChallenge(loginRequest) {
6807
6833
  if (loginRequest.codeChallenge) {
6808
- return {
6809
- codeChallenge: loginRequest.codeChallenge,
6810
- codeChallengeMethod: loginRequest.codeChallengeMethod,
6811
- };
6812
- }
6813
- const codeChallenge = sessionStorage.getItem('codeChallenge');
6814
- if (codeChallenge) {
6815
- return {
6816
- codeChallenge,
6817
- codeChallengeMethod: 'S256',
6818
- };
6834
+ return loginRequest;
6819
6835
  }
6820
- return {};
6836
+ return { ...loginRequest, ...(await this.startPkce()) };
6821
6837
  }
6822
6838
  /**
6823
6839
  * Signs out locally.
@@ -6832,12 +6848,13 @@
6832
6848
  * Returns true if the user is signed in.
6833
6849
  * This may result in navigating away to the sign in page.
6834
6850
  * @category Authentication
6851
+ * @param loginParams Optional login parameters.
6835
6852
  */
6836
- async signInWithRedirect() {
6853
+ async signInWithRedirect(loginParams) {
6837
6854
  const urlParams = new URLSearchParams(window.location.search);
6838
6855
  const code = urlParams.get('code');
6839
6856
  if (!code) {
6840
- await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this);
6857
+ await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_requestAuthorization).call(this, loginParams);
6841
6858
  return undefined;
6842
6859
  }
6843
6860
  else {
@@ -7690,13 +7707,11 @@
7690
7707
  * @category Authentication
7691
7708
  */
7692
7709
  async setActiveLogin(login) {
7710
+ this.clearActiveLogin();
7693
7711
  __classPrivateFieldSet(this, _MedplumClient_accessToken, login.accessToken, "f");
7694
7712
  __classPrivateFieldSet(this, _MedplumClient_refreshToken, login.refreshToken, "f");
7695
- __classPrivateFieldSet(this, _MedplumClient_profile, undefined, "f");
7696
- __classPrivateFieldSet(this, _MedplumClient_config, undefined, "f");
7697
7713
  __classPrivateFieldGet(this, _MedplumClient_storage, "f").setObject('activeLogin', login);
7698
7714
  __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_addLogin).call(this, login);
7699
- __classPrivateFieldGet(this, _MedplumClient_requestCache, "f").clear();
7700
7715
  __classPrivateFieldSet(this, _MedplumClient_refreshPromise, undefined, "f");
7701
7716
  await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refreshProfile).call(this);
7702
7717
  }
@@ -7775,6 +7790,7 @@
7775
7790
  const arrayHash = await encryptSHA256(codeVerifier);
7776
7791
  const codeChallenge = arrayBufferToBase64(arrayHash).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
7777
7792
  sessionStorage.setItem('codeChallenge', codeChallenge);
7793
+ return { codeChallengeMethod: 'S256', codeChallenge };
7778
7794
  }
7779
7795
  /**
7780
7796
  * Processes an OAuth authorization code.
@@ -7910,7 +7926,7 @@
7910
7926
  if (__classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_refresh).call(this)) {
7911
7927
  return __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_request).call(this, method, url, options);
7912
7928
  }
7913
- this.clear();
7929
+ this.clearActiveLogin();
7914
7930
  if (__classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f")) {
7915
7931
  __classPrivateFieldGet(this, _MedplumClient_onUnauthenticated, "f").call(this);
7916
7932
  }
@@ -7921,15 +7937,16 @@
7921
7937
  * Clears all auth state including local storage and session storage.
7922
7938
  * See: https://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
7923
7939
  */
7924
- async function _MedplumClient_requestAuthorization() {
7925
- await this.startPkce();
7940
+ async function _MedplumClient_requestAuthorization(loginParams) {
7941
+ const loginRequest = await this.ensureCodeChallenge(loginParams || {});
7926
7942
  const url = new URL(__classPrivateFieldGet(this, _MedplumClient_authorizeUrl, "f"));
7927
7943
  url.searchParams.set('response_type', 'code');
7928
7944
  url.searchParams.set('state', sessionStorage.getItem('pkceState'));
7929
- url.searchParams.set('client_id', __classPrivateFieldGet(this, _MedplumClient_clientId, "f"));
7930
- url.searchParams.set('redirect_uri', getBaseUrl());
7931
- url.searchParams.set('code_challenge_method', 'S256');
7932
- url.searchParams.set('code_challenge', sessionStorage.getItem('codeChallenge'));
7945
+ url.searchParams.set('client_id', loginRequest.clientId || __classPrivateFieldGet(this, _MedplumClient_clientId, "f"));
7946
+ url.searchParams.set('redirect_uri', loginRequest.redirectUri || getBaseUrl());
7947
+ url.searchParams.set('code_challenge_method', loginRequest.codeChallengeMethod);
7948
+ url.searchParams.set('code_challenge', loginRequest.codeChallenge);
7949
+ url.searchParams.set('scope', loginRequest.scope || 'openid profile');
7933
7950
  window.location.assign(url.toString());
7934
7951
  }, _MedplumClient_refresh = function _MedplumClient_refresh() {
7935
7952
  if (__classPrivateFieldGet(this, _MedplumClient_refreshPromise, "f")) {
@@ -7955,20 +7972,19 @@
7955
7972
  * @param formBody Token parameters in URL encoded format.
7956
7973
  */
7957
7974
  async function _MedplumClient_fetchTokens(formBody) {
7958
- return __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, __classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f"), {
7975
+ const response = await __classPrivateFieldGet(this, _MedplumClient_fetch, "f").call(this, __classPrivateFieldGet(this, _MedplumClient_tokenUrl, "f"), {
7959
7976
  method: 'POST',
7960
7977
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
7961
7978
  body: formBody,
7962
7979
  credentials: 'include',
7963
- })
7964
- .then((response) => {
7965
- if (!response.ok) {
7966
- throw new Error('Failed to fetch tokens');
7967
- }
7968
- return response.json();
7969
- })
7970
- .then((tokens) => __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_verifyTokens).call(this, tokens))
7971
- .then(() => this.getProfile());
7980
+ });
7981
+ if (!response.ok) {
7982
+ this.clearActiveLogin();
7983
+ throw new Error('Failed to fetch tokens');
7984
+ }
7985
+ const tokens = await response.json();
7986
+ await __classPrivateFieldGet(this, _MedplumClient_instances, "m", _MedplumClient_verifyTokens).call(this, tokens);
7987
+ return this.getProfile();
7972
7988
  }, _MedplumClient_verifyTokens =
7973
7989
  /**
7974
7990
  * Verifies the tokens received from the auth server.
@@ -7981,15 +7997,15 @@
7981
7997
  // Verify token has not expired
7982
7998
  const tokenPayload = parseJWTPayload(token);
7983
7999
  if (Date.now() >= tokenPayload.exp * 1000) {
7984
- this.clear();
8000
+ this.clearActiveLogin();
7985
8001
  throw new Error('Token expired');
7986
8002
  }
7987
8003
  // Verify app_client_id
7988
8004
  if (__classPrivateFieldGet(this, _MedplumClient_clientId, "f") && tokenPayload.client_id !== __classPrivateFieldGet(this, _MedplumClient_clientId, "f")) {
7989
- this.clear();
8005
+ this.clearActiveLogin();
7990
8006
  throw new Error('Token was not issued for this audience');
7991
8007
  }
7992
- await this.setActiveLogin({
8008
+ return this.setActiveLogin({
7993
8009
  accessToken: token,
7994
8010
  refreshToken: tokens.refresh_token,
7995
8011
  project: tokens.project,
@@ -11731,6 +11747,7 @@
11731
11747
  exports.getDisplayString = getDisplayString;
11732
11748
  exports.getElementDefinition = getElementDefinition;
11733
11749
  exports.getExpressionForResourceType = getExpressionForResourceType;
11750
+ exports.getExtension = getExtension;
11734
11751
  exports.getExtensionValue = getExtensionValue;
11735
11752
  exports.getIdentifier = getIdentifier;
11736
11753
  exports.getImageSrc = getImageSrc;