@dereekb/firebase-server 13.28.0 → 13.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/oidc/index.cjs.js CHANGED
@@ -2431,6 +2431,9 @@ function _ts_generator$7(thisArg, body) {
2431
2431
  if (params.dbx_max_session_ttl != null) {
2432
2432
  properties.dbx_max_session_ttl = params.dbx_max_session_ttl;
2433
2433
  }
2434
+ if (params.dbx_provider_profiles != null) {
2435
+ properties.dbx_provider_profiles = params.dbx_provider_profiles;
2436
+ }
2434
2437
  // Merge any pre-validated metadata (e.g., inline jwks for private_key_jwt in tests)
2435
2438
  if (validatedMetadata === null || validatedMetadata === void 0 ? void 0 : validatedMetadata.jwks) {
2436
2439
  properties.jwks = validatedMetadata.jwks;
@@ -2488,7 +2491,7 @@ function _ts_generator$7(thisArg, body) {
2488
2491
  * @throws {Error} When the client is not found.
2489
2492
  */ function updateClient(clientId, params) {
2490
2493
  return _async_to_generator$7(function() {
2491
- var provider, ProviderClient, existing, updatedMetadata, _params_logo_uri, _params_client_uri, _params_dbx_max_session_ttl, client;
2494
+ var provider, ProviderClient, existing, updatedMetadata, _params_logo_uri, _params_client_uri, _params_dbx_max_session_ttl, _params_dbx_provider_profiles, client;
2492
2495
  return _ts_generator$7(this, function(_state) {
2493
2496
  switch(_state.label){
2494
2497
  case 0:
@@ -2520,6 +2523,12 @@ function _ts_generator$7(thisArg, body) {
2520
2523
  if (params.dbx_max_session_ttl !== undefined) {
2521
2524
  updatedMetadata.dbx_max_session_ttl = (_params_dbx_max_session_ttl = params.dbx_max_session_ttl) !== null && _params_dbx_max_session_ttl !== void 0 ? _params_dbx_max_session_ttl : undefined;
2522
2525
  }
2526
+ // Only overwrite the profile assignment when the field is explicitly provided. An omitted value
2527
+ // (including one stripped for a non-admin caller) preserves the existing assignment; `null`/`[]`
2528
+ // clears it. This is what makes the non-admin strip a no-op rather than a wipe.
2529
+ if (params.dbx_provider_profiles !== undefined) {
2530
+ updatedMetadata.dbx_provider_profiles = (_params_dbx_provider_profiles = params.dbx_provider_profiles) !== null && _params_dbx_provider_profiles !== void 0 ? _params_dbx_provider_profiles : undefined;
2531
+ }
2523
2532
  // Mirrors oidc-provider's lib/helpers/add_client.js: re-validates and persists.
2524
2533
  return [
2525
2534
  4,
@@ -3538,6 +3547,25 @@ exports.OidcProviderConfigService = __decorate([
3538
3547
  __param(2, common.Inject(firebaseServer.FirebaseServerEnvService))
3539
3548
  ], exports.OidcProviderConfigService);
3540
3549
 
3550
+ /**
3551
+ * Custom oidc-provider client metadata field holding the {@link OidcProviderProfile} keys assigned
3552
+ * to a client. Registered via `extraClientMetadata` and persisted on the client's `OidcEntry` payload.
3553
+ */ var DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = 'dbx_provider_profiles';
3554
+ /**
3555
+ * Resolves the unlocked + required scope sets for a client from the provider-profile registry and
3556
+ * the profile keys assigned to that client.
3557
+ *
3558
+ * @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
3559
+ * @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
3560
+ * @returns The unlocked and required scope sets for the client.
3561
+ */ function oidcClientProviderProfileScopes(providerProfiles, clientProfileKeys) {
3562
+ var clientProfiles = firebase.oidcProviderProfilesForKeys(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
3563
+ return {
3564
+ unlocked: firebase.scopesForOidcProviderProfiles(clientProfiles),
3565
+ required: firebase.requiredScopesForOidcProviderProfiles(clientProfiles)
3566
+ };
3567
+ }
3568
+
3541
3569
  function _array_like_to_array$4(arr, len) {
3542
3570
  if (len == null || len > arr.length) len = arr.length;
3543
3571
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -4042,7 +4070,8 @@ function _ts_generator$5(thisArg, body) {
4042
4070
  logo_uri: existing.logo_uri,
4043
4071
  client_uri: existing.client_uri,
4044
4072
  created_at: existing.created_at,
4045
- dbx_max_session_ttl: existing.dbx_max_session_ttl
4073
+ dbx_max_session_ttl: existing.dbx_max_session_ttl,
4074
+ dbx_provider_profiles: existing.dbx_provider_profiles
4046
4075
  };
4047
4076
  }
4048
4077
  return [
@@ -4149,7 +4178,8 @@ function _ts_generator$5(thisArg, body) {
4149
4178
  ],
4150
4179
  extraClientMetadata: {
4151
4180
  properties: [
4152
- DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA
4181
+ DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA,
4182
+ DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA
4153
4183
  ],
4154
4184
  validator: function validator(_ctx, key, value) {
4155
4185
  if (key === DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA && value !== undefined && value !== null) {
@@ -4159,6 +4189,22 @@ function _ts_generator$5(thisArg, body) {
4159
4189
  if (value > serverMaxSeconds) {
4160
4190
  throw new oidcProvider.errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA, " cannot exceed the server max of ").concat(serverMaxSeconds, " seconds."));
4161
4191
  }
4192
+ } else if (key === DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA && value !== undefined && value !== null) {
4193
+ var _providerConfig_providerProfiles;
4194
+ if (!Array.isArray(value) || value.some(function(entry) {
4195
+ return typeof entry !== 'string';
4196
+ })) {
4197
+ throw new oidcProvider.errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, " must be an array of provider profile keys."));
4198
+ }
4199
+ var knownProfileKeys = new Set(((_providerConfig_providerProfiles = providerConfig.providerProfiles) !== null && _providerConfig_providerProfiles !== void 0 ? _providerConfig_providerProfiles : []).map(function(profile) {
4200
+ return profile.key;
4201
+ }));
4202
+ var unknownKey = value.find(function(entry) {
4203
+ return !knownProfileKeys.has(entry);
4204
+ });
4205
+ if (unknownKey !== undefined) {
4206
+ throw new oidcProvider.errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, " contains an unknown provider profile key: ").concat(unknownKey, "."));
4207
+ }
4162
4208
  }
4163
4209
  }
4164
4210
  },
@@ -4186,7 +4232,7 @@ function _ts_generator$5(thisArg, body) {
4186
4232
  interactions: {
4187
4233
  url: function url(_ctx, interaction) {
4188
4234
  return _async_to_generator$5(function() {
4189
- var baseUrl, client_id, paramsToEncode, client, scopes, interactionLoginDetails, paramsString;
4235
+ var baseUrl, client_id, paramsToEncode, client, _client_dbx_provider_profiles, scopes, requestedScopeSet, required, requiredScopes, interactionLoginDetails, paramsString;
4190
4236
  return _ts_generator$5(this, function(_state) {
4191
4237
  switch(_state.label){
4192
4238
  case 0:
@@ -4214,13 +4260,23 @@ function _ts_generator$5(thisArg, body) {
4214
4260
  client = _state.sent();
4215
4261
  if (client) {
4216
4262
  scopes = interaction.params.scope;
4217
- interactionLoginDetails = {
4263
+ // Surface the scopes the client's provider profiles force-require so the consent UI can
4264
+ // render them as required. Intersect with the actually-requested scopes so only visible
4265
+ // scope rows are flagged.
4266
+ requestedScopeSet = new Set(scopes.split(' ').filter(Boolean));
4267
+ required = oidcClientProviderProfileScopes(providerConfig.providerProfiles, (_client_dbx_provider_profiles = client.dbx_provider_profiles) !== null && _client_dbx_provider_profiles !== void 0 ? _client_dbx_provider_profiles : undefined).required;
4268
+ requiredScopes = Array.from(required).filter(function(scope) {
4269
+ return requestedScopeSet.has(scope);
4270
+ });
4271
+ interactionLoginDetails = _object_spread$3({
4218
4272
  client_id: client_id,
4219
4273
  client_name: client.client_name,
4220
4274
  client_uri: client.client_uri,
4221
4275
  logo_uri: client.logo_uri,
4222
4276
  scopes: scopes
4223
- };
4277
+ }, requiredScopes.length > 0 ? {
4278
+ requiredScopes: requiredScopes.join(' ')
4279
+ } : {});
4224
4280
  paramsToEncode = _object_spread$3({}, paramsToEncode, interactionLoginDetails);
4225
4281
  }
4226
4282
  _state.label = 3;
@@ -6159,7 +6215,7 @@ var OidcInteractionController_1;
6159
6215
  * @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
6160
6216
  */ function postConsent(uid, body, res) {
6161
6217
  return _async_to_generator$2(function() {
6162
- var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref3, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, requestedOIDCScopeSet, adminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref4, redirectTo1, requestedRawTtl, clientPayload, 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, _ref5, redirectTo2, err;
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;
6163
6219
  return _ts_generator$2(this, function(_state) {
6164
6220
  switch(_state.label){
6165
6221
  case 0:
@@ -6174,9 +6230,9 @@ var OidcInteractionController_1;
6174
6230
  case 2:
6175
6231
  _state.trys.push([
6176
6232
  2,
6177
- 20,
6233
+ 24,
6178
6234
  ,
6179
- 21
6235
+ 25
6180
6236
  ]);
6181
6237
  if (!!body.approved) return [
6182
6238
  3,
@@ -6192,7 +6248,7 @@ var OidcInteractionController_1;
6192
6248
  })
6193
6249
  ];
6194
6250
  case 3:
6195
- _ref3 = _state.sent(), redirectTo = _ref3.returnTo;
6251
+ _ref4 = _state.sent(), redirectTo = _ref4.returnTo;
6196
6252
  res.json({
6197
6253
  redirectTo: redirectTo
6198
6254
  });
@@ -6235,6 +6291,12 @@ var OidcInteractionController_1;
6235
6291
  // and re-submits them all. We accept already-encountered values as silent no-ops so a re-consent on a
6236
6292
  // client the user has previously authorized doesn't fail validation.
6237
6293
  encounteredOIDCScopes = existingGrant ? existingGrant.getOIDCScopeEncountered().split(' ').filter(Boolean) : [];
6294
+ return [
6295
+ 4,
6296
+ this.oidcService.findClientPayload(clientId)
6297
+ ];
6298
+ case 9:
6299
+ clientPayload = _state.sent();
6238
6300
  // Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
6239
6301
  // (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
6240
6302
  // `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
@@ -6246,26 +6308,26 @@ var OidcInteractionController_1;
6246
6308
  });
6247
6309
  if (!accountId) return [
6248
6310
  3,
6249
- 10
6311
+ 11
6250
6312
  ];
6251
6313
  return [
6252
6314
  4,
6253
6315
  (_ref1 = (_this_accountService_delegate_isAdminUser = (_this_accountService_delegate = this.accountService.delegate).isAdminUser) === null || _this_accountService_delegate_isAdminUser === void 0 ? void 0 : _this_accountService_delegate_isAdminUser.call(_this_accountService_delegate, this.accountService.userContext(accountId).authUserContext)) !== null && _ref1 !== void 0 ? _ref1 : false
6254
6316
  ];
6255
- case 9:
6317
+ case 10:
6256
6318
  _tmp1 = _state.sent();
6257
6319
  return [
6258
6320
  3,
6259
- 11
6321
+ 12
6260
6322
  ];
6261
- case 10:
6262
- _tmp1 = false;
6263
- _state.label = 11;
6264
6323
  case 11:
6324
+ _tmp1 = false;
6325
+ _state.label = 12;
6326
+ case 12:
6265
6327
  isAdmin = _tmp1;
6266
6328
  if (!(requestsServiceToken && !isAdmin)) return [
6267
6329
  3,
6268
- 13
6330
+ 14
6269
6331
  ];
6270
6332
  return [
6271
6333
  4,
@@ -6276,8 +6338,8 @@ var OidcInteractionController_1;
6276
6338
  mergeWithLastSubmission: true
6277
6339
  })
6278
6340
  ];
6279
- case 12:
6280
- _ref4 = _state.sent(), redirectTo1 = _ref4.returnTo;
6341
+ case 13:
6342
+ _ref5 = _state.sent(), redirectTo1 = _ref5.returnTo;
6281
6343
  emitOidcAnalyticsEvent(this._analytics, {
6282
6344
  type: 'consent',
6283
6345
  isSuccessful: false,
@@ -6294,20 +6356,89 @@ var OidcInteractionController_1;
6294
6356
  return [
6295
6357
  2
6296
6358
  ];
6297
- case 13:
6359
+ case 14:
6360
+ // 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;
6364
+ _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
+ profileGatedScopes = firebase.scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
6366
+ // Unlock gate: reject any requested gated scope this client's profiles do not unlock.
6367
+ disallowedGatedScopes = Array.from(requestedOIDCScopeSet).filter(function(scope) {
6368
+ return profileGatedScopes.has(scope) && !clientUnlockedScopes.has(scope);
6369
+ });
6370
+ if (!(disallowedGatedScopes.length > 0)) return [
6371
+ 3,
6372
+ 16
6373
+ ];
6374
+ return [
6375
+ 4,
6376
+ this.oidcInteractionService.finishInteractionByUid(uid, {
6377
+ error: 'access_denied',
6378
+ error_description: "The following scope(s) are not available to this client: ".concat(disallowedGatedScopes.join(', '), ".")
6379
+ }, {
6380
+ mergeWithLastSubmission: true
6381
+ })
6382
+ ];
6383
+ case 15:
6384
+ _ref6 = _state.sent(), redirectTo2 = _ref6.returnTo;
6385
+ emitOidcAnalyticsEvent(this._analytics, {
6386
+ type: 'consent',
6387
+ isSuccessful: false,
6388
+ uid: accountId,
6389
+ clientId: clientId,
6390
+ reason: 'scope_not_unlocked_for_client',
6391
+ durationMs: Date.now() - startedAt
6392
+ }, this._logger);
6393
+ res.json({
6394
+ redirectTo: redirectTo2
6395
+ });
6396
+ return [
6397
+ 2
6398
+ ];
6399
+ case 16:
6400
+ // Required gate: a scope a client's profiles force-require (`require: 'required'`) must be present
6401
+ // in the request. Reject when the client did not request a required scope.
6402
+ missingRequiredScopes = Array.from(clientRequiredScopes).filter(function(scope) {
6403
+ return !requestedOIDCScopeSet.has(scope);
6404
+ });
6405
+ if (!(missingRequiredScopes.length > 0)) return [
6406
+ 3,
6407
+ 18
6408
+ ];
6409
+ return [
6410
+ 4,
6411
+ this.oidcInteractionService.finishInteractionByUid(uid, {
6412
+ error: 'access_denied',
6413
+ error_description: "This client must request the required scope(s): ".concat(missingRequiredScopes.join(', '), ".")
6414
+ }, {
6415
+ mergeWithLastSubmission: true
6416
+ })
6417
+ ];
6418
+ case 17:
6419
+ _ref7 = _state.sent(), redirectTo3 = _ref7.returnTo;
6420
+ emitOidcAnalyticsEvent(this._analytics, {
6421
+ type: 'consent',
6422
+ isSuccessful: false,
6423
+ uid: accountId,
6424
+ clientId: clientId,
6425
+ reason: 'required_scope_missing',
6426
+ durationMs: Date.now() - startedAt
6427
+ }, this._logger);
6428
+ res.json({
6429
+ redirectTo: redirectTo3
6430
+ });
6431
+ return [
6432
+ 2
6433
+ ];
6434
+ case 18:
6298
6435
  // Resolve the requested login duration up-front. The configured Grant TTL function (in
6299
6436
  // OidcService.buildProviderConfiguration) only fires when oidc-provider's koa middleware
6300
6437
  // drives `grant.save()`, so its `ctx.oidc.params` lookup of `dbx_session_ttl` returns
6301
6438
  // undefined when the consent submit runs in this controller. We pre-set `expiresIn` on
6302
6439
  // newly-created grants so they persist with the correct (tiered) TTL.
6303
6440
  requestedRawTtl = params[DBX_FIREBASE_SERVER_OIDC_SESSION_TTL_PARAM];
6304
- return [
6305
- 4,
6306
- this.oidcService.findClientPayload(clientId)
6307
- ];
6308
- case 14:
6309
- clientPayload = _state.sent();
6310
- clientMaxSessionTtl = (_ref2 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_max_session_ttl) !== null && _ref2 !== void 0 ? _ref2 : undefined;
6441
+ clientMaxSessionTtl = (_ref3 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_max_session_ttl) !== null && _ref3 !== void 0 ? _ref3 : undefined;
6311
6442
  expiresInSeconds = this.oidcService.resolveLoginDurationForGrant(requestedRawTtl, {
6312
6443
  dbx_max_session_ttl: clientMaxSessionTtl
6313
6444
  }, {
@@ -6316,28 +6447,30 @@ var OidcInteractionController_1;
6316
6447
  });
6317
6448
  if (!(existingGrant !== null && existingGrant !== void 0)) return [
6318
6449
  3,
6319
- 15
6450
+ 19
6320
6451
  ];
6321
6452
  _tmp2 = existingGrant;
6322
6453
  return [
6323
6454
  3,
6324
- 17
6455
+ 21
6325
6456
  ];
6326
- case 15:
6457
+ case 19:
6327
6458
  return [
6328
6459
  4,
6329
6460
  this.oidcInteractionService.findOrCreateGrant(interaction.grantId, accountId, clientId, expiresInSeconds)
6330
6461
  ];
6331
- case 16:
6462
+ case 20:
6332
6463
  _tmp2 = _state.sent();
6333
- _state.label = 17;
6334
- case 17:
6464
+ _state.label = 21;
6465
+ case 21:
6335
6466
  grant = _tmp2;
6336
6467
  if (missingOIDCScope.length > 0) {
6468
+ // Force-grant the client's required profile scopes (in addition to `openid`) so they cannot be
6469
+ // dropped at consent — the required gate above already guarantees they were requested.
6337
6470
  _resolveEffectiveSubset = resolveEffectiveSubset({
6338
6471
  missing: missingOIDCScope,
6339
6472
  requestedSubset: body.grantedOIDCScopes,
6340
- alwaysGranted: ALWAYS_GRANTED_OIDC_SCOPES,
6473
+ alwaysGranted: _to_consumable_array$1(ALWAYS_GRANTED_OIDC_SCOPES).concat(_to_consumable_array$1(clientRequiredScopes)),
6341
6474
  alreadyEncountered: encounteredOIDCScopes
6342
6475
  }), granted = _resolveEffectiveSubset.granted, rejected = _resolveEffectiveSubset.rejected;
6343
6476
  if (granted.length > 0) {
@@ -6434,7 +6567,7 @@ var OidcInteractionController_1;
6434
6567
  4,
6435
6568
  grant.save()
6436
6569
  ];
6437
- case 18:
6570
+ case 22:
6438
6571
  grantId = _state.sent();
6439
6572
  return [
6440
6573
  4,
@@ -6446,8 +6579,8 @@ var OidcInteractionController_1;
6446
6579
  mergeWithLastSubmission: true
6447
6580
  })
6448
6581
  ];
6449
- case 19:
6450
- _ref5 = _state.sent(), redirectTo2 = _ref5.returnTo;
6582
+ case 23:
6583
+ _ref8 = _state.sent(), redirectTo4 = _ref8.returnTo;
6451
6584
  emitOidcAnalyticsEvent(this._analytics, {
6452
6585
  type: 'consent',
6453
6586
  isSuccessful: true,
@@ -6459,13 +6592,13 @@ var OidcInteractionController_1;
6459
6592
  durationMs: Date.now() - startedAt
6460
6593
  }, this._logger);
6461
6594
  res.json({
6462
- redirectTo: redirectTo2
6595
+ redirectTo: redirectTo4
6463
6596
  });
6464
6597
  return [
6465
6598
  3,
6466
- 21
6599
+ 25
6467
6600
  ];
6468
- case 20:
6601
+ case 24:
6469
6602
  err = _state.sent();
6470
6603
  emitOidcAnalyticsEvent(this._analytics, {
6471
6604
  type: 'consent',
@@ -6480,7 +6613,7 @@ var OidcInteractionController_1;
6480
6613
  throw err;
6481
6614
  }
6482
6615
  throw new common.HttpException('Consent interaction failed', common.HttpStatus.BAD_REQUEST);
6483
- case 21:
6616
+ case 25:
6484
6617
  return [
6485
6618
  2
6486
6619
  ];
@@ -7650,31 +7783,43 @@ function _unsupported_iterable_to_array(o, minLen) {
7650
7783
 
7651
7784
  /**
7652
7785
  * Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
7653
- * lacking the OIDC scope mapped from the call type.
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`.
7654
7791
  *
7655
7792
  * Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
7656
7793
  * when {@link getOidcScopesFromRequest} returns `undefined` because there is no
7657
7794
  * `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
7658
7795
  * unaffected and continue to be gated by auth roles.
7659
7796
  *
7660
- * Bypasses the check for custom (non-CRUD) call types so app-specific verbs
7661
- * remain unrestricted unless an app wires its own assertion.
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`.
7662
7800
  *
7663
7801
  * Wire as the `preAssert` of {@link onCallModel}.
7664
7802
  *
7665
7803
  * @returns The pre-assertion function ready to plug into `OnCallModelConfig.preAssert`.
7666
7804
  */ function oidcCallModelScopePreAssert() {
7667
7805
  var fn = function fn(context) {
7668
- var requiredScope = firebase.callModelOidcScopeForCallType(context.call);
7669
- var scopes = requiredScope == null ? undefined : getOidcScopesFromRequest(context.request);
7670
- var isMissingScope = requiredScope != null && scopes != null && !scopes.has(requiredScope);
7671
- if (isMissingScope) {
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) {
7672
7817
  throw firebaseServer.forbiddenError({
7673
7818
  status: 403,
7674
7819
  code: firebase.CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE,
7675
- message: "Missing required OIDC scope for callModel: ".concat(requiredScope),
7820
+ message: "Missing required OIDC scope for callModel: ".concat(missingScopes.join(', ')),
7676
7821
  data: {
7677
- requiredScope: requiredScope,
7822
+ requiredScopes: missingScopes,
7678
7823
  call: context.call
7679
7824
  }
7680
7825
  });
@@ -7683,6 +7828,7 @@ function _unsupported_iterable_to_array(o, minLen) {
7683
7828
  return fn;
7684
7829
  }
7685
7830
 
7831
+ exports.DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA;
7686
7832
  exports.DEFAULT_APP_OAUTH_CONSENT_PATH_PART = DEFAULT_APP_OAUTH_CONSENT_PATH_PART;
7687
7833
  exports.DEFAULT_APP_OAUTH_INTERACTION_PATH = DEFAULT_APP_OAUTH_INTERACTION_PATH;
7688
7834
  exports.DEFAULT_APP_OAUTH_LOGIN_PATH_PART = DEFAULT_APP_OAUTH_LOGIN_PATH_PART;
@@ -7745,6 +7891,7 @@ exports.jwksKeysWithStatusQuery = jwksKeysWithStatusQuery;
7745
7891
  exports.nonRetiredJwksKeysQuery = nonRetiredJwksKeysQuery;
7746
7892
  exports.noopOidcAnalyticsService = noopOidcAnalyticsService;
7747
7893
  exports.oidcCallModelScopePreAssert = oidcCallModelScopePreAssert;
7894
+ exports.oidcClientProviderProfileScopes = oidcClientProviderProfileScopes;
7748
7895
  exports.oidcCorsOriginDelegate = oidcCorsOriginDelegate;
7749
7896
  exports.oidcFirestoreCollectionsFactory = oidcFirestoreCollectionsFactory;
7750
7897
  exports.oidcModelServerActions = oidcModelServerActions;