Skip to content

Commit

Permalink
feat: use session in operation manager, schema manager and schema pub…
Browse files Browse the repository at this point in the history
…lisher
  • Loading branch information
n1ru4l committed Oct 25, 2024
1 parent c01a742 commit 0443888
Show file tree
Hide file tree
Showing 15 changed files with 600 additions and 523 deletions.
2 changes: 1 addition & 1 deletion integration-tests/tests/api/artifacts-cdn.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")`,
}),
]),
);
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/tests/api/target/tokens.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")',
);
},
);
4 changes: 2 additions & 2 deletions integration-tests/tests/api/target/usage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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];
Expand Down
13 changes: 11 additions & 2 deletions packages/services/api/src/modules/auth/lib/authz.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand Down Expand Up @@ -70,16 +72,22 @@ export abstract class Session {
throw new AccessError('Authorization header is missing');
}

@cache<string>(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<object>(args => stringify(args))
public async assertPerformAction<TAction extends keyof typeof actionDefinitions>(args: {
action: TAction;
organizationId: string;
params: Parameters<(typeof actionDefinitions)[TAction]>[0];
}): Promise<void> {
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;
Expand Down Expand Up @@ -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,
Expand Down
112 changes: 0 additions & 112 deletions packages/services/api/src/modules/auth/lib/legacy-permissions.ts

This file was deleted.

126 changes: 122 additions & 4 deletions packages/services/api/src/modules/auth/lib/supertokens-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 [];
Expand Down Expand Up @@ -162,3 +166,117 @@ const SuperTokenAccessTokenModel = zod.object({
externalUserId: zod.optional(zod.union([zod.string(), zod.null()])),
email: zod.string(),
});

function transformOrganizationMemberLegacyScopes(args: {
organizationId: string;
scopes: Array<OrganizationAccessScope | ProjectAccessScope | TargetAccessScope>;
}) {
const policies: Array<AuthorizationPolicyStatement> = [];
for (const scope of args.scopes) {
switch (scope) {
case OrganizationAccessScope.READ: {
policies.push({
effect: 'allow',
action: ['support:manageTickets', 'project:create', 'project:describe'],
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 ProjectAccessScope.DELETE: {
policies.push({
effect: 'allow',
action: ['project:delete'],
resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`],
});
break;
}
case ProjectAccessScope.SETTINGS: {
policies.push({
effect: 'allow',
action: ['project:modifySlug', 'project:delete'],
resource: [`hrn:${args.organizationId}:organization/${args.organizationId}`],
});
break;
}
case TargetAccessScope.READ: {
policies.push({
effect: 'allow',
action: ['project:modifySlug', 'project:delete', 'appDeployment: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;
}
Loading

0 comments on commit 0443888

Please sign in to comment.