From c40a89de4f1becd95d1ea893c41228471a258986 Mon Sep 17 00:00:00 2001 From: Laurin Quast Date: Fri, 25 Oct 2024 12:38:37 +0200 Subject: [PATCH] feat: use session in operation manager, schema manager and schema publisher --- .../tests/api/artifacts-cdn.spec.ts | 2 +- .../tests/api/target/tokens.spec.ts | 2 +- .../tests/api/target/usage.spec.ts | 4 +- .../api/src/modules/auth/lib/authz.ts | 13 +- .../modules/auth/lib/legacy-permissions.ts | 112 ------ .../modules/auth/lib/supertokens-strategy.ts | 102 +++++- .../auth/lib/target-access-token-strategy.ts | 55 ++- .../src/modules/lab/resolvers/Query/lab.ts | 26 +- .../providers/operations-manager.ts | 332 ++++++++++-------- .../schema/providers/schema-manager.ts | 49 +-- .../schema/providers/schema-publisher.ts | 244 ++++++------- .../resolvers/Query/latestValidVersion.ts | 6 +- .../schema/resolvers/Query/latestVersion.ts | 7 +- .../src/modules/schema/resolvers/Target.ts | 12 +- .../target/providers/target-manager.ts | 133 ++++--- 15 files changed, 576 insertions(+), 523 deletions(-) delete mode 100644 packages/services/api/src/modules/auth/lib/legacy-permissions.ts diff --git a/integration-tests/tests/api/artifacts-cdn.spec.ts b/integration-tests/tests/api/artifacts-cdn.spec.ts index 2b000091669..7b045fae6bb 100644 --- a/integration-tests/tests/api/artifacts-cdn.spec.ts +++ b/integration-tests/tests/api/artifacts-cdn.spec.ts @@ -662,7 +662,7 @@ describe('CDN token', () => { expect(deleteResult).toEqual( expect.arrayContaining([ expect.objectContaining({ - message: `No access (reason: "Missing permission for performing 'accessToken:delete' on resource")`, + message: `No access (reason: "Missing permission for performing 'cdnAccessToken:delete' on resource")`, }), ]), ); diff --git a/integration-tests/tests/api/target/tokens.spec.ts b/integration-tests/tests/api/target/tokens.spec.ts index a104f6001d5..5e95e55faaf 100644 --- a/integration-tests/tests/api/target/tokens.spec.ts +++ b/integration-tests/tests/api/target/tokens.spec.ts @@ -57,7 +57,7 @@ test.concurrent( }); await expect(tokenResult).rejects.toThrowError( - 'No access (reason: "Missing permission for performing \'targetAccessToken:modify\' on resource")', + 'No access (reason: "Missing permission for performing \'targetAccessToken:create\' on resource")', ); }, ); diff --git a/integration-tests/tests/api/target/usage.spec.ts b/integration-tests/tests/api/target/usage.spec.ts index 4c3308b3d7f..e411e4ffe59 100644 --- a/integration-tests/tests/api/target/usage.spec.ts +++ b/integration-tests/tests/api/target/usage.spec.ts @@ -2350,7 +2350,7 @@ test.concurrent( test.concurrent( 'subscription operation is used for conditional breaking change detection', async ({ expect }) => { - const { createOrg } = await initSeed().createOwner(); + const { createOrg, ownerToken } = await initSeed().createOwner(); const { organization, createProject } = await createOrg(); const { project, @@ -2500,7 +2500,7 @@ test.concurrent( targetSlug: target.slug, }, }, - authToken: token.secret, + authToken: ownerToken, }).then(r => r.expectNoGraphQLErrors()); const node = firstSchemaCheck.target?.schemaCheck?.breakingSchemaChanges?.nodes[0]; diff --git a/packages/services/api/src/modules/auth/lib/authz.ts b/packages/services/api/src/modules/auth/lib/authz.ts index 7d917de6e55..c4df44611e5 100644 --- a/packages/services/api/src/modules/auth/lib/authz.ts +++ b/packages/services/api/src/modules/auth/lib/authz.ts @@ -1,6 +1,8 @@ +import stringify from 'fast-json-stable-stringify'; import { FastifyReply, FastifyRequest } from '@hive/service-common'; import type { User } from '../../../shared/entities'; import { AccessError } from '../../../shared/errors'; +import { cache } from '../../../shared/helpers'; import { isUUID } from '../../../shared/is-uuid'; export type AuthorizationPolicyStatement = { @@ -70,16 +72,22 @@ export abstract class Session { throw new AccessError('Authorization header is missing'); } + @cache(organizationId => organizationId) + private async _loadPolicyStatementsForOrganization(organizationId: string) { + return await this.loadPolicyStatementsForOrganization(organizationId); + } + /** * Check whether a session is allowed to perform a specific action. * Throws a AccessError if the action is not allowed. */ + @cache(args => stringify(args)) public async assertPerformAction(args: { action: TAction; organizationId: string; params: Parameters<(typeof actionDefinitions)[TAction]>[0]; }): Promise { - const permissions = await this.loadPolicyStatementsForOrganization(args.organizationId); + const permissions = await this._loadPolicyStatementsForOrganization(args.organizationId); const resourceIdsForAction = actionDefinitions[args.action](args.params as any); let isAllowed = false; @@ -255,11 +263,12 @@ const actionDefinitions = { 'project:delete': defaultProjectIdentity, 'alert:describe': defaultProjectIdentity, 'alert:modify': defaultProjectIdentity, - 'project:updateSlug': defaultProjectIdentity, + 'project:modifySlug': defaultProjectIdentity, 'schemaLinting:manageOrganization': defaultProjectIdentity, 'schemaLinting:manageProject': defaultProjectIdentity, 'target:create': defaultProjectIdentity, 'target:delete': defaultTargetIdentity, + 'target:modifySettings': defaultTargetIdentity, 'schema:check': schemaCheckOrPublishIdentity, 'schema:approve': schemaCheckOrPublishIdentity, 'schema:publish': schemaCheckOrPublishIdentity, diff --git a/packages/services/api/src/modules/auth/lib/legacy-permissions.ts b/packages/services/api/src/modules/auth/lib/legacy-permissions.ts deleted file mode 100644 index 28219a48522..00000000000 --- a/packages/services/api/src/modules/auth/lib/legacy-permissions.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - OrganizationAccessScope, - ProjectAccessScope, - TargetAccessScope, -} from '../providers/scopes'; -import type { AuthorizationPolicyStatement } from './authz'; - -/** Transform the legacy access scopes to policy statements */ -export function transformLegacyPolicies( - organizationId: string, - projectId: string, - targetId: string, - scopes: Array, -): Array { - const policies: Array = []; - for (const scope of scopes) { - switch (scope) { - case OrganizationAccessScope.READ: { - policies.push({ - effect: 'allow', - action: ['support:manageTickets'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case OrganizationAccessScope.SETTINGS: { - policies.push({ - effect: 'allow', - action: ['organization:updateSlug'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case OrganizationAccessScope.INTEGRATIONS: { - policies.push({ - effect: 'allow', - action: ['oidc:modify', 'gitHubIntegration:modify', 'slackIntegration:modify'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case ProjectAccessScope.ALERTS: { - policies.push({ - effect: 'allow', - action: ['alert:modify', 'alert:describe'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case TargetAccessScope.REGISTRY_READ: { - policies.push({ - effect: 'allow', - action: ['appDeployment:describe', 'schema:check'], - resource: [`hrn:${organizationId}:target/${targetId}`], - }); - break; - } - case TargetAccessScope.REGISTRY_WRITE: { - policies.push({ - effect: 'allow', - action: [ - 'appDeployment:describe', - 'appDeployment:create', - 'appDeployment:publish', - 'appDeployment:retire', - 'cdnAccessToken:describe', - 'cdnAccessToken:create', - 'schema:publish', - 'schema:deleteService', - 'schema:check', - 'schema:approve', - ], - resource: [`hrn:${organizationId}:target/${targetId}`], - }); - break; - } - case TargetAccessScope.TOKENS_READ: { - policies.push({ - effect: 'allow', - action: ['cdnAccessToken:describe', 'targetAccessToken:describe'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case TargetAccessScope.TOKENS_WRITE: { - policies.push({ - effect: 'allow', - action: [ - 'targetAccessToken:create', - 'targetAccessToken:delete', - 'targetAccessToken:describe', - 'cdnAccessToken:create', - 'cdnAccessToken:delete', - 'cdnAccessToken:describe', - ], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - case TargetAccessScope.SETTINGS: { - policies.push({ - effect: 'allow', - action: ['schemaContract:create', 'schemaContract:disable', 'schemaContract:describe'], - resource: [`hrn:${organizationId}:organization/${organizationId}`], - }); - break; - } - } - } - - return policies; -} diff --git a/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts b/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts index 305d29846f0..0fcdeac96e5 100644 --- a/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts +++ b/packages/services/api/src/modules/auth/lib/supertokens-strategy.ts @@ -6,8 +6,12 @@ import type { User } from '../../../shared/entities'; import { AccessError, HiveError } from '../../../shared/errors'; import { isUUID } from '../../../shared/is-uuid'; import type { Storage } from '../../shared/providers/storage'; +import { + OrganizationAccessScope, + ProjectAccessScope, + TargetAccessScope, +} from '../providers/scopes'; import { AuthNStrategy, AuthorizationPolicyStatement, Session } from './authz'; -import { transformLegacyPolicies } from './legacy-permissions'; export class SuperTokensCookieBasedSession extends Session { public superTokensUserId: string; @@ -29,12 +33,12 @@ export class SuperTokensCookieBasedSession extends Session { } const member = await this.storage.getOrganizationMember({ - organization: organizationId, - user: user.id, + organizationId, + userId: user.id, }); if (Array.isArray(member?.scopes)) { - return transformLegacyPolicies(organizationId, '*', '*', member.scopes); + return transformOrganizationMemberLegacyScopes({ organizationId, scopes: member.scopes }); } return []; @@ -162,3 +166,93 @@ const SuperTokenAccessTokenModel = zod.object({ externalUserId: zod.optional(zod.union([zod.string(), zod.null()])), email: zod.string(), }); + +function transformOrganizationMemberLegacyScopes(args: { + organizationId: string; + scopes: Array; +}) { + const policies: Array = []; + for (const scope of args.scopes) { + switch (scope) { + case OrganizationAccessScope.READ: { + policies.push({ + effect: 'allow', + action: ['support:manageTickets'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case OrganizationAccessScope.SETTINGS: { + policies.push({ + effect: 'allow', + action: ['organization:updateSlug'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case OrganizationAccessScope.INTEGRATIONS: { + policies.push({ + effect: 'allow', + action: ['oidc:modify', 'gitHubIntegration:modify', 'slackIntegration:modify'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case ProjectAccessScope.ALERTS: { + policies.push({ + effect: 'allow', + action: ['alert:modify', 'alert:describe'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case ProjectAccessScope.READ: { + policies.push({ + effect: 'allow', + action: ['project:describe'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case TargetAccessScope.TOKENS_READ: { + policies.push({ + effect: 'allow', + action: ['cdnAccessToken:describe', 'targetAccessToken:describe', 'target:create'], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case TargetAccessScope.TOKENS_WRITE: { + policies.push({ + effect: 'allow', + action: [ + 'targetAccessToken:create', + 'targetAccessToken:delete', + 'targetAccessToken:describe', + 'cdnAccessToken:create', + 'cdnAccessToken:delete', + 'cdnAccessToken:describe', + ], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + case TargetAccessScope.SETTINGS: { + policies.push({ + effect: 'allow', + action: [ + 'schemaContract:create', + 'schemaContract:disable', + 'schemaContract:describe', + 'target:delete', + 'target:modifySettings', + ], + resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`], + }); + break; + } + } + } + + return policies; +} diff --git a/packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts b/packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts index 167678b95dc..4411da1da87 100644 --- a/packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts +++ b/packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts @@ -6,8 +6,7 @@ import { ProjectAccessScope, TargetAccessScope, } from '../providers/scopes'; -import { AuthNStrategy, AuthorizationPolicyStatement, Session } from './authz'; -import { transformLegacyPolicies } from './legacy-permissions'; +import { AuthNStrategy, Session, type AuthorizationPolicyStatement } from './authz'; export class TargetAccessTokenSession extends Session { public readonly organizationId: string; @@ -121,12 +120,13 @@ export class TargetAccessTokenStrategy extends AuthNStrategy, - ), + policies: transformAccessTokenLegacyScopes({ + organizationId: result.organization, + targetId: result.target, + scopes: result.scopes as Array< + OrganizationAccessScope | ProjectAccessScope | TargetAccessScope + >, + }), }); } } @@ -138,3 +138,42 @@ function maskToken(token: string) { return '*'.repeat(token.length); } + +function transformAccessTokenLegacyScopes(args: { + organizationId: string; + targetId: string; + scopes: Array; +}): Array { + const policies: Array = []; + for (const policy of args.scopes) { + switch (policy) { + case TargetAccessScope.REGISTRY_READ: { + policies.push({ + effect: 'allow', + action: ['schema:check'], + resource: [`hrn:${args.organizationId}:target/${args.targetId}`], + }); + break; + } + case TargetAccessScope.REGISTRY_WRITE: { + policies.push({ + effect: 'allow', + action: [ + 'appDeployment:describe', + 'appDeployment:create', + 'appDeployment:publish', + 'appDeployment:retire', + 'schema:publish', + 'schema:deleteService', + 'schema:check', + 'schema:approve', + ], + resource: [`hrn:${args.organizationId}:target/${args.targetId}`], + }); + break; + } + } + } + + return policies; +} diff --git a/packages/services/api/src/modules/lab/resolvers/Query/lab.ts b/packages/services/api/src/modules/lab/resolvers/Query/lab.ts index 7b0a1bd79e3..891e720babe 100644 --- a/packages/services/api/src/modules/lab/resolvers/Query/lab.ts +++ b/packages/services/api/src/modules/lab/resolvers/Query/lab.ts @@ -1,32 +1,34 @@ -import { AuthManager } from '../../../auth/providers/auth-manager'; -import { TargetAccessScope } from '../../../auth/providers/target-access'; +import { Session } from '../../../auth/lib/authz'; import { SchemaManager } from '../../../schema/providers/schema-manager'; import { SchemaVersionHelper } from '../../../schema/providers/schema-version-helper'; import { IdTranslator } from '../../../shared/providers/id-translator'; +import { TargetManager } from '../../../target/providers/target-manager'; import type { QueryResolvers } from './../../../../__generated__/types.next'; export const lab: NonNullable = async (_, { selector }, { injector }) => { const translator = injector.get(IdTranslator); - const [organization, project, target] = await Promise.all([ + const [organization, project, targetId] = await Promise.all([ translator.translateOrganizationId(selector), translator.translateProjectId(selector), translator.translateTargetId(selector), ]); - await injector.get(AuthManager).ensureTargetAccess({ + // @ts-expect-error AbstractType is missing here. + await injector.get(Session).assertPerformAction({ + action: 'laboratory:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + targetId, + }, }); + const target = await injector.get(TargetManager).getTargetById({ targetId }); + const schemaManager = injector.get(SchemaManager); - const latestSchema = await schemaManager.getMaybeLatestValidVersion({ - organizationId: organization, - projectId: project, - targetId: target, - }); + const latestSchema = await schemaManager.getMaybeLatestValidVersion(target); if (!latestSchema) { return null; diff --git a/packages/services/api/src/modules/operations/providers/operations-manager.ts b/packages/services/api/src/modules/operations/providers/operations-manager.ts index 51e8d2e4d3f..b66438541a3 100644 --- a/packages/services/api/src/modules/operations/providers/operations-manager.ts +++ b/packages/services/api/src/modules/operations/providers/operations-manager.ts @@ -4,9 +4,7 @@ import LRU from 'lru-cache'; import type { DateRange } from '../../../shared/entities'; import type { Listify, Optional } from '../../../shared/helpers'; import { cache } from '../../../shared/helpers'; -import { AuthManager } from '../../auth/providers/auth-manager'; -import { OrganizationAccessScope } from '../../auth/providers/organization-access'; -import { TargetAccessScope } from '../../auth/providers/target-access'; +import { Session } from '../../auth/lib/authz'; import { Logger } from '../../shared/providers/logger'; import type { OrganizationSelector, @@ -80,7 +78,7 @@ export class OperationsManager { constructor( logger: Logger, - private authManager: AuthManager, + private session: Session, private reader: OperationsReader, private storage: Storage, ) { @@ -104,33 +102,37 @@ export class OperationsManager { } async getOperation({ - organizationId: organization, - projectId: project, - targetId: target, + organizationId, + projectId, + targetId, hash, }: { hash: string } & TargetSelector) { - await this.authManager.ensureTargetAccess({ - organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId: organizationId, + params: { + organizationId: organizationId, + projectId: projectId, + }, }); return await this.reader.readOperation({ - target, + target: targetId, hash, }); } - async readMonthlyUsage({ organizationId: organization }: OrganizationSelector) { - this.logger.info('Reading monthly usage (organization=%s)', organization); - await this.authManager.ensureOrganizationAccess({ - organizationId: organization, - // Why? Monthly usage is specific to organization settings (subscription page) - scope: OrganizationAccessScope.SETTINGS, + async readMonthlyUsage({ organizationId }: OrganizationSelector) { + this.logger.info('Reading monthly usage (organization=%s)', organizationId); + await this.session.assertPerformAction({ + action: 'billing:describe', + organizationId: organizationId, + params: { + organizationId: organizationId, + }, }); - return this.reader.readMonthlyUsage({ organization }); + return this.reader.readMonthlyUsage({ organization: organizationId }); } async countUniqueOperations({ @@ -146,11 +148,13 @@ export class OperationsManager { clients?: readonly string[]; } & TargetSelector) { this.logger.info('Counting unique operations (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return await this.reader.countUniqueDocuments({ @@ -167,11 +171,13 @@ export class OperationsManager { targetId: target, }: TargetSelector) { this.logger.info('Checking existence of collected operations (target=%s)', target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return hasCollectedOperationsCached(target, () => @@ -187,11 +193,13 @@ export class OperationsManager { targetId: target, }: TargetSelector) { this.logger.info('Checking existence of collected subscription operations (target=%s)', target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.getHasCollectedSubscriptionOperations({ @@ -200,9 +208,9 @@ export class OperationsManager { } async countRequestsWithSchemaCoordinate({ - organizationId: organization, - projectId: project, - targetId: target, + organizationId, + projectId, + targetId, period, schemaCoordinate, }: { @@ -212,19 +220,21 @@ export class OperationsManager { this.logger.info( 'Counting requests with schema coordinate (period=%o, target=%s, coordinate=%s)', period, - target, + targetId, schemaCoordinate, ); - await this.authManager.ensureTargetAccess({ - organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId, + params: { + organizationId, + projectId, + }, }); return this.reader .countRequests({ - target, + target: targetId, period, schemaCoordinate, }) @@ -244,11 +254,13 @@ export class OperationsManager { clients?: readonly string[]; } & Listify) { this.logger.info('Counting requests and failures (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader @@ -270,11 +282,13 @@ export class OperationsManager { period: DateRange; } & Listify): Promise { this.logger.info('Counting requests (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.countOperationsWithoutDetails({ @@ -316,11 +330,13 @@ export class OperationsManager { clients?: readonly string[]; } & TargetSelector) { this.logger.info('Counting failures (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.countFailures({ @@ -346,11 +362,13 @@ export class OperationsManager { targetId: target, } = input; this.logger.info('Counting a field (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); const [totalField, total] = await Promise.all([ @@ -397,11 +415,13 @@ export class OperationsManager { excludedClients?.join(', ') ?? 'none', ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.readFieldListStats({ @@ -427,11 +447,13 @@ export class OperationsManager { schemaCoordinate?: string; } & TargetSelector) { this.logger.info('Reading operations stats (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); // Maybe it needs less data @@ -468,16 +490,15 @@ export class OperationsManager { organizationId: organization, projectId: project, }); - await Promise.all( - targets.map(target => - this.authManager.ensureTargetAccess({ - organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, - }), - ), - ); + + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId: organization, + params: { + organizationId: organization, + projectId: project, + }, + }); const groups = await this.requestsOverTimeOfTargetsLoader.load({ targets, @@ -529,16 +550,15 @@ export class OperationsManager { resolution, targets.join(';'), ); - await Promise.all( - targets.map(target => - this.authManager.ensureTargetAccess({ - organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, - }), - ), - ); + + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId: organization, + params: { + organizationId: organization, + projectId: project, + }, + }); return this.requestsOverTimeOfTargetsLoader.load({ targets, @@ -569,11 +589,13 @@ export class OperationsManager { resolution, target, ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.requestsOverTime({ @@ -606,11 +628,13 @@ export class OperationsManager { resolution, target, ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.failuresOverTime({ @@ -642,11 +666,13 @@ export class OperationsManager { resolution, target, ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.durationOverTime({ @@ -671,11 +697,13 @@ export class OperationsManager { clients?: readonly string[]; } & TargetSelector) { this.logger.info('Reading overall duration percentiles (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.generalDurationPercentiles({ @@ -707,11 +735,13 @@ export class OperationsManager { target, clients, ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.durationPercentiles({ @@ -738,11 +768,13 @@ export class OperationsManager { schemaCoordinate?: string; } & TargetSelector) { this.logger.info('Counting unique clients (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.countUniqueClients({ @@ -762,11 +794,13 @@ export class OperationsManager { operations, }: { period: DateRange; operations?: readonly string[] } & Listify) { this.logger.info('Read unique client names (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.readUniqueClientNames({ @@ -789,11 +823,13 @@ export class OperationsManager { limit: number; } & TargetSelector) { this.logger.info('Read client versions (period=%o, target=%s)', period, target); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.readClientVersions({ @@ -820,11 +856,13 @@ export class OperationsManager { target, clientName, ); - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); return this.reader.countClientVersions({ @@ -874,11 +912,13 @@ export class OperationsManager { typename: string; } & TargetSelector, ) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: args.organizationId, - projectId: args.projectId, - targetId: args.targetId, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: args.organizationId, + projectId: args.projectId, + }, }); const loader = this.getClientNamesPerCoordinateOfTypeLoader({ @@ -953,11 +993,13 @@ export class OperationsManager { limit: number; coordinate: string; }) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: args.organizationId, - projectId: args.projectId, - targetId: args.targetId, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: args.organizationId, + projectId: args.projectId, + }, }); const loader = this.getTopOperationForTypeLoader({ @@ -975,11 +1017,13 @@ export class OperationsManager { organizationId: string; period: DateRange; }): Promise> { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: args.organizationId, - projectId: args.projectId, - targetId: args.targetId, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: args.organizationId, + projectId: args.projectId, + }, }); return this.reader.getReportedSchemaCoordinates({ @@ -1031,11 +1075,13 @@ export class OperationsManager { period: DateRange; typename: string; } & TargetSelector) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); const rows = await this.reader.countCoordinatesOfType({ @@ -1077,11 +1123,13 @@ export class OperationsManager { }: { period: DateRange; } & TargetSelector) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'project:describe', organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.REGISTRY_READ, + params: { + organizationId: organization, + projectId: project, + }, }); const rows = await this.reader.countCoordinatesOfTarget({ diff --git a/packages/services/api/src/modules/schema/providers/schema-manager.ts b/packages/services/api/src/modules/schema/providers/schema-manager.ts index 0db420d6305..9b65e0f847d 100644 --- a/packages/services/api/src/modules/schema/providers/schema-manager.ts +++ b/packages/services/api/src/modules/schema/providers/schema-manager.ts @@ -18,6 +18,7 @@ import { Organization, Project, ProjectType, + Target, } from '../../../shared/entities'; import { HiveError } from '../../../shared/errors'; import { atomic, cache, stringifySelector } from '../../../shared/helpers'; @@ -257,24 +258,21 @@ export class SchemaManager { return this.storage.getMatchingServiceSchemaOfVersions(versions); } - async getMaybeLatestValidVersion(selector: TargetSelector) { - this.logger.debug('Fetching maybe latest valid version (selector=%o)', selector); - await this.authManager.ensureTargetAccess({ - ...selector, - scope: TargetAccessScope.REGISTRY_READ, + async getMaybeLatestValidVersion(target: Target) { + this.logger.debug('Fetching maybe latest valid version (targetId=%o)', target.id); + const version = await this.storage.getMaybeLatestValidVersion({ + targetId: target.id, }); - const version = await this.storage.getMaybeLatestValidVersion(selector); - if (!version) { return null; } return { ...version, - projectId: selector.projectId, - targetId: selector.targetId, - organizationId: selector.organizationId, + projectId: target.projectId, + targetId: target.id, + organizationId: target.orgId, }; } @@ -306,33 +304,28 @@ export class SchemaManager { }; } - async getMaybeLatestVersion(selector: TargetSelector) { - this.logger.debug('Fetching maybe latest version (selector=%o)', selector); - await this.authManager.ensureTargetAccess({ - ...selector, - scope: TargetAccessScope.REGISTRY_READ, + async getMaybeLatestVersion(target: Target) { + this.logger.debug('Fetching maybe latest version (targetId=%o)', target.id); + const latest = await this.storage.getMaybeLatestVersion({ + targetId: target.id, + projectId: target.projectId, + organizationId: target.orgId, }); - const latest = await this.storage.getMaybeLatestVersion(selector); - if (!latest) { return null; } return { ...latest, - projectId: selector.projectId, - targetId: selector.targetId, - organizationId: selector.organizationId, + projectId: target.projectId, + targetId: target.id, + organizationId: target.orgId, }; } async getSchemaVersion(selector: TargetSelector & { versionId: string }) { this.logger.debug('Fetching single schema version (selector=%o)', selector); - await this.authManager.ensureTargetAccess({ - ...selector, - scope: TargetAccessScope.REGISTRY_READ, - }); const result = await this.storage.getVersion(selector); return { @@ -1179,13 +1172,7 @@ export class SchemaManager { }); const possibleVersions = await Promise.all( - targets.map(t => - this.getMaybeLatestValidVersion({ - organizationId: project.orgId, - projectId: project.id, - targetId: t.id, - }), - ), + targets.map(target => this.getMaybeLatestValidVersion(target)), ); const versions = possibleVersions.filter((v): v is SchemaVersion => !!v); diff --git a/packages/services/api/src/modules/schema/providers/schema-publisher.ts b/packages/services/api/src/modules/schema/providers/schema-publisher.ts index 99e6a188ed5..5186f4a299d 100644 --- a/packages/services/api/src/modules/schema/providers/schema-publisher.ts +++ b/packages/services/api/src/modules/schema/providers/schema-publisher.ts @@ -298,48 +298,36 @@ export class SchemaPublisher { }, }); - const [ - target, - project, - organization, - latestVersion, - latestComposableVersion, - latestSchemaVersion, - latestComposableSchemaVersion, - ] = await Promise.all([ - this.targetManager.getTarget({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), - this.projectManager.getProject({ - organizationId: input.organizationId, - projectId: input.projectId, - }), - this.organizationManager.getOrganization({ - organizationId: input.organizationId, - }), - this.schemaManager.getLatestSchemas({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), - this.schemaManager.getLatestSchemas({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - onlyComposable: true, - }), - this.schemaManager.getMaybeLatestVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), - this.schemaManager.getMaybeLatestValidVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), + const [target, project, organization, latestVersion, latestComposableVersion] = + await Promise.all([ + this.storage.getTarget({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }), + this.storage.getProject({ + organizationId: input.organizationId, + projectId: input.projectId, + }), + this.storage.getOrganization({ + organizationId: input.organizationId, + }), + this.storage.getLatestSchemas({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }), + this.storage.getLatestSchemas({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + onlyComposable: true, + }), + ]); + + const [latestSchemaVersion, latestComposableSchemaVersion] = await Promise.all([ + this.schemaManager.getMaybeLatestVersion(target), + this.schemaManager.getMaybeLatestValidVersion(target), ]); const projectModelVersion = project.legacyRegistryModel ? 'legacy' : 'modern'; @@ -1024,13 +1012,15 @@ export class SchemaPublisher { const selector = this.session.getLegacySelector(); + const target = await this.storage.getTarget({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }); + const [contracts, latestVersion] = await Promise.all([ this.contracts.getActiveContractsByTargetId({ targetId: input.targetId }), - this.schemaManager.getMaybeLatestVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), + this.schemaManager.getMaybeLatestVersion(target), ]); const checksum = createHash('md5') @@ -1208,48 +1198,41 @@ export class SchemaPublisher { }, }); - const [ - project, - organization, - latestVersion, - latestComposableVersion, - baseSchema, - latestSchemaVersion, - latestComposableSchemaVersion, - ] = await Promise.all([ - this.projectManager.getProject({ - organizationId: input.organizationId, - projectId: input.projectId, - }), - this.organizationManager.getOrganization({ - organizationId: input.organizationId, - }), - this.schemaManager.getLatestSchemas({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.target.id, - }), - this.schemaManager.getLatestSchemas({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.target.id, - onlyComposable: true, - }), - this.schemaManager.getBaseSchema({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.target.id, - }), - this.schemaManager.getMaybeLatestVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.target.id, - }), - this.schemaManager.getMaybeLatestValidVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.target.id, - }), + const [organization, project, target, latestVersion, latestComposableVersion, baseSchema] = + await Promise.all([ + this.storage.getOrganization({ + organizationId: input.organizationId, + }), + this.storage.getProject({ + organizationId: input.organizationId, + projectId: input.projectId, + }), + this.storage.getTarget({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.projectId, + }), + this.schemaManager.getLatestSchemas({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.target.id, + }), + this.schemaManager.getLatestSchemas({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.target.id, + onlyComposable: true, + }), + this.schemaManager.getBaseSchema({ + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.target.id, + }), + ]); + + const [latestSchemaVersion, latestComposableSchemaVersion] = await Promise.all([ + this.schemaManager.getMaybeLatestVersion(target), + this.schemaManager.getMaybeLatestValidVersion(target), ]); const compareToPreviousComposableVersion = shouldUseLatestComposableVersion( @@ -1511,54 +1494,41 @@ export class SchemaPublisher { metadata: !!input.metadata, }); - const [ - organization, - project, - target, - latestVersion, - latestComposable, - baseSchema, - latestSchemaVersion, - latestComposableSchemaVersion, - ] = await Promise.all([ - this.organizationManager.getOrganization({ - organizationId: organizationId, - }), - this.projectManager.getProject({ - organizationId: organizationId, - projectId: projectId, - }), - this.targetManager.getTarget({ - organizationId: organizationId, - projectId: projectId, - targetId: targetId, - }), - this.schemaManager.getLatestSchemas({ - organizationId: organizationId, - projectId: projectId, - targetId: targetId, - }), - this.schemaManager.getLatestSchemas({ - organizationId: organizationId, - projectId: projectId, - targetId: targetId, - onlyComposable: true, - }), - this.schemaManager.getBaseSchema({ - organizationId: organizationId, - projectId: projectId, - targetId: targetId, - }), - this.schemaManager.getMaybeLatestVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), - this.schemaManager.getMaybeLatestValidVersion({ - organizationId: input.organizationId, - projectId: input.projectId, - targetId: input.targetId, - }), + const [organization, project, target, latestVersion, latestComposable, baseSchema] = + await Promise.all([ + this.storage.getOrganization({ + organizationId: organizationId, + }), + this.storage.getProject({ + organizationId: organizationId, + projectId: projectId, + }), + this.storage.getTarget({ + organizationId: organizationId, + projectId: projectId, + targetId: targetId, + }), + this.storage.getLatestSchemas({ + organizationId: organizationId, + projectId: projectId, + targetId: targetId, + }), + this.storage.getLatestSchemas({ + organizationId: organizationId, + projectId: projectId, + targetId: targetId, + onlyComposable: true, + }), + this.storage.getBaseSchema({ + organizationId: organizationId, + projectId: projectId, + targetId: targetId, + }), + ]); + + const [latestSchemaVersion, latestComposableSchemaVersion] = await Promise.all([ + this.schemaManager.getMaybeLatestVersion(target), + this.schemaManager.getMaybeLatestValidVersion(target), ]); const modelVersion = project.legacyRegistryModel ? 'legacy' : 'modern'; diff --git a/packages/services/api/src/modules/schema/resolvers/Query/latestValidVersion.ts b/packages/services/api/src/modules/schema/resolvers/Query/latestValidVersion.ts index 0654604e8d1..dc62eae3046 100644 --- a/packages/services/api/src/modules/schema/resolvers/Query/latestValidVersion.ts +++ b/packages/services/api/src/modules/schema/resolvers/Query/latestValidVersion.ts @@ -9,9 +9,5 @@ export const latestValidVersion: NonNullable { const target = await injector.get(TargetManager).getTargetFromToken(); - return injector.get(SchemaManager).getMaybeLatestValidVersion({ - organizationId: target.orgId, - projectId: target.projectId, - targetId: target.id, - }); + return injector.get(SchemaManager).getMaybeLatestValidVersion(target); }; diff --git a/packages/services/api/src/modules/schema/resolvers/Query/latestVersion.ts b/packages/services/api/src/modules/schema/resolvers/Query/latestVersion.ts index e4ffd830255..8b534e925eb 100644 --- a/packages/services/api/src/modules/schema/resolvers/Query/latestVersion.ts +++ b/packages/services/api/src/modules/schema/resolvers/Query/latestVersion.ts @@ -8,10 +8,5 @@ export const latestVersion: NonNullable = async { injector }, ) => { const target = await injector.get(TargetManager).getTargetFromToken(); - - return injector.get(SchemaManager).getMaybeLatestVersion({ - organizationId: target.orgId, - projectId: target.projectId, - targetId: target.id, - }); + return injector.get(SchemaManager).getMaybeLatestVersion(target); }; diff --git a/packages/services/api/src/modules/schema/resolvers/Target.ts b/packages/services/api/src/modules/schema/resolvers/Target.ts index 65ee0217613..ca9c80edede 100644 --- a/packages/services/api/src/modules/schema/resolvers/Target.ts +++ b/packages/services/api/src/modules/schema/resolvers/Target.ts @@ -50,18 +50,10 @@ export const Target: Pick< }; }, latestSchemaVersion: (target, _, { injector }) => { - return injector.get(SchemaManager).getMaybeLatestVersion({ - targetId: target.id, - projectId: target.projectId, - organizationId: target.orgId, - }); + return injector.get(SchemaManager).getMaybeLatestVersion(target); }, latestValidSchemaVersion: async (target, __, { injector }) => { - return injector.get(SchemaManager).getMaybeLatestValidVersion({ - organizationId: target.orgId, - projectId: target.projectId, - targetId: target.id, - }); + return injector.get(SchemaManager).getMaybeLatestValidVersion(target); }, baseSchema: (target, _, { injector }) => { return injector.get(SchemaManager).getBaseSchema({ diff --git a/packages/services/api/src/modules/target/providers/target-manager.ts b/packages/services/api/src/modules/target/providers/target-manager.ts index e348631d984..edfa3037d41 100644 --- a/packages/services/api/src/modules/target/providers/target-manager.ts +++ b/packages/services/api/src/modules/target/providers/target-manager.ts @@ -2,9 +2,7 @@ import { Injectable, Scope } from 'graphql-modules'; import * as zod from 'zod'; import type { Target, TargetSettings } from '../../../shared/entities'; import { share } from '../../../shared/helpers'; -import { AuthManager } from '../../auth/providers/auth-manager'; -import { ProjectAccessScope } from '../../auth/providers/project-access'; -import { TargetAccessScope } from '../../auth/providers/target-access'; +import { Session } from '../../auth/lib/authz'; import { ActivityManager } from '../../shared/providers/activity-manager'; import { IdTranslator } from '../../shared/providers/id-translator'; import { Logger } from '../../shared/providers/logger'; @@ -29,7 +27,7 @@ export class TargetManager { logger: Logger, private storage: Storage, private tokenStorage: TokenStorage, - private authManager: AuthManager, + private session: Session, private activityManager: ActivityManager, private idTranslator: IdTranslator, ) { @@ -58,10 +56,13 @@ export class TargetManager { project, organization, ); - await this.authManager.ensureProjectAccess({ - projectId: project, + await this.session.assertPerformAction({ + action: 'target:create', organizationId: organization, - scope: ProjectAccessScope.READ, + params: { + organizationId: organization, + projectId: project, + }, }); if (reservedSlugs.includes(slug)) { @@ -103,11 +104,14 @@ export class TargetManager { project, organization, ); - await this.authManager.ensureTargetAccess({ - projectId: project, + await this.session.assertPerformAction({ + action: 'target:delete', organizationId: organization, - targetId: target, - scope: TargetAccessScope.DELETE, + params: { + organizationId: organization, + projectId: project, + targetId: target, + }, }); const deletedTarget = await this.storage.deleteTarget({ @@ -134,40 +138,42 @@ export class TargetManager { async getTargets(selector: ProjectSelector): Promise { this.logger.debug('Fetching targets (selector=%o)', selector); - await this.authManager.ensureProjectAccess({ - ...selector, - scope: ProjectAccessScope.READ, + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId: selector.organizationId, + params: { + organizationId: selector.organizationId, + projectId: selector.projectId, + }, }); + return this.storage.getTargets(selector); } - async getTarget(selector: TargetSelector, scope = TargetAccessScope.READ): Promise { + async getTarget(selector: TargetSelector): Promise { this.logger.debug('Fetching target (selector=%o)', selector); - await this.authManager.ensureTargetAccess({ - ...selector, - scope, + await this.session.assertPerformAction({ + action: 'project:describe', + organizationId: selector.organizationId, + params: { + organizationId: selector.organizationId, + projectId: selector.projectId, + }, }); return this.storage.getTarget(selector); } getTargetIdByToken: () => Promise = share(async () => { - const token = this.authManager.ensureApiToken(); - const { target } = await this.tokenStorage.getToken({ token }); + const selector = this.session.getLegacySelector(); + const { target } = await this.tokenStorage.getToken({ token: selector.token }); return target; }); getTargetFromToken: () => Promise = share(async () => { - const token = this.authManager.ensureApiToken(); + const selector = this.session.getLegacySelector(); const { target, project, organization } = await this.tokenStorage.getToken({ - token, - }); - - await this.authManager.ensureTargetAccess({ - organizationId: organization, - projectId: project, - targetId: target, - scope: TargetAccessScope.READ, + token: selector.token, }); return this.storage.getTarget({ @@ -179,9 +185,14 @@ export class TargetManager { async getTargetSettings(selector: TargetSelector): Promise { this.logger.debug('Fetching target settings (selector=%o)', selector); - await this.authManager.ensureTargetAccess({ - ...selector, - scope: TargetAccessScope.SETTINGS, + await this.session.assertPerformAction({ + action: 'target:modifySettings', + organizationId: selector.organizationId, + params: { + organizationId: selector.organizationId, + projectId: selector.projectId, + targetId: selector.targetId, + }, }); return this.storage.getTargetSettings(selector); @@ -193,9 +204,14 @@ export class TargetManager { } & TargetSelector, ): Promise { this.logger.debug('Setting target validation (input=%o)', input); - await this.authManager.ensureTargetAccess({ - ...input, - scope: TargetAccessScope.SETTINGS, + await this.session.assertPerformAction({ + action: 'target:modifySettings', + organizationId: input.organizationId, + params: { + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }, }); await this.storage.completeGetStartedStep({ @@ -210,9 +226,14 @@ export class TargetManager { input: Omit & TargetSelector, ): Promise { this.logger.debug('Updating target validation settings (input=%o)', input); - await this.authManager.ensureTargetAccess({ - ...input, - scope: TargetAccessScope.SETTINGS, + await this.session.assertPerformAction({ + action: 'target:modifySettings', + organizationId: input.organizationId, + params: { + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }, }); if (input.targets.length === 0) { @@ -238,11 +259,17 @@ export class TargetManager { > { const { slug, organizationId: organization, projectId: project, targetId: target } = input; this.logger.info('Updating a target slug (input=%o)', input); - await this.authManager.ensureTargetAccess({ - ...input, - scope: TargetAccessScope.SETTINGS, + await this.session.assertPerformAction({ + action: 'target:modifySettings', + organizationId: input.organizationId, + params: { + organizationId: input.organizationId, + projectId: input.projectId, + targetId: input.targetId, + }, }); - const user = await this.authManager.getCurrentUser(); + + const user = await this.session.getViewer(); if (reservedSlugs.includes(slug)) { return { @@ -282,11 +309,14 @@ export class TargetManager { targetId: string; graphqlEndpointUrl: string | null; }) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'target:modifySettings', organizationId: args.organizationId, - projectId: args.projectId, - targetId: args.targetId, - scope: TargetAccessScope.SETTINGS, + params: { + organizationId: args.organizationId, + projectId: args.projectId, + targetId: args.targetId, + }, }); const graphqlEndpointUrl = TargetGraphQLEndpointUrlModel.safeParse(args.graphqlEndpointUrl); @@ -347,11 +377,14 @@ export class TargetManager { targetId: string; nativeComposition: boolean; }) { - await this.authManager.ensureTargetAccess({ + await this.session.assertPerformAction({ + action: 'target:modifySettings', organizationId: args.organizationId, - projectId: args.projectId, - targetId: args.targetId, - scope: TargetAccessScope.SETTINGS, + params: { + organizationId: args.organizationId, + projectId: args.projectId, + targetId: args.targetId, + }, }); this.logger.info(