@dereekb/firebase-server 14.2.0 → 14.3.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/oidc/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/oidc",
3
- "version": "14.2.0",
3
+ "version": "14.3.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/model": "14.2.0",
12
- "@dereekb/nestjs": "14.2.0",
13
- "@dereekb/rxjs": "14.2.0",
14
- "@dereekb/util": "14.2.0",
15
- "@dereekb/zoho": "14.2.0",
7
+ "@dereekb/analytics": "14.3.0",
8
+ "@dereekb/date": "14.3.0",
9
+ "@dereekb/firebase": "14.3.0",
10
+ "@dereekb/firebase-server": "14.3.0",
11
+ "@dereekb/model": "14.3.0",
12
+ "@dereekb/nestjs": "14.3.0",
13
+ "@dereekb/rxjs": "14.3.0",
14
+ "@dereekb/util": "14.3.0",
15
+ "@dereekb/zoho": "14.3.0",
16
16
  "@nestjs/common": "^12.0.1",
17
17
  "@nestjs/config": "^12.0.0",
18
18
  "express": "^5.2.1",
@@ -86,7 +86,9 @@ export interface ResolveEffectiveSubsetInput {
86
86
  */
87
87
  readonly alwaysGranted?: readonly string[];
88
88
  /**
89
- * Entries the existing Grant has previously granted or rejected. Tolerated as no-ops on re-consent.
89
+ * Entries the existing Grant has already decided and this consent leaves as-is. Tolerated as no-ops on
90
+ * re-consent. A previously-rejected entry the request names again belongs in `missing` instead — see
91
+ * `reconsiderRejectedValues` — or it can never be granted on that Grant.
90
92
  */
91
93
  readonly alreadyEncountered?: readonly string[];
92
94
  }
@@ -9,4 +9,5 @@ export * from './oidc.jwks.service';
9
9
  export * from './oidc.config.service';
10
10
  export * from './oidc.client.service';
11
11
  export * from './oidc.interaction.service';
12
+ export * from './oidc.grant';
12
13
  export * from './oidc.interaction-policy';
@@ -0,0 +1,103 @@
1
+ import { type Grant } from 'oidc-provider';
2
+ /**
3
+ * Removes previously-rejected OIDC scopes from a Grant's rejected set.
4
+ *
5
+ * oidc-provider exposes `rejectOIDCScope()` but no inverse, and a rejected scope is subtracted from
6
+ * `getOIDCScope()` on every read — so `addOIDCScope()` alone cannot put a once-rejected scope back in
7
+ * force. `rejected` is plain persisted data (`Pick<Grant, 'openid' | 'resources'>`), which is what
8
+ * makes editing it directly the supported shape of an un-reject.
9
+ *
10
+ * @param grant - The grant to edit in place.
11
+ * @param scopes - The scopes to un-reject. Scopes not currently rejected are ignored.
12
+ */
13
+ export declare function unrejectOIDCScopes(grant: Grant, scopes: Iterable<string>): void;
14
+ /**
15
+ * Removes previously-rejected OIDC claims from a Grant's rejected set.
16
+ *
17
+ * @param grant - The grant to edit in place.
18
+ * @param claims - The claims to un-reject. Claims not currently rejected are ignored.
19
+ * @see unrejectOIDCScopes
20
+ */
21
+ export declare function unrejectOIDCClaims(grant: Grant, claims: Iterable<string>): void;
22
+ /**
23
+ * Removes previously-rejected resource scopes for one resource indicator from a Grant's rejected set.
24
+ *
25
+ * @param grant - The grant to edit in place.
26
+ * @param resource - The resource indicator.
27
+ * @param scopes - The scopes to un-reject. Scopes not currently rejected for the resource are ignored.
28
+ * @see unrejectOIDCScopes
29
+ */
30
+ export declare function unrejectResourceScopes(grant: Grant, resource: string, scopes: Iterable<string>): void;
31
+ /**
32
+ * Input for {@link reconsiderRejectedValues}.
33
+ */
34
+ export interface ReconsiderRejectedValuesInput {
35
+ /**
36
+ * Values oidc-provider reports as still undecided for this consent (e.g. `prompt.details.missingOIDCScope`).
37
+ */
38
+ readonly missing: readonly string[];
39
+ /**
40
+ * Values the existing Grant has already decided — granted or rejected.
41
+ */
42
+ readonly encountered: readonly string[];
43
+ /**
44
+ * The subset of `encountered` the existing Grant has REJECTED.
45
+ */
46
+ readonly rejected: readonly string[];
47
+ /**
48
+ * Values the current authorization request asks for. Only a rejected value the request names again is
49
+ * reconsidered; a rejection from an earlier, wider request stays in force.
50
+ */
51
+ readonly requested: ReadonlySet<string>;
52
+ }
53
+ /**
54
+ * Output of {@link reconsiderRejectedValues}.
55
+ */
56
+ export interface ReconsiderRejectedValuesResult {
57
+ /**
58
+ * `missing` plus the reconsidered values, so a consent decides them again.
59
+ */
60
+ readonly missing: string[];
61
+ /**
62
+ * `encountered` minus the reconsidered values, so they are no longer treated as settled no-ops.
63
+ */
64
+ readonly encountered: string[];
65
+ /**
66
+ * The values moved from `encountered` back into `missing`: rejected on the Grant and named by the
67
+ * current request. A consent that grants one of these must un-reject it on the Grant as well.
68
+ */
69
+ readonly reconsidered: string[];
70
+ }
71
+ /**
72
+ * Moves previously-rejected values the current request asks for again from the "encountered" set back into
73
+ * the "missing" set, so a re-consent decides them afresh.
74
+ *
75
+ * oidc-provider counts a rejected value as encountered: it never reappears in the prompt's `missing*`
76
+ * details, so without this a consent that names it again is a silent no-op and the value can never be
77
+ * granted on that Grant — even when the user explicitly ticks it. Values the Grant has GRANTED are left in
78
+ * `encountered`: they are already in force and need no re-application.
79
+ *
80
+ * @param input - The missing, encountered, rejected, and requested sets.
81
+ * @returns The adjusted missing/encountered sets plus the reconsidered values.
82
+ *
83
+ * @__NO_SIDE_EFFECTS__
84
+ */
85
+ export declare function reconsiderRejectedValues(input: ReconsiderRejectedValuesInput): ReconsiderRejectedValuesResult;
86
+ /**
87
+ * Names every claim an authorization request's `claims` parameter asks for, across `id_token` and `userinfo`.
88
+ *
89
+ * @param claimsParam - The raw `claims` request parameter (a JSON string), or undefined.
90
+ * @returns The requested claim names; empty when the parameter is absent or malformed.
91
+ *
92
+ * @__NO_SIDE_EFFECTS__
93
+ */
94
+ export declare function requestedOIDCClaimNames(claimsParam: unknown): Set<string>;
95
+ /**
96
+ * Normalizes an authorization request's `resource` parameter to its list of resource indicators.
97
+ *
98
+ * @param resourceParam - The raw `resource` request parameter (a string, an array of strings, or undefined).
99
+ * @returns The resource indicators; empty when the parameter is absent.
100
+ *
101
+ * @__NO_SIDE_EFFECTS__
102
+ */
103
+ export declare function requestedResourceIndicators(resourceParam: unknown): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server",
3
- "version": "14.2.0",
3
+ "version": "14.3.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -57,18 +57,18 @@
57
57
  }
58
58
  },
59
59
  "peerDependencies": {
60
- "@cantoo/pdf-lib": "^2.6.5",
61
- "@dereekb/analytics": "14.2.0",
62
- "@dereekb/calcom": "14.2.0",
63
- "@dereekb/date": "14.2.0",
64
- "@dereekb/dbx-core": "14.2.0",
65
- "@dereekb/discord": "14.2.0",
66
- "@dereekb/firebase": "14.2.0",
67
- "@dereekb/model": "14.2.0",
68
- "@dereekb/nestjs": "14.2.0",
69
- "@dereekb/rxjs": "14.2.0",
70
- "@dereekb/util": "14.2.0",
71
- "@dereekb/zoho": "14.2.0",
60
+ "@cantoo/pdf-lib": ">=2.6.5 <2.11.0",
61
+ "@dereekb/analytics": "14.3.0",
62
+ "@dereekb/calcom": "14.3.0",
63
+ "@dereekb/date": "14.3.0",
64
+ "@dereekb/dbx-core": "14.3.0",
65
+ "@dereekb/discord": "14.3.0",
66
+ "@dereekb/firebase": "14.3.0",
67
+ "@dereekb/model": "14.3.0",
68
+ "@dereekb/nestjs": "14.3.0",
69
+ "@dereekb/rxjs": "14.3.0",
70
+ "@dereekb/util": "14.3.0",
71
+ "@dereekb/zoho": "14.3.0",
72
72
  "@google-cloud/firestore": "^7.11.6",
73
73
  "@google-cloud/storage": "^7.22.0",
74
74
  "@modelcontextprotocol/node": "2.0.0",
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");
@@ -4767,6 +4767,16 @@ function _unsupported_iterable_to_array(o, minLen) {
4767
4767
  var url = location.startsWith('/') ? new URL(location, 'http://localhost') : new URL(location);
4768
4768
  return url.searchParams.get('uid');
4769
4769
  }
4770
+ /**
4771
+ * Whether a redirect response points at the given interaction frontend URL (the login or consent screen).
4772
+ *
4773
+ * @param res - Supertest response to inspect.
4774
+ * @param frontendUrl - The frontend URL to match, without its query string.
4775
+ * @returns True when the response's `Location` header starts with `frontendUrl`.
4776
+ */ function isRedirectTo(res, frontendUrl) {
4777
+ var location = res.headers['location'];
4778
+ return location != null && location.startsWith(frontendUrl);
4779
+ }
4770
4780
  /**
4771
4781
  * Cookie jar helpers for the OAuth flow.
4772
4782
  *
@@ -4873,15 +4883,16 @@ function _unsupported_iterable_to_array(o, minLen) {
4873
4883
  * @throws {Error} When the token exchange step fails (the response body and status are included in the message).
4874
4884
  */ function performFullOAuthFlow(input) {
4875
4885
  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;
4886
+ var _ref, _ref1, _ref2, _ref3, _ref4, _config_session, _config_session1, server, oidcClientService, nestApp, uid, config, cookieJar, collectCookies, cookieHeader, redirectUri, clientName, tokenEndpointAuthMethod, providerProfiles, scopes, client, _ref5, client_id, client_secret, client_id1, client_secret1, codeVerifier, codeChallenge, authRes, idToken, providerConfigService, interactionRes, loginUid, loginRes, resumeAfterLoginPath, callbackRedirectRes, consentUid, consentRes, resumeAfterConsentPath, callbackUrl, authorizationCode, tokenRes;
4877
4887
  return _ts_generator$1(this, function(_state) {
4878
4888
  switch(_state.label){
4879
4889
  case 0:
4880
4890
  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';
4891
+ 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();
4892
+ collectCookies = cookieJar.collectCookies, cookieHeader = cookieJar.cookieHeader;
4893
+ redirectUri = (_ref1 = (_ref2 = config === null || config === void 0 ? void 0 : (_config_session1 = config.session) === null || _config_session1 === void 0 ? void 0 : _config_session1.client.redirectUri) !== null && _ref2 !== void 0 ? _ref2 : config === null || config === void 0 ? void 0 : config.redirectUri) !== null && _ref1 !== void 0 ? _ref1 : 'https://example.com/callback';
4894
+ clientName = (_ref3 = config === null || config === void 0 ? void 0 : config.clientName) !== null && _ref3 !== void 0 ? _ref3 : 'test-oauth-context';
4895
+ tokenEndpointAuthMethod = (_ref4 = config === null || config === void 0 ? void 0 : config.tokenEndpointAuthMethod) !== null && _ref4 !== void 0 ? _ref4 : 'client_secret_post';
4885
4896
  providerProfiles = config === null || config === void 0 ? void 0 : config.providerProfiles;
4886
4897
  return [
4887
4898
  4,
@@ -4889,6 +4900,16 @@ function _unsupported_iterable_to_array(o, minLen) {
4889
4900
  ];
4890
4901
  case 1:
4891
4902
  scopes = _state.sent();
4903
+ if (!(config === null || config === void 0 ? void 0 : config.session)) return [
4904
+ 3,
4905
+ 2
4906
+ ];
4907
+ client = config.session.client;
4908
+ return [
4909
+ 3,
4910
+ 4
4911
+ ];
4912
+ case 2:
4892
4913
  return [
4893
4914
  4,
4894
4915
  oidcClientService.createClient(_object_spread({
@@ -4901,15 +4922,23 @@ function _unsupported_iterable_to_array(o, minLen) {
4901
4922
  dbx_provider_profiles: _to_consumable_array(providerProfiles)
4902
4923
  }))
4903
4924
  ];
4904
- case 2:
4905
- _ref3 = _state.sent(), client_id = _ref3.client_id, client_secret = _ref3.client_secret;
4925
+ case 3:
4926
+ _ref5 = _state.sent(), client_id = _ref5.client_id, client_secret = _ref5.client_secret;
4927
+ client = {
4928
+ client_id: client_id,
4929
+ client_secret: client_secret,
4930
+ redirectUri: redirectUri
4931
+ };
4932
+ _state.label = 4;
4933
+ case 4:
4934
+ client_id1 = client.client_id, client_secret1 = client.client_secret;
4906
4935
  // 2. Generate PKCE code_verifier and code_challenge
4907
4936
  codeVerifier = randomBytes(32).toString('base64url');
4908
4937
  codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
4909
4938
  return [
4910
4939
  4,
4911
- request(server).get('/oidc/auth').query({
4912
- client_id: client_id,
4940
+ request(server).get('/oidc/auth').query(_object_spread({
4941
+ client_id: client_id1,
4913
4942
  redirect_uri: redirectUri,
4914
4943
  response_type: 'code',
4915
4944
  scope: scopes,
@@ -4917,25 +4946,33 @@ function _unsupported_iterable_to_array(o, minLen) {
4917
4946
  code_challenge_method: 'S256',
4918
4947
  state: 'test-state',
4919
4948
  nonce: 'test-nonce'
4920
- }).redirects(0)
4949
+ }, (config === null || config === void 0 ? void 0 : config.prompt) == null ? {} : {
4950
+ prompt: config.prompt
4951
+ })).redirects(0)
4921
4952
  ];
4922
- case 3:
4953
+ case 5:
4923
4954
  authRes = _state.sent();
4924
4955
  collectCookies(authRes);
4925
- loginUid = extractInteractionUid(authRes);
4926
4956
  return [
4927
4957
  4,
4928
4958
  createTestIdToken(nestApp, uid)
4929
4959
  ];
4930
- case 4:
4960
+ case 6:
4931
4961
  idToken = _state.sent();
4962
+ providerConfigService = nestApp.get(OidcProviderConfigService);
4963
+ interactionRes = authRes;
4964
+ if (!isRedirectTo(authRes, providerConfigService.appLoginUrl)) return [
4965
+ 3,
4966
+ 9
4967
+ ];
4968
+ loginUid = extractInteractionUid(authRes);
4932
4969
  return [
4933
4970
  4,
4934
4971
  request(server).post("/interaction/".concat(loginUid, "/login")).set('Cookie', cookieHeader()).send({
4935
4972
  idToken: idToken
4936
4973
  })
4937
4974
  ];
4938
- case 5:
4975
+ case 7:
4939
4976
  loginRes = _state.sent();
4940
4977
  // 5. Resume after login → consent redirect
4941
4978
  resumeAfterLoginPath = new URL(loginRes.body.redirectTo).pathname + new URL(loginRes.body.redirectTo).search;
@@ -4943,18 +4980,28 @@ function _unsupported_iterable_to_array(o, minLen) {
4943
4980
  4,
4944
4981
  request(server).get(resumeAfterLoginPath).set('Cookie', cookieHeader()).redirects(0)
4945
4982
  ];
4946
- case 6:
4947
- consentRedirectRes = _state.sent();
4948
- collectCookies(consentRedirectRes);
4949
- consentUid = extractInteractionUid(consentRedirectRes);
4983
+ case 8:
4984
+ interactionRes = _state.sent();
4985
+ collectCookies(interactionRes);
4986
+ _state.label = 9;
4987
+ case 9:
4988
+ // 6. Approve consent — unless nothing needed consenting and the provider went straight to the callback
4989
+ callbackRedirectRes = interactionRes;
4990
+ if (!isRedirectTo(interactionRes, providerConfigService.appConsentUrl)) return [
4991
+ 3,
4992
+ 12
4993
+ ];
4994
+ consentUid = extractInteractionUid(interactionRes);
4950
4995
  return [
4951
4996
  4,
4952
- request(server).post("/interaction/".concat(consentUid, "/consent")).set('Cookie', cookieHeader()).send({
4997
+ request(server).post("/interaction/".concat(consentUid, "/consent")).set('Cookie', cookieHeader()).send(_object_spread({
4953
4998
  idToken: idToken,
4954
4999
  approved: true
4955
- })
5000
+ }, (config === null || config === void 0 ? void 0 : config.grantedOIDCScopes) == null ? {} : {
5001
+ grantedOIDCScopes: _to_consumable_array(config.grantedOIDCScopes)
5002
+ }))
4956
5003
  ];
4957
- case 7:
5004
+ case 10:
4958
5005
  consentRes = _state.sent();
4959
5006
  // 7. Follow resume redirect → callback with authorization code
4960
5007
  resumeAfterConsentPath = new URL(consentRes.body.redirectTo).pathname + new URL(consentRes.body.redirectTo).search;
@@ -4962,9 +5009,11 @@ function _unsupported_iterable_to_array(o, minLen) {
4962
5009
  4,
4963
5010
  request(server).get(resumeAfterConsentPath).set('Cookie', cookieHeader()).redirects(0)
4964
5011
  ];
4965
- case 8:
5012
+ case 11:
4966
5013
  callbackRedirectRes = _state.sent();
4967
5014
  collectCookies(callbackRedirectRes);
5015
+ _state.label = 12;
5016
+ case 12:
4968
5017
  callbackUrl = new URL(callbackRedirectRes.headers['location']);
4969
5018
  authorizationCode = callbackUrl.searchParams.get('code');
4970
5019
  return [
@@ -4973,12 +5022,12 @@ function _unsupported_iterable_to_array(o, minLen) {
4973
5022
  grant_type: 'authorization_code',
4974
5023
  code: authorizationCode,
4975
5024
  redirect_uri: redirectUri,
4976
- client_id: client_id,
4977
- client_secret: client_secret,
5025
+ client_id: client_id1,
5026
+ client_secret: client_secret1,
4978
5027
  code_verifier: codeVerifier
4979
5028
  })
4980
5029
  ];
4981
- case 9:
5030
+ case 13:
4982
5031
  tokenRes = _state.sent();
4983
5032
  if (!tokenRes.body.access_token) {
4984
5033
  throw new Error("OAuth token exchange failed (status ".concat(tokenRes.status, "): ").concat(JSON.stringify(tokenRes.body)));
@@ -4987,7 +5036,12 @@ function _unsupported_iterable_to_array(o, minLen) {
4987
5036
  2,
4988
5037
  {
4989
5038
  accessToken: tokenRes.body.access_token,
4990
- idToken: tokenRes.body.id_token
5039
+ idToken: tokenRes.body.id_token,
5040
+ scope: tokenRes.body.scope,
5041
+ session: {
5042
+ client: client,
5043
+ cookieJar: cookieJar
5044
+ }
4991
5045
  }
4992
5046
  ];
4993
5047
  }
@@ -5425,7 +5479,9 @@ function _type_of(obj) {
5425
5479
  "makeFixture",
5426
5480
  "makeInstance"
5427
5481
  ]);
5428
- var flowConfig = flowConfigOverrides.scopes || flowConfigOverrides.redirectUri || flowConfigOverrides.clientName || flowConfigOverrides.providerProfiles ? flowConfigOverrides : undefined;
5482
+ var flowConfig = Object.values(flowConfigOverrides).some(function(value) {
5483
+ return value != null;
5484
+ }) ? flowConfigOverrides : undefined;
5429
5485
  return function(params, buildTests) {
5430
5486
  var f = params.f, u = params.u;
5431
5487
  describe('(oauth)', function() {
package/test/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/test",
3
- "version": "14.2.0",
3
+ "version": "14.3.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.3.0",
8
+ "@dereekb/date": "14.3.0",
9
+ "@dereekb/firebase": "14.3.0",
10
+ "@dereekb/firebase-server": "14.3.0",
11
+ "@dereekb/firebase-server/oidc": "14.3.0",
12
+ "@dereekb/model": "14.3.0",
13
+ "@dereekb/nestjs": "14.3.0",
14
+ "@dereekb/rxjs": "14.3.0",
15
+ "@dereekb/util": "14.3.0",
16
16
  "@google-cloud/firestore": "^7.11.6",
17
17
  "@google-cloud/storage": "^7.22.0",
18
18
  "@nestjs/common": "^12.0.1",
@@ -25,7 +25,7 @@
25
25
  "supertest": "^7.2.2"
26
26
  },
27
27
  "devDependencies": {
28
- "@dereekb/nestjs": "14.2.0"
28
+ "@dereekb/nestjs": "14.3.0"
29
29
  },
30
30
  "exports": {
31
31
  "./package.json": "./package.json",
@@ -1,3 +1,4 @@
1
+ import request from 'supertest';
1
2
  import { type INestApplication } from '@nestjs/common';
2
3
  import { type OidcProviderProfileKey, type OidcTokenEndpointAuthMethod } from '@dereekb/firebase';
3
4
  import { OidcClientService } from '@dereekb/firebase-server/oidc';
@@ -33,10 +34,60 @@ export interface OAuthTestFlowConfig {
33
34
  * "all registered scopes" resolution deliberately drops assignment-only scopes.
34
35
  */
35
36
  readonly providerProfiles?: readonly OidcProviderProfileKey[];
37
+ /**
38
+ * OAuth `prompt` parameter for the authorization request (e.g. `'consent'` to force the consent screen
39
+ * on a session that has already authorized the client). Omitted by default.
40
+ */
41
+ readonly prompt?: string;
42
+ /**
43
+ * Explicit subset of the requested OIDC scopes to grant at consent (the consent request's
44
+ * `grantedOIDCScopes`). Omitted by default, which grants every requested scope.
45
+ */
46
+ readonly grantedOIDCScopes?: readonly string[];
47
+ /**
48
+ * A prior flow's client and cookies to run this flow against — the same OAuth client, the same
49
+ * provider session, and therefore the same Grant. Omitted by default, which creates a fresh client
50
+ * and starts a fresh session.
51
+ *
52
+ * When the session is still logged in, the provider skips the login prompt and the flow goes straight
53
+ * to consent (or straight to the callback when nothing new needs consenting and `prompt` is unset).
54
+ */
55
+ readonly session?: OAuthTestFlowSession;
56
+ }
57
+ /**
58
+ * The client and cookies a flow ran with, for chaining a second flow onto the same provider session.
59
+ */
60
+ export interface OAuthTestFlowSession {
61
+ readonly client: OAuthTestFlowClient;
62
+ readonly cookieJar: OAuthTestFlowCookieJar;
63
+ }
64
+ /**
65
+ * The OAuth client a flow created (or reused).
66
+ */
67
+ export interface OAuthTestFlowClient {
68
+ readonly client_id: string;
69
+ readonly client_secret?: string;
70
+ readonly redirectUri: string;
71
+ }
72
+ /**
73
+ * Cookie jar helpers for the OAuth flow. See {@link createCookieJar}.
74
+ */
75
+ export interface OAuthTestFlowCookieJar {
76
+ readonly collectCookies: (res: request.Response) => void;
77
+ readonly cookieHeader: () => string;
36
78
  }
37
79
  export interface PerformFullOAuthFlowResult {
38
80
  readonly accessToken: string;
39
81
  readonly idToken: string;
82
+ /**
83
+ * The space-separated scope the token endpoint reported for the access token.
84
+ */
85
+ readonly scope: string;
86
+ /**
87
+ * The client and cookies this flow ran with, for chaining another flow onto the same session via
88
+ * {@link OAuthTestFlowConfig.session}.
89
+ */
90
+ readonly session: OAuthTestFlowSession;
40
91
  }
41
92
  /**
42
93
  * Input for {@link performFullOAuthFlow}.
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/twilio",
3
- "version": "14.2.0",
3
+ "version": "14.3.0",
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
- "@dereekb/date": "14.2.0",
8
- "@dereekb/firebase": "14.2.0",
9
- "@dereekb/firebase-server": "14.2.0",
10
- "@dereekb/model": "14.2.0",
11
- "@dereekb/nestjs": "14.2.0",
12
- "@dereekb/rxjs": "14.2.0",
13
- "@dereekb/util": "14.2.0"
7
+ "@dereekb/date": "14.3.0",
8
+ "@dereekb/firebase": "14.3.0",
9
+ "@dereekb/firebase-server": "14.3.0",
10
+ "@dereekb/model": "14.3.0",
11
+ "@dereekb/nestjs": "14.3.0",
12
+ "@dereekb/rxjs": "14.3.0",
13
+ "@dereekb/util": "14.3.0"
14
14
  },
15
15
  "exports": {
16
16
  "./package.json": "./package.json",
package/zoho/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/zoho",
3
- "version": "14.2.0",
3
+ "version": "14.3.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/model": "14.2.0",
10
- "@dereekb/nestjs": "14.2.0",
11
- "@dereekb/rxjs": "14.2.0",
12
- "@dereekb/firebase": "14.2.0",
13
- "@dereekb/firebase-server": "14.2.0",
14
- "@dereekb/util": "14.2.0",
15
- "@dereekb/zoho": "14.2.0",
7
+ "@dereekb/analytics": "14.3.0",
8
+ "@dereekb/date": "14.3.0",
9
+ "@dereekb/model": "14.3.0",
10
+ "@dereekb/nestjs": "14.3.0",
11
+ "@dereekb/rxjs": "14.3.0",
12
+ "@dereekb/firebase": "14.3.0",
13
+ "@dereekb/firebase-server": "14.3.0",
14
+ "@dereekb/util": "14.3.0",
15
+ "@dereekb/zoho": "14.3.0",
16
16
  "@nestjs/common": "^12.0.1",
17
17
  "@nestjs/config": "^12.0.0",
18
18
  "express": "^5.2.1"