@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/index.cjs.js +32 -1
- package/index.esm.js +32 -2
- package/mailgun/package.json +9 -9
- package/mcp/index.cjs.js +14 -6
- package/mcp/index.esm.js +15 -7
- package/mcp/package.json +11 -11
- package/mcp/src/lib/service/mcp.visibility.d.ts +6 -4
- package/model/package.json +9 -9
- package/oidc/index.cjs.js +197 -50
- package/oidc/index.esm.js +198 -53
- package/oidc/package.json +10 -10
- package/oidc/src/lib/index.d.ts +1 -0
- package/oidc/src/lib/oidc.config.d.ts +14 -1
- package/oidc/src/lib/profile.d.ts +29 -0
- package/oidc/src/lib/scope.d.ts +8 -3
- package/package.json +10 -10
- package/src/lib/nest/model/api.details.d.ts +25 -1
- package/src/lib/nest/model/crud.assert.function.d.ts +9 -1
- package/test/index.cjs.js +10 -3
- package/test/index.esm.js +11 -4
- package/test/package.json +11 -11
- package/test/src/lib/oidc/oidc.test.flow.d.ts +1 -1
- package/twilio/package.json +8 -8
- package/zoho/package.json +9 -9
package/oidc/index.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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 } from '@dereekb/util';
|
|
3
|
+
import { SECONDS_IN_MINUTE, SECONDS_IN_DAY, cachedGetter, unixDateTimeSecondsNumberToDate, unixDateTimeSecondsNumberForNow, selectiveFieldEncryptor, filterUndefinedValues, websiteUrlFromPaths, firstValue, hasHttpPrefix, filterMaybeArrayValues } 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, oidcEntryFirestoreCollection, callModelOidcScopeForCallType, CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE } from '@dereekb/firebase';
|
|
6
|
+
import { where, iterateFirestoreDocumentSnapshotPairs, firestoreModelIdentity, snapshotConverterFunctions, optionalFirestoreDate, firestoreDate, firestoreEnum, firestorePassThroughField, AbstractFirestoreDocument, createOidcClientParamsType, deleteOidcClientParamsType, deleteOidcTokenParamsType, rotateOidcClientSecretParamsType, updateOidcClientParamsType, firestoreModelKey, oidcEntryIdentity, PUBLIC_PKCE_TOKEN_ENDPOINT_AUTH_METHOD, OIDC_ENTRY_CLIENT_TYPE, oidcEntriesByUserCodeQuery, oidcEntriesByUidQuery, oidcEntriesByGrantIdQuery, oidcProviderProfilesForKeys, requiredScopesForOidcProviderProfiles, scopesForOidcProviderProfiles, oidcEntryFirestoreCollection, callModelOidcScopeForCallType, CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE } from '@dereekb/firebase';
|
|
7
7
|
import { firestoreEncryptedField, firebaseServerActionsContext, FirebaseServerEnvService, FirebaseServerAnalyticsService, FIREBASE_FIRESTORE_CONTEXT_TOKEN, FirebaseServerFirestoreContextModule, forbiddenError } from '@dereekb/firebase-server';
|
|
8
8
|
import { nanoid } from 'nanoid';
|
|
9
9
|
import { safeToJsDate } from '@dereekb/date';
|
|
@@ -2429,6 +2429,9 @@ function _ts_generator$7(thisArg, body) {
|
|
|
2429
2429
|
if (params.dbx_max_session_ttl != null) {
|
|
2430
2430
|
properties.dbx_max_session_ttl = params.dbx_max_session_ttl;
|
|
2431
2431
|
}
|
|
2432
|
+
if (params.dbx_provider_profiles != null) {
|
|
2433
|
+
properties.dbx_provider_profiles = params.dbx_provider_profiles;
|
|
2434
|
+
}
|
|
2432
2435
|
// Merge any pre-validated metadata (e.g., inline jwks for private_key_jwt in tests)
|
|
2433
2436
|
if (validatedMetadata === null || validatedMetadata === void 0 ? void 0 : validatedMetadata.jwks) {
|
|
2434
2437
|
properties.jwks = validatedMetadata.jwks;
|
|
@@ -2486,7 +2489,7 @@ function _ts_generator$7(thisArg, body) {
|
|
|
2486
2489
|
* @throws {Error} When the client is not found.
|
|
2487
2490
|
*/ function updateClient(clientId, params) {
|
|
2488
2491
|
return _async_to_generator$7(function() {
|
|
2489
|
-
var provider, ProviderClient, existing, updatedMetadata, _params_logo_uri, _params_client_uri, _params_dbx_max_session_ttl, client;
|
|
2492
|
+
var provider, ProviderClient, existing, updatedMetadata, _params_logo_uri, _params_client_uri, _params_dbx_max_session_ttl, _params_dbx_provider_profiles, client;
|
|
2490
2493
|
return _ts_generator$7(this, function(_state) {
|
|
2491
2494
|
switch(_state.label){
|
|
2492
2495
|
case 0:
|
|
@@ -2518,6 +2521,12 @@ function _ts_generator$7(thisArg, body) {
|
|
|
2518
2521
|
if (params.dbx_max_session_ttl !== undefined) {
|
|
2519
2522
|
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;
|
|
2520
2523
|
}
|
|
2524
|
+
// Only overwrite the profile assignment when the field is explicitly provided. An omitted value
|
|
2525
|
+
// (including one stripped for a non-admin caller) preserves the existing assignment; `null`/`[]`
|
|
2526
|
+
// clears it. This is what makes the non-admin strip a no-op rather than a wipe.
|
|
2527
|
+
if (params.dbx_provider_profiles !== undefined) {
|
|
2528
|
+
updatedMetadata.dbx_provider_profiles = (_params_dbx_provider_profiles = params.dbx_provider_profiles) !== null && _params_dbx_provider_profiles !== void 0 ? _params_dbx_provider_profiles : undefined;
|
|
2529
|
+
}
|
|
2521
2530
|
// Mirrors oidc-provider's lib/helpers/add_client.js: re-validates and persists.
|
|
2522
2531
|
return [
|
|
2523
2532
|
4,
|
|
@@ -3536,6 +3545,25 @@ OidcProviderConfigService = __decorate([
|
|
|
3536
3545
|
__param(2, Inject(FirebaseServerEnvService))
|
|
3537
3546
|
], OidcProviderConfigService);
|
|
3538
3547
|
|
|
3548
|
+
/**
|
|
3549
|
+
* Custom oidc-provider client metadata field holding the {@link OidcProviderProfile} keys assigned
|
|
3550
|
+
* to a client. Registered via `extraClientMetadata` and persisted on the client's `OidcEntry` payload.
|
|
3551
|
+
*/ var DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = 'dbx_provider_profiles';
|
|
3552
|
+
/**
|
|
3553
|
+
* Resolves the unlocked + required scope sets for a client from the provider-profile registry and
|
|
3554
|
+
* the profile keys assigned to that client.
|
|
3555
|
+
*
|
|
3556
|
+
* @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
|
|
3557
|
+
* @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
|
|
3558
|
+
* @returns The unlocked and required scope sets for the client.
|
|
3559
|
+
*/ function oidcClientProviderProfileScopes(providerProfiles, clientProfileKeys) {
|
|
3560
|
+
var clientProfiles = oidcProviderProfilesForKeys(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
|
|
3561
|
+
return {
|
|
3562
|
+
unlocked: scopesForOidcProviderProfiles(clientProfiles),
|
|
3563
|
+
required: requiredScopesForOidcProviderProfiles(clientProfiles)
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3566
|
+
|
|
3539
3567
|
function _array_like_to_array$4(arr, len) {
|
|
3540
3568
|
if (len == null || len > arr.length) len = arr.length;
|
|
3541
3569
|
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
@@ -4040,7 +4068,8 @@ function _ts_generator$5(thisArg, body) {
|
|
|
4040
4068
|
logo_uri: existing.logo_uri,
|
|
4041
4069
|
client_uri: existing.client_uri,
|
|
4042
4070
|
created_at: existing.created_at,
|
|
4043
|
-
dbx_max_session_ttl: existing.dbx_max_session_ttl
|
|
4071
|
+
dbx_max_session_ttl: existing.dbx_max_session_ttl,
|
|
4072
|
+
dbx_provider_profiles: existing.dbx_provider_profiles
|
|
4044
4073
|
};
|
|
4045
4074
|
}
|
|
4046
4075
|
return [
|
|
@@ -4147,7 +4176,8 @@ function _ts_generator$5(thisArg, body) {
|
|
|
4147
4176
|
],
|
|
4148
4177
|
extraClientMetadata: {
|
|
4149
4178
|
properties: [
|
|
4150
|
-
DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA
|
|
4179
|
+
DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA,
|
|
4180
|
+
DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA
|
|
4151
4181
|
],
|
|
4152
4182
|
validator: function validator(_ctx, key, value) {
|
|
4153
4183
|
if (key === DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA && value !== undefined && value !== null) {
|
|
@@ -4157,6 +4187,22 @@ function _ts_generator$5(thisArg, body) {
|
|
|
4157
4187
|
if (value > serverMaxSeconds) {
|
|
4158
4188
|
throw new errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA, " cannot exceed the server max of ").concat(serverMaxSeconds, " seconds."));
|
|
4159
4189
|
}
|
|
4190
|
+
} else if (key === DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA && value !== undefined && value !== null) {
|
|
4191
|
+
var _providerConfig_providerProfiles;
|
|
4192
|
+
if (!Array.isArray(value) || value.some(function(entry) {
|
|
4193
|
+
return typeof entry !== 'string';
|
|
4194
|
+
})) {
|
|
4195
|
+
throw new errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, " must be an array of provider profile keys."));
|
|
4196
|
+
}
|
|
4197
|
+
var knownProfileKeys = new Set(((_providerConfig_providerProfiles = providerConfig.providerProfiles) !== null && _providerConfig_providerProfiles !== void 0 ? _providerConfig_providerProfiles : []).map(function(profile) {
|
|
4198
|
+
return profile.key;
|
|
4199
|
+
}));
|
|
4200
|
+
var unknownKey = value.find(function(entry) {
|
|
4201
|
+
return !knownProfileKeys.has(entry);
|
|
4202
|
+
});
|
|
4203
|
+
if (unknownKey !== undefined) {
|
|
4204
|
+
throw new errors.InvalidClientMetadata("".concat(DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA, " contains an unknown provider profile key: ").concat(unknownKey, "."));
|
|
4205
|
+
}
|
|
4160
4206
|
}
|
|
4161
4207
|
}
|
|
4162
4208
|
},
|
|
@@ -4184,7 +4230,7 @@ function _ts_generator$5(thisArg, body) {
|
|
|
4184
4230
|
interactions: {
|
|
4185
4231
|
url: function url(_ctx, interaction) {
|
|
4186
4232
|
return _async_to_generator$5(function() {
|
|
4187
|
-
var baseUrl, client_id, paramsToEncode, client, scopes, interactionLoginDetails, paramsString;
|
|
4233
|
+
var baseUrl, client_id, paramsToEncode, client, _client_dbx_provider_profiles, scopes, requestedScopeSet, required, requiredScopes, interactionLoginDetails, paramsString;
|
|
4188
4234
|
return _ts_generator$5(this, function(_state) {
|
|
4189
4235
|
switch(_state.label){
|
|
4190
4236
|
case 0:
|
|
@@ -4212,13 +4258,23 @@ function _ts_generator$5(thisArg, body) {
|
|
|
4212
4258
|
client = _state.sent();
|
|
4213
4259
|
if (client) {
|
|
4214
4260
|
scopes = interaction.params.scope;
|
|
4215
|
-
|
|
4261
|
+
// Surface the scopes the client's provider profiles force-require so the consent UI can
|
|
4262
|
+
// render them as required. Intersect with the actually-requested scopes so only visible
|
|
4263
|
+
// scope rows are flagged.
|
|
4264
|
+
requestedScopeSet = new Set(scopes.split(' ').filter(Boolean));
|
|
4265
|
+
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;
|
|
4266
|
+
requiredScopes = Array.from(required).filter(function(scope) {
|
|
4267
|
+
return requestedScopeSet.has(scope);
|
|
4268
|
+
});
|
|
4269
|
+
interactionLoginDetails = _object_spread$3({
|
|
4216
4270
|
client_id: client_id,
|
|
4217
4271
|
client_name: client.client_name,
|
|
4218
4272
|
client_uri: client.client_uri,
|
|
4219
4273
|
logo_uri: client.logo_uri,
|
|
4220
4274
|
scopes: scopes
|
|
4221
|
-
}
|
|
4275
|
+
}, requiredScopes.length > 0 ? {
|
|
4276
|
+
requiredScopes: requiredScopes.join(' ')
|
|
4277
|
+
} : {});
|
|
4222
4278
|
paramsToEncode = _object_spread$3({}, paramsToEncode, interactionLoginDetails);
|
|
4223
4279
|
}
|
|
4224
4280
|
_state.label = 3;
|
|
@@ -6157,7 +6213,7 @@ var OidcInteractionController_1;
|
|
|
6157
6213
|
* @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
|
|
6158
6214
|
*/ function postConsent(uid, body, res) {
|
|
6159
6215
|
return _async_to_generator$2(function() {
|
|
6160
|
-
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,
|
|
6216
|
+
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;
|
|
6161
6217
|
return _ts_generator$2(this, function(_state) {
|
|
6162
6218
|
switch(_state.label){
|
|
6163
6219
|
case 0:
|
|
@@ -6172,9 +6228,9 @@ var OidcInteractionController_1;
|
|
|
6172
6228
|
case 2:
|
|
6173
6229
|
_state.trys.push([
|
|
6174
6230
|
2,
|
|
6175
|
-
|
|
6231
|
+
24,
|
|
6176
6232
|
,
|
|
6177
|
-
|
|
6233
|
+
25
|
|
6178
6234
|
]);
|
|
6179
6235
|
if (!!body.approved) return [
|
|
6180
6236
|
3,
|
|
@@ -6190,7 +6246,7 @@ var OidcInteractionController_1;
|
|
|
6190
6246
|
})
|
|
6191
6247
|
];
|
|
6192
6248
|
case 3:
|
|
6193
|
-
|
|
6249
|
+
_ref4 = _state.sent(), redirectTo = _ref4.returnTo;
|
|
6194
6250
|
res.json({
|
|
6195
6251
|
redirectTo: redirectTo
|
|
6196
6252
|
});
|
|
@@ -6233,6 +6289,12 @@ var OidcInteractionController_1;
|
|
|
6233
6289
|
// and re-submits them all. We accept already-encountered values as silent no-ops so a re-consent on a
|
|
6234
6290
|
// client the user has previously authorized doesn't fail validation.
|
|
6235
6291
|
encounteredOIDCScopes = existingGrant ? existingGrant.getOIDCScopeEncountered().split(' ').filter(Boolean) : [];
|
|
6292
|
+
return [
|
|
6293
|
+
4,
|
|
6294
|
+
this.oidcService.findClientPayload(clientId)
|
|
6295
|
+
];
|
|
6296
|
+
case 9:
|
|
6297
|
+
clientPayload = _state.sent();
|
|
6236
6298
|
// Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
|
|
6237
6299
|
// (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
|
|
6238
6300
|
// `encountered`). If that set intersects the provider's `adminOnlyScopes` and the resolving user is
|
|
@@ -6244,26 +6306,26 @@ var OidcInteractionController_1;
|
|
|
6244
6306
|
});
|
|
6245
6307
|
if (!accountId) return [
|
|
6246
6308
|
3,
|
|
6247
|
-
|
|
6309
|
+
11
|
|
6248
6310
|
];
|
|
6249
6311
|
return [
|
|
6250
6312
|
4,
|
|
6251
6313
|
(_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
|
|
6252
6314
|
];
|
|
6253
|
-
case
|
|
6315
|
+
case 10:
|
|
6254
6316
|
_tmp1 = _state.sent();
|
|
6255
6317
|
return [
|
|
6256
6318
|
3,
|
|
6257
|
-
|
|
6319
|
+
12
|
|
6258
6320
|
];
|
|
6259
|
-
case 10:
|
|
6260
|
-
_tmp1 = false;
|
|
6261
|
-
_state.label = 11;
|
|
6262
6321
|
case 11:
|
|
6322
|
+
_tmp1 = false;
|
|
6323
|
+
_state.label = 12;
|
|
6324
|
+
case 12:
|
|
6263
6325
|
isAdmin = _tmp1;
|
|
6264
6326
|
if (!(requestsServiceToken && !isAdmin)) return [
|
|
6265
6327
|
3,
|
|
6266
|
-
|
|
6328
|
+
14
|
|
6267
6329
|
];
|
|
6268
6330
|
return [
|
|
6269
6331
|
4,
|
|
@@ -6274,8 +6336,8 @@ var OidcInteractionController_1;
|
|
|
6274
6336
|
mergeWithLastSubmission: true
|
|
6275
6337
|
})
|
|
6276
6338
|
];
|
|
6277
|
-
case
|
|
6278
|
-
|
|
6339
|
+
case 13:
|
|
6340
|
+
_ref5 = _state.sent(), redirectTo1 = _ref5.returnTo;
|
|
6279
6341
|
emitOidcAnalyticsEvent(this._analytics, {
|
|
6280
6342
|
type: 'consent',
|
|
6281
6343
|
isSuccessful: false,
|
|
@@ -6292,20 +6354,89 @@ var OidcInteractionController_1;
|
|
|
6292
6354
|
return [
|
|
6293
6355
|
2
|
|
6294
6356
|
];
|
|
6295
|
-
case
|
|
6357
|
+
case 14:
|
|
6358
|
+
// Provider-profile scope gate. Any scope referenced by a provider profile is "gated" — available
|
|
6359
|
+
// only to clients whose assigned profiles unlock it. Independent of the admin-only gate above; a
|
|
6360
|
+
// scope may be subject to both.
|
|
6361
|
+
providerProfiles = this.accountService.providerConfig.providerProfiles;
|
|
6362
|
+
_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;
|
|
6363
|
+
profileGatedScopes = scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
|
|
6364
|
+
// Unlock gate: reject any requested gated scope this client's profiles do not unlock.
|
|
6365
|
+
disallowedGatedScopes = Array.from(requestedOIDCScopeSet).filter(function(scope) {
|
|
6366
|
+
return profileGatedScopes.has(scope) && !clientUnlockedScopes.has(scope);
|
|
6367
|
+
});
|
|
6368
|
+
if (!(disallowedGatedScopes.length > 0)) return [
|
|
6369
|
+
3,
|
|
6370
|
+
16
|
|
6371
|
+
];
|
|
6372
|
+
return [
|
|
6373
|
+
4,
|
|
6374
|
+
this.oidcInteractionService.finishInteractionByUid(uid, {
|
|
6375
|
+
error: 'access_denied',
|
|
6376
|
+
error_description: "The following scope(s) are not available to this client: ".concat(disallowedGatedScopes.join(', '), ".")
|
|
6377
|
+
}, {
|
|
6378
|
+
mergeWithLastSubmission: true
|
|
6379
|
+
})
|
|
6380
|
+
];
|
|
6381
|
+
case 15:
|
|
6382
|
+
_ref6 = _state.sent(), redirectTo2 = _ref6.returnTo;
|
|
6383
|
+
emitOidcAnalyticsEvent(this._analytics, {
|
|
6384
|
+
type: 'consent',
|
|
6385
|
+
isSuccessful: false,
|
|
6386
|
+
uid: accountId,
|
|
6387
|
+
clientId: clientId,
|
|
6388
|
+
reason: 'scope_not_unlocked_for_client',
|
|
6389
|
+
durationMs: Date.now() - startedAt
|
|
6390
|
+
}, this._logger);
|
|
6391
|
+
res.json({
|
|
6392
|
+
redirectTo: redirectTo2
|
|
6393
|
+
});
|
|
6394
|
+
return [
|
|
6395
|
+
2
|
|
6396
|
+
];
|
|
6397
|
+
case 16:
|
|
6398
|
+
// Required gate: a scope a client's profiles force-require (`require: 'required'`) must be present
|
|
6399
|
+
// in the request. Reject when the client did not request a required scope.
|
|
6400
|
+
missingRequiredScopes = Array.from(clientRequiredScopes).filter(function(scope) {
|
|
6401
|
+
return !requestedOIDCScopeSet.has(scope);
|
|
6402
|
+
});
|
|
6403
|
+
if (!(missingRequiredScopes.length > 0)) return [
|
|
6404
|
+
3,
|
|
6405
|
+
18
|
|
6406
|
+
];
|
|
6407
|
+
return [
|
|
6408
|
+
4,
|
|
6409
|
+
this.oidcInteractionService.finishInteractionByUid(uid, {
|
|
6410
|
+
error: 'access_denied',
|
|
6411
|
+
error_description: "This client must request the required scope(s): ".concat(missingRequiredScopes.join(', '), ".")
|
|
6412
|
+
}, {
|
|
6413
|
+
mergeWithLastSubmission: true
|
|
6414
|
+
})
|
|
6415
|
+
];
|
|
6416
|
+
case 17:
|
|
6417
|
+
_ref7 = _state.sent(), redirectTo3 = _ref7.returnTo;
|
|
6418
|
+
emitOidcAnalyticsEvent(this._analytics, {
|
|
6419
|
+
type: 'consent',
|
|
6420
|
+
isSuccessful: false,
|
|
6421
|
+
uid: accountId,
|
|
6422
|
+
clientId: clientId,
|
|
6423
|
+
reason: 'required_scope_missing',
|
|
6424
|
+
durationMs: Date.now() - startedAt
|
|
6425
|
+
}, this._logger);
|
|
6426
|
+
res.json({
|
|
6427
|
+
redirectTo: redirectTo3
|
|
6428
|
+
});
|
|
6429
|
+
return [
|
|
6430
|
+
2
|
|
6431
|
+
];
|
|
6432
|
+
case 18:
|
|
6296
6433
|
// Resolve the requested login duration up-front. The configured Grant TTL function (in
|
|
6297
6434
|
// OidcService.buildProviderConfiguration) only fires when oidc-provider's koa middleware
|
|
6298
6435
|
// drives `grant.save()`, so its `ctx.oidc.params` lookup of `dbx_session_ttl` returns
|
|
6299
6436
|
// undefined when the consent submit runs in this controller. We pre-set `expiresIn` on
|
|
6300
6437
|
// newly-created grants so they persist with the correct (tiered) TTL.
|
|
6301
6438
|
requestedRawTtl = params[DBX_FIREBASE_SERVER_OIDC_SESSION_TTL_PARAM];
|
|
6302
|
-
|
|
6303
|
-
4,
|
|
6304
|
-
this.oidcService.findClientPayload(clientId)
|
|
6305
|
-
];
|
|
6306
|
-
case 14:
|
|
6307
|
-
clientPayload = _state.sent();
|
|
6308
|
-
clientMaxSessionTtl = (_ref2 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_max_session_ttl) !== null && _ref2 !== void 0 ? _ref2 : undefined;
|
|
6439
|
+
clientMaxSessionTtl = (_ref3 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_max_session_ttl) !== null && _ref3 !== void 0 ? _ref3 : undefined;
|
|
6309
6440
|
expiresInSeconds = this.oidcService.resolveLoginDurationForGrant(requestedRawTtl, {
|
|
6310
6441
|
dbx_max_session_ttl: clientMaxSessionTtl
|
|
6311
6442
|
}, {
|
|
@@ -6314,28 +6445,30 @@ var OidcInteractionController_1;
|
|
|
6314
6445
|
});
|
|
6315
6446
|
if (!(existingGrant !== null && existingGrant !== void 0)) return [
|
|
6316
6447
|
3,
|
|
6317
|
-
|
|
6448
|
+
19
|
|
6318
6449
|
];
|
|
6319
6450
|
_tmp2 = existingGrant;
|
|
6320
6451
|
return [
|
|
6321
6452
|
3,
|
|
6322
|
-
|
|
6453
|
+
21
|
|
6323
6454
|
];
|
|
6324
|
-
case
|
|
6455
|
+
case 19:
|
|
6325
6456
|
return [
|
|
6326
6457
|
4,
|
|
6327
6458
|
this.oidcInteractionService.findOrCreateGrant(interaction.grantId, accountId, clientId, expiresInSeconds)
|
|
6328
6459
|
];
|
|
6329
|
-
case
|
|
6460
|
+
case 20:
|
|
6330
6461
|
_tmp2 = _state.sent();
|
|
6331
|
-
_state.label =
|
|
6332
|
-
case
|
|
6462
|
+
_state.label = 21;
|
|
6463
|
+
case 21:
|
|
6333
6464
|
grant = _tmp2;
|
|
6334
6465
|
if (missingOIDCScope.length > 0) {
|
|
6466
|
+
// Force-grant the client's required profile scopes (in addition to `openid`) so they cannot be
|
|
6467
|
+
// dropped at consent — the required gate above already guarantees they were requested.
|
|
6335
6468
|
_resolveEffectiveSubset = resolveEffectiveSubset({
|
|
6336
6469
|
missing: missingOIDCScope,
|
|
6337
6470
|
requestedSubset: body.grantedOIDCScopes,
|
|
6338
|
-
alwaysGranted: ALWAYS_GRANTED_OIDC_SCOPES,
|
|
6471
|
+
alwaysGranted: _to_consumable_array$1(ALWAYS_GRANTED_OIDC_SCOPES).concat(_to_consumable_array$1(clientRequiredScopes)),
|
|
6339
6472
|
alreadyEncountered: encounteredOIDCScopes
|
|
6340
6473
|
}), granted = _resolveEffectiveSubset.granted, rejected = _resolveEffectiveSubset.rejected;
|
|
6341
6474
|
if (granted.length > 0) {
|
|
@@ -6432,7 +6565,7 @@ var OidcInteractionController_1;
|
|
|
6432
6565
|
4,
|
|
6433
6566
|
grant.save()
|
|
6434
6567
|
];
|
|
6435
|
-
case
|
|
6568
|
+
case 22:
|
|
6436
6569
|
grantId = _state.sent();
|
|
6437
6570
|
return [
|
|
6438
6571
|
4,
|
|
@@ -6444,8 +6577,8 @@ var OidcInteractionController_1;
|
|
|
6444
6577
|
mergeWithLastSubmission: true
|
|
6445
6578
|
})
|
|
6446
6579
|
];
|
|
6447
|
-
case
|
|
6448
|
-
|
|
6580
|
+
case 23:
|
|
6581
|
+
_ref8 = _state.sent(), redirectTo4 = _ref8.returnTo;
|
|
6449
6582
|
emitOidcAnalyticsEvent(this._analytics, {
|
|
6450
6583
|
type: 'consent',
|
|
6451
6584
|
isSuccessful: true,
|
|
@@ -6457,13 +6590,13 @@ var OidcInteractionController_1;
|
|
|
6457
6590
|
durationMs: Date.now() - startedAt
|
|
6458
6591
|
}, this._logger);
|
|
6459
6592
|
res.json({
|
|
6460
|
-
redirectTo:
|
|
6593
|
+
redirectTo: redirectTo4
|
|
6461
6594
|
});
|
|
6462
6595
|
return [
|
|
6463
6596
|
3,
|
|
6464
|
-
|
|
6597
|
+
25
|
|
6465
6598
|
];
|
|
6466
|
-
case
|
|
6599
|
+
case 24:
|
|
6467
6600
|
err = _state.sent();
|
|
6468
6601
|
emitOidcAnalyticsEvent(this._analytics, {
|
|
6469
6602
|
type: 'consent',
|
|
@@ -6478,7 +6611,7 @@ var OidcInteractionController_1;
|
|
|
6478
6611
|
throw err;
|
|
6479
6612
|
}
|
|
6480
6613
|
throw new HttpException('Consent interaction failed', HttpStatus.BAD_REQUEST);
|
|
6481
|
-
case
|
|
6614
|
+
case 25:
|
|
6482
6615
|
return [
|
|
6483
6616
|
2
|
|
6484
6617
|
];
|
|
@@ -7648,31 +7781,43 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
7648
7781
|
|
|
7649
7782
|
/**
|
|
7650
7783
|
* Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
|
|
7651
|
-
* lacking the OIDC scope
|
|
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`.
|
|
7652
7789
|
*
|
|
7653
7790
|
* Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
|
|
7654
7791
|
* when {@link getOidcScopesFromRequest} returns `undefined` because there is no
|
|
7655
7792
|
* `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
|
|
7656
7793
|
* unaffected and continue to be gated by auth roles.
|
|
7657
7794
|
*
|
|
7658
|
-
* Bypasses the check for custom (non-CRUD) call types
|
|
7659
|
-
* remain unrestricted unless an app wires its own
|
|
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`.
|
|
7660
7798
|
*
|
|
7661
7799
|
* Wire as the `preAssert` of {@link onCallModel}.
|
|
7662
7800
|
*
|
|
7663
7801
|
* @returns The pre-assertion function ready to plug into `OnCallModelConfig.preAssert`.
|
|
7664
7802
|
*/ function oidcCallModelScopePreAssert() {
|
|
7665
7803
|
var fn = function fn(context) {
|
|
7666
|
-
var
|
|
7667
|
-
|
|
7668
|
-
|
|
7669
|
-
|
|
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) {
|
|
7670
7815
|
throw forbiddenError({
|
|
7671
7816
|
status: 403,
|
|
7672
7817
|
code: CALL_MODEL_MISSING_OIDC_SCOPE_ERROR_CODE,
|
|
7673
|
-
message: "Missing required OIDC scope for callModel: ".concat(
|
|
7818
|
+
message: "Missing required OIDC scope for callModel: ".concat(missingScopes.join(', ')),
|
|
7674
7819
|
data: {
|
|
7675
|
-
|
|
7820
|
+
requiredScopes: missingScopes,
|
|
7676
7821
|
call: context.call
|
|
7677
7822
|
}
|
|
7678
7823
|
});
|
|
@@ -7681,4 +7826,4 @@ function _unsupported_iterable_to_array(o, minLen) {
|
|
|
7681
7826
|
return fn;
|
|
7682
7827
|
}
|
|
7683
7828
|
|
|
7684
|
-
export { 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, oidcCorsOriginDelegate, oidcFirestoreCollectionsFactory, oidcModelServerActions, oidcModelServerActionsFactory, oidcModuleConfigFactory, oidcModuleMetadata, resolveEffectiveSubset, rotateOidcClientSecretFactory, rotatedJwksKeysQuery, updateOidcClientFactory };
|
|
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 };
|
package/oidc/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server/oidc",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.29.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@dereekb/analytics": "13.
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/firebase": "13.
|
|
8
|
-
"@dereekb/firebase-server": "13.
|
|
9
|
-
"@dereekb/model": "13.
|
|
10
|
-
"@dereekb/nestjs": "13.
|
|
11
|
-
"@dereekb/rxjs": "13.
|
|
12
|
-
"@dereekb/util": "13.
|
|
13
|
-
"@dereekb/zoho": "13.
|
|
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",
|
|
14
14
|
"@nestjs/common": "^11.1.19",
|
|
15
15
|
"@nestjs/config": "^4.0.4",
|
|
16
16
|
"express": "^5.2.1",
|
package/oidc/src/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Configuration } from 'oidc-provider';
|
|
2
2
|
import { type WebsitePath, type SlashPath, type Seconds } from '@dereekb/util';
|
|
3
|
-
import { type OidcScope, type OidcTokenEndpointAuthMethod } from '@dereekb/firebase';
|
|
3
|
+
import { type OidcProviderProfile, type OidcScope, type OidcTokenEndpointAuthMethod } from '@dereekb/firebase';
|
|
4
4
|
import { type JwksServiceConfig } from './service/oidc.jwks.service';
|
|
5
5
|
import { type JwksKeyConverterConfig } from './model';
|
|
6
6
|
/**
|
|
@@ -104,6 +104,19 @@ export interface OidcProviderConfig<S extends OidcScope = OidcScope> {
|
|
|
104
104
|
* All other grants keep oidc-provider's default rotation behavior.
|
|
105
105
|
*/
|
|
106
106
|
readonly nonRotatingScopes?: readonly string[];
|
|
107
|
+
/**
|
|
108
|
+
* Named provider profiles that unlock (and optionally force-require) scopes for the OIDC clients
|
|
109
|
+
* they are assigned to. Any scope referenced by a profile is treated as restricted: a client may
|
|
110
|
+
* only obtain it when one of its assigned profiles (persisted as `dbx_provider_profiles` client
|
|
111
|
+
* metadata) unlocks it. A profile scope marked `require: 'required'` is force-required — surfaced
|
|
112
|
+
* as a required scope at consent and hard-rejected if not granted.
|
|
113
|
+
*
|
|
114
|
+
* Keeps the generic package app-agnostic: apps declare their profiles statically and list them
|
|
115
|
+
* here, rather than the package hard-coding any profile or scope name. This gate is independent of
|
|
116
|
+
* {@link adminOnlyScopes} (which gates by the resolving user's admin status); a scope may be
|
|
117
|
+
* subject to both.
|
|
118
|
+
*/
|
|
119
|
+
readonly providerProfiles?: readonly OidcProviderProfile<S>[];
|
|
107
120
|
}
|
|
108
121
|
/**
|
|
109
122
|
* Returns the space-delimited list of every scope declared on `providerConfig.claims`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type OidcProviderProfile, type OidcScope, type OidcProviderProfileKey } from '@dereekb/firebase';
|
|
2
|
+
/**
|
|
3
|
+
* Custom oidc-provider client metadata field holding the {@link OidcProviderProfile} keys assigned
|
|
4
|
+
* to a client. Registered via `extraClientMetadata` and persisted on the client's `OidcEntry` payload.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DBX_FIREBASE_SERVER_OIDC_PROVIDER_PROFILES_CLIENT_METADATA = "dbx_provider_profiles";
|
|
7
|
+
/**
|
|
8
|
+
* The scopes a client with the given assigned profile keys is allowed/required to obtain, resolved
|
|
9
|
+
* against the provider-profile registry.
|
|
10
|
+
*/
|
|
11
|
+
export interface OidcClientProviderProfileScopes<S extends OidcScope = OidcScope> {
|
|
12
|
+
/**
|
|
13
|
+
* Scopes unlocked for the client (the union of its assigned profiles' scopes).
|
|
14
|
+
*/
|
|
15
|
+
readonly unlocked: Set<S>;
|
|
16
|
+
/**
|
|
17
|
+
* Scopes the client's profiles force-require (`require: 'required'`).
|
|
18
|
+
*/
|
|
19
|
+
readonly required: Set<S>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the unlocked + required scope sets for a client from the provider-profile registry and
|
|
23
|
+
* the profile keys assigned to that client.
|
|
24
|
+
*
|
|
25
|
+
* @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
|
|
26
|
+
* @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
|
|
27
|
+
* @returns The unlocked and required scope sets for the client.
|
|
28
|
+
*/
|
|
29
|
+
export declare function oidcClientProviderProfileScopes<S extends OidcScope = OidcScope>(providerProfiles: readonly OidcProviderProfile<S>[] | undefined, clientProfileKeys: readonly OidcProviderProfileKey[] | undefined): OidcClientProviderProfileScopes<S>;
|
package/oidc/src/lib/scope.d.ts
CHANGED
|
@@ -2,15 +2,20 @@ import { type OnCallTypedModelParams } from '@dereekb/firebase';
|
|
|
2
2
|
import { type AssertModelCrudRequestFunction } from '@dereekb/firebase-server';
|
|
3
3
|
/**
|
|
4
4
|
* Builds a {@link AssertModelCrudRequestFunction} that rejects callModel requests
|
|
5
|
-
* lacking the OIDC scope
|
|
5
|
+
* lacking the required OIDC scope(s) for the call.
|
|
6
|
+
*
|
|
7
|
+
* Enforcement is additive: an OIDC caller must hold BOTH the per-verb scope mapped
|
|
8
|
+
* from the call type (`model.<call>`) AND any per-function scope a handler declared
|
|
9
|
+
* via `withApiDetails({ requiredScope })` — surfaced on `context.requiredScope`.
|
|
6
10
|
*
|
|
7
11
|
* Bypasses the check (no-op) when the request is not OIDC-authenticated — i.e.
|
|
8
12
|
* when {@link getOidcScopesFromRequest} returns `undefined` because there is no
|
|
9
13
|
* `auth.token.scope` claim on the request. Regular Firebase ID-token callers are
|
|
10
14
|
* unaffected and continue to be gated by auth roles.
|
|
11
15
|
*
|
|
12
|
-
* Bypasses the check for custom (non-CRUD) call types
|
|
13
|
-
* remain unrestricted unless an app wires its own
|
|
16
|
+
* Bypasses the check for custom (non-CRUD) call types that declare no per-function
|
|
17
|
+
* scope, so app-specific verbs remain unrestricted unless an app wires its own
|
|
18
|
+
* assertion or declares a `requiredScope`.
|
|
14
19
|
*
|
|
15
20
|
* Wire as the `preAssert` of {@link onCallModel}.
|
|
16
21
|
*
|