@dereekb/firebase-server 13.29.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.
package/mcp/index.esm.js CHANGED
@@ -2,12 +2,12 @@ import { Injectable, Inject, Optional, Logger, Post, Req, Res, Controller, Get }
2
2
  import { existsSync, readFileSync } from 'node:fs';
3
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
4
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
+ import { callModelOidcScopeForCallType, resolveEffectiveOidcScopeTerms, inferKeyFromTwoWayFlatFirestoreModelKey, oidcScopeTermsSatisfied } from '@dereekb/firebase';
5
6
  import { getOidcScopesFromRequest, OidcProviderConfigService } from '@dereekb/firebase-server/oidc';
6
- import { filterMaybeArrayValues, makeValuesGroupMap, IMPERSONATION_URL_QUERY_PARAM, performAsyncTasks, authRolesSetHasRoles } from '@dereekb/util';
7
+ import { makeValuesGroupMap, IMPERSONATION_URL_QUERY_PARAM, performAsyncTasks, authRolesSetHasRoles } from '@dereekb/util';
7
8
  import { ModelApiCallModelDispatchService, ModelApiGetService, FirebaseServerStorageService, FirebaseServerEnvService, FirebaseServerAnalyticsService } from '@dereekb/firebase-server';
8
9
  import { inspect } from 'node:util';
9
10
  import { arktypeToJsonSchemaForExport } from '@dereekb/model';
10
- import { callModelOidcScopeForCallType, inferKeyFromTwoWayFlatFirestoreModelKey } from '@dereekb/firebase';
11
11
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
12
12
 
13
13
  function _class_call_check$5(instance, Constructor) {
@@ -165,6 +165,22 @@ function _define_property$9(obj, key, value) {
165
165
  * fills with a short justification for the call. The value is forwarded to analytics and stripped
166
166
  * from the dispatched handler body. See {@link McpReasonParameterConfig}.
167
167
  */ _define_property$9(this, "reasonParameter", void 0);
168
+ /**
169
+ * Default OIDC scope group term required on EVERY `callModel` op, mirrored into MCP tool-list
170
+ * visibility, unless a finer term overrides it (a per-function `requiredScope`, then a
171
+ * {@link McpModuleConfig.modelRequiredScopes} entry). Threaded into the same
172
+ * `resolveEffectiveOidcScopeTerms` composition the server-side model-api scope gate
173
+ * (`assertModelApiOidcScope`) uses, so a tool is advertised only when the caller could actually invoke it.
174
+ *
175
+ * Set this to the SAME value passed to `ModelApiDispatchConfig.defaultRequiredScope` to
176
+ * keep tool visibility and enforcement in lockstep.
177
+ */ _define_property$9(this, "defaultRequiredScope", void 0);
178
+ /**
179
+ * Per-model OIDC scope group-term overrides, keyed by {@link FirestoreModelType}, mirrored into MCP
180
+ * tool-list visibility. Set this to the SAME value passed to
181
+ * `ModelApiDispatchConfig.modelRequiredScopes` so per-model / verb-keyed restrictions that
182
+ * confine a subset client apply identically to the advertised tool list.
183
+ */ _define_property$9(this, "modelRequiredScopes", void 0);
168
184
  };
169
185
  /**
170
186
  * NestJS injection token for the optional {@link McpAuthRoleReader} provider.
@@ -1398,6 +1414,8 @@ function _unsupported_iterable_to_array$9(o, minLen) {
1398
1414
  var seenNames = new Set();
1399
1415
  var manifest = context === null || context === void 0 ? void 0 : context.manifest;
1400
1416
  var naming = context === null || context === void 0 ? void 0 : context.naming;
1417
+ var defaultRequiredScope = context === null || context === void 0 ? void 0 : context.defaultRequiredScope;
1418
+ var modelRequiredScopes = context === null || context === void 0 ? void 0 : context.modelRequiredScopes;
1401
1419
  var candidates = planMcpToolCandidates(apiDetails, naming);
1402
1420
  var clashCounts = countVisibleAutoNameClashes(candidates);
1403
1421
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
@@ -1409,6 +1427,8 @@ function _unsupported_iterable_to_array$9(o, minLen) {
1409
1427
  clashCounts: clashCounts,
1410
1428
  options: options,
1411
1429
  manifest: manifest,
1430
+ defaultRequiredScope: defaultRequiredScope,
1431
+ modelRequiredScopes: modelRequiredScopes,
1412
1432
  seenNames: seenNames,
1413
1433
  outTools: tools,
1414
1434
  outNeverVisibleTools: neverVisibleTools,
@@ -1580,7 +1600,7 @@ function _unsupported_iterable_to_array$9(o, minLen) {
1580
1600
  */ function buildToolFromCandidate(context) {
1581
1601
  var _clashCounts_get, _ref;
1582
1602
  var _handlerDetails_mcp, _handlerDetails_mcp1;
1583
- var candidate = context.candidate, clashCounts = context.clashCounts, options = context.options, manifest = context.manifest, seenNames = context.seenNames, outTools = context.outTools, outNeverVisibleTools = context.outNeverVisibleTools, outSkipped = context.outSkipped, outWarnings = context.outWarnings;
1603
+ var candidate = context.candidate, clashCounts = context.clashCounts, options = context.options, manifest = context.manifest, defaultRequiredScope = context.defaultRequiredScope, modelRequiredScopes = context.modelRequiredScopes, seenNames = context.seenNames, outTools = context.outTools, outNeverVisibleTools = context.outNeverVisibleTools, outSkipped = context.outSkipped, outWarnings = context.outWarnings;
1584
1604
  var modelType = candidate.modelType, callType = candidate.callType, handlerDetails = candidate.handlerDetails, specifier = candidate.specifier, dispatch = candidate.dispatch, modelSegment = candidate.modelSegment, overrideName = candidate.overrideName, baseName = candidate.baseName, classified = candidate.classified, isVisible = candidate.isVisible;
1585
1605
  // A visible auto-named tool whose preferred name is produced by more than one visible tool is
1586
1606
  // re-derived with the abbreviated call type so both survive on the wire. Overrides and hidden tools
@@ -1630,13 +1650,18 @@ function _unsupported_iterable_to_array$9(o, minLen) {
1630
1650
  });
1631
1651
  }
1632
1652
  }
1633
- // Additive scope set: the per-verb call-type scope (model.<call>) plus any per-function scope
1634
- // declared via withApiDetails({ requiredScope }). Nullish entries drop out, so a non-CRUD call
1635
- // type with no per-function scope yields an empty set (no scope gate).
1636
- var requiredScopes = filterMaybeArrayValues([
1637
- resolveRequiredScope(callType),
1638
- handlerDetails.requiredScope
1639
- ]);
1653
+ // AND-of-ORs scope terms: the per-verb call-type scope (model.<call>) plus the effective group term,
1654
+ // resolved by the same precedence the server model-api scope gate uses (per-function requiredScope > per-model
1655
+ // requirement > module default) via the shared resolveEffectiveOidcScopeTerms — so tool visibility
1656
+ // tracks callability. Empty when no term applies (a non-CRUD call type with no per-function/model/
1657
+ // default term → no scope gate).
1658
+ var requiredScopeTerms = resolveEffectiveOidcScopeTerms({
1659
+ perVerbScope: resolveRequiredScope(callType),
1660
+ requiredScope: handlerDetails.requiredScope,
1661
+ modelRequirement: modelRequiredScopes === null || modelRequiredScopes === void 0 ? void 0 : modelRequiredScopes[modelType],
1662
+ call: callType,
1663
+ defaultRequiredScope: defaultRequiredScope
1664
+ });
1640
1665
  var effectiveReadOnly = resolveEffectiveReadOnly((_handlerDetails_mcp = handlerDetails.mcp) === null || _handlerDetails_mcp === void 0 ? void 0 : _handlerDetails_mcp.readOnly, callType);
1641
1666
  var annotations = resolveMcpToolAnnotations(effectiveReadOnly);
1642
1667
  var description = applyWriteMarker(baseDescription, annotations);
@@ -1645,20 +1670,20 @@ function _unsupported_iterable_to_array$9(o, minLen) {
1645
1670
  filterMetadata = {
1646
1671
  visibilityKind: 'declarative',
1647
1672
  rule: classified.rule,
1648
- requiredScopes: requiredScopes,
1673
+ requiredScopeTerms: requiredScopeTerms,
1649
1674
  effectiveReadOnly: effectiveReadOnly
1650
1675
  };
1651
1676
  } else if (classified.visibilityKind === 'dynamic') {
1652
1677
  filterMetadata = {
1653
1678
  visibilityKind: 'dynamic',
1654
1679
  visibilityFn: classified.visibilityFn,
1655
- requiredScopes: requiredScopes,
1680
+ requiredScopeTerms: requiredScopeTerms,
1656
1681
  effectiveReadOnly: effectiveReadOnly
1657
1682
  };
1658
1683
  } else {
1659
1684
  filterMetadata = {
1660
1685
  visibilityKind: classified.visibilityKind,
1661
- requiredScopes: requiredScopes,
1686
+ requiredScopeTerms: requiredScopeTerms,
1662
1687
  effectiveReadOnly: effectiveReadOnly
1663
1688
  };
1664
1689
  }
@@ -6161,7 +6186,9 @@ var McpServerFactoryService_1;
6161
6186
  } else {
6162
6187
  result = generateMcpToolDefinitions(apiDetails, undefined, {
6163
6188
  manifest: manifest,
6164
- naming: this._resolveToolNamingOptions()
6189
+ naming: this._resolveToolNamingOptions(),
6190
+ defaultRequiredScope: this.mcpConfig.defaultRequiredScope,
6191
+ modelRequiredScopes: this.mcpConfig.modelRequiredScopes
6165
6192
  });
6166
6193
  }
6167
6194
  this._cachedTools = result;
@@ -6630,7 +6657,7 @@ var McpServerFactoryService_1;
6630
6657
  * Synthesizes the same `{ auth: { token } }` shape that `getOidcScopesFromRequest`
6631
6658
  * expects post-dispatch, so the upstream helper stays the single source of scope parsing.
6632
6659
  * Returns `undefined` for non-OIDC callers (no `oidcValidatedToken.scope`) — the filter
6633
- * loop treats that as "skip scope enforcement", matching `oidcCallModelScopePreAssert`.
6660
+ * loop treats that as "skip scope enforcement", matching the model-api scope gate (`assertModelApiOidcScope`).
6634
6661
  *
6635
6662
  * @param ctx - The per-request context carrying the validated auth payload.
6636
6663
  * @returns The set of granted OIDC scopes, or `undefined` when scope enforcement should be skipped.
@@ -6674,9 +6701,7 @@ var McpServerFactoryService_1;
6674
6701
  for(var _iterator = tools[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
6675
6702
  var tool = _step.value;
6676
6703
  var filterMetadata = tool.filterMetadata;
6677
- if (scopes != null && filterMetadata.requiredScopes != null && !filterMetadata.requiredScopes.every(function(scope) {
6678
- return scopes.has(scope);
6679
- })) {
6704
+ if (scopes != null && filterMetadata.requiredScopeTerms != null && !oidcScopeTermsSatisfied(filterMetadata.requiredScopeTerms, scopes)) {
6680
6705
  continue;
6681
6706
  }
6682
6707
  if (readOnlyMode && filterMetadata.effectiveReadOnly !== true) {
package/mcp/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/mcp",
3
- "version": "13.29.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.29.0",
6
- "@dereekb/date": "13.29.0",
7
- "@dereekb/firebase": "13.29.0",
8
- "@dereekb/firebase-server": "13.29.0",
9
- "@dereekb/firebase-server/oidc": "13.29.0",
10
- "@dereekb/model": "13.29.0",
11
- "@dereekb/nestjs": "13.29.0",
12
- "@dereekb/rxjs": "13.29.0",
13
- "@dereekb/util": "13.29.0",
14
- "@dereekb/zoho": "13.29.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,5 +1,5 @@
1
1
  import { type AuthClaims, type AuthRoleSet } from '@dereekb/util';
2
- import { type OidcScope } from '@dereekb/firebase';
2
+ import { type FirestoreModelType, type OidcModelScopeRequirement, type OidcScope, type OidcScopeTerm } from '@dereekb/firebase';
3
3
  /**
4
4
  * Default path the MCP Streamable HTTP transport is mounted at.
5
5
  *
@@ -200,6 +200,24 @@ export declare abstract class McpModuleConfig {
200
200
  * from the dispatched handler body. See {@link McpReasonParameterConfig}.
201
201
  */
202
202
  readonly reasonParameter?: McpReasonParameterConfig | boolean;
203
+ /**
204
+ * Default OIDC scope group term required on EVERY `callModel` op, mirrored into MCP tool-list
205
+ * visibility, unless a finer term overrides it (a per-function `requiredScope`, then a
206
+ * {@link McpModuleConfig.modelRequiredScopes} entry). Threaded into the same
207
+ * `resolveEffectiveOidcScopeTerms` composition the server-side model-api scope gate
208
+ * (`assertModelApiOidcScope`) uses, so a tool is advertised only when the caller could actually invoke it.
209
+ *
210
+ * Set this to the SAME value passed to `ModelApiDispatchConfig.defaultRequiredScope` to
211
+ * keep tool visibility and enforcement in lockstep.
212
+ */
213
+ readonly defaultRequiredScope?: OidcScopeTerm;
214
+ /**
215
+ * Per-model OIDC scope group-term overrides, keyed by {@link FirestoreModelType}, mirrored into MCP
216
+ * tool-list visibility. Set this to the SAME value passed to
217
+ * `ModelApiDispatchConfig.modelRequiredScopes` so per-model / verb-keyed restrictions that
218
+ * confine a subset client apply identically to the advertised tool list.
219
+ */
220
+ readonly modelRequiredScopes?: Record<FirestoreModelType, OidcModelScopeRequirement>;
203
221
  }
204
222
  /**
205
223
  * Signature for the optional role reader the MCP module uses when evaluating
@@ -152,7 +152,7 @@ export declare class McpServerFactoryService {
152
152
  * Synthesizes the same `{ auth: { token } }` shape that `getOidcScopesFromRequest`
153
153
  * expects post-dispatch, so the upstream helper stays the single source of scope parsing.
154
154
  * Returns `undefined` for non-OIDC callers (no `oidcValidatedToken.scope`) — the filter
155
- * loop treats that as "skip scope enforcement", matching `oidcCallModelScopePreAssert`.
155
+ * loop treats that as "skip scope enforcement", matching the model-api scope gate (`assertModelApiOidcScope`).
156
156
  *
157
157
  * @param ctx - The per-request context carrying the validated auth payload.
158
158
  * @returns The set of granted OIDC scopes, or `undefined` when scope enforcement should be skipped.
@@ -1,5 +1,5 @@
1
1
  import { type Maybe } from '@dereekb/util';
2
- import { type KnownOnCallFunctionType } from '@dereekb/firebase';
2
+ import { type FirestoreModelType, type KnownOnCallFunctionType, type OidcModelScopeRequirement, type OidcScopeTerm } from '@dereekb/firebase';
3
3
  import { type ModelApiDetailsResult, type OnCallModelFunctionApiDetails, type FirebaseServerAuthData, type McpToolDetailsBuilder, type McpVisibilityRule } from '@dereekb/firebase-server';
4
4
  import { type Request } from 'express';
5
5
  import { type CallToolResult, type ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
@@ -452,6 +452,17 @@ export interface GenerateMcpToolDefinitionsContext {
452
452
  * Per-model name segment overrides (e.g. collection prefixes).
453
453
  */
454
454
  readonly naming?: McpToolGenerationNamingOptions;
455
+ /**
456
+ * Default OIDC scope group term required on every op unless a finer term overrides it. Mirrors the
457
+ * model-api layer's `ModelApiDispatchConfig.defaultRequiredScope` so a tool is advertised only when
458
+ * the caller could invoke it.
459
+ */
460
+ readonly defaultRequiredScope?: OidcScopeTerm;
461
+ /**
462
+ * Per-model OIDC scope group-term overrides, keyed by {@link FirestoreModelType}. Mirrors the model-api
463
+ * layer's `ModelApiDispatchConfig.modelRequiredScopes` so per-model / verb-keyed restrictions apply to tool visibility.
464
+ */
465
+ readonly modelRequiredScopes?: Record<FirestoreModelType, OidcModelScopeRequirement>;
455
466
  }
456
467
  /**
457
468
  * Generates MCP tool definitions from a model-first API details tree.
@@ -1,5 +1,5 @@
1
1
  import { type Maybe } from '@dereekb/util';
2
- import { type CallModelOidcScope, type OidcScope } from '@dereekb/firebase';
2
+ import { type CallModelOidcScope, type OidcScopeTerm } from '@dereekb/firebase';
3
3
  import { type McpToolVisibility, type McpVisibilityContext, type McpVisibilityRule } from '@dereekb/firebase-server';
4
4
  import { type ToolAnnotations } from '@modelcontextprotocol/sdk/types.js';
5
5
  /**
@@ -41,12 +41,15 @@ export interface ClassifiedMcpToolVisibilityDynamic {
41
41
  export type McpToolFilterMetadata = McpToolFilterMetadataAlways | McpToolFilterMetadataNever | McpToolFilterMetadataDeclarative | McpToolFilterMetadataDynamic;
42
42
  interface McpToolFilterMetadataBase {
43
43
  /**
44
- * OIDC scopes required to invoke this tool (AND-semantics — the caller must hold every entry).
45
- * Precomputed from the dispatch call type's per-verb scope (`model.<call>`) plus any per-function
46
- * scope declared via `withApiDetails({ requiredScope })`. Empty when no scope is enforced (a
47
- * non-CRUD call type with no per-function scope); apps gate custom verbs via their own preAssert.
44
+ * OIDC scope TERMS required to invoke this tool, evaluated AND-of-ORs — the caller must satisfy
45
+ * EVERY term, and an array term (OR-group) is satisfied by holding ANY member. Checked with the
46
+ * shared `oidcScopeTermsSatisfied` so tool-list visibility matches the server-side `callModel` gate
47
+ * exactly. Precomputed from the dispatch call type's per-verb scope (`model.<call>`) plus the
48
+ * effective group term (per-function `requiredScope` > per-model requirement > module default).
49
+ * Empty when no scope is enforced (a non-CRUD call type with no per-function/model/default term);
50
+ * apps gate custom verbs via their own preAssert.
48
51
  */
49
- readonly requiredScopes?: readonly OidcScope[];
52
+ readonly requiredScopeTerms?: readonly OidcScopeTerm[];
50
53
  /**
51
54
  * Effective read-only classification used by the module-level `readOnly` filter.
52
55
  *
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/model",
3
- "version": "13.29.0",
3
+ "version": "13.31.0",
4
4
  "peerDependencies": {
5
- "@dereekb/analytics": "13.29.0",
6
- "@dereekb/date": "13.29.0",
7
- "@dereekb/firebase": "13.29.0",
8
- "@dereekb/firebase-server": "13.29.0",
9
- "@dereekb/model": "13.29.0",
10
- "@dereekb/nestjs": "13.29.0",
11
- "@dereekb/rxjs": "13.29.0",
12
- "@dereekb/util": "13.29.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)
@@ -5395,7 +5402,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5395
5402
  * `request.auth.token.scope` for OIDC callers and is `undefined` for non-OIDC
5396
5403
  * (regular Firebase ID-token) callers.
5397
5404
  *
5398
- * Returning `undefined` for non-OIDC callers lets callers (e.g. `oidcCallModelScopePreAssert`)
5405
+ * Returning `undefined` for non-OIDC callers lets consumers (e.g. the MCP tool-visibility filter)
5399
5406
  * skip scope enforcement instead of falsely treating the missing claim as "no scopes granted".
5400
5407
  *
5401
5408
  * The parameter is intentionally typed as `unknown` because `DecodedIdToken` (Firebase
@@ -5407,10 +5414,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5407
5414
  */ function getOidcScopesFromRequest(request) {
5408
5415
  var _request_auth_token, _request_auth;
5409
5416
  var scope = request === null || request === void 0 ? void 0 : (_request_auth = request.auth) === null || _request_auth === void 0 ? void 0 : (_request_auth_token = _request_auth.token) === null || _request_auth_token === void 0 ? void 0 : _request_auth_token.scope;
5410
- var result = typeof scope === 'string' ? new Set(scope.split(' ').filter(function(value) {
5411
- return value.length > 0;
5412
- })) : undefined;
5413
- return result;
5417
+ return firebase.oidcScopesFromScopeClaim(scope);
5414
5418
  }
5415
5419
 
5416
5420
  function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
@@ -6215,7 +6219,7 @@ var OidcInteractionController_1;
6215
6219
  * @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
6216
6220
  */ function postConsent(uid, body, res) {
6217
6221
  return _async_to_generator$2(function() {
6218
- 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;
6219
6223
  return _ts_generator$2(this, function(_state) {
6220
6224
  switch(_state.label){
6221
6225
  case 0:
@@ -6299,11 +6303,20 @@ var OidcInteractionController_1;
6299
6303
  clientPayload = _state.sent();
6300
6304
  // Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
6301
6305
  // (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
6302
- // `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
6303
- // 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.
6304
6309
  requestedOIDCScopeSet = new Set(_to_consumable_array$1(missingOIDCScope).concat(_to_consumable_array$1(encounteredOIDCScopes)));
6305
- adminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
6306
- 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) {
6307
6320
  return requestedOIDCScopeSet.has(scope);
6308
6321
  });
6309
6322
  if (!accountId) return [
@@ -6325,7 +6338,7 @@ var OidcInteractionController_1;
6325
6338
  _state.label = 12;
6326
6339
  case 12:
6327
6340
  isAdmin = _tmp1;
6328
- if (!(requestsServiceToken && !isAdmin)) return [
6341
+ if (!(requestedAdminOnlyScopes.length > 0 && !isAdmin)) return [
6329
6342
  3,
6330
6343
  14
6331
6344
  ];
@@ -6333,7 +6346,7 @@ var OidcInteractionController_1;
6333
6346
  4,
6334
6347
  this.oidcInteractionService.finishInteractionByUid(uid, {
6335
6348
  error: 'access_denied',
6336
- error_description: 'token.service is restricted to admins.'
6349
+ error_description: "The following scope(s) are restricted to admins: ".concat(requestedAdminOnlyScopes.join(', '), ".")
6337
6350
  }, {
6338
6351
  mergeWithLastSubmission: true
6339
6352
  })
@@ -6345,7 +6358,7 @@ var OidcInteractionController_1;
6345
6358
  isSuccessful: false,
6346
6359
  uid: accountId,
6347
6360
  clientId: clientId,
6348
- serviceToken: true,
6361
+ serviceToken: requestsServiceToken,
6349
6362
  isAdmin: false,
6350
6363
  reason: 'service_token_non_admin',
6351
6364
  durationMs: Date.now() - startedAt
@@ -6358,9 +6371,9 @@ var OidcInteractionController_1;
6358
6371
  ];
6359
6372
  case 14:
6360
6373
  // Provider-profile scope gate. Any scope referenced by a provider profile is "gated" — available
6361
- // only to clients whose assigned profiles unlock it. Independent of the admin-only gate above; a
6362
- // scope may be subject to both.
6363
- 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.
6364
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;
6365
6378
  profileGatedScopes = firebase.scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
6366
6379
  // Unlock gate: reject any requested gated scope this client's profiles do not unlock.
@@ -7781,53 +7794,6 @@ function _unsupported_iterable_to_array(o, minLen) {
7781
7794
  };
7782
7795
  }
7783
7796
 
7784
- /**
7785
- * Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
7786
- * lacking the required OIDC scope(s) for the call.
7787
- *
7788
- * Enforcement is additive: an OIDC caller must hold BOTH the per-verb scope mapped
7789
- * from the call type (`model.<call>`) AND any per-function scope a handler declared
7790
- * via `withApiDetails({ requiredScope })` — surfaced on `context.requiredScope`.
7791
- *
7792
- * Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
7793
- * when {@link getOidcScopesFromRequest} returns `undefined` because there is no
7794
- * `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
7795
- * unaffected and continue to be gated by auth roles.
7796
- *
7797
- * Bypasses the check for custom (non-CRUD) call types that declare no per-function
7798
- * scope, so app-specific verbs remain unrestricted unless an app wires its own
7799
- * assertion or declares a `requiredScope`.
7800
- *
7801
- * Wire as the `preAssert` of {@link onCallModel}.
7802
- *
7803
- * @returns The pre-assertion function ready to plug into `OnCallModelConfig.preAssert`.
7804
- */ function oidcCallModelScopePreAssert() {
7805
- var fn = function fn(context) {
7806
- var requiredScopes = util.filterMaybeArrayValues([
7807
- firebase.callModelOidcScopeForCallType(context.call),
7808
- context.requiredScope
7809
- ]);
7810
- // No requirement (custom verb + no per-function scope) short-circuits without reading scopes.
7811
- // A non-OIDC caller yields `undefined` scopes below and bypasses just like the per-verb check.
7812
- var scopes = requiredScopes.length === 0 ? undefined : getOidcScopesFromRequest(context.request);
7813
- var missingScopes = scopes == null ? [] : requiredScopes.filter(function(scope) {
7814
- return !scopes.has(scope);
7815
- });
7816
- if (missingScopes.length > 0) {
7817
- throw firebaseServer.forbiddenError({
7818
- status: 403,
7819
- code: firebase.CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE,
7820
- message: "Missing required OIDC scope for callModel: ".concat(missingScopes.join(', ')),
7821
- data: {
7822
- requiredScopes: missingScopes,
7823
- call: context.call
7824
- }
7825
- });
7826
- }
7827
- };
7828
- return fn;
7829
- }
7830
-
7831
7797
  exports.DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA;
7832
7798
  exports.DEFAULT_APP_OAUTH_CONSENT_PATH_PART = DEFAULT_APP_OAUTH_CONSENT_PATH_PART;
7833
7799
  exports.DEFAULT_APP_OAUTH_INTERACTION_PATH = DEFAULT_APP_OAUTH_INTERACTION_PATH;
@@ -7890,7 +7856,6 @@ exports.jwksKeyIdentity = jwksKeyIdentity;
7890
7856
  exports.jwksKeysWithStatusQuery = jwksKeysWithStatusQuery;
7891
7857
  exports.nonRetiredJwksKeysQuery = nonRetiredJwksKeysQuery;
7892
7858
  exports.noopOidcAnalyticsService = noopOidcAnalyticsService;
7893
- exports.oidcCallModelScopePreAssert = oidcCallModelScopePreAssert;
7894
7859
  exports.oidcClientProviderProfileScopes = oidcClientProviderProfileScopes;
7895
7860
  exports.oidcCorsOriginDelegate = oidcCorsOriginDelegate;
7896
7861
  exports.oidcFirestoreCollectionsFactory = oidcFirestoreCollectionsFactory;