diff --git a/package-lock.json b/package-lock.json index 139c614..57cbf9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1990,9 +1990,9 @@ } }, "node_modules/@lightbase/pull-through-cache": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@lightbase/pull-through-cache/-/pull-through-cache-0.1.2.tgz", - "integrity": "sha512-nTosjLM02B/I30CO60u7EvIc+k0DGqxW5uCMzI58zZ4UjgjG2/RDjdoscOFkTizZ8pK+qKgE1zOWaMdjNVZwsQ==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@lightbase/pull-through-cache/-/pull-through-cache-0.2.1.tgz", + "integrity": "sha512-2FtndHp4ywU7VHTFsIhYzsmbdLBmX5Yo2uokiPMNdWswvc0Xp0kTB50lJEeAuXgYoNtUuV66nSeObqkH6C79rg==" }, "node_modules/@lightbasenl/backend": { "resolved": "vendor/backend", @@ -9019,7 +9019,7 @@ "name": "@lightbasenl/backend", "version": "0.50.2", "dependencies": { - "@lightbase/pull-through-cache": "0.1.2", + "@lightbase/pull-through-cache": "0.2.1", "@xmldom/xmldom": "0.8.10", "bcrypt": "5.1.1", "rate-limiter-flexible": "5.0.3", diff --git a/vendor/backend/package.json b/vendor/backend/package.json index cfb7cde..517a994 100644 --- a/vendor/backend/package.json +++ b/vendor/backend/package.json @@ -13,7 +13,7 @@ ], "scripts": {}, "dependencies": { - "@lightbase/pull-through-cache": "0.1.2", + "@lightbase/pull-through-cache": "0.2.1", "@xmldom/xmldom": "0.8.10", "bcrypt": "5.1.1", "rate-limiter-flexible": "5.0.3", @@ -30,5 +30,5 @@ "url": "https://github.com/lightbasenl/platform-components.git", "directory": "packages/backend" }, - "gitHead": "b335a6c8aa6f5e14489b582b02b6fa45beda00b6" + "gitHead": "83e6a49eeabee3f24478f79ea7cd26f24680f4a4" } diff --git a/vendor/backend/src/auth/user.events.js b/vendor/backend/src/auth/user.events.js index 7b78005..1ba3066 100644 --- a/vendor/backend/src/auth/user.events.js +++ b/vendor/backend/src/auth/user.events.js @@ -89,7 +89,10 @@ const authQueries = { * @property {boolean|undefined} [requireDigidBased] * @property {boolean|undefined} [requireKeycloakBased] * @property {boolean|undefined} [requirePasswordBased] - * @property {AuthPermissionIdentifier[]|undefined} [requiredPermissions] + * @property {AuthPermissionIdentifier[]|undefined} [requiredPermissions] Require all + * provided permissions + * @property {AuthPermissionIdentifier[]|undefined} [oneOfRequiredPermissions] Require + * one of the provided permissions */ /** @@ -633,8 +636,8 @@ export async function authRequireUser( } if ( - Array.isArray(options.requiredPermissions) && - options.requiredPermissions.length > 0 + Array.isArray(options.requiredPermissions) || + Array.isArray(options.oneOfRequiredPermissions) ) { const permissionSet = new Set(); // @ts-expect-error @@ -645,17 +648,33 @@ export async function authRequireUser( } } - const missingPermissions = []; - for (const requiredPermission of options.requiredPermissions) { - if (!permissionSet.has(requiredPermission)) { - missingPermissions.push(requiredPermission); + if (options.requiredPermissions) { + const missingPermissions = []; + for (const requiredPermission of options.requiredPermissions) { + if (!permissionSet.has(requiredPermission)) { + missingPermissions.push(requiredPermission); + } } - } - if (missingPermissions.length > 0) { - throw AppError.validationError(`${eventKey}.missingPermissions`, { - missingPermissions, - }); + if (missingPermissions.length > 0) { + throw AppError.validationError(`${eventKey}.missingPermissions`, { + missingPermissions, + }); + } + } else if (options.oneOfRequiredPermissions) { + let hasOnePermission = false; + for (const requiredPermission of options.oneOfRequiredPermissions) { + if (permissionSet.has(requiredPermission)) { + hasOnePermission = true; + break; + } + } + + if (!hasOnePermission) { + throw AppError.validationError(`${eventKey}.missingPermissions`, { + missingPermissions: options.oneOfRequiredPermissions, + }); + } } } diff --git a/vendor/backend/src/feature-flag/cache.js b/vendor/backend/src/feature-flag/cache.js index 4089824..67d1579 100644 --- a/vendor/backend/src/feature-flag/cache.js +++ b/vendor/backend/src/feature-flag/cache.js @@ -1,6 +1,7 @@ import { AppError } from "@compas/stdlib"; import { PullThroughCache } from "@lightbase/pull-through-cache"; import { queryFeatureFlag, sql } from "../services.js"; +import { cacheEventToSentryMetric } from "../util.js"; /** * Short TTL feature flag cache. Keeps all flags for 5 seconds in memory, @@ -16,6 +17,9 @@ export const featureFlagCache = new PullThroughCache() }) .withFetcher({ fetcher: featureFlagFetcher, + }) + .withEventCallback({ + callback: cacheEventToSentryMetric("featureFlag"), }); /** diff --git a/vendor/backend/src/feature-flag/events.js b/vendor/backend/src/feature-flag/events.js index 45992f5..99f7219 100644 --- a/vendor/backend/src/feature-flag/events.js +++ b/vendor/backend/src/feature-flag/events.js @@ -158,8 +158,7 @@ export async function featureFlagSetDynamic( if (featureFlagCache.isEnabled()) { // Clear the cache if enabled. This method function is often only used in test code, which most likely disables the cache anyways. - featureFlagCache.disable(); - featureFlagCache.enable(); + featureFlagCache.clearAll(); } eventStop(event); diff --git a/vendor/backend/src/multitenant/cache.js b/vendor/backend/src/multitenant/cache.js index 3b4bde4..0688b7d 100644 --- a/vendor/backend/src/multitenant/cache.js +++ b/vendor/backend/src/multitenant/cache.js @@ -1,6 +1,7 @@ import { isNil, uuid } from "@compas/stdlib"; import { PullThroughCache } from "@lightbase/pull-through-cache"; import { queryTenant, sql, tenantBuilder } from "../services.js"; +import { cacheEventToSentryMetric } from "../util.js"; /** * Frequently sampled tenant cache. @@ -19,6 +20,9 @@ export const tenantCache = new PullThroughCache() }) .withFetcher({ fetcher: tenantFetcher, + }) + .withEventCallback({ + callback: cacheEventToSentryMetric("tenant"), }); /** diff --git a/vendor/backend/src/util.js b/vendor/backend/src/util.js index 964f996..b4e7937 100644 --- a/vendor/backend/src/util.js +++ b/vendor/backend/src/util.js @@ -1,5 +1,24 @@ import { existsSync } from "node:fs"; -import { AppError, isNil, pathJoin } from "@compas/stdlib"; +import { _compasSentryExport, AppError, isNil, pathJoin } from "@compas/stdlib"; + +/** + * Count the different PullThroughCache events as their own metric + * + * @param {string} cacheName + * @returns {(function(string): void)} + */ +export function cacheEventToSentryMetric(cacheName) { + return (event) => { + if (_compasSentryExport?.metrics?.increment) { + _compasSentryExport.metrics.increment(`cache.${event}`, 1, { + tags: { + cacheName, + }, + unit: "none", + }); + } + }; +} /** * Takes an AppError and normalizes it to a 401, to simplify frontend error handling on