@dereekb/firebase-server 13.27.1 → 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
@@ -344,6 +344,14 @@ function _define_property$g(obj, key, value) {
344
344
  *
345
345
  * Defaults to `false`.
346
346
  */ _define_property$g(this, "trustProxyInNonProduction", void 0);
347
+ /**
348
+ * Unified CORS configuration for the cross-origin browser OIDC endpoints
349
+ * (discovery GET, token POST). When unset, the Express CORS layer preserves the
350
+ * pre-existing behavior: `Access-Control-Allow-Origin: <appUrl>` on every
351
+ * response, which pre-empts oidc-provider's own CORS handling.
352
+ *
353
+ * @see OidcCorsConfig
354
+ */ _define_property$g(this, "cors", void 0);
347
355
  }
348
356
  _create_class$e(OidcModuleConfig, null, [
349
357
  {
@@ -2423,6 +2431,9 @@ function _ts_generator$7(thisArg, body) {
2423
2431
  if (params.dbx_max_session_ttl != null) {
2424
2432
  properties.dbx_max_session_ttl = params.dbx_max_session_ttl;
2425
2433
  }
2434
+ if (params.dbx_provider_profiles != null) {
2435
+ properties.dbx_provider_profiles = params.dbx_provider_profiles;
2436
+ }
2426
2437
  // Merge any pre-validated metadata (e.g., inline jwks for private_key_jwt in tests)
2427
2438
  if (validatedMetadata === null || validatedMetadata === void 0 ? void 0 : validatedMetadata.jwks) {
2428
2439
  properties.jwks = validatedMetadata.jwks;
@@ -2480,7 +2491,7 @@ function _ts_generator$7(thisArg, body) {
2480
2491
  * @throws {Error} When the client is not found.
2481
2492
  */ function updateClient(clientId, params) {
2482
2493
  return _async_to_generator$7(function() {
2483
- 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;
2484
2495
  return _ts_generator$7(this, function(_state) {
2485
2496
  switch(_state.label){
2486
2497
  case 0:
@@ -2512,6 +2523,12 @@ function _ts_generator$7(thisArg, body) {
2512
2523
  if (params.dbx_max_session_ttl !== undefined) {
2513
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;
2514
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
+ }
2515
2532
  // Mirrors oidc-provider's lib/helpers/add_client.js: re-validates and persists.
2516
2533
  return [
2517
2534
  4,
@@ -3225,13 +3242,13 @@ var GRANTABLE_MODELS = new Set(GRANTABLE_MODEL_NAMES);
3225
3242
  return FirestoreAdapter;
3226
3243
  }
3227
3244
 
3228
- function _array_like_to_array$5(arr, len) {
3245
+ function _array_like_to_array$6(arr, len) {
3229
3246
  if (len == null || len > arr.length) len = arr.length;
3230
3247
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
3231
3248
  return arr2;
3232
3249
  }
3233
- function _array_without_holes$5(arr) {
3234
- if (Array.isArray(arr)) return _array_like_to_array$5(arr);
3250
+ function _array_without_holes$6(arr) {
3251
+ if (Array.isArray(arr)) return _array_like_to_array$6(arr);
3235
3252
  }
3236
3253
  function _class_call_check$a(instance, Constructor) {
3237
3254
  if (!(instance instanceof Constructor)) {
@@ -3264,22 +3281,22 @@ function _define_property$a(obj, key, value) {
3264
3281
  }
3265
3282
  return obj;
3266
3283
  }
3267
- function _iterable_to_array$5(iter) {
3284
+ function _iterable_to_array$6(iter) {
3268
3285
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
3269
3286
  }
3270
- function _non_iterable_spread$5() {
3287
+ function _non_iterable_spread$6() {
3271
3288
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
3272
3289
  }
3273
- function _to_consumable_array$5(arr) {
3274
- return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$5(arr) || _non_iterable_spread$5();
3290
+ function _to_consumable_array$6(arr) {
3291
+ return _array_without_holes$6(arr) || _iterable_to_array$6(arr) || _unsupported_iterable_to_array$6(arr) || _non_iterable_spread$6();
3275
3292
  }
3276
- function _unsupported_iterable_to_array$5(o, minLen) {
3293
+ function _unsupported_iterable_to_array$6(o, minLen) {
3277
3294
  if (!o) return;
3278
- if (typeof o === "string") return _array_like_to_array$5(o, minLen);
3295
+ if (typeof o === "string") return _array_like_to_array$6(o, minLen);
3279
3296
  var n = Object.prototype.toString.call(o).slice(8, -1);
3280
3297
  if (n === "Object" && o.constructor) n = o.constructor.name;
3281
3298
  if (n === "Map" || n === "Set") return Array.from(n);
3282
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
3299
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$6(o, minLen);
3283
3300
  }
3284
3301
  // MARK: Encrypted Fields
3285
3302
  /**
@@ -3307,7 +3324,7 @@ function _unsupported_iterable_to_array$5(o, minLen) {
3307
3324
  this.provider = nestjs.createAesStringEncryptionProvider(config.jwksKeyConverterConfig.encryptionSecret);
3308
3325
  this.adapterPayloadEncryptor = util.selectiveFieldEncryptor({
3309
3326
  provider: this.provider,
3310
- fields: _to_consumable_array$5(OIDC_ENCRYPTED_PAYLOAD_FIELDS)
3327
+ fields: _to_consumable_array$6(OIDC_ENCRYPTED_PAYLOAD_FIELDS)
3311
3328
  });
3312
3329
  }
3313
3330
  _create_class$8(OidcEncryptionService, [
@@ -3343,13 +3360,13 @@ exports.OidcEncryptionService = __decorate([
3343
3360
  __param(0, common.Inject(OidcModuleConfig))
3344
3361
  ], exports.OidcEncryptionService);
3345
3362
 
3346
- function _array_like_to_array$4(arr, len) {
3363
+ function _array_like_to_array$5(arr, len) {
3347
3364
  if (len == null || len > arr.length) len = arr.length;
3348
3365
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
3349
3366
  return arr2;
3350
3367
  }
3351
- function _array_without_holes$4(arr) {
3352
- if (Array.isArray(arr)) return _array_like_to_array$4(arr);
3368
+ function _array_without_holes$5(arr) {
3369
+ if (Array.isArray(arr)) return _array_like_to_array$5(arr);
3353
3370
  }
3354
3371
  function _class_call_check$9(instance, Constructor) {
3355
3372
  if (!(instance instanceof Constructor)) {
@@ -3382,22 +3399,22 @@ function _define_property$9(obj, key, value) {
3382
3399
  }
3383
3400
  return obj;
3384
3401
  }
3385
- function _iterable_to_array$4(iter) {
3402
+ function _iterable_to_array$5(iter) {
3386
3403
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
3387
3404
  }
3388
- function _non_iterable_spread$4() {
3405
+ function _non_iterable_spread$5() {
3389
3406
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
3390
3407
  }
3391
- function _to_consumable_array$4(arr) {
3392
- return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
3408
+ function _to_consumable_array$5(arr) {
3409
+ return _array_without_holes$5(arr) || _iterable_to_array$5(arr) || _unsupported_iterable_to_array$5(arr) || _non_iterable_spread$5();
3393
3410
  }
3394
- function _unsupported_iterable_to_array$4(o, minLen) {
3411
+ function _unsupported_iterable_to_array$5(o, minLen) {
3395
3412
  if (!o) return;
3396
- if (typeof o === "string") return _array_like_to_array$4(o, minLen);
3413
+ if (typeof o === "string") return _array_like_to_array$5(o, minLen);
3397
3414
  var n = Object.prototype.toString.call(o).slice(8, -1);
3398
3415
  if (n === "Object" && o.constructor) n = o.constructor.name;
3399
3416
  if (n === "Map" || n === "Set") return Array.from(n);
3400
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
3417
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$5(o, minLen);
3401
3418
  }
3402
3419
  // MARK: Routes
3403
3420
  /**
@@ -3474,7 +3491,7 @@ var DEFAULT_OIDC_CODE_CHALLENGE_METHODS = [
3474
3491
  this.providerConfig = accountService.providerConfig;
3475
3492
  this.scopesSupported = Object.keys(this.providerConfig.claims);
3476
3493
  this.claimsSupported = Array.from(new Set(Object.values(this.providerConfig.claims).flat()));
3477
- this.tokenEndpointAuthMethodsSupported = (_this_config_tokenEndpointAuthMethods = this.config.tokenEndpointAuthMethods) !== null && _this_config_tokenEndpointAuthMethods !== void 0 ? _this_config_tokenEndpointAuthMethods : _to_consumable_array$4(DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS);
3494
+ this.tokenEndpointAuthMethodsSupported = (_this_config_tokenEndpointAuthMethods = this.config.tokenEndpointAuthMethods) !== null && _this_config_tokenEndpointAuthMethods !== void 0 ? _this_config_tokenEndpointAuthMethods : _to_consumable_array$5(DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS);
3478
3495
  var appUrl = envService.appUrl;
3479
3496
  this.appLoginUrl = util.websiteUrlFromPaths(appUrl, [
3480
3497
  this.config.appOAuthInteractionPath,
@@ -3507,16 +3524,16 @@ var DEFAULT_OIDC_CODE_CHALLENGE_METHODS = [
3507
3524
  jwks_uri: jwksUri !== null && jwksUri !== void 0 ? jwksUri : "".concat(issuer).concat(routes.jwks),
3508
3525
  registration_endpoint: this.oidcRegistrationRouteEnabled ? "".concat(issuer).concat(routes.registration) : undefined,
3509
3526
  scopes_supported: this.scopesSupported,
3510
- response_types_supported: _to_consumable_array$4(providerConfig.responseTypes),
3527
+ response_types_supported: _to_consumable_array$5(providerConfig.responseTypes),
3511
3528
  response_modes_supported: [
3512
3529
  'query'
3513
3530
  ],
3514
- grant_types_supported: _to_consumable_array$4(providerConfig.grantTypes),
3515
- subject_types_supported: _to_consumable_array$4(DEFAULT_OIDC_SUBJECT_TYPES),
3516
- id_token_signing_alg_values_supported: _to_consumable_array$4(DEFAULT_OIDC_ID_TOKEN_SIGNING_ALG_VALUES),
3517
- token_endpoint_auth_methods_supported: _to_consumable_array$4(this.tokenEndpointAuthMethodsSupported),
3531
+ grant_types_supported: _to_consumable_array$5(providerConfig.grantTypes),
3532
+ subject_types_supported: _to_consumable_array$5(DEFAULT_OIDC_SUBJECT_TYPES),
3533
+ id_token_signing_alg_values_supported: _to_consumable_array$5(DEFAULT_OIDC_ID_TOKEN_SIGNING_ALG_VALUES),
3534
+ token_endpoint_auth_methods_supported: _to_consumable_array$5(this.tokenEndpointAuthMethodsSupported),
3518
3535
  claims_supported: this.claimsSupported,
3519
- code_challenge_methods_supported: _to_consumable_array$4(DEFAULT_OIDC_CODE_CHALLENGE_METHODS)
3536
+ code_challenge_methods_supported: _to_consumable_array$5(DEFAULT_OIDC_CODE_CHALLENGE_METHODS)
3520
3537
  };
3521
3538
  }
3522
3539
  }
@@ -3530,13 +3547,32 @@ exports.OidcProviderConfigService = __decorate([
3530
3547
  __param(2, common.Inject(firebaseServer.FirebaseServerEnvService))
3531
3548
  ], exports.OidcProviderConfigService);
3532
3549
 
3533
- function _array_like_to_array$3(arr, len) {
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
+
3569
+ function _array_like_to_array$4(arr, len) {
3534
3570
  if (len == null || len > arr.length) len = arr.length;
3535
3571
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
3536
3572
  return arr2;
3537
3573
  }
3538
- function _array_without_holes$3(arr) {
3539
- if (Array.isArray(arr)) return _array_like_to_array$3(arr);
3574
+ function _array_without_holes$4(arr) {
3575
+ if (Array.isArray(arr)) return _array_like_to_array$4(arr);
3540
3576
  }
3541
3577
  function asyncGeneratorStep$5(gen, resolve, reject, _next, _throw, key, arg) {
3542
3578
  try {
@@ -3598,10 +3634,10 @@ function _define_property$8(obj, key, value) {
3598
3634
  }
3599
3635
  return obj;
3600
3636
  }
3601
- function _iterable_to_array$3(iter) {
3637
+ function _iterable_to_array$4(iter) {
3602
3638
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
3603
3639
  }
3604
- function _non_iterable_spread$3() {
3640
+ function _non_iterable_spread$4() {
3605
3641
  throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
3606
3642
  }
3607
3643
  function _object_spread$3(target) {
@@ -3674,16 +3710,16 @@ function _object_without_properties_loose(source, excluded) {
3674
3710
  }
3675
3711
  return target;
3676
3712
  }
3677
- function _to_consumable_array$3(arr) {
3678
- return _array_without_holes$3(arr) || _iterable_to_array$3(arr) || _unsupported_iterable_to_array$3(arr) || _non_iterable_spread$3();
3713
+ function _to_consumable_array$4(arr) {
3714
+ return _array_without_holes$4(arr) || _iterable_to_array$4(arr) || _unsupported_iterable_to_array$4(arr) || _non_iterable_spread$4();
3679
3715
  }
3680
- function _unsupported_iterable_to_array$3(o, minLen) {
3716
+ function _unsupported_iterable_to_array$4(o, minLen) {
3681
3717
  if (!o) return;
3682
- if (typeof o === "string") return _array_like_to_array$3(o, minLen);
3718
+ if (typeof o === "string") return _array_like_to_array$4(o, minLen);
3683
3719
  var n = Object.prototype.toString.call(o).slice(8, -1);
3684
3720
  if (n === "Object" && o.constructor) n = o.constructor.name;
3685
3721
  if (n === "Map" || n === "Set") return Array.from(n);
3686
- if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
3722
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$4(o, minLen);
3687
3723
  }
3688
3724
  function _ts_generator$5(thisArg, body) {
3689
3725
  var f, y, t, _ = {
@@ -4034,7 +4070,8 @@ function _ts_generator$5(thisArg, body) {
4034
4070
  logo_uri: existing.logo_uri,
4035
4071
  client_uri: existing.client_uri,
4036
4072
  created_at: existing.created_at,
4037
- 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
4038
4075
  };
4039
4076
  }
4040
4077
  return [
@@ -4060,6 +4097,7 @@ function _ts_generator$5(thisArg, body) {
4060
4097
  value: function buildProviderConfiguration(cookieKeys) {
4061
4098
  var _this = this;
4062
4099
  var _config_maxRequestedLoginDuration, _config_minRequestedLoginDuration;
4100
+ var _config_cors;
4063
4101
  var config = this.config;
4064
4102
  var providerConfig = this.providerConfigService.providerConfig;
4065
4103
  var serverMaxSeconds = (_config_maxRequestedLoginDuration = config.maxRequestedLoginDuration) !== null && _config_maxRequestedLoginDuration !== void 0 ? _config_maxRequestedLoginDuration : DEFAULT_MAX_REQUESTED_LOGIN_DURATION_SECONDS;
@@ -4077,7 +4115,7 @@ function _ts_generator$5(thisArg, body) {
4077
4115
  return _object_spread_props(_object_spread$3({
4078
4116
  routes: _object_spread$3({}, this.providerConfigService.routes),
4079
4117
  claims: _object_spread$3({}, providerConfig.claims),
4080
- responseTypes: _to_consumable_array$3(providerConfig.responseTypes),
4118
+ responseTypes: _to_consumable_array$4(providerConfig.responseTypes),
4081
4119
  pkce: {
4082
4120
  required: function required() {
4083
4121
  return true;
@@ -4140,7 +4178,8 @@ function _ts_generator$5(thisArg, body) {
4140
4178
  ],
4141
4179
  extraClientMetadata: {
4142
4180
  properties: [
4143
- 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
4144
4183
  ],
4145
4184
  validator: function validator(_ctx, key, value) {
4146
4185
  if (key === DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA && value !== undefined && value !== null) {
@@ -4150,6 +4189,22 @@ function _ts_generator$5(thisArg, body) {
4150
4189
  if (value > serverMaxSeconds) {
4151
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."));
4152
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
+ }
4153
4208
  }
4154
4209
  }
4155
4210
  },
@@ -4177,7 +4232,7 @@ function _ts_generator$5(thisArg, body) {
4177
4232
  interactions: {
4178
4233
  url: function url(_ctx, interaction) {
4179
4234
  return _async_to_generator$5(function() {
4180
- 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;
4181
4236
  return _ts_generator$5(this, function(_state) {
4182
4237
  switch(_state.label){
4183
4238
  case 0:
@@ -4205,13 +4260,23 @@ function _ts_generator$5(thisArg, body) {
4205
4260
  client = _state.sent();
4206
4261
  if (client) {
4207
4262
  scopes = interaction.params.scope;
4208
- 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({
4209
4272
  client_id: client_id,
4210
4273
  client_name: client.client_name,
4211
4274
  client_uri: client.client_uri,
4212
4275
  logo_uri: client.logo_uri,
4213
4276
  scopes: scopes
4214
- };
4277
+ }, requiredScopes.length > 0 ? {
4278
+ requiredScopes: requiredScopes.join(' ')
4279
+ } : {});
4215
4280
  paramsToEncode = _object_spread$3({}, paramsToEncode, interactionLoginDetails);
4216
4281
  }
4217
4282
  _state.label = 3;
@@ -4233,6 +4298,19 @@ function _ts_generator$5(thisArg, body) {
4233
4298
  }
4234
4299
  }, config.renderError ? {
4235
4300
  renderError: config.renderError
4301
+ } : {}, ((_config_cors = config.cors) === null || _config_cors === void 0 ? void 0 : _config_cors.clientBased) ? {
4302
+ clientBasedCORS: function clientBasedCORS(_ctx, origin, client) {
4303
+ var _client_redirectUris;
4304
+ return ((_client_redirectUris = client.redirectUris) !== null && _client_redirectUris !== void 0 ? _client_redirectUris : []).some(function(uri) {
4305
+ var matches = false;
4306
+ try {
4307
+ matches = new URL(uri).origin === origin;
4308
+ } catch (unused) {
4309
+ // malformed persisted redirect_uri — treat as non-matching
4310
+ }
4311
+ return matches;
4312
+ });
4313
+ }
4236
4314
  } : {}), {
4237
4315
  // Setting `rotateRefreshToken` disables oidc-provider's built-in default, so we both honor the
4238
4316
  // dbx-components `nonRotatingScopes` extension (service tokens never rotate, keeping a stable
@@ -4862,9 +4940,69 @@ exports.OidcAuthBearerTokenMiddleware = __decorate([
4862
4940
  __param(1, common.Inject(OidcAuthMiddlewareConfig))
4863
4941
  ], exports.OidcAuthBearerTokenMiddleware);
4864
4942
 
4943
+ function _array_like_to_array$3(arr, len) {
4944
+ if (len == null || len > arr.length) len = arr.length;
4945
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
4946
+ return arr2;
4947
+ }
4948
+ function _array_without_holes$3(arr) {
4949
+ if (Array.isArray(arr)) return _array_like_to_array$3(arr);
4950
+ }
4951
+ function _iterable_to_array$3(iter) {
4952
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
4953
+ }
4954
+ function _non_iterable_spread$3() {
4955
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
4956
+ }
4957
+ function _to_consumable_array$3(arr) {
4958
+ return _array_without_holes$3(arr) || _iterable_to_array$3(arr) || _unsupported_iterable_to_array$3(arr) || _non_iterable_spread$3();
4959
+ }
4960
+ function _unsupported_iterable_to_array$3(o, minLen) {
4961
+ if (!o) return;
4962
+ if (typeof o === "string") return _array_like_to_array$3(o, minLen);
4963
+ var n = Object.prototype.toString.call(o).slice(8, -1);
4964
+ if (n === "Object" && o.constructor) n = o.constructor.name;
4965
+ if (n === "Map" || n === "Set") return Array.from(n);
4966
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
4967
+ }
4968
+ /**
4969
+ * Builds the `cors`-package origin delegate implementing the composed OIDC CORS policy.
4970
+ *
4971
+ * - An allowlisted origin (`appUrl` or any {@link OidcCorsConfig.allowOrigins} entry) is
4972
+ * reflected exactly, which pre-empts oidc-provider's own CORS handling.
4973
+ * - Otherwise, when {@link OidcCorsConfig.clientBased} is enabled, the header is OMITTED so
4974
+ * oidc-provider runs its `clientBasedCORS` check on client routes (`/token`) and its builtin
4975
+ * reflect on client-less routes (discovery/JWKS).
4976
+ * - Otherwise (client-based off), `appUrl` is returned unconditionally so oidc-provider stays
4977
+ * pre-empted and there is never a reflect-any.
4978
+ *
4979
+ * Exported for unit testing without a running Nest application.
4980
+ *
4981
+ * @param appUrl - The authoritative frontend origin (always allowed).
4982
+ * @param cors - The unified CORS configuration.
4983
+ * @returns A `cors`-package origin delegate.
4984
+ */ function oidcCorsOriginDelegate(appUrl, cors) {
4985
+ var _cors_allowOrigins;
4986
+ var clientBased = cors.clientBased === true;
4987
+ var allowlist = new Set([
4988
+ appUrl
4989
+ ].concat(_to_consumable_array$3((_cors_allowOrigins = cors.allowOrigins) !== null && _cors_allowOrigins !== void 0 ? _cors_allowOrigins : [])));
4990
+ return function(requestOrigin, callback) {
4991
+ var resolved;
4992
+ if (requestOrigin && allowlist.has(requestOrigin)) {
4993
+ resolved = requestOrigin; // reflect the exact allowlisted origin; pre-empts oidc-provider
4994
+ } else if (clientBased) {
4995
+ resolved = false; // OMIT ACAO → oidc-provider runs clientBasedCORS (token) / builtin reflect (discovery)
4996
+ } else {
4997
+ resolved = appUrl; // legacy fallback: ACAO=appUrl always; no reflect-any
4998
+ }
4999
+ callback(null, resolved);
5000
+ };
5001
+ }
4865
5002
  // MARK: Express-Level Helper
4866
5003
  /**
4867
- * Enables CORS on the NestJS app for OIDC cross-origin requests originating from the frontend.
5004
+ * Enables CORS on the NestJS app for OIDC cross-origin requests originating from the frontend
5005
+ * and any additional relying-party origins.
4868
5006
  *
4869
5007
  * When the OIDC issuer lives on a different origin than the frontend app (e.g. the frontend is
4870
5008
  * `https://app.example.com` and the OIDC server is at `https://api.example.com`), the frontend's
@@ -4872,16 +5010,22 @@ exports.OidcAuthBearerTokenMiddleware = __decorate([
4872
5010
  * and `/interaction/{uid}/consent`. Because the POST body is `application/json`, the browser
4873
5011
  * sends a CORS preflight. Without an `Access-Control-Allow-Origin` reply the preflight fails,
4874
5012
  * the POST never reaches the server, and the OAuth flow stalls before `finishInteractionByUid`
4875
- * can run.
5013
+ * can run. Browser-based OIDC relying parties (public PKCE clients) likewise need CORS on the
5014
+ * discovery GET and the token POST.
5015
+ *
5016
+ * `appUrl` (from `FirebaseServerEnvService.appUrl`) is always allowed. Additional behavior is
5017
+ * driven by the optional {@link OidcModuleConfig.cors} configuration:
5018
+ * - {@link OidcCorsConfig.allowOrigins} adds an explicit allowlist of relying-party origins.
5019
+ * - {@link OidcCorsConfig.clientBased} defers non-allowlisted origins to oidc-provider's
5020
+ * `clientBasedCORS`, which trusts each requesting client's registered `redirect_uris`.
4876
5021
  *
4877
- * Origin is sourced from `FirebaseServerEnvService.appUrl`, which is the authoritative frontend
4878
- * origin per environment. Single-origin deployments (e.g. local dev where the frontend and the
4879
- * OIDC server share `http://localhost:9010`) still benefit: their cross-origin requests are
4880
- * actually same-origin and CORS is a no-op.
5022
+ * When `cors` is unset, this preserves the legacy single-origin behavior exactly:
5023
+ * `Access-Control-Allow-Origin: <appUrl>` on every response, which pre-empts oidc-provider's
5024
+ * own CORS handling.
4881
5025
  *
4882
5026
  * Credentials are intentionally NOT enabled (`Access-Control-Allow-Credentials` stays off). The
4883
- * `/interaction/{uid}/*` POSTs carry the Firebase ID token in the JSON body, not in cookies, so
4884
- * we keep the CORS surface minimal.
5027
+ * `/interaction/{uid}/*` POSTs and the token exchange carry their bearer material in the request
5028
+ * body, not in cookies, so we keep the CORS surface minimal.
4885
5029
  *
4886
5030
  * @param nestApp - The NestJS application to configure.
4887
5031
  *
@@ -4897,18 +5041,40 @@ exports.OidcAuthBearerTokenMiddleware = __decorate([
4897
5041
  * ```
4898
5042
  */ function applyOidcCorsMiddleware(nestApp) {
4899
5043
  var envService = nestApp.get(firebaseServer.FirebaseServerEnvService);
4900
- var origin = envService.appUrl;
4901
- if (!origin) {
5044
+ var appUrl = envService.appUrl;
5045
+ if (!appUrl) {
4902
5046
  return;
4903
5047
  }
4904
- nestApp.enableCors({
4905
- origin: origin,
4906
- methods: [
4907
- 'GET',
4908
- 'POST',
4909
- 'OPTIONS'
4910
- ]
4911
- });
5048
+ // OidcModuleConfig may be absent (module not registered / out of the resolution scope).
5049
+ var cors;
5050
+ try {
5051
+ var _nestApp_get;
5052
+ cors = (_nestApp_get = nestApp.get(OidcModuleConfig, {
5053
+ strict: false
5054
+ })) === null || _nestApp_get === void 0 ? void 0 : _nestApp_get.cors;
5055
+ } catch (unused) {
5056
+ cors = undefined;
5057
+ }
5058
+ if (cors) {
5059
+ nestApp.enableCors({
5060
+ origin: oidcCorsOriginDelegate(appUrl, cors),
5061
+ methods: [
5062
+ 'GET',
5063
+ 'POST',
5064
+ 'OPTIONS'
5065
+ ]
5066
+ });
5067
+ } else {
5068
+ // No cors config → byte-identical to the legacy behavior: ACAO=appUrl unconditionally.
5069
+ nestApp.enableCors({
5070
+ origin: appUrl,
5071
+ methods: [
5072
+ 'GET',
5073
+ 'POST',
5074
+ 'OPTIONS'
5075
+ ]
5076
+ });
5077
+ }
4912
5078
  }
4913
5079
 
4914
5080
  /**
@@ -6049,7 +6215,7 @@ var OidcInteractionController_1;
6049
6215
  * @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
6050
6216
  */ function postConsent(uid, body, res) {
6051
6217
  return _async_to_generator$2(function() {
6052
- 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;
6053
6219
  return _ts_generator$2(this, function(_state) {
6054
6220
  switch(_state.label){
6055
6221
  case 0:
@@ -6064,9 +6230,9 @@ var OidcInteractionController_1;
6064
6230
  case 2:
6065
6231
  _state.trys.push([
6066
6232
  2,
6067
- 20,
6233
+ 24,
6068
6234
  ,
6069
- 21
6235
+ 25
6070
6236
  ]);
6071
6237
  if (!!body.approved) return [
6072
6238
  3,
@@ -6082,7 +6248,7 @@ var OidcInteractionController_1;
6082
6248
  })
6083
6249
  ];
6084
6250
  case 3:
6085
- _ref3 = _state.sent(), redirectTo = _ref3.returnTo;
6251
+ _ref4 = _state.sent(), redirectTo = _ref4.returnTo;
6086
6252
  res.json({
6087
6253
  redirectTo: redirectTo
6088
6254
  });
@@ -6125,6 +6291,12 @@ var OidcInteractionController_1;
6125
6291
  // and re-submits them all. We accept already-encountered values as silent no-ops so a re-consent on a
6126
6292
  // client the user has previously authorized doesn't fail validation.
6127
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();
6128
6300
  // Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
6129
6301
  // (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
6130
6302
  // `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
@@ -6136,26 +6308,26 @@ var OidcInteractionController_1;
6136
6308
  });
6137
6309
  if (!accountId) return [
6138
6310
  3,
6139
- 10
6311
+ 11
6140
6312
  ];
6141
6313
  return [
6142
6314
  4,
6143
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
6144
6316
  ];
6145
- case 9:
6317
+ case 10:
6146
6318
  _tmp1 = _state.sent();
6147
6319
  return [
6148
6320
  3,
6149
- 11
6321
+ 12
6150
6322
  ];
6151
- case 10:
6152
- _tmp1 = false;
6153
- _state.label = 11;
6154
6323
  case 11:
6324
+ _tmp1 = false;
6325
+ _state.label = 12;
6326
+ case 12:
6155
6327
  isAdmin = _tmp1;
6156
6328
  if (!(requestsServiceToken && !isAdmin)) return [
6157
6329
  3,
6158
- 13
6330
+ 14
6159
6331
  ];
6160
6332
  return [
6161
6333
  4,
@@ -6166,8 +6338,8 @@ var OidcInteractionController_1;
6166
6338
  mergeWithLastSubmission: true
6167
6339
  })
6168
6340
  ];
6169
- case 12:
6170
- _ref4 = _state.sent(), redirectTo1 = _ref4.returnTo;
6341
+ case 13:
6342
+ _ref5 = _state.sent(), redirectTo1 = _ref5.returnTo;
6171
6343
  emitOidcAnalyticsEvent(this._analytics, {
6172
6344
  type: 'consent',
6173
6345
  isSuccessful: false,
@@ -6184,20 +6356,89 @@ var OidcInteractionController_1;
6184
6356
  return [
6185
6357
  2
6186
6358
  ];
6187
- 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:
6188
6435
  // Resolve the requested login duration up-front. The configured Grant TTL function (in
6189
6436
  // OidcService.buildProviderConfiguration) only fires when oidc-provider's koa middleware
6190
6437
  // drives `grant.save()`, so its `ctx.oidc.params` lookup of `dbx_session_ttl` returns
6191
6438
  // undefined when the consent submit runs in this controller. We pre-set `expiresIn` on
6192
6439
  // newly-created grants so they persist with the correct (tiered) TTL.
6193
6440
  requestedRawTtl = params[DBX_FIREBASE_SERVER_OIDC_SESSION_TTL_PARAM];
6194
- return [
6195
- 4,
6196
- this.oidcService.findClientPayload(clientId)
6197
- ];
6198
- case 14:
6199
- clientPayload = _state.sent();
6200
- 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;
6201
6442
  expiresInSeconds = this.oidcService.resolveLoginDurationForGrant(requestedRawTtl, {
6202
6443
  dbx_max_session_ttl: clientMaxSessionTtl
6203
6444
  }, {
@@ -6206,28 +6447,30 @@ var OidcInteractionController_1;
6206
6447
  });
6207
6448
  if (!(existingGrant !== null && existingGrant !== void 0)) return [
6208
6449
  3,
6209
- 15
6450
+ 19
6210
6451
  ];
6211
6452
  _tmp2 = existingGrant;
6212
6453
  return [
6213
6454
  3,
6214
- 17
6455
+ 21
6215
6456
  ];
6216
- case 15:
6457
+ case 19:
6217
6458
  return [
6218
6459
  4,
6219
6460
  this.oidcInteractionService.findOrCreateGrant(interaction.grantId, accountId, clientId, expiresInSeconds)
6220
6461
  ];
6221
- case 16:
6462
+ case 20:
6222
6463
  _tmp2 = _state.sent();
6223
- _state.label = 17;
6224
- case 17:
6464
+ _state.label = 21;
6465
+ case 21:
6225
6466
  grant = _tmp2;
6226
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.
6227
6470
  _resolveEffectiveSubset = resolveEffectiveSubset({
6228
6471
  missing: missingOIDCScope,
6229
6472
  requestedSubset: body.grantedOIDCScopes,
6230
- alwaysGranted: ALWAYS_GRANTED_OIDC_SCOPES,
6473
+ alwaysGranted: _to_consumable_array$1(ALWAYS_GRANTED_OIDC_SCOPES).concat(_to_consumable_array$1(clientRequiredScopes)),
6231
6474
  alreadyEncountered: encounteredOIDCScopes
6232
6475
  }), granted = _resolveEffectiveSubset.granted, rejected = _resolveEffectiveSubset.rejected;
6233
6476
  if (granted.length > 0) {
@@ -6324,7 +6567,7 @@ var OidcInteractionController_1;
6324
6567
  4,
6325
6568
  grant.save()
6326
6569
  ];
6327
- case 18:
6570
+ case 22:
6328
6571
  grantId = _state.sent();
6329
6572
  return [
6330
6573
  4,
@@ -6336,8 +6579,8 @@ var OidcInteractionController_1;
6336
6579
  mergeWithLastSubmission: true
6337
6580
  })
6338
6581
  ];
6339
- case 19:
6340
- _ref5 = _state.sent(), redirectTo2 = _ref5.returnTo;
6582
+ case 23:
6583
+ _ref8 = _state.sent(), redirectTo4 = _ref8.returnTo;
6341
6584
  emitOidcAnalyticsEvent(this._analytics, {
6342
6585
  type: 'consent',
6343
6586
  isSuccessful: true,
@@ -6349,13 +6592,13 @@ var OidcInteractionController_1;
6349
6592
  durationMs: Date.now() - startedAt
6350
6593
  }, this._logger);
6351
6594
  res.json({
6352
- redirectTo: redirectTo2
6595
+ redirectTo: redirectTo4
6353
6596
  });
6354
6597
  return [
6355
6598
  3,
6356
- 21
6599
+ 25
6357
6600
  ];
6358
- case 20:
6601
+ case 24:
6359
6602
  err = _state.sent();
6360
6603
  emitOidcAnalyticsEvent(this._analytics, {
6361
6604
  type: 'consent',
@@ -6370,7 +6613,7 @@ var OidcInteractionController_1;
6370
6613
  throw err;
6371
6614
  }
6372
6615
  throw new common.HttpException('Consent interaction failed', common.HttpStatus.BAD_REQUEST);
6373
- case 21:
6616
+ case 25:
6374
6617
  return [
6375
6618
  2
6376
6619
  ];
@@ -7540,31 +7783,43 @@ function _unsupported_iterable_to_array(o, minLen) {
7540
7783
 
7541
7784
  /**
7542
7785
  * Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
7543
- * 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`.
7544
7791
  *
7545
7792
  * Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
7546
7793
  * when {@link getOidcScopesFromRequest} returns `undefined` because there is no
7547
7794
  * `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
7548
7795
  * unaffected and continue to be gated by auth roles.
7549
7796
  *
7550
- * Bypasses the check for custom (non-CRUD) call types so app-specific verbs
7551
- * 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`.
7552
7800
  *
7553
7801
  * Wire as the `preAssert` of {@link onCallModel}.
7554
7802
  *
7555
7803
  * @returns The pre-assertion function ready to plug into `OnCallModelConfig.preAssert`.
7556
7804
  */ function oidcCallModelScopePreAssert() {
7557
7805
  var fn = function fn(context) {
7558
- var requiredScope = firebase.callModelOidcScopeForCallType(context.call);
7559
- var scopes = requiredScope == null ? undefined : getOidcScopesFromRequest(context.request);
7560
- var isMissingScope = requiredScope != null && scopes != null && !scopes.has(requiredScope);
7561
- 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) {
7562
7817
  throw firebaseServer.forbiddenError({
7563
7818
  status: 403,
7564
7819
  code: firebase.CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE,
7565
- message: "Missing required OIDC scope for callModel: ".concat(requiredScope),
7820
+ message: "Missing required OIDC scope for callModel: ".concat(missingScopes.join(', ')),
7566
7821
  data: {
7567
- requiredScope: requiredScope,
7822
+ requiredScopes: missingScopes,
7568
7823
  call: context.call
7569
7824
  }
7570
7825
  });
@@ -7573,6 +7828,7 @@ function _unsupported_iterable_to_array(o, minLen) {
7573
7828
  return fn;
7574
7829
  }
7575
7830
 
7831
+ exports.DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA;
7576
7832
  exports.DEFAULT_APP_OAUTH_CONSENT_PATH_PART = DEFAULT_APP_OAUTH_CONSENT_PATH_PART;
7577
7833
  exports.DEFAULT_APP_OAUTH_INTERACTION_PATH = DEFAULT_APP_OAUTH_INTERACTION_PATH;
7578
7834
  exports.DEFAULT_APP_OAUTH_LOGIN_PATH_PART = DEFAULT_APP_OAUTH_LOGIN_PATH_PART;
@@ -7635,6 +7891,8 @@ exports.jwksKeysWithStatusQuery = jwksKeysWithStatusQuery;
7635
7891
  exports.nonRetiredJwksKeysQuery = nonRetiredJwksKeysQuery;
7636
7892
  exports.noopOidcAnalyticsService = noopOidcAnalyticsService;
7637
7893
  exports.oidcCallModelScopePreAssert = oidcCallModelScopePreAssert;
7894
+ exports.oidcClientProviderProfileScopes = oidcClientProviderProfileScopes;
7895
+ exports.oidcCorsOriginDelegate = oidcCorsOriginDelegate;
7638
7896
  exports.oidcFirestoreCollectionsFactory = oidcFirestoreCollectionsFactory;
7639
7897
  exports.oidcModelServerActions = oidcModelServerActions;
7640
7898
  exports.oidcModelServerActionsFactory = oidcModelServerActionsFactory;