@dereekb/firebase-server 14.2.0 → 14.4.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/test/index.esm.js CHANGED
@@ -13,7 +13,7 @@ import { HttpsError } from 'firebase-functions/https';
13
13
  import { BaseError } from 'make-error';
14
14
  import request from 'supertest';
15
15
  import { randomBytes, createHash } from 'node:crypto';
16
- import { OidcClientService, JwksService, OidcAccountService } from '@dereekb/firebase-server/oidc';
16
+ import { OidcProviderConfigService, OidcClientService, JwksService, OidcAccountService } from '@dereekb/firebase-server/oidc';
17
17
 
18
18
  function _assert_this_initialized$9(self) {
19
19
  if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
@@ -118,7 +118,7 @@ function _object_spread$6(target) {
118
118
  }
119
119
  return target;
120
120
  }
121
- function ownKeys$3(object, enumerableOnly) {
121
+ function ownKeys$4(object, enumerableOnly) {
122
122
  var keys = Object.keys(object);
123
123
  if (Object.getOwnPropertySymbols) {
124
124
  var symbols = Object.getOwnPropertySymbols(object);
@@ -126,11 +126,11 @@ function ownKeys$3(object, enumerableOnly) {
126
126
  }
127
127
  return keys;
128
128
  }
129
- function _object_spread_props$3(target, source) {
129
+ function _object_spread_props$4(target, source) {
130
130
  source = source != null ? source : {};
131
131
  if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
132
132
  else {
133
- ownKeys$3(Object(source)).forEach(function(key) {
133
+ ownKeys$4(Object(source)).forEach(function(key) {
134
134
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
135
135
  });
136
136
  }
@@ -482,7 +482,7 @@ function _type_of$9(obj) {
482
482
  value: function callAuthBlockingFunction(input) {
483
483
  var fn = input.fn, userRecord = input.userRecord, eventType = input.eventType, eventOverride = input.eventOverride, _input_skipJsonConversion = input.skipJsonConversion, skipJsonConversion = _input_skipJsonConversion === void 0 ? false : _input_skipJsonConversion;
484
484
  var timestamp = new Date().toISOString();
485
- var event = _object_spread_props$3(_object_spread$6({
485
+ var event = _object_spread_props$4(_object_spread$6({
486
486
  ipAddress: '127.0.0.1',
487
487
  userAgent: 'testing',
488
488
  eventId: '0',
@@ -583,7 +583,7 @@ function _type_of$9(obj) {
583
583
  }
584
584
  return [
585
585
  4,
586
- auth.createUser(_object_spread$6(_object_spread_props$3(_object_spread$6({
586
+ auth.createUser(_object_spread$6(_object_spread_props$4(_object_spread$6({
587
587
  uid: uid,
588
588
  displayName: 'Test Person'
589
589
  }, details), {
@@ -787,7 +787,7 @@ function _type_of$9(obj) {
787
787
  var _ref;
788
788
  var _decoded_claims;
789
789
  var decoded = decode(token);
790
- var decodedToken = _object_spread_props$3(_object_spread$6({}, decoded, decoded.claims), {
790
+ var decodedToken = _object_spread_props$4(_object_spread$6({}, decoded, decoded.claims), {
791
791
  auth_time: decoded.iat,
792
792
  firebase: (_ref = (_decoded_claims = decoded.claims) === null || _decoded_claims === void 0 ? void 0 : _decoded_claims.firebase) !== null && _ref !== void 0 ? _ref : {}
793
793
  });
@@ -2498,7 +2498,7 @@ function _object_spread$4(target) {
2498
2498
  }
2499
2499
  return target;
2500
2500
  }
2501
- function ownKeys$2(object, enumerableOnly) {
2501
+ function ownKeys$3(object, enumerableOnly) {
2502
2502
  var keys = Object.keys(object);
2503
2503
  if (Object.getOwnPropertySymbols) {
2504
2504
  var symbols = Object.getOwnPropertySymbols(object);
@@ -2506,11 +2506,11 @@ function ownKeys$2(object, enumerableOnly) {
2506
2506
  }
2507
2507
  return keys;
2508
2508
  }
2509
- function _object_spread_props$2(target, source) {
2509
+ function _object_spread_props$3(target, source) {
2510
2510
  source = source != null ? source : {};
2511
2511
  if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
2512
2512
  else {
2513
- ownKeys$2(Object(source)).forEach(function(key) {
2513
+ ownKeys$3(Object(source)).forEach(function(key) {
2514
2514
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
2515
2515
  });
2516
2516
  }
@@ -2640,7 +2640,7 @@ function _ts_generator$5(thisArg, body) {
2640
2640
  return _async_to_generator$5(function() {
2641
2641
  var _ref, request;
2642
2642
  return _ts_generator$5(this, function(_state) {
2643
- request = _object_spread_props$2(_object_spread$4({}, context), {
2643
+ request = _object_spread_props$3(_object_spread$4({}, context), {
2644
2644
  data: data,
2645
2645
  // NOTE: These will typically not be used/available as they are express.js properties that are not available or useful to the handlers
2646
2646
  rawRequest: (_ref = context === null || context === void 0 ? void 0 : context.rawRequest) !== null && _ref !== void 0 ? _ref : {},
@@ -2823,7 +2823,7 @@ function _object_spread$3(target) {
2823
2823
  }
2824
2824
  return target;
2825
2825
  }
2826
- function ownKeys$1(object, enumerableOnly) {
2826
+ function ownKeys$2(object, enumerableOnly) {
2827
2827
  var keys = Object.keys(object);
2828
2828
  if (Object.getOwnPropertySymbols) {
2829
2829
  var symbols = Object.getOwnPropertySymbols(object);
@@ -2831,11 +2831,11 @@ function ownKeys$1(object, enumerableOnly) {
2831
2831
  }
2832
2832
  return keys;
2833
2833
  }
2834
- function _object_spread_props$1(target, source) {
2834
+ function _object_spread_props$2(target, source) {
2835
2835
  source = source != null ? source : {};
2836
2836
  if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
2837
2837
  else {
2838
- ownKeys$1(Object(source)).forEach(function(key) {
2838
+ ownKeys$2(Object(source)).forEach(function(key) {
2839
2839
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
2840
2840
  });
2841
2841
  }
@@ -3129,7 +3129,7 @@ var functionsInitialized = false;
3129
3129
  */ var firebaseAdminFunctionTestBuilder = testContextBuilder({
3130
3130
  buildConfig: function buildConfig(input) {
3131
3131
  var _ref;
3132
- var config = _object_spread_props$1(_object_spread$3({}, input), {
3132
+ var config = _object_spread_props$2(_object_spread$3({}, input), {
3133
3133
  useFunctionSingletonContext: (_ref = input === null || input === void 0 ? void 0 : input.useFunctionSingletonContext) !== null && _ref !== void 0 ? _ref : DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT
3134
3134
  });
3135
3135
  return config;
@@ -3390,7 +3390,7 @@ function _object_spread$2(target) {
3390
3390
  }
3391
3391
  return target;
3392
3392
  }
3393
- function ownKeys(object, enumerableOnly) {
3393
+ function ownKeys$1(object, enumerableOnly) {
3394
3394
  var keys = Object.keys(object);
3395
3395
  if (Object.getOwnPropertySymbols) {
3396
3396
  var symbols = Object.getOwnPropertySymbols(object);
@@ -3398,11 +3398,11 @@ function ownKeys(object, enumerableOnly) {
3398
3398
  }
3399
3399
  return keys;
3400
3400
  }
3401
- function _object_spread_props(target, source) {
3401
+ function _object_spread_props$1(target, source) {
3402
3402
  source = source != null ? source : {};
3403
3403
  if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
3404
3404
  else {
3405
- ownKeys(Object(source)).forEach(function(key) {
3405
+ ownKeys$1(Object(source)).forEach(function(key) {
3406
3406
  Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
3407
3407
  });
3408
3408
  }
@@ -3716,7 +3716,7 @@ function _unsupported_iterable_to_array$1(o, minLen) {
3716
3716
  switch(_state.label){
3717
3717
  case 0:
3718
3718
  additionalProviders = (_makeProviders = makeProviders(f.instance)) !== null && _makeProviders !== void 0 ? _makeProviders : [];
3719
- rootModule = buildNestServerRootModule(_object_spread_props(_object_spread$2({}, serverInstanceConfig), {
3719
+ rootModule = buildNestServerRootModule(_object_spread_props$1(_object_spread$2({}, serverInstanceConfig), {
3720
3720
  modules: _to_consumable_array$1(asArray(nestModules)).concat(_to_consumable_array$1(asArray(serverInstanceConfig === null || serverInstanceConfig === void 0 ? void 0 : serverInstanceConfig.modules))),
3721
3721
  firebaseAppGetter: config.injectFirebaseServerAppTokenProvider ? asGetter(f.instance.app) : undefined,
3722
3722
  providers: additionalProviders,
@@ -4602,6 +4602,24 @@ function _object_spread(target) {
4602
4602
  }
4603
4603
  return target;
4604
4604
  }
4605
+ function ownKeys(object, enumerableOnly) {
4606
+ var keys = Object.keys(object);
4607
+ if (Object.getOwnPropertySymbols) {
4608
+ var symbols = Object.getOwnPropertySymbols(object);
4609
+ keys.push.apply(keys, symbols);
4610
+ }
4611
+ return keys;
4612
+ }
4613
+ function _object_spread_props(target, source) {
4614
+ source = source != null ? source : {};
4615
+ if (Object.getOwnPropertyDescriptors) Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
4616
+ else {
4617
+ ownKeys(Object(source)).forEach(function(key) {
4618
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
4619
+ });
4620
+ }
4621
+ return target;
4622
+ }
4605
4623
  function _sliced_to_array(arr, i) {
4606
4624
  return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
4607
4625
  }
@@ -4767,6 +4785,16 @@ function _unsupported_iterable_to_array(o, minLen) {
4767
4785
  var url = location.startsWith('/') ? new URL(location, 'http://localhost') : new URL(location);
4768
4786
  return url.searchParams.get('uid');
4769
4787
  }
4788
+ /**
4789
+ * Whether a redirect response points at the given interaction frontend URL (the login or consent screen).
4790
+ *
4791
+ * @param res - Supertest response to inspect.
4792
+ * @param frontendUrl - The frontend URL to match, without its query string.
4793
+ * @returns True when the response's `Location` header starts with `frontendUrl`.
4794
+ */ function isRedirectTo(res, frontendUrl) {
4795
+ var location = res.headers['location'];
4796
+ return location != null && location.startsWith(frontendUrl);
4797
+ }
4770
4798
  /**
4771
4799
  * Cookie jar helpers for the OAuth flow.
4772
4800
  *
@@ -4859,29 +4887,39 @@ function _unsupported_iterable_to_array(o, minLen) {
4859
4887
  })();
4860
4888
  }
4861
4889
  /**
4862
- * Performs the full OAuth authorization code flow with PKCE and returns tokens.
4890
+ * Performs the OAuth authorization code flow with PKCE, up to the configured
4891
+ * {@link OAuthTestFlowConfig.stopAtStage}.
4863
4892
  *
4864
4893
  * Steps: create client → PKCE → auth redirect → login → consent → code exchange → token
4865
4894
  *
4866
- * @param input - Bag of services and overrides needed to drive the flow end-to-end.
4895
+ * Stopping early is how a test asserts on an intermediate the completed flow discards — the raw
4896
+ * `/oidc/auth` response, the consent screen's offered `scopes`, or a callback that came back carrying
4897
+ * an `error` instead of a `code`. A stage before `'token'` never exchanges the code, so it never
4898
+ * throws on such a callback; the caller asserts on {@link OAuthTestFlowResult.callbackUrl} instead,
4899
+ * and can run its own `/oidc/token` request with {@link OAuthTestFlowResult.codeVerifier} and the
4900
+ * session's cookie header.
4901
+ *
4902
+ * @param input - Bag of services and overrides needed to drive the flow.
4867
4903
  * @param input.server - HTTP server returned by `nestApp.getHttpServer()` against which all supertest requests are issued.
4868
4904
  * @param input.oidcClientService - Service used to create the OAuth client whose credentials drive the flow.
4869
4905
  * @param input.nestApp - Initialized NestJS application; used to resolve {@link OidcAccountService} for project-id-derived ID tokens and default scopes.
4870
- * @param input.uid - Firebase user ID for whom the test ID token is minted and the OAuth flow is authorized.
4871
- * @param input.config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles).
4872
- * @returns The exchanged access token and ID token from the OIDC `/token` endpoint.
4873
- * @throws {Error} When the token exchange step fails (the response body and status are included in the message).
4874
- */ function performFullOAuthFlow(input) {
4906
+ * @param input.uid - Firebase user ID for whom the test ID token is minted; required for any stage past `'auth'`.
4907
+ * @param input.config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles, stop stage).
4908
+ * @returns The intermediates the flow produced, plus the tokens when it ran to the `'token'` stage.
4909
+ * @throws {Error} When a stage past `'auth'` is requested without a `uid`, or when the token exchange step fails (the response body and status are included in the message).
4910
+ */ function performOAuthFlow(input) {
4875
4911
  return _async_to_generator$1(function() {
4876
- var _ref, _ref1, _ref2, server, oidcClientService, nestApp, uid, config, _createCookieJar, collectCookies, cookieHeader, redirectUri, clientName, tokenEndpointAuthMethod, providerProfiles, scopes, _ref3, client_id, client_secret, codeVerifier, codeChallenge, authRes, loginUid, idToken, loginRes, resumeAfterLoginPath, consentRedirectRes, consentUid, consentRes, resumeAfterConsentPath, callbackRedirectRes, callbackUrl, authorizationCode, tokenRes;
4912
+ var _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _config_session, _config_session1, server, oidcClientService, nestApp, uid, config, cookieJar, collectCookies, cookieHeader, stage, redirectUri, clientName, tokenEndpointAuthMethod, providerProfiles, scopes, client, _ref6, client_id, client_secret, client_id1, client_secret1, codeVerifier, codeChallenge, authRes, consentRedirectUrl, callbackUrl, tokenBody, idToken, providerConfigService, interactionRes, loginUid, loginRes, resumeAfterLoginPath, callbackRedirectRes, consentUid, consentRes, resumeAfterConsentPath, authorizationCode, tokenRes;
4877
4913
  return _ts_generator$1(this, function(_state) {
4878
4914
  switch(_state.label){
4879
4915
  case 0:
4880
4916
  server = input.server, oidcClientService = input.oidcClientService, nestApp = input.nestApp, uid = input.uid, config = input.config;
4881
- _createCookieJar = createCookieJar(), collectCookies = _createCookieJar.collectCookies, cookieHeader = _createCookieJar.cookieHeader;
4882
- redirectUri = (_ref = config === null || config === void 0 ? void 0 : config.redirectUri) !== null && _ref !== void 0 ? _ref : 'https://example.com/callback';
4883
- clientName = (_ref1 = config === null || config === void 0 ? void 0 : config.clientName) !== null && _ref1 !== void 0 ? _ref1 : 'test-oauth-context';
4884
- tokenEndpointAuthMethod = (_ref2 = config === null || config === void 0 ? void 0 : config.tokenEndpointAuthMethod) !== null && _ref2 !== void 0 ? _ref2 : 'client_secret_post';
4917
+ cookieJar = (_ref = config === null || config === void 0 ? void 0 : (_config_session = config.session) === null || _config_session === void 0 ? void 0 : _config_session.cookieJar) !== null && _ref !== void 0 ? _ref : createCookieJar();
4918
+ collectCookies = cookieJar.collectCookies, cookieHeader = cookieJar.cookieHeader;
4919
+ stage = (_ref1 = config === null || config === void 0 ? void 0 : config.stopAtStage) !== null && _ref1 !== void 0 ? _ref1 : 'token';
4920
+ redirectUri = (_ref2 = (_ref3 = config === null || config === void 0 ? void 0 : (_config_session1 = config.session) === null || _config_session1 === void 0 ? void 0 : _config_session1.client.redirectUri) !== null && _ref3 !== void 0 ? _ref3 : config === null || config === void 0 ? void 0 : config.redirectUri) !== null && _ref2 !== void 0 ? _ref2 : 'https://example.com/callback';
4921
+ clientName = (_ref4 = config === null || config === void 0 ? void 0 : config.clientName) !== null && _ref4 !== void 0 ? _ref4 : 'test-oauth-context';
4922
+ tokenEndpointAuthMethod = (_ref5 = config === null || config === void 0 ? void 0 : config.tokenEndpointAuthMethod) !== null && _ref5 !== void 0 ? _ref5 : 'client_secret_post';
4885
4923
  providerProfiles = config === null || config === void 0 ? void 0 : config.providerProfiles;
4886
4924
  return [
4887
4925
  4,
@@ -4889,6 +4927,16 @@ function _unsupported_iterable_to_array(o, minLen) {
4889
4927
  ];
4890
4928
  case 1:
4891
4929
  scopes = _state.sent();
4930
+ if (!(config === null || config === void 0 ? void 0 : config.session)) return [
4931
+ 3,
4932
+ 2
4933
+ ];
4934
+ client = config.session.client;
4935
+ return [
4936
+ 3,
4937
+ 4
4938
+ ];
4939
+ case 2:
4892
4940
  return [
4893
4941
  4,
4894
4942
  oidcClientService.createClient(_object_spread({
@@ -4901,15 +4949,23 @@ function _unsupported_iterable_to_array(o, minLen) {
4901
4949
  dbx_provider_profiles: _to_consumable_array(providerProfiles)
4902
4950
  }))
4903
4951
  ];
4904
- case 2:
4905
- _ref3 = _state.sent(), client_id = _ref3.client_id, client_secret = _ref3.client_secret;
4952
+ case 3:
4953
+ _ref6 = _state.sent(), client_id = _ref6.client_id, client_secret = _ref6.client_secret;
4954
+ client = {
4955
+ client_id: client_id,
4956
+ client_secret: client_secret,
4957
+ redirectUri: redirectUri
4958
+ };
4959
+ _state.label = 4;
4960
+ case 4:
4961
+ client_id1 = client.client_id, client_secret1 = client.client_secret;
4906
4962
  // 2. Generate PKCE code_verifier and code_challenge
4907
4963
  codeVerifier = randomBytes(32).toString('base64url');
4908
4964
  codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
4909
4965
  return [
4910
4966
  4,
4911
- request(server).get('/oidc/auth').query({
4912
- client_id: client_id,
4967
+ request(server).get('/oidc/auth').query(_object_spread({
4968
+ client_id: client_id1,
4913
4969
  redirect_uri: redirectUri,
4914
4970
  response_type: 'code',
4915
4971
  scope: scopes,
@@ -4917,25 +4973,42 @@ function _unsupported_iterable_to_array(o, minLen) {
4917
4973
  code_challenge_method: 'S256',
4918
4974
  state: 'test-state',
4919
4975
  nonce: 'test-nonce'
4920
- }).redirects(0)
4976
+ }, (config === null || config === void 0 ? void 0 : config.prompt) == null ? {} : {
4977
+ prompt: config.prompt
4978
+ }, (config === null || config === void 0 ? void 0 : config.resource) == null ? {} : {
4979
+ resource: config.resource
4980
+ }, config === null || config === void 0 ? void 0 : config.extraAuthParams)).redirects(0)
4921
4981
  ];
4922
- case 3:
4982
+ case 5:
4923
4983
  authRes = _state.sent();
4924
4984
  collectCookies(authRes);
4925
- loginUid = extractInteractionUid(authRes);
4985
+ if (!(stage !== 'auth')) return [
4986
+ 3,
4987
+ 14
4988
+ ];
4989
+ if (uid == null) {
4990
+ throw new Error('performOAuthFlow requires a uid to reach the "'.concat(stage, '" stage (only the "auth" stage skips the login interaction).'));
4991
+ }
4926
4992
  return [
4927
4993
  4,
4928
4994
  createTestIdToken(nestApp, uid)
4929
4995
  ];
4930
- case 4:
4996
+ case 6:
4931
4997
  idToken = _state.sent();
4998
+ providerConfigService = nestApp.get(OidcProviderConfigService);
4999
+ interactionRes = authRes;
5000
+ if (!isRedirectTo(authRes, providerConfigService.appLoginUrl)) return [
5001
+ 3,
5002
+ 9
5003
+ ];
5004
+ loginUid = extractInteractionUid(authRes);
4932
5005
  return [
4933
5006
  4,
4934
5007
  request(server).post("/interaction/".concat(loginUid, "/login")).set('Cookie', cookieHeader()).send({
4935
5008
  idToken: idToken
4936
5009
  })
4937
5010
  ];
4938
- case 5:
5011
+ case 7:
4939
5012
  loginRes = _state.sent();
4940
5013
  // 5. Resume after login → consent redirect
4941
5014
  resumeAfterLoginPath = new URL(loginRes.body.redirectTo).pathname + new URL(loginRes.body.redirectTo).search;
@@ -4943,18 +5016,29 @@ function _unsupported_iterable_to_array(o, minLen) {
4943
5016
  4,
4944
5017
  request(server).get(resumeAfterLoginPath).set('Cookie', cookieHeader()).redirects(0)
4945
5018
  ];
4946
- case 6:
4947
- consentRedirectRes = _state.sent();
4948
- collectCookies(consentRedirectRes);
4949
- consentUid = extractInteractionUid(consentRedirectRes);
5019
+ case 8:
5020
+ interactionRes = _state.sent();
5021
+ collectCookies(interactionRes);
5022
+ _state.label = 9;
5023
+ case 9:
5024
+ // 6. Approve consent — unless nothing needed consenting and the provider went straight to the callback
5025
+ callbackRedirectRes = interactionRes;
5026
+ if (!isRedirectTo(interactionRes, providerConfigService.appConsentUrl)) return [
5027
+ 3,
5028
+ 12
5029
+ ];
5030
+ consentRedirectUrl = new URL(interactionRes.headers['location'], 'http://localhost');
5031
+ consentUid = extractInteractionUid(interactionRes);
4950
5032
  return [
4951
5033
  4,
4952
- request(server).post("/interaction/".concat(consentUid, "/consent")).set('Cookie', cookieHeader()).send({
5034
+ request(server).post("/interaction/".concat(consentUid, "/consent")).set('Cookie', cookieHeader()).send(_object_spread({
4953
5035
  idToken: idToken,
4954
5036
  approved: true
4955
- })
5037
+ }, (config === null || config === void 0 ? void 0 : config.grantedOIDCScopes) == null ? {} : {
5038
+ grantedOIDCScopes: _to_consumable_array(config.grantedOIDCScopes)
5039
+ }))
4956
5040
  ];
4957
- case 7:
5041
+ case 10:
4958
5042
  consentRes = _state.sent();
4959
5043
  // 7. Follow resume redirect → callback with authorization code
4960
5044
  resumeAfterConsentPath = new URL(consentRes.body.redirectTo).pathname + new URL(consentRes.body.redirectTo).search;
@@ -4962,49 +5046,112 @@ function _unsupported_iterable_to_array(o, minLen) {
4962
5046
  4,
4963
5047
  request(server).get(resumeAfterConsentPath).set('Cookie', cookieHeader()).redirects(0)
4964
5048
  ];
4965
- case 8:
5049
+ case 11:
4966
5050
  callbackRedirectRes = _state.sent();
4967
5051
  collectCookies(callbackRedirectRes);
5052
+ _state.label = 12;
5053
+ case 12:
4968
5054
  callbackUrl = new URL(callbackRedirectRes.headers['location']);
5055
+ if (!(stage !== 'callback')) return [
5056
+ 3,
5057
+ 14
5058
+ ];
5059
+ // 8. Exchange authorization code for tokens
4969
5060
  authorizationCode = callbackUrl.searchParams.get('code');
4970
5061
  return [
4971
5062
  4,
4972
- request(server).post('/oidc/token').set('Cookie', cookieHeader()).type('form').send({
5063
+ request(server).post('/oidc/token').set('Cookie', cookieHeader()).type('form').send(_object_spread({
4973
5064
  grant_type: 'authorization_code',
4974
5065
  code: authorizationCode,
4975
5066
  redirect_uri: redirectUri,
4976
- client_id: client_id,
4977
- client_secret: client_secret,
5067
+ client_id: client_id1,
5068
+ client_secret: client_secret1,
4978
5069
  code_verifier: codeVerifier
4979
- })
5070
+ }, (config === null || config === void 0 ? void 0 : config.resource) == null ? {} : {
5071
+ resource: config.resource
5072
+ }))
4980
5073
  ];
4981
- case 9:
5074
+ case 13:
4982
5075
  tokenRes = _state.sent();
4983
5076
  if (!tokenRes.body.access_token) {
4984
5077
  throw new Error("OAuth token exchange failed (status ".concat(tokenRes.status, "): ").concat(JSON.stringify(tokenRes.body)));
4985
5078
  }
5079
+ tokenBody = tokenRes.body;
5080
+ _state.label = 14;
5081
+ case 14:
4986
5082
  return [
4987
5083
  2,
4988
5084
  {
4989
- accessToken: tokenRes.body.access_token,
4990
- idToken: tokenRes.body.id_token
5085
+ stage: stage,
5086
+ authResponse: authRes,
5087
+ codeVerifier: codeVerifier,
5088
+ consentRedirectUrl: consentRedirectUrl,
5089
+ callbackUrl: callbackUrl,
5090
+ accessToken: tokenBody === null || tokenBody === void 0 ? void 0 : tokenBody.access_token,
5091
+ idToken: tokenBody === null || tokenBody === void 0 ? void 0 : tokenBody.id_token,
5092
+ tokenType: tokenBody === null || tokenBody === void 0 ? void 0 : tokenBody.token_type,
5093
+ scope: tokenBody === null || tokenBody === void 0 ? void 0 : tokenBody.scope,
5094
+ session: {
5095
+ client: client,
5096
+ cookieJar: cookieJar
5097
+ }
4991
5098
  }
4992
5099
  ];
4993
5100
  }
4994
5101
  });
4995
5102
  })();
4996
5103
  }
5104
+ /**
5105
+ * Performs the full OAuth authorization code flow with PKCE and returns tokens.
5106
+ *
5107
+ * Steps: create client → PKCE → auth redirect → login → consent → code exchange → token
5108
+ *
5109
+ * Always runs to the `'token'` stage — {@link OAuthTestFlowConfig.stopAtStage} is ignored here, since
5110
+ * this function's result guarantees tokens. Use {@link performOAuthFlow} to stop earlier.
5111
+ *
5112
+ * @param input - Bag of services and overrides needed to drive the flow end-to-end.
5113
+ * @param input.server - HTTP server returned by `nestApp.getHttpServer()` against which all supertest requests are issued.
5114
+ * @param input.oidcClientService - Service used to create the OAuth client whose credentials drive the flow.
5115
+ * @param input.nestApp - Initialized NestJS application; used to resolve {@link OidcAccountService} for project-id-derived ID tokens and default scopes.
5116
+ * @param input.uid - Firebase user ID for whom the test ID token is minted and the OAuth flow is authorized.
5117
+ * @param input.config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles).
5118
+ * @returns The exchanged access token and ID token from the OIDC `/token` endpoint, plus the flow's intermediates.
5119
+ * @throws {Error} When the token exchange step fails (the response body and status are included in the message).
5120
+ */ function performFullOAuthFlow(input) {
5121
+ return _async_to_generator$1(function() {
5122
+ var result;
5123
+ return _ts_generator$1(this, function(_state) {
5124
+ switch(_state.label){
5125
+ case 0:
5126
+ return [
5127
+ 4,
5128
+ performOAuthFlow(_object_spread_props(_object_spread({}, input), {
5129
+ config: _object_spread_props(_object_spread({}, input.config), {
5130
+ stopAtStage: 'token'
5131
+ })
5132
+ }))
5133
+ ];
5134
+ case 1:
5135
+ result = _state.sent();
5136
+ return [
5137
+ 2,
5138
+ result
5139
+ ];
5140
+ }
5141
+ });
5142
+ })();
5143
+ }
4997
5144
  /**
4998
5145
  * Higher-level helper that resolves OIDC services from the NestJS DI container,
4999
- * rotates JWKS keys, and then performs the full OAuth flow.
5146
+ * rotates JWKS keys, and then performs the OAuth flow up to {@link OAuthTestFlowConfig.stopAtStage}.
5000
5147
  *
5001
5148
  * This avoids callers needing to import from `@dereekb/firebase-server/oidc` directly.
5002
5149
  *
5003
5150
  * @param nestApp - Initialized NestJS application from which {@link JwksService} and {@link OidcClientService} are resolved.
5004
- * @param uid - Firebase user ID for whom the OAuth flow is authorized.
5005
- * @param config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles).
5006
- * @returns The exchanged access token and ID token from {@link performFullOAuthFlow}.
5007
- */ function setupAndPerformFullOAuthFlow(nestApp, uid, config) {
5151
+ * @param uid - Firebase user ID for whom the OAuth flow is authorized; only omittable for the `'auth'` stage.
5152
+ * @param config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles, stop stage).
5153
+ * @returns The result of {@link performOAuthFlow}.
5154
+ */ function setupAndPerformOAuthFlow(nestApp, uid, config) {
5008
5155
  return _async_to_generator$1(function() {
5009
5156
  var jwksService, oidcClientService, server;
5010
5157
  return _ts_generator$1(this, function(_state) {
@@ -5023,7 +5170,7 @@ function _unsupported_iterable_to_array(o, minLen) {
5023
5170
  server = nestApp.getHttpServer();
5024
5171
  return [
5025
5172
  2,
5026
- performFullOAuthFlow({
5173
+ performOAuthFlow({
5027
5174
  server: server,
5028
5175
  oidcClientService: oidcClientService,
5029
5176
  nestApp: nestApp,
@@ -5035,6 +5182,38 @@ function _unsupported_iterable_to_array(o, minLen) {
5035
5182
  });
5036
5183
  })();
5037
5184
  }
5185
+ /**
5186
+ * Higher-level helper that resolves OIDC services from the NestJS DI container,
5187
+ * rotates JWKS keys, and then performs the full OAuth flow.
5188
+ *
5189
+ * This avoids callers needing to import from `@dereekb/firebase-server/oidc` directly.
5190
+ *
5191
+ * @param nestApp - Initialized NestJS application from which {@link JwksService} and {@link OidcClientService} are resolved.
5192
+ * @param uid - Firebase user ID for whom the OAuth flow is authorized.
5193
+ * @param config - Optional flow overrides (scopes, redirect URI, client name, token endpoint auth method, provider profiles).
5194
+ * @returns The exchanged access token and ID token from {@link performFullOAuthFlow}.
5195
+ */ function setupAndPerformFullOAuthFlow(nestApp, uid, config) {
5196
+ return _async_to_generator$1(function() {
5197
+ var result;
5198
+ return _ts_generator$1(this, function(_state) {
5199
+ switch(_state.label){
5200
+ case 0:
5201
+ return [
5202
+ 4,
5203
+ setupAndPerformOAuthFlow(nestApp, uid, _object_spread_props(_object_spread({}, config), {
5204
+ stopAtStage: 'token'
5205
+ }))
5206
+ ];
5207
+ case 1:
5208
+ result = _state.sent();
5209
+ return [
5210
+ 2,
5211
+ result
5212
+ ];
5213
+ }
5214
+ });
5215
+ })();
5216
+ }
5038
5217
 
5039
5218
  function _assert_this_initialized(self) {
5040
5219
  if (self === void 0) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
@@ -5425,7 +5604,9 @@ function _type_of(obj) {
5425
5604
  "makeFixture",
5426
5605
  "makeInstance"
5427
5606
  ]);
5428
- var flowConfig = flowConfigOverrides.scopes || flowConfigOverrides.redirectUri || flowConfigOverrides.clientName || flowConfigOverrides.providerProfiles ? flowConfigOverrides : undefined;
5607
+ var flowConfig = Object.values(flowConfigOverrides).some(function(value) {
5608
+ return value != null;
5609
+ }) ? flowConfigOverrides : undefined;
5429
5610
  return function(params, buildTests) {
5430
5611
  var f = params.f, u = params.u;
5431
5612
  describe('(oauth)', function() {
@@ -5484,4 +5665,4 @@ function _type_of(obj) {
5484
5665
  * Uses @google-cloud/storage
5485
5666
  */ var dbxComponentsAdminTestWithMockItemStorage = testWithMockItemStorageFixture()(adminFirebaseStorageFactory);
5486
5667
 
5487
- export { AUTHORIZED_USER_RANDOM_EMAIL_FACTORY, AUTHORIZED_USER_RANDOM_PHONE_NUMBER_FACTORY, AbstractFirebaseAdminTestContextInstanceChild, AuthorizedUserTestContextFixture, AuthorizedUserTestContextInstance, CallableRequestTestMultipleFixtureSuffix, DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT, ExpectedHttpErrorWithSpecificServerErrorCode, FIREBASE_ADMIN_NEST_TEST_SERVER_INSTANCE_CONFIG_TOKEN, FirebaseAdminFunctionNestRootModule, FirebaseAdminFunctionNestTestContextFixture, FirebaseAdminFunctionNestTestContextInstance, FirebaseAdminFunctionTestContextFixture, FirebaseAdminFunctionTestContextInstance, FirebaseAdminNestTestContextFixture, FirebaseAdminNestTestContextInstance, FirebaseAdminTestContextFixture, FirebaseAdminTestContextInstance, GoogleCloudTestFirebaseStorageContextFixture, GoogleCloudTestFirebaseStorageInstance, GoogleCloudTestFirestoreContextFixture, GoogleCloudTestFirestoreInstance, ModelTestContextFixture, ModelTestContextInstance, OAuthAuthorizedSuperTestFixture, OAuthAuthorizedSuperTestInstance, adminFirebaseStorageFactory, adminFirestoreFactory, applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv, authorizedUserContext, authorizedUserContextFactory, callableRequestTest, cloudFunctionTest, createEncodedTestFirestoreTokenForUserRecord, createTestFirestoreTokenForUserRecord, createTestFunctionContextAuthData, createTestFunctionContextOptions, dbxComponentsAdminTestWithMockItemCollection, dbxComponentsAdminTestWithMockItemStorage, decodeEncodedCreateCustomTokenResult, describeCallableRequestTest, describeCloudFunctionTest, describeFirestoreTest, expectFailAssertHttpErrorServerErrorCode, firebaseAdminFirestoreContextFixture, firebaseAdminFirestoreContextWithFixture, firebaseAdminFunctionNestContextFactory, firebaseAdminFunctionNestContextFixture, firebaseAdminFunctionNestContextWithFixture, firebaseAdminFunctionTestBuilder, firebaseAdminFunctionTestContextFactory, firebaseAdminNestContextFactory, firebaseAdminNestContextFixture, firebaseAdminNestContextWithFixture, firebaseAdminTestBuilder, firebaseAdminTestContextFactory, generateNewProjectId, getGCloudProjectId, getGCloudTestProjectId, googleCloudTestFirebaseStorageBuilder, googleCloudTestFirestoreBuilder, initFirebaseAdminTestEnvironment, initFirebaseServerAdminTestEnvironment, isAdminEnvironmentInitialized, isCallableRequestTestSingleConfig, isCloudFunctionTestSingleConfig, makeGoogleFirebaseStorageContext, makeGoogleFirestoreContext, modelTestContextFactory, oAuthAuthorizedSuperTestContextFactory, performFullOAuthFlow, rerollFirebaseAdminFunctionTestSingleton, rollNewGCloudProjectEnvironmentVariable, setDefaultFirebaseAdminFunctionTestUseFunctionSingleton, setupAndPerformFullOAuthFlow, setupFirebaseAdminFunctionTestSingleton, testFirestoreClaimsFromUserRecord, testUidFactory, wrapCallableRequestForNestTestsGetter, wrapCloudFunctionForNestTestsGetter };
5668
+ export { AUTHORIZED_USER_RANDOM_EMAIL_FACTORY, AUTHORIZED_USER_RANDOM_PHONE_NUMBER_FACTORY, AbstractFirebaseAdminTestContextInstanceChild, AuthorizedUserTestContextFixture, AuthorizedUserTestContextInstance, CallableRequestTestMultipleFixtureSuffix, DEFAULT_FIREBASE_ADMIN_FUNCTION_TEST_USE_FUNCTION_SINGLETON_CONTEXT, ExpectedHttpErrorWithSpecificServerErrorCode, FIREBASE_ADMIN_NEST_TEST_SERVER_INSTANCE_CONFIG_TOKEN, FirebaseAdminFunctionNestRootModule, FirebaseAdminFunctionNestTestContextFixture, FirebaseAdminFunctionNestTestContextInstance, FirebaseAdminFunctionTestContextFixture, FirebaseAdminFunctionTestContextInstance, FirebaseAdminNestTestContextFixture, FirebaseAdminNestTestContextInstance, FirebaseAdminTestContextFixture, FirebaseAdminTestContextInstance, GoogleCloudTestFirebaseStorageContextFixture, GoogleCloudTestFirebaseStorageInstance, GoogleCloudTestFirestoreContextFixture, GoogleCloudTestFirestoreInstance, ModelTestContextFixture, ModelTestContextInstance, OAuthAuthorizedSuperTestFixture, OAuthAuthorizedSuperTestInstance, adminFirebaseStorageFactory, adminFirestoreFactory, applyFirebaseGCloudTestProjectIdToFirebaseConfigEnv, authorizedUserContext, authorizedUserContextFactory, callableRequestTest, cloudFunctionTest, createEncodedTestFirestoreTokenForUserRecord, createTestFirestoreTokenForUserRecord, createTestFunctionContextAuthData, createTestFunctionContextOptions, dbxComponentsAdminTestWithMockItemCollection, dbxComponentsAdminTestWithMockItemStorage, decodeEncodedCreateCustomTokenResult, describeCallableRequestTest, describeCloudFunctionTest, describeFirestoreTest, expectFailAssertHttpErrorServerErrorCode, firebaseAdminFirestoreContextFixture, firebaseAdminFirestoreContextWithFixture, firebaseAdminFunctionNestContextFactory, firebaseAdminFunctionNestContextFixture, firebaseAdminFunctionNestContextWithFixture, firebaseAdminFunctionTestBuilder, firebaseAdminFunctionTestContextFactory, firebaseAdminNestContextFactory, firebaseAdminNestContextFixture, firebaseAdminNestContextWithFixture, firebaseAdminTestBuilder, firebaseAdminTestContextFactory, generateNewProjectId, getGCloudProjectId, getGCloudTestProjectId, googleCloudTestFirebaseStorageBuilder, googleCloudTestFirestoreBuilder, initFirebaseAdminTestEnvironment, initFirebaseServerAdminTestEnvironment, isAdminEnvironmentInitialized, isCallableRequestTestSingleConfig, isCloudFunctionTestSingleConfig, makeGoogleFirebaseStorageContext, makeGoogleFirestoreContext, modelTestContextFactory, oAuthAuthorizedSuperTestContextFactory, performFullOAuthFlow, performOAuthFlow, rerollFirebaseAdminFunctionTestSingleton, rollNewGCloudProjectEnvironmentVariable, setDefaultFirebaseAdminFunctionTestUseFunctionSingleton, setupAndPerformFullOAuthFlow, setupAndPerformOAuthFlow, setupFirebaseAdminFunctionTestSingleton, testFirestoreClaimsFromUserRecord, testUidFactory, wrapCallableRequestForNestTestsGetter, wrapCloudFunctionForNestTestsGetter };
package/test/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/test",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/analytics": "14.2.0",
8
- "@dereekb/date": "14.2.0",
9
- "@dereekb/firebase": "14.2.0",
10
- "@dereekb/firebase-server": "14.2.0",
11
- "@dereekb/firebase-server/oidc": "14.2.0",
12
- "@dereekb/model": "14.2.0",
13
- "@dereekb/nestjs": "14.2.0",
14
- "@dereekb/rxjs": "14.2.0",
15
- "@dereekb/util": "14.2.0",
7
+ "@dereekb/analytics": "14.4.0",
8
+ "@dereekb/date": "14.4.0",
9
+ "@dereekb/firebase": "14.4.0",
10
+ "@dereekb/firebase-server": "14.4.0",
11
+ "@dereekb/firebase-server/oidc": "14.4.0",
12
+ "@dereekb/model": "14.4.0",
13
+ "@dereekb/nestjs": "14.4.0",
14
+ "@dereekb/oauth-resource": "14.4.0",
15
+ "@dereekb/rxjs": "14.4.0",
16
+ "@dereekb/util": "14.4.0",
16
17
  "@google-cloud/firestore": "^7.11.6",
17
18
  "@google-cloud/storage": "^7.22.0",
18
19
  "@nestjs/common": "^12.0.1",
@@ -25,7 +26,7 @@
25
26
  "supertest": "^7.2.2"
26
27
  },
27
28
  "devDependencies": {
28
- "@dereekb/nestjs": "14.2.0"
29
+ "@dereekb/nestjs": "14.4.0"
29
30
  },
30
31
  "exports": {
31
32
  "./package.json": "./package.json",