@dereekb/firebase-server 13.29.0 → 13.30.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.30.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.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",
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.30.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.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
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
@@ -5395,7 +5395,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5395
5395
  * `request.auth.token.scope` for OIDC callers and is `undefined` for non-OIDC
5396
5396
  * (regular Firebase ID-token) callers.
5397
5397
  *
5398
- * Returning `undefined` for non-OIDC callers lets callers (e.g. `oidcCallModelScopePreAssert`)
5398
+ * Returning `undefined` for non-OIDC callers lets consumers (e.g. the MCP tool-visibility filter)
5399
5399
  * skip scope enforcement instead of falsely treating the missing claim as "no scopes granted".
5400
5400
  *
5401
5401
  * The parameter is intentionally typed as `unknown` because `DecodedIdToken` (Firebase
@@ -5407,10 +5407,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5407
5407
  */ function getOidcScopesFromRequest(request) {
5408
5408
  var _request_auth_token, _request_auth;
5409
5409
  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;
5410
+ return firebase.oidcScopesFromScopeClaim(scope);
5414
5411
  }
5415
5412
 
5416
5413
  function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
@@ -7781,53 +7778,6 @@ function _unsupported_iterable_to_array(o, minLen) {
7781
7778
  };
7782
7779
  }
7783
7780
 
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
7781
  exports.DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA;
7832
7782
  exports.DEFAULT_APP_OAUTH_CONSENT_PATH_PART = DEFAULT_APP_OAUTH_CONSENT_PATH_PART;
7833
7783
  exports.DEFAULT_APP_OAUTH_INTERACTION_PATH = DEFAULT_APP_OAUTH_INTERACTION_PATH;
@@ -7890,7 +7840,6 @@ exports.jwksKeyIdentity = jwksKeyIdentity;
7890
7840
  exports.jwksKeysWithStatusQuery = jwksKeysWithStatusQuery;
7891
7841
  exports.nonRetiredJwksKeysQuery = nonRetiredJwksKeysQuery;
7892
7842
  exports.noopOidcAnalyticsService = noopOidcAnalyticsService;
7893
- exports.oidcCallModelScopePreAssert = oidcCallModelScopePreAssert;
7894
7843
  exports.oidcClientProviderProfileScopes = oidcClientProviderProfileScopes;
7895
7844
  exports.oidcCorsOriginDelegate = oidcCorsOriginDelegate;
7896
7845
  exports.oidcFirestoreCollectionsFactory = oidcFirestoreCollectionsFactory;
package/oidc/index.esm.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { createParamDecorator, Injectable, Inject, Optional, Logger, UnauthorizedException, Get, Param, Req, Res, Post, HttpCode, HttpStatus, Body, Controller, HttpException, All } from '@nestjs/common';
2
2
  import { errors } from 'oidc-provider';
3
- import { SECONDS_IN_MINUTE, SECONDS_IN_DAY, cachedGetter, unixDateTimeSecondsNumberToDate, unixDateTimeSecondsNumberForNow, selectiveFieldEncryptor, filterUndefinedValues, websiteUrlFromPaths, firstValue, hasHttpPrefix, filterMaybeArrayValues } from '@dereekb/util';
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, oidcEntryFirestoreCollection, callModelOidcScopeForCallType, CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE } from '@dereekb/firebase';
7
- import { firestoreEncryptedField, firebaseServerActionsContext, FirebaseServerEnvService, FirebaseServerAnalyticsService, FIREBASE_FIRESTORE_CONTEXT_TOKEN, FirebaseServerFirestoreContextModule, forbiddenError } from '@dereekb/firebase-server';
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';
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';
10
10
  import { makeUrlSearchParamsString } from '@dereekb/util/fetch';
@@ -5393,7 +5393,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5393
5393
  * `request.auth.token.scope` for OIDC callers and is `undefined` for non-OIDC
5394
5394
  * (regular Firebase ID-token) callers.
5395
5395
  *
5396
- * Returning `undefined` for non-OIDC callers lets callers (e.g. `oidcCallModelScopePreAssert`)
5396
+ * Returning `undefined` for non-OIDC callers lets consumers (e.g. the MCP tool-visibility filter)
5397
5397
  * skip scope enforcement instead of falsely treating the missing claim as "no scopes granted".
5398
5398
  *
5399
5399
  * The parameter is intentionally typed as `unknown` because `DecodedIdToken` (Firebase
@@ -5405,10 +5405,7 @@ function _unsupported_iterable_to_array$2(o, minLen) {
5405
5405
  */ function getOidcScopesFromRequest(request) {
5406
5406
  var _request_auth_token, _request_auth;
5407
5407
  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;
5408
- var result = typeof scope === 'string' ? new Set(scope.split(' ').filter(function(value) {
5409
- return value.length > 0;
5410
- })) : undefined;
5411
- return result;
5408
+ return oidcScopesFromScopeClaim(scope);
5412
5409
  }
5413
5410
 
5414
5411
  function asyncGeneratorStep$3(gen, resolve, reject, _next, _throw, key, arg) {
@@ -7779,51 +7776,4 @@ function _unsupported_iterable_to_array(o, minLen) {
7779
7776
  };
7780
7777
  }
7781
7778
 
7782
- /**
7783
- * Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
7784
- * lacking the required OIDC scope(s) for the call.
7785
- *
7786
- * Enforcement is additive: an OIDC caller must hold BOTH the per-verb scope mapped
7787
- * from the call type (`model.<call>`) AND any per-function scope a handler declared
7788
- * via `withApiDetails({ requiredScope })` — surfaced on `context.requiredScope`.
7789
- *
7790
- * Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
7791
- * when {@link getOidcScopesFromRequest} returns `undefined` because there is no
7792
- * `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
7793
- * unaffected and continue to be gated by auth roles.
7794
- *
7795
- * Bypasses the check for custom (non-CRUD) call types that declare no per-function
7796
- * scope, so app-specific verbs remain unrestricted unless an app wires its own
7797
- * assertion or declares a `requiredScope`.
7798
- *
7799
- * Wire as the `preAssert` of {@link onCallModel}.
7800
- *
7801
- * @returns The pre-assertion function ready to plug into `OnCallModelConfig.preAssert`.
7802
- */ function oidcCallModelScopePreAssert() {
7803
- var fn = function fn(context) {
7804
- var requiredScopes = filterMaybeArrayValues([
7805
- callModelOidcScopeForCallType(context.call),
7806
- context.requiredScope
7807
- ]);
7808
- // No requirement (custom verb + no per-function scope) short-circuits without reading scopes.
7809
- // A non-OIDC caller yields `undefined` scopes below and bypasses just like the per-verb check.
7810
- var scopes = requiredScopes.length === 0 ? undefined : getOidcScopesFromRequest(context.request);
7811
- var missingScopes = scopes == null ? [] : requiredScopes.filter(function(scope) {
7812
- return !scopes.has(scope);
7813
- });
7814
- if (missingScopes.length > 0) {
7815
- throw forbiddenError({
7816
- status: 403,
7817
- code: CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE,
7818
- message: "Missing required OIDC scope for callModel: ".concat(missingScopes.join(', ')),
7819
- data: {
7820
- requiredScopes: missingScopes,
7821
- call: context.call
7822
- }
7823
- });
7824
- }
7825
- };
7826
- return fn;
7827
- }
7828
-
7829
- export { DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, DEFAULT_APP_OAUTH_CONSENT_PATH_PART, DEFAULT_APP_OAUTH_INTERACTION_PATH, DEFAULT_APP_OAUTH_LOGIN_PATH_PART, DEFAULT_MAX_ADMIN_LOGIN_DURATION_SECONDS, DEFAULT_MAX_NONADMIN_LOGIN_DURATION_SECONDS, DEFAULT_MAX_REQUESTED_LOGIN_DURATION_SECONDS, DEFAULT_MAX_SERVICE_TOKEN_LOGIN_DURATION_SECONDS, DEFAULT_MIN_REQUESTED_LOGIN_DURATION_SECONDS, DEFAULT_OIDC_ANALYTICS_EVENT_NAMES, DEFAULT_OIDC_ANALYTICS_EVENT_NAME_PREFIX, DEFAULT_OIDC_CODE_CHALLENGE_METHODS, DEFAULT_OIDC_ID_TOKEN_SIGNING_ALG_VALUES, DEFAULT_OIDC_ISSUER_PATH, DEFAULT_OIDC_ROUTES, DEFAULT_OIDC_SUBJECT_TYPES, DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS, DEFAULT_OIDC_TOKEN_LIFETIMES, DEFAULT_ROTATED_KEY_MAX_AGE, FIREBASE_SERVER_OIDC_ANALYTICS_CONFIG, FIREBASE_SERVER_OIDC_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerOidcAnalyticsService, GRANTABLE_MODEL_NAMES, JwksFirestoreCollections, JwksKeyDocument, JwksService, JwksServiceConfig, JwksServiceStorageConfig, OIDC_ANALYTICS_SERVICE, OIDC_ENCRYPTED_PAYLOAD_FIELDS, OIDC_JSON_RENDER_ERROR_FUNCTION, OIDC_JWKS_ENCRYPTION_SECRET_ENV_KEY, OidcAccountService, OidcAccountServiceDelegate, OidcAccountServiceUserContext, OidcAnalyticsService, OidcAuth, OidcAuthBearerTokenMiddleware, OidcAuthMiddlewareConfig, OidcClientService, OidcEncryptionService, OidcInteractionController, OidcInteractionService, OidcModelServerActions, OidcModuleConfig, OidcProviderConfigService, OidcProviderController, OidcServerFirestoreCollections, OidcService, OidcWellKnownController, activeJwksKeysQuery, allOidcScopesStringForProviderConfig, appOidcAnalyticsModuleMetadata, appOidcModelModuleMetadata, applyOidcAuthMiddleware, applyOidcCorsMiddleware, buildBearerChallenge, buildFirebaseServerMcpResourceServer, createAdapterFactory, createOidcClientFactory, deleteOidcClientFactory, deleteOidcTokenFactory, deriveResourceMetadataUrlFromEnv, emitOidcAnalyticsEvent, getOidcScopesFromRequest, jwksKeyCollectionReference, jwksKeyConverter, jwksKeyFirestoreCollection, jwksKeyIdentity, jwksKeysWithStatusQuery, nonRetiredJwksKeysQuery, noopOidcAnalyticsService, oidcCallModelScopePreAssert, oidcClientProviderProfileScopes, oidcCorsOriginDelegate, oidcFirestoreCollectionsFactory, oidcModelServerActions, oidcModelServerActionsFactory, oidcModuleConfigFactory, oidcModuleMetadata, resolveEffectiveSubset, rotateOidcClientSecretFactory, rotatedJwksKeysQuery, updateOidcClientFactory };
7779
+ export { DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, DEFAULT_APP_OAUTH_CONSENT_PATH_PART, DEFAULT_APP_OAUTH_INTERACTION_PATH, DEFAULT_APP_OAUTH_LOGIN_PATH_PART, DEFAULT_MAX_ADMIN_LOGIN_DURATION_SECONDS, DEFAULT_MAX_NONADMIN_LOGIN_DURATION_SECONDS, DEFAULT_MAX_REQUESTED_LOGIN_DURATION_SECONDS, DEFAULT_MAX_SERVICE_TOKEN_LOGIN_DURATION_SECONDS, DEFAULT_MIN_REQUESTED_LOGIN_DURATION_SECONDS, DEFAULT_OIDC_ANALYTICS_EVENT_NAMES, DEFAULT_OIDC_ANALYTICS_EVENT_NAME_PREFIX, DEFAULT_OIDC_CODE_CHALLENGE_METHODS, DEFAULT_OIDC_ID_TOKEN_SIGNING_ALG_VALUES, DEFAULT_OIDC_ISSUER_PATH, DEFAULT_OIDC_ROUTES, DEFAULT_OIDC_SUBJECT_TYPES, DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS, DEFAULT_OIDC_TOKEN_LIFETIMES, DEFAULT_ROTATED_KEY_MAX_AGE, FIREBASE_SERVER_OIDC_ANALYTICS_CONFIG, FIREBASE_SERVER_OIDC_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE, FirebaseServerOidcAnalyticsService, GRANTABLE_MODEL_NAMES, JwksFirestoreCollections, JwksKeyDocument, JwksService, JwksServiceConfig, JwksServiceStorageConfig, OIDC_ANALYTICS_SERVICE, OIDC_ENCRYPTED_PAYLOAD_FIELDS, OIDC_JSON_RENDER_ERROR_FUNCTION, OIDC_JWKS_ENCRYPTION_SECRET_ENV_KEY, OidcAccountService, OidcAccountServiceDelegate, OidcAccountServiceUserContext, OidcAnalyticsService, OidcAuth, OidcAuthBearerTokenMiddleware, OidcAuthMiddlewareConfig, OidcClientService, OidcEncryptionService, OidcInteractionController, OidcInteractionService, OidcModelServerActions, OidcModuleConfig, OidcProviderConfigService, OidcProviderController, OidcServerFirestoreCollections, OidcService, OidcWellKnownController, activeJwksKeysQuery, allOidcScopesStringForProviderConfig, appOidcAnalyticsModuleMetadata, appOidcModelModuleMetadata, applyOidcAuthMiddleware, applyOidcCorsMiddleware, buildBearerChallenge, buildFirebaseServerMcpResourceServer, createAdapterFactory, createOidcClientFactory, deleteOidcClientFactory, deleteOidcTokenFactory, deriveResourceMetadataUrlFromEnv, emitOidcAnalyticsEvent, getOidcScopesFromRequest, jwksKeyCollectionReference, jwksKeyConverter, jwksKeyFirestoreCollection, jwksKeyIdentity, jwksKeysWithStatusQuery, nonRetiredJwksKeysQuery, noopOidcAnalyticsService, oidcClientProviderProfileScopes, oidcCorsOriginDelegate, oidcFirestoreCollectionsFactory, oidcModelServerActions, oidcModelServerActionsFactory, oidcModuleConfigFactory, oidcModuleMetadata, resolveEffectiveSubset, rotateOidcClientSecretFactory, rotatedJwksKeysQuery, updateOidcClientFactory };
package/oidc/package.json CHANGED
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server/oidc",
3
- "version": "13.29.0",
3
+ "version": "13.30.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",
13
- "@dereekb/zoho": "13.29.0",
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",
14
14
  "@nestjs/common": "^11.1.19",
15
15
  "@nestjs/config": "^4.0.4",
16
16
  "express": "^5.2.1",
@@ -4,5 +4,4 @@ export * from './oidc.config';
4
4
  export * from './controller';
5
5
  export * from './oidc.module';
6
6
  export * from './profile';
7
- export * from './scope';
8
7
  export * from './service';
@@ -33,7 +33,7 @@ export type OidcAuthenticatedRequest = FirebaseServerAuthenticatedRequest<OidcAu
33
33
  * `request.auth.token.scope` for OIDC callers and is `undefined` for non-OIDC
34
34
  * (regular Firebase ID-token) callers.
35
35
  *
36
- * Returning `undefined` for non-OIDC callers lets callers (e.g. `oidcCallModelScopePreAssert`)
36
+ * Returning `undefined` for non-OIDC callers lets consumers (e.g. the MCP tool-visibility filter)
37
37
  * skip scope enforcement instead of falsely treating the missing claim as "no scopes granted".
38
38
  *
39
39
  * The parameter is intentionally typed as `unknown` because `DecodedIdToken` (Firebase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server",
3
- "version": "13.29.0",
3
+ "version": "13.30.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.29.0",
61
- "@dereekb/date": "13.29.0",
62
- "@dereekb/dbx-core": "13.29.0",
63
- "@dereekb/firebase": "13.29.0",
64
- "@dereekb/model": "13.29.0",
65
- "@dereekb/nestjs": "13.29.0",
66
- "@dereekb/rxjs": "13.29.0",
67
- "@dereekb/util": "13.29.0",
68
- "@dereekb/zoho": "13.29.0",
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",
69
69
  "@google-cloud/firestore": "^7.11.6",
70
70
  "@google-cloud/storage": "^7.19.0",
71
71
  "@modelcontextprotocol/sdk": "1.29.0",
@@ -1,3 +1,4 @@
1
+ export * from './model.api.scope';
1
2
  export * from './model.api.get.service';
2
3
  export * from './model.api.dispatch';
3
4
  export * from './model.api.controller';
@@ -1,11 +1,12 @@
1
1
  import { type Maybe } from '@dereekb/util';
2
- import { type OnCallTypedModelParams } from '@dereekb/firebase';
2
+ import { type FirestoreModelType, type OidcModelScopeRequirement, type OidcScopeTerm, type OnCallTypedModelParams } from '@dereekb/firebase';
3
3
  import { type INestApplicationContext } from '@nestjs/common';
4
4
  import { type Request } from 'express';
5
5
  import { type OnCallWithNestContext } from '../../function/call';
6
6
  import { type OnCallApiDetailsRef, type ModelApiDetailsResult } from '../../model/api.details';
7
7
  import { type MakeNestContext } from '../../nest.provider';
8
8
  import { type FirebaseServerAuthData } from '../auth.context.server';
9
+ import { type ModelApiOidcScopeConfig } from './model.api.scope';
9
10
  /**
10
11
  * The combined type of the function returned by onCallModel() with _apiDetails attached.
11
12
  */
@@ -27,7 +28,7 @@ export type OnCallModelFnWithApiDetails = OnCallWithNestContext<unknown, OnCallT
27
28
  * }
28
29
  * ```
29
30
  */
30
- export declare abstract class ModelApiDispatchConfig {
31
+ export declare abstract class ModelApiDispatchConfig implements ModelApiOidcScopeConfig {
31
32
  /**
32
33
  * The onCallModel() return value with _apiDetails attached.
33
34
  */
@@ -36,6 +37,17 @@ export declare abstract class ModelApiDispatchConfig {
36
37
  * Factory to create typed nest context from INestApplicationContext.
37
38
  */
38
39
  readonly makeNestContext: MakeNestContext<unknown>;
40
+ /**
41
+ * Optional model-api-layer OIDC group-scope default. See {@link ModelApiOidcScopeConfig.defaultRequiredScope}.
42
+ * This is the home for the callModel group-scope default; providing it here enforces it across
43
+ * dispatch AND the `/get` reads.
44
+ */
45
+ readonly defaultRequiredScope?: OidcScopeTerm;
46
+ /**
47
+ * Optional per-model OIDC group-scope overrides. See {@link ModelApiOidcScopeConfig.modelRequiredScopes}.
48
+ * The only place a plain `/get` read (no per-function handler) can be scope-gated beyond `model.read`.
49
+ */
50
+ readonly modelRequiredScopes?: Record<FirestoreModelType, OidcModelScopeRequirement>;
39
51
  }
40
52
  /**
41
53
  * Injection token for providing the NestJS application context to the dispatch service.
@@ -61,8 +61,22 @@ export declare function modelAccessReadErrorFromUseMultipleModelsFailure(entry:
61
61
  */
62
62
  export declare class ModelApiGetService {
63
63
  private readonly _nestContext;
64
+ private readonly _defaultRequiredScope;
65
+ private readonly _modelRequiredScopes;
64
66
  private _identityByModelType;
65
67
  constructor(config: ModelApiDispatchConfig, nestApplication: INestApplicationContext);
68
+ /**
69
+ * Enforces the OIDC read-scope requirement for a direct document read before it hits Firestore.
70
+ *
71
+ * A direct `/get` read is the `read` verb: it requires the per-verb `model.read` scope AND any
72
+ * effective group term for the target model (per-model requirement > module default). This is the
73
+ * ONLY gate on the direct-read path — it does not touch the callModel dispatch chain — so without it
74
+ * an OIDC client scoped to a subset could read any model through `/get`. Non-OIDC callers bypass.
75
+ *
76
+ * @param modelType - The Firestore model type being read.
77
+ * @param auth - The request's auth data (OIDC scopes are read from it).
78
+ */
79
+ private _assertReadScope;
66
80
  /**
67
81
  * Returns the registered {@link FirestoreModelIdentity} for the given `modelType` string, or
68
82
  * `undefined` when no model of that type is registered.