@dereekb/firebase-server 13.30.0 → 13.31.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.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/mailgun",
3
- "version": "13.30.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.30.0",
6
- "@dereekb/firebase": "13.30.0",
7
- "@dereekb/firebase-server": "13.30.0",
8
- "@dereekb/date": "13.30.0",
9
- "@dereekb/nestjs": "13.30.0",
10
- "@dereekb/model": "13.30.0",
11
- "@dereekb/rxjs": "13.30.0",
12
- "@dereekb/util": "13.30.0"
5
+ "@dereekb/analytics": "13.31.0",
6
+ "@dereekb/firebase": "13.31.0",
7
+ "@dereekb/firebase-server": "13.31.0",
8
+ "@dereekb/date": "13.31.0",
9
+ "@dereekb/nestjs": "13.31.0",
10
+ "@dereekb/model": "13.31.0",
11
+ "@dereekb/rxjs": "13.31.0",
12
+ "@dereekb/util": "13.31.0"
13
13
  },
14
14
  "exports": {
15
15
  "./package.json": "./package.json",
package/mcp/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/mcp",
3
- "version": "13.30.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.30.0",
6
- "@dereekb/date": "13.30.0",
7
- "@dereekb/firebase": "13.30.0",
8
- "@dereekb/firebase-server": "13.30.0",
9
- "@dereekb/firebase-server/oidc": "13.30.0",
10
- "@dereekb/model": "13.30.0",
11
- "@dereekb/nestjs": "13.30.0",
12
- "@dereekb/rxjs": "13.30.0",
13
- "@dereekb/util": "13.30.0",
14
- "@dereekb/zoho": "13.30.0",
5
+ "@dereekb/analytics": "13.31.0",
6
+ "@dereekb/date": "13.31.0",
7
+ "@dereekb/firebase": "13.31.0",
8
+ "@dereekb/firebase-server": "13.31.0",
9
+ "@dereekb/firebase-server/oidc": "13.31.0",
10
+ "@dereekb/model": "13.31.0",
11
+ "@dereekb/nestjs": "13.31.0",
12
+ "@dereekb/rxjs": "13.31.0",
13
+ "@dereekb/util": "13.31.0",
14
+ "@dereekb/zoho": "13.31.0",
15
15
  "@modelcontextprotocol/sdk": "1.29.0",
16
16
  "@nestjs/common": "^11.1.19",
17
17
  "@nestjs/core": "^11.1.19",
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/model",
3
- "version": "13.30.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.30.0",
6
- "@dereekb/date": "13.30.0",
7
- "@dereekb/firebase": "13.30.0",
8
- "@dereekb/firebase-server": "13.30.0",
9
- "@dereekb/model": "13.30.0",
10
- "@dereekb/nestjs": "13.30.0",
11
- "@dereekb/rxjs": "13.30.0",
12
- "@dereekb/util": "13.30.0",
5
+ "@dereekb/analytics": "13.31.0",
6
+ "@dereekb/date": "13.31.0",
7
+ "@dereekb/firebase": "13.31.0",
8
+ "@dereekb/firebase-server": "13.31.0",
9
+ "@dereekb/model": "13.31.0",
10
+ "@dereekb/nestjs": "13.31.0",
11
+ "@dereekb/rxjs": "13.31.0",
12
+ "@dereekb/util": "13.31.0",
13
13
  "@nestjs/common": "^11.1.19",
14
14
  "@nestjs/config": "^4.0.4",
15
15
  "archiver": "^7.0.1",
package/oidc/index.cjs.js CHANGED
@@ -3555,11 +3555,18 @@ exports.OidcProviderConfigService = __decorate([
3555
3555
  * Resolves the unlocked + required scope sets for a client from the provider-profile registry and
3556
3556
  * the profile keys assigned to that client.
3557
3557
  *
3558
+ * A client with NO assigned profiles resolves to the registry's default profiles (those marked
3559
+ * `isDefault`), so a registry declaring no default resolves exactly as it would from the assigned
3560
+ * keys alone.
3561
+ *
3562
+ * This is the single choke point for a client's profile resolution — the consent unlock gate, the
3563
+ * consent required gate, and the `requiredScopes` interaction param all read through it.
3564
+ *
3558
3565
  * @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
3559
3566
  * @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
3560
3567
  * @returns The unlocked and required scope sets for the client.
3561
3568
  */ function oidcClientProviderProfileScopes(providerProfiles, clientProfileKeys) {
3562
- var clientProfiles = firebase.oidcProviderProfilesForKeys(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
3569
+ var clientProfiles = firebase.oidcProviderProfilesForClient(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
3563
3570
  return {
3564
3571
  unlocked: firebase.scopesForOidcProviderProfiles(clientProfiles),
3565
3572
  required: firebase.requiredScopesForOidcProviderProfiles(clientProfiles)
@@ -6212,7 +6219,7 @@ var OidcInteractionController_1;
6212
6219
  * @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
6213
6220
  */ function postConsent(uid, body, res) {
6214
6221
  return _async_to_generator$2(function() {
6215
- var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, adminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1, providerProfiles, _oidcClientProviderProfileScopes, clientUnlockedScopes, clientRequiredScopes, profileGatedScopes, disallowedGatedScopes, _ref6, redirectTo2, missingRequiredScopes, _ref7, redirectTo3, requestedRawTtl, clientMaxSessionTtl, expiresInSeconds, grant, _tmp2, _resolveEffectiveSubset, granted, rejected, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, value, encounteredOIDCClaims, missingOIDCClaims, _resolveEffectiveSubset1, granted1, rejected1, missingResourceScopes, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, indicator, scopes, _body_grantedResourceScopes, requestedSubset, encounteredResourceScopes, _resolveEffectiveSubset2, granted2, rejected2, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, value1, grantId, _ref8, redirectTo4, err;
6222
+ var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, providerProfiles, configAdminOnlyScopes, adminOnlyScopes, requestedAdminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1, _oidcClientProviderProfileScopes, clientUnlockedScopes, clientRequiredScopes, profileGatedScopes, disallowedGatedScopes, _ref6, redirectTo2, missingRequiredScopes, _ref7, redirectTo3, requestedRawTtl, clientMaxSessionTtl, expiresInSeconds, grant, _tmp2, _resolveEffectiveSubset, granted, rejected, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, value, encounteredOIDCClaims, missingOIDCClaims, _resolveEffectiveSubset1, granted1, rejected1, missingResourceScopes, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, indicator, scopes, _body_grantedResourceScopes, requestedSubset, encounteredResourceScopes, _resolveEffectiveSubset2, granted2, rejected2, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, value1, grantId, _ref8, redirectTo4, err;
6216
6223
  return _ts_generator$2(this, function(_state) {
6217
6224
  switch(_state.label){
6218
6225
  case 0:
@@ -6296,11 +6303,20 @@ var OidcInteractionController_1;
6296
6303
  clientPayload = _state.sent();
6297
6304
  // Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
6298
6305
  // (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
6299
- // `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
6300
- // not an admin, hard-reject with `access_denied` rather than silently dropping the scope.
6306
+ // `encountered`). If that set intersects the admin-only scopes — the provider's `adminOnlyScopes`
6307
+ // unioned with the scopes of every profile marked `adminOnly` — and the resolving user is not an
6308
+ // admin, hard-reject with `access_denied` rather than silently dropping the scope.
6301
6309
  requestedOIDCScopeSet = new Set(_to_consumable_array$1(missingOIDCScope).concat(_to_consumable_array$1(encounteredOIDCScopes)));
6302
- adminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
6303
- requestsServiceToken = adminOnlyScopes.some(function(scope) {
6310
+ providerProfiles = this.accountService.providerConfig.providerProfiles;
6311
+ configAdminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
6312
+ adminOnlyScopes = new Set(_to_consumable_array$1(configAdminOnlyScopes).concat(_to_consumable_array$1(firebase.adminOnlyScopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []))));
6313
+ requestedAdminOnlyScopes = Array.from(adminOnlyScopes).filter(function(scope) {
6314
+ return requestedOIDCScopeSet.has(scope);
6315
+ });
6316
+ // Deliberately sourced from the provider config's `adminOnlyScopes` ALONE, unlike the gate above:
6317
+ // this flag also selects the (highest) service-token TTL tier further down, and a profile marked
6318
+ // `adminOnly` must gate by admin status WITHOUT widening its grant's lifetime to that tier.
6319
+ requestsServiceToken = configAdminOnlyScopes.some(function(scope) {
6304
6320
  return requestedOIDCScopeSet.has(scope);
6305
6321
  });
6306
6322
  if (!accountId) return [
@@ -6322,7 +6338,7 @@ var OidcInteractionController_1;
6322
6338
  _state.label = 12;
6323
6339
  case 12:
6324
6340
  isAdmin = _tmp1;
6325
- if (!(requestsServiceToken && !isAdmin)) return [
6341
+ if (!(requestedAdminOnlyScopes.length > 0 && !isAdmin)) return [
6326
6342
  3,
6327
6343
  14
6328
6344
  ];
@@ -6330,7 +6346,7 @@ var OidcInteractionController_1;
6330
6346
  4,
6331
6347
  this.oidcInteractionService.finishInteractionByUid(uid, {
6332
6348
  error: 'access_denied',
6333
- error_description: 'token.service is restricted to admins.'
6349
+ error_description: "The following scope(s) are restricted to admins: ".concat(requestedAdminOnlyScopes.join(', '), ".")
6334
6350
  }, {
6335
6351
  mergeWithLastSubmission: true
6336
6352
  })
@@ -6342,7 +6358,7 @@ var OidcInteractionController_1;
6342
6358
  isSuccessful: false,
6343
6359
  uid: accountId,
6344
6360
  clientId: clientId,
6345
- serviceToken: true,
6361
+ serviceToken: requestsServiceToken,
6346
6362
  isAdmin: false,
6347
6363
  reason: 'service_token_non_admin',
6348
6364
  durationMs: Date.now() - startedAt
@@ -6355,9 +6371,9 @@ var OidcInteractionController_1;
6355
6371
  ];
6356
6372
  case 14:
6357
6373
  // Provider-profile scope gate. Any scope referenced by a provider profile is "gated" — available
6358
- // only to clients whose assigned profiles unlock it. Independent of the admin-only gate above; a
6359
- // scope may be subject to both.
6360
- providerProfiles = this.accountService.providerConfig.providerProfiles;
6374
+ // only to clients whose assigned profiles unlock it (a client with NO assigned profiles resolves
6375
+ // to the registry's default profiles). Independent of the admin-only gate above; a scope may be
6376
+ // subject to both.
6361
6377
  _oidcClientProviderProfileScopes = oidcClientProviderProfileScopes(providerProfiles, (_ref2 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_provider_profiles) !== null && _ref2 !== void 0 ? _ref2 : undefined), clientUnlockedScopes = _oidcClientProviderProfileScopes.unlocked, clientRequiredScopes = _oidcClientProviderProfileScopes.required;
6362
6378
  profileGatedScopes = firebase.scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
6363
6379
  // Unlock gate: reject any requested gated scope this client's profiles do not unlock.
package/oidc/index.esm.js CHANGED
@@ -3,7 +3,7 @@ import { errors } from 'oidc-provider';
3
3
  import { SECONDS_IN_MINUTE, SECONDS_IN_DAY, cachedGetter, unixDateTimeSecondsNumberToDate, unixDateTimeSecondsNumberForNow, selectiveFieldEncryptor, filterUndefinedValues, websiteUrlFromPaths, firstValue, hasHttpPrefix } from '@dereekb/util';
4
4
  import { generateKeyPairSync, randomBytes } from 'node:crypto';
5
5
  import { resolveEncryptionKey, encryptValue, decryptValue, createAesStringEncryptionProvider, isValidAES256GCMEncryptionSecret } from '@dereekb/nestjs';
6
- import { where, iterateFirestoreDocumentSnapshotPairs, firestoreModelIdentity, snapshotConverterFunctions, optionalFirestoreDate, firestoreDate, firestoreEnum, firestorePassThroughField, AbstractFirestoreDocument, createOidcClientParamsType, deleteOidcClientParamsType, deleteOidcTokenParamsType, rotateOidcClientSecretParamsType, updateOidcClientParamsType, firestoreModelKey, oidcEntryIdentity, PUBLIC_PKCE_TOKEN_ENDPOINT_AUTH_METHOD, OIDC_ENTRY_CLIENT_TYPE, oidcEntriesByUserCodeQuery, oidcEntriesByUidQuery, oidcEntriesByGrantIdQuery, oidcProviderProfilesForKeys, requiredScopesForOidcProviderProfiles, scopesForOidcProviderProfiles, oidcScopesFromScopeClaim, oidcEntryFirestoreCollection } from '@dereekb/firebase';
6
+ import { where, iterateFirestoreDocumentSnapshotPairs, firestoreModelIdentity, snapshotConverterFunctions, optionalFirestoreDate, firestoreDate, firestoreEnum, firestorePassThroughField, AbstractFirestoreDocument, createOidcClientParamsType, deleteOidcClientParamsType, deleteOidcTokenParamsType, rotateOidcClientSecretParamsType, updateOidcClientParamsType, firestoreModelKey, oidcEntryIdentity, PUBLIC_PKCE_TOKEN_ENDPOINT_AUTH_METHOD, OIDC_ENTRY_CLIENT_TYPE, oidcEntriesByUserCodeQuery, oidcEntriesByUidQuery, oidcEntriesByGrantIdQuery, oidcProviderProfilesForClient, requiredScopesForOidcProviderProfiles, scopesForOidcProviderProfiles, oidcScopesFromScopeClaim, adminOnlyScopesForOidcProviderProfiles, oidcEntryFirestoreCollection } from '@dereekb/firebase';
7
7
  import { firestoreEncryptedField, firebaseServerActionsContext, FirebaseServerEnvService, FirebaseServerAnalyticsService, FIREBASE_FIRESTORE_CONTEXT_TOKEN, FirebaseServerFirestoreContextModule } from '@dereekb/firebase-server';
8
8
  import { nanoid } from 'nanoid';
9
9
  import { safeToJsDate } from '@dereekb/date';
@@ -3553,11 +3553,18 @@ OidcProviderConfigService = __decorate([
3553
3553
  * Resolves the unlocked + required scope sets for a client from the provider-profile registry and
3554
3554
  * the profile keys assigned to that client.
3555
3555
  *
3556
+ * A client with NO assigned profiles resolves to the registry's default profiles (those marked
3557
+ * `isDefault`), so a registry declaring no default resolves exactly as it would from the assigned
3558
+ * keys alone.
3559
+ *
3560
+ * This is the single choke point for a client's profile resolution — the consent unlock gate, the
3561
+ * consent required gate, and the `requiredScopes` interaction param all read through it.
3562
+ *
3556
3563
  * @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
3557
3564
  * @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
3558
3565
  * @returns The unlocked and required scope sets for the client.
3559
3566
  */ function oidcClientProviderProfileScopes(providerProfiles, clientProfileKeys) {
3560
- var clientProfiles = oidcProviderProfilesForKeys(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
3567
+ var clientProfiles = oidcProviderProfilesForClient(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
3561
3568
  return {
3562
3569
  unlocked: scopesForOidcProviderProfiles(clientProfiles),
3563
3570
  required: requiredScopesForOidcProviderProfiles(clientProfiles)
@@ -6210,7 +6217,7 @@ var OidcInteractionController_1;
6210
6217
  * @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
6211
6218
  */ function postConsent(uid, body, res) {
6212
6219
  return _async_to_generator$2(function() {
6213
- var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, adminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1, providerProfiles, _oidcClientProviderProfileScopes, clientUnlockedScopes, clientRequiredScopes, profileGatedScopes, disallowedGatedScopes, _ref6, redirectTo2, missingRequiredScopes, _ref7, redirectTo3, requestedRawTtl, clientMaxSessionTtl, expiresInSeconds, grant, _tmp2, _resolveEffectiveSubset, granted, rejected, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, value, encounteredOIDCClaims, missingOIDCClaims, _resolveEffectiveSubset1, granted1, rejected1, missingResourceScopes, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, indicator, scopes, _body_grantedResourceScopes, requestedSubset, encounteredResourceScopes, _resolveEffectiveSubset2, granted2, rejected2, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, value1, grantId, _ref8, redirectTo4, err;
6220
+ var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, providerProfiles, configAdminOnlyScopes, adminOnlyScopes, requestedAdminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1, _oidcClientProviderProfileScopes, clientUnlockedScopes, clientRequiredScopes, profileGatedScopes, disallowedGatedScopes, _ref6, redirectTo2, missingRequiredScopes, _ref7, redirectTo3, requestedRawTtl, clientMaxSessionTtl, expiresInSeconds, grant, _tmp2, _resolveEffectiveSubset, granted, rejected, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, value, encounteredOIDCClaims, missingOIDCClaims, _resolveEffectiveSubset1, granted1, rejected1, missingResourceScopes, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, indicator, scopes, _body_grantedResourceScopes, requestedSubset, encounteredResourceScopes, _resolveEffectiveSubset2, granted2, rejected2, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, value1, grantId, _ref8, redirectTo4, err;
6214
6221
  return _ts_generator$2(this, function(_state) {
6215
6222
  switch(_state.label){
6216
6223
  case 0:
@@ -6294,11 +6301,20 @@ var OidcInteractionController_1;
6294
6301
  clientPayload = _state.sent();
6295
6302
  // Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
6296
6303
  // (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
6297
- // `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
6298
- // not an admin, hard-reject with `access_denied` rather than silently dropping the scope.
6304
+ // `encountered`). If that set intersects the admin-only scopes — the provider's `adminOnlyScopes`
6305
+ // unioned with the scopes of every profile marked `adminOnly` — and the resolving user is not an
6306
+ // admin, hard-reject with `access_denied` rather than silently dropping the scope.
6299
6307
  requestedOIDCScopeSet = new Set(_to_consumable_array$1(missingOIDCScope).concat(_to_consumable_array$1(encounteredOIDCScopes)));
6300
- adminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
6301
- requestsServiceToken = adminOnlyScopes.some(function(scope) {
6308
+ providerProfiles = this.accountService.providerConfig.providerProfiles;
6309
+ configAdminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
6310
+ adminOnlyScopes = new Set(_to_consumable_array$1(configAdminOnlyScopes).concat(_to_consumable_array$1(adminOnlyScopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []))));
6311
+ requestedAdminOnlyScopes = Array.from(adminOnlyScopes).filter(function(scope) {
6312
+ return requestedOIDCScopeSet.has(scope);
6313
+ });
6314
+ // Deliberately sourced from the provider config's `adminOnlyScopes` ALONE, unlike the gate above:
6315
+ // this flag also selects the (highest) service-token TTL tier further down, and a profile marked
6316
+ // `adminOnly` must gate by admin status WITHOUT widening its grant's lifetime to that tier.
6317
+ requestsServiceToken = configAdminOnlyScopes.some(function(scope) {
6302
6318
  return requestedOIDCScopeSet.has(scope);
6303
6319
  });
6304
6320
  if (!accountId) return [
@@ -6320,7 +6336,7 @@ var OidcInteractionController_1;
6320
6336
  _state.label = 12;
6321
6337
  case 12:
6322
6338
  isAdmin = _tmp1;
6323
- if (!(requestsServiceToken && !isAdmin)) return [
6339
+ if (!(requestedAdminOnlyScopes.length > 0 && !isAdmin)) return [
6324
6340
  3,
6325
6341
  14
6326
6342
  ];
@@ -6328,7 +6344,7 @@ var OidcInteractionController_1;
6328
6344
  4,
6329
6345
  this.oidcInteractionService.finishInteractionByUid(uid, {
6330
6346
  error: 'access_denied',
6331
- error_description: 'token.service is restricted to admins.'
6347
+ error_description: "The following scope(s) are restricted to admins: ".concat(requestedAdminOnlyScopes.join(', '), ".")
6332
6348
  }, {
6333
6349
  mergeWithLastSubmission: true
6334
6350
  })
@@ -6340,7 +6356,7 @@ var OidcInteractionController_1;
6340
6356
  isSuccessful: false,
6341
6357
  uid: accountId,
6342
6358
  clientId: clientId,
6343
- serviceToken: true,
6359
+ serviceToken: requestsServiceToken,
6344
6360
  isAdmin: false,
6345
6361
  reason: 'service_token_non_admin',
6346
6362
  durationMs: Date.now() - startedAt
@@ -6353,9 +6369,9 @@ var OidcInteractionController_1;
6353
6369
  ];
6354
6370
  case 14:
6355
6371
  // Provider-profile scope gate. Any scope referenced by a provider profile is "gated" — available
6356
- // only to clients whose assigned profiles unlock it. Independent of the admin-only gate above; a
6357
- // scope may be subject to both.
6358
- providerProfiles = this.accountService.providerConfig.providerProfiles;
6372
+ // only to clients whose assigned profiles unlock it (a client with NO assigned profiles resolves
6373
+ // to the registry's default profiles). Independent of the admin-only gate above; a scope may be
6374
+ // subject to both.
6359
6375
  _oidcClientProviderProfileScopes = oidcClientProviderProfileScopes(providerProfiles, (_ref2 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_provider_profiles) !== null && _ref2 !== void 0 ? _ref2 : undefined), clientUnlockedScopes = _oidcClientProviderProfileScopes.unlocked, clientRequiredScopes = _oidcClientProviderProfileScopes.required;
6360
6376
  profileGatedScopes = scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
6361
6377
  // Unlock gate: reject any requested gated scope this client's profiles do not unlock.
package/oidc/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/oidc",
3
- "version": "13.30.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.30.0",
6
- "@dereekb/date": "13.30.0",
7
- "@dereekb/firebase": "13.30.0",
8
- "@dereekb/firebase-server": "13.30.0",
9
- "@dereekb/model": "13.30.0",
10
- "@dereekb/nestjs": "13.30.0",
11
- "@dereekb/rxjs": "13.30.0",
12
- "@dereekb/util": "13.30.0",
13
- "@dereekb/zoho": "13.30.0",
5
+ "@dereekb/analytics": "13.31.0",
6
+ "@dereekb/date": "13.31.0",
7
+ "@dereekb/firebase": "13.31.0",
8
+ "@dereekb/firebase-server": "13.31.0",
9
+ "@dereekb/model": "13.31.0",
10
+ "@dereekb/nestjs": "13.31.0",
11
+ "@dereekb/rxjs": "13.31.0",
12
+ "@dereekb/util": "13.31.0",
13
+ "@dereekb/zoho": "13.31.0",
14
14
  "@nestjs/common": "^11.1.19",
15
15
  "@nestjs/config": "^4.0.4",
16
16
  "express": "^5.2.1",
@@ -94,6 +94,11 @@ export interface OidcProviderConfig<S extends OidcScope = OidcScope> {
94
94
  *
95
95
  * Keeps the generic package app-agnostic: apps opt a scope (e.g. `token.service`) into admin-only
96
96
  * behavior by listing it here, rather than the package hard-coding any scope name.
97
+ *
98
+ * The consent gate applies this list UNIONED with the scopes of every {@link providerProfiles} entry
99
+ * marked `adminOnly`. Only the scopes listed HERE additionally select the (highest) service-token
100
+ * login-duration tier — a profile marked `adminOnly` gates by admin status without widening its
101
+ * grant's lifetime.
97
102
  */
98
103
  readonly adminOnlyScopes?: readonly string[];
99
104
  /**
@@ -111,6 +116,12 @@ export interface OidcProviderConfig<S extends OidcScope = OidcScope> {
111
116
  * metadata) unlocks it. A profile scope marked `require: 'required'` is force-required — surfaced
112
117
  * as a required scope at consent and hard-rejected if not granted.
113
118
  *
119
+ * A profile marked `isDefault` applies to a client with NO profiles assigned, so an app can make a
120
+ * coarse, broadly-available scope profile-gated without breaking already-registered clients. The
121
+ * fallback is exclusive — assigning any other profile does not additionally confer the default's
122
+ * scopes. A profile marked `adminOnly` has its scopes unioned into {@link adminOnlyScopes} for the
123
+ * consent gate.
124
+ *
114
125
  * Keeps the generic package app-agnostic: apps declare their profiles statically and list them
115
126
  * here, rather than the package hard-coding any profile or scope name. This gate is independent of
116
127
  * {@link adminOnlyScopes} (which gates by the resolving user's admin status); a scope may be
@@ -22,6 +22,13 @@ export interface OidcClientProviderProfileScopes<S extends OidcScope = OidcScope
22
22
  * Resolves the unlocked + required scope sets for a client from the provider-profile registry and
23
23
  * the profile keys assigned to that client.
24
24
  *
25
+ * A client with NO assigned profiles resolves to the registry's default profiles (those marked
26
+ * `isDefault`), so a registry declaring no default resolves exactly as it would from the assigned
27
+ * keys alone.
28
+ *
29
+ * This is the single choke point for a client's profile resolution — the consent unlock gate, the
30
+ * consent required gate, and the `requiredScopes` interaction param all read through it.
31
+ *
25
32
  * @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
26
33
  * @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
27
34
  * @returns The unlocked and required scope sets for the client.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server",
3
- "version": "13.30.0",
3
+ "version": "13.31.0",
4
4
  "sideEffects": false,
5
5
  "exports": {
6
6
  "./test": {
@@ -57,15 +57,15 @@
57
57
  "types": "./src/index.d.ts",
58
58
  "peerDependencies": {
59
59
  "@cantoo/pdf-lib": "^2.6.5",
60
- "@dereekb/analytics": "13.30.0",
61
- "@dereekb/date": "13.30.0",
62
- "@dereekb/dbx-core": "13.30.0",
63
- "@dereekb/firebase": "13.30.0",
64
- "@dereekb/model": "13.30.0",
65
- "@dereekb/nestjs": "13.30.0",
66
- "@dereekb/rxjs": "13.30.0",
67
- "@dereekb/util": "13.30.0",
68
- "@dereekb/zoho": "13.30.0",
60
+ "@dereekb/analytics": "13.31.0",
61
+ "@dereekb/date": "13.31.0",
62
+ "@dereekb/dbx-core": "13.31.0",
63
+ "@dereekb/firebase": "13.31.0",
64
+ "@dereekb/model": "13.31.0",
65
+ "@dereekb/nestjs": "13.31.0",
66
+ "@dereekb/rxjs": "13.31.0",
67
+ "@dereekb/util": "13.31.0",
68
+ "@dereekb/zoho": "13.31.0",
69
69
  "@google-cloud/firestore": "^7.11.6",
70
70
  "@google-cloud/storage": "^7.19.0",
71
71
  "@modelcontextprotocol/sdk": "1.29.0",