diff --git a/package-lock.json b/package-lock.json index 0bc80b660..ccb70dc41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@balena/env-parsing": "^1.2.0", "@balena/es-version": "^1.0.3", "@balena/node-metrics-gatherer": "^6.0.3", - "@balena/pinejs": "^19.0.2", + "@balena/pinejs": "19.1.0-build-compile-auth-f615d178643e42fe5cd03afca19cef8d2867887d-2", "@balena/pinejs-webresource-cloudfront": "^0.2.1", "@sentry/node": "^8.30.0", "@types/basic-auth": "^1.1.8", @@ -1441,10 +1441,9 @@ } }, "node_modules/@balena/pinejs": { - "version": "19.0.2", - "resolved": "https://registry.npmjs.org/@balena/pinejs/-/pinejs-19.0.2.tgz", - "integrity": "sha512-Ismg+VJvzgZm8NEO1BIFazHxL0dG5qSxg9AsavTx7emRt6L/rX1ZgUynz7ExPtFJglnF7nvvkfT/e/oZj4nhaA==", - "license": "Apache-2.0", + "version": "19.1.0-build-compile-auth-f615d178643e42fe5cd03afca19cef8d2867887d-2", + "resolved": "https://registry.npmjs.org/@balena/pinejs/-/pinejs-19.1.0-build-compile-auth-f615d178643e42fe5cd03afca19cef8d2867887d-2.tgz", + "integrity": "sha512-8BJvUt1ZdxyQxOII3Y5qBS18RgbxweIq8GUz+gi6nRqGkdJP1AhrZXA844TDi1cfObx620lr7eAL8lQezvA6/A==", "dependencies": { "@balena/abstract-sql-compiler": "^9.2.0", "@balena/abstract-sql-to-typescript": "^4.0.6", @@ -1481,7 +1480,7 @@ "express-session": "^1.18.0", "lodash": "^4.17.21", "memoizee": "^0.4.17", - "pinejs-client-core": "^8.0.1", + "pinejs-client-core": "8.1.0-build-add-support-to-odata-fns-00fa6b67e2a144706b67db0b0c29b87a01552f27-1", "randomstring": "^1.3.0", "typed-error": "^3.2.2" }, @@ -1534,6 +1533,18 @@ "undici-types": "~6.19.2" } }, + "node_modules/@balena/pinejs/node_modules/pinejs-client-core": { + "version": "8.1.0-build-add-support-to-odata-fns-00fa6b67e2a144706b67db0b0c29b87a01552f27-1", + "resolved": "https://registry.npmjs.org/pinejs-client-core/-/pinejs-client-core-8.1.0-build-add-support-to-odata-fns-00fa6b67e2a144706b67db0b0c29b87a01552f27-1.tgz", + "integrity": "sha512-LQ29vurN4YDh++E9ZZxjioU29jC1jBivYoQxN+NdNtMYKYXCYlIRERxGfS7Ra5CVG3zBVjW7lUEzZJ+xOJOVRQ==", + "dependencies": { + "@balena/abstract-sql-to-typescript": "^4.0.0" + }, + "engines": { + "node": ">=10.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/@balena/sbvr-parser": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@balena/sbvr-parser/-/sbvr-parser-1.4.6.tgz", diff --git a/package.json b/package.json index 74c959b76..0b46dbac9 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@balena/env-parsing": "^1.2.0", "@balena/es-version": "^1.0.3", "@balena/node-metrics-gatherer": "^6.0.3", - "@balena/pinejs": "^19.0.2", + "@balena/pinejs": "19.1.0-build-compile-auth-f615d178643e42fe5cd03afca19cef8d2867887d-2", "@balena/pinejs-webresource-cloudfront": "^0.2.1", "@sentry/node": "^8.30.0", "@types/basic-auth": "^1.1.8", diff --git a/src/lib/auth.ts b/src/lib/auth.ts index d50d39064..df596bfc7 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -2,14 +2,15 @@ // Declares permissions assigned to default roles and API keys // -import type { sbvrUtils } from '@balena/pinejs'; - +import { permissions, sbvrUtils } from '@balena/pinejs'; +import type Model from '../balena-model.js'; import { API_VPN_SERVICE_API_KEY, IGNORE_FROZEN_DEVICE_PERMISSIONS, VPN_GUEST_API_KEY, VPN_SERVICE_API_KEY, } from './config.js'; +const { api } = sbvrUtils; const defaultWritePerms = ['create', 'update', 'delete'] as const; @@ -19,8 +20,30 @@ const writePerms = ( access: ReadonlyArray<(typeof defaultWritePerms)[number]> = defaultWritePerms, ): string[] => access.map((verb) => `${resource}.${verb}?${filter}`); +const { compileAuth: resinAuth } = permissions.getAuthCompiler({ + pinejsClient: api.resin, + modelName: 'resin', +}); + +const matchesActorFilter = { + actor: { + '@': '__ACTOR_ID', + }, +} as const; + +const matchesUserFilter = { + user: { + $any: { + $alias: 'u', + $expr: { + u: matchesActorFilter, + }, + }, + }, +} as const; + const matchesActor = 'actor eq @__ACTOR_ID'; -const matchesUser = `user/any(u:u/${matchesActor})`; +// const matchesUser = `user/any(u:u/${matchesActor})`; const matchesNonFrozenDeviceActor = (alias = '') => { if (alias) { alias += '/'; @@ -40,39 +63,141 @@ export const ROLES: { ], // also default-user (see below) 'named-user-api-key': [ - 'resin.actor.delete?id eq @__ACTOR_ID', - 'resin.api_key.read?is_of__actor eq @__ACTOR_ID', - 'resin.application.all', - 'resin.device_type.read', - 'resin.device_type_alias.read', - 'resin.cpu_architecture.read', - 'resin.application_config_variable.all', - 'resin.application_environment_variable.all', - 'resin.application_tag.all', - 'resin.application_type.all', - 'resin.device.all', - 'resin.device.tunnel-22222', - 'resin.device_config_variable.all', - 'resin.device_environment_variable.all', - 'resin.device_tag.all', - 'resin.device_service_environment_variable.all', - 'resin.image.all', - 'resin.image__is_part_of__release.all', - 'resin.image_environment_variable.all', - 'resin.image_install.all', - 'resin.image_label.all', - 'resin.organization.read', - 'resin.organization_membership.read', - 'resin.release.all', - 'resin.release_tag.all', - 'resin.service.all', - 'resin.service_environment_variable.all', - 'resin.service_install.all', - "resin.service_instance.read?service_type eq 'vpn'", - 'resin.service_label.all', - 'resin.user.read', - `resin.user__has__public_key.all?${matchesUser}`, - 'resin.release_asset.all', + // 'resin.actor.delete?id eq @__ACTOR_ID', + resinAuth({ + resource: 'actor', + access: 'delete', + options: { + $filter: { + id: { + '@': '__ACTOR_ID', + }, + }, + }, + }), + + // 'resin.api_key.read?is_of__actor eq @__ACTOR_ID', + resinAuth({ + resource: 'api_key', + access: 'read', + options: { + $filter: { + is_of__actor: { + '@': '__ACTOR_ID', + }, + }, + }, + }), + + // 'resin.application.all', + resinAuth({ resource: 'application', access: 'all' }), + + // 'resin.device_type.read', + resinAuth({ resource: 'device_type', access: 'read' }), + + // 'resin.device_type_alias.read', + resinAuth({ resource: 'device_type_alias', access: 'read' }), + + // 'resin.cpu_architecture.read', + resinAuth({ resource: 'cpu_architecture', access: 'read' }), + + // 'resin.application_config_variable.all', + resinAuth({ resource: 'application_config_variable', access: 'all' }), + + // 'resin.application_environment_variable.all', + resinAuth({ resource: 'application_environment_variable', access: 'all' }), + + // 'resin.application_tag.all', + resinAuth({ resource: 'application_tag', access: 'all' }), + + // 'resin.application_type.all', + resinAuth({ resource: 'application_type', access: 'all' }), + + // 'resin.device.all', + resinAuth({ resource: 'device', access: 'all' }), + + // 'resin.device.tunnel-22222', + resinAuth({ resource: 'device', access: 'tunnel-22222' }), + + // 'resin.device_config_variable.all', + resinAuth({ resource: 'device_config_variable', access: 'all' }), + + // 'resin.device_environment_variable.all', + resinAuth({ resource: 'device_environment_variable', access: 'all' }), + + // 'resin.device_tag.all', + resinAuth({ resource: 'device_tag', access: 'all' }), + + // 'resin.device_service_environment_variable.all', + resinAuth({ + resource: 'device_service_environment_variable', + access: 'all', + }), + + // 'resin.image.all', + resinAuth({ resource: 'image', access: 'all' }), + + // 'resin.image__is_part_of__release.all', + resinAuth({ resource: 'image__is_part_of__release', access: 'all' }), + + // 'resin.image_environment_variable.all', + resinAuth({ resource: 'image_environment_variable', access: 'all' }), + + // 'resin.image_install.all', + resinAuth({ resource: 'image_install', access: 'all' }), + + // 'resin.image_label.all', + resinAuth({ resource: 'image_label', access: 'all' }), + + // 'resin.organization.read', + resinAuth({ resource: 'organization', access: 'read' }), + + // 'resin.organization_membership.read', + resinAuth({ resource: 'organization_membership', access: 'read' }), + + // 'resin.release.all', + resinAuth({ resource: 'release', access: 'all' }), + + // 'resin.release_tag.all', + resinAuth({ resource: 'release_tag', access: 'all' }), + + // 'resin.service.all', + resinAuth({ resource: 'service', access: 'all' }), + + // 'resin.service_environment_variable.all', + resinAuth({ resource: 'service_environment_variable', access: 'all' }), + + // 'resin.service_install.all', + resinAuth({ resource: 'service_install', access: 'all' }), + + // "resin.service_instance.read?service_type eq 'vpn'", + resinAuth({ + resource: 'service_instance', + access: 'read', + options: { + $filter: { + service_type: 'vpn', + }, + }, + }), + + // 'resin.service_label.all', + resinAuth({ resource: 'service_label', access: 'all' }), + + // 'resin.user.read', + resinAuth({ resource: 'user', access: 'read' }), + + // `resin.user__has__public_key.all?${matchesUser}`, + resinAuth({ + resource: 'user__has__public_key', + access: 'all', + options: { + $filter: matchesUserFilter, + }, + }), + + // 'resin.release_asset.all', + resinAuth({ resource: 'release_asset', access: 'all' }), ], };