diff --git a/src/features/api-keys/lib.ts b/src/features/api-keys/lib.ts index d9bb3c9cd..27d2f0262 100644 --- a/src/features/api-keys/lib.ts +++ b/src/features/api-keys/lib.ts @@ -335,10 +335,27 @@ export const augmentReqApiKeyPermissions = < T extends permissions.PermissionReq, >( req: T, - ...extraPermissions: string[] + extraPermissions: string[], + /** + * When mutateRequestObject is + * false: A new request object with augmented permissions is returned to be used for specific tasks, + * while the rest of the code (eg: middleware & hooks) still runs with permissions the original request. + * true: The permissions are augmented for the whole lifetime of the request. + */ + mutateRequestObject = false, ): T => { - const augmentedReq = _.clone(req); - augmentedReq.apiKey = _.cloneDeep(augmentedReq.apiKey); + let augmentedReq = req; + + if (!mutateRequestObject) { + augmentedReq = _.clone(req); + augmentedReq.apiKey = _.cloneDeep(augmentedReq.apiKey); + } else if (augmentedReq.apiKey?.permissions) { + // When mutateRequestObject === true we still need to clone + // the permissions array rather than modifying it directly + // so that we do not pollute pine's apiKeyPermissions cache. + augmentedReq.apiKey.permissions = [...augmentedReq.apiKey.permissions]; + } + augmentedReq.apiKey?.permissions?.push(...extraPermissions); return augmentedReq; }; diff --git a/src/features/auth/public-keys.ts b/src/features/auth/public-keys.ts index 2b7b6c15d..73941e296 100644 --- a/src/features/auth/public-keys.ts +++ b/src/features/auth/public-keys.ts @@ -16,7 +16,7 @@ export const getUserPublicKeys: RequestHandler = async (req, res) => { // Augment with the ability to resolve the user's username for this request only, there's no need // for device keys to have the ability by default. Access to the public key will still be restricted // by `user__has__public_key` so this only affects the ability to resolve the username they apply to - req = augmentReqApiKeyPermissions(req, 'resin.user.read'); + req = augmentReqApiKeyPermissions(req, ['resin.user.read']); const data = (await sbvrUtils.api.resin.get({ resource: 'user__has__public_key', options: { diff --git a/src/features/device-provisioning/register.ts b/src/features/device-provisioning/register.ts index 7d1380ab5..6801549a0 100644 --- a/src/features/device-provisioning/register.ts +++ b/src/features/device-provisioning/register.ts @@ -58,14 +58,13 @@ export const register: RequestHandler = async (req, res) => { * - Fetch the device we create & create an api key for it * - Read the hostApp releases that should be operating the device */ - req = augmentReqApiKeyPermissions( - req, + req = augmentReqApiKeyPermissions(req, [ 'resin.device.read', 'resin.device.create-device-api-key', `resin.application.read?is_public eq true and is_host eq true and is_for__device_type/canAccess()`, 'resin.release.read?belongs_to__application/canAccess()', `resin.release_tag.read?release/canAccess()`, - ); + ]); const response = await sbvrUtils.db.transaction(async (tx) => { // TODO: Replace this manual rollback on request closure with a more generic/automated version diff --git a/src/index.ts b/src/index.ts index a176fd517..1f62777e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,7 +92,10 @@ import { import { createScopedAccessToken, createJwt } from './infra/auth/jwt'; import { resolveOrDenyDevicesWithStatus } from './features/device-state/middleware'; import { middleware as authMiddleware } from './infra/auth'; -import { isApiKeyWithRole } from './features/api-keys/lib'; +import { + augmentReqApiKeyPermissions, + isApiKeyWithRole, +} from './features/api-keys/lib'; import { setupDeleteCascade as addDeleteHookForDependents } from './features/cascade-delete/setup-delete-cascade'; import { updateOrInsertModel, @@ -225,6 +228,7 @@ export const utils = { throttledForEach, }; export const apiKeys = { + augmentReqApiKeyPermissions, isApiKeyWithRole, }; export const application = {