feat(gatekeeper): feature access resolver (#3514)

This commit is contained in:
Gergő Jedlicska 2024-11-19 15:44:43 +01:00 коммит произвёл GitHub
Родитель c40693e22e
Коммит 381c4e2a85
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 64 добавлений и 0 удалений

Просмотреть файл

@ -84,4 +84,11 @@ extend type Workspace {
subscription: WorkspaceSubscription @hasScope(scope: "workspace:billing")
# this can only be created if there is an active subscription
customerPortalUrl: String @hasScope(scope: "workspace:billing")
hasAccessToFeature(featureName: WorkspaceFeatureName!): Boolean!
}
enum WorkspaceFeatureName {
domainBasedSecurityPolicies
oidcSso
workspaceDataRegionSpecificity
}

Просмотреть файл

@ -4049,6 +4049,7 @@ export type Workspace = {
domainBasedMembershipProtectionEnabled: Scalars['Boolean']['output'];
/** Verified workspace domains */
domains?: Maybe<Array<WorkspaceDomain>>;
hasAccessToFeature: Scalars['Boolean']['output'];
id: Scalars['ID']['output'];
/** Only available to workspace owners/members */
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
@ -4068,6 +4069,11 @@ export type Workspace = {
};
export type WorkspaceHasAccessToFeatureArgs = {
featureName: WorkspaceFeatureName;
};
export type WorkspaceInvitedTeamArgs = {
filter?: InputMaybe<PendingWorkspaceCollaboratorsFilter>;
};
@ -4178,6 +4184,13 @@ export type WorkspaceDomainDeleteInput = {
workspaceId: Scalars['ID']['input'];
};
export enum WorkspaceFeatureName {
DomainBasedSecurityPolicies = 'domainBasedSecurityPolicies',
OidcSso = 'oidcSso',
Workspace = 'workspace',
WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity'
}
export type WorkspaceInviteCreateInput = {
/** Either this or userId must be filled */
email?: InputMaybe<Scalars['String']['input']>;
@ -4790,6 +4803,7 @@ export type ResolversTypes = {
WorkspaceCreateInput: WorkspaceCreateInput;
WorkspaceDomain: ResolverTypeWrapper<WorkspaceDomain>;
WorkspaceDomainDeleteInput: WorkspaceDomainDeleteInput;
WorkspaceFeatureName: WorkspaceFeatureName;
WorkspaceInviteCreateInput: WorkspaceInviteCreateInput;
WorkspaceInviteLookupOptions: WorkspaceInviteLookupOptions;
WorkspaceInviteMutations: ResolverTypeWrapper<WorkspaceInviteMutationsGraphQLReturn>;
@ -6463,6 +6477,7 @@ export type WorkspaceResolvers<ContextType = GraphQLContext, ParentType extends
discoverabilityEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
domainBasedMembershipProtectionEnabled?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
domains?: Resolver<Maybe<Array<ResolversTypes['WorkspaceDomain']>>, ParentType, ContextType>;
hasAccessToFeature?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<WorkspaceHasAccessToFeatureArgs, 'featureName'>>;
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
invitedTeam?: Resolver<Maybe<Array<ResolversTypes['PendingWorkspaceCollaborator']>>, ParentType, ContextType, Partial<WorkspaceInvitedTeamArgs>>;
logo?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;

Просмотреть файл

@ -4030,6 +4030,7 @@ export type Workspace = {
domainBasedMembershipProtectionEnabled: Scalars['Boolean']['output'];
/** Verified workspace domains */
domains?: Maybe<Array<WorkspaceDomain>>;
hasAccessToFeature: Scalars['Boolean']['output'];
id: Scalars['ID']['output'];
/** Only available to workspace owners/members */
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
@ -4049,6 +4050,11 @@ export type Workspace = {
};
export type WorkspaceHasAccessToFeatureArgs = {
featureName: WorkspaceFeatureName;
};
export type WorkspaceInvitedTeamArgs = {
filter?: InputMaybe<PendingWorkspaceCollaboratorsFilter>;
};
@ -4159,6 +4165,13 @@ export type WorkspaceDomainDeleteInput = {
workspaceId: Scalars['ID']['input'];
};
export enum WorkspaceFeatureName {
DomainBasedSecurityPolicies = 'domainBasedSecurityPolicies',
OidcSso = 'oidcSso',
Workspace = 'workspace',
WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity'
}
export type WorkspaceInviteCreateInput = {
/** Either this or userId must be filled */
email?: InputMaybe<Scalars['String']['input']>;

Просмотреть файл

@ -22,6 +22,7 @@ import {
getWorkspaceSubscriptionFactory,
saveCheckoutSessionFactory
} from '@/modules/gatekeeper/repositories/billing'
import { canWorkspaceAccessFeatureFactory } from '@/modules/gatekeeper/services/featureAuthorization'
const { FF_GATEKEEPER_MODULE_ENABLED } = getFeatureFlags()
@ -69,6 +70,21 @@ export = FF_GATEKEEPER_MODULE_ENABLED
workspaceSlug: workspace.slug,
customerId: workspaceSubscription.subscriptionData.customerId
})
},
hasAccessToFeature: async (parent, args, ctx) => {
await authorizeResolver(
ctx.userId,
parent.id,
Roles.Workspace.Member,
ctx.resourceAccessRules
)
const hasAccess = await canWorkspaceAccessFeatureFactory({
getWorkspacePlan: getWorkspacePlanFactory({ db })
})({
workspaceId: parent.id,
workspaceFeature: args.featureName
})
return hasAccess
}
},
WorkspaceMutations: () => ({}),

Просмотреть файл

@ -4031,6 +4031,7 @@ export type Workspace = {
domainBasedMembershipProtectionEnabled: Scalars['Boolean']['output'];
/** Verified workspace domains */
domains?: Maybe<Array<WorkspaceDomain>>;
hasAccessToFeature: Scalars['Boolean']['output'];
id: Scalars['ID']['output'];
/** Only available to workspace owners/members */
invitedTeam?: Maybe<Array<PendingWorkspaceCollaborator>>;
@ -4050,6 +4051,11 @@ export type Workspace = {
};
export type WorkspaceHasAccessToFeatureArgs = {
featureName: WorkspaceFeatureName;
};
export type WorkspaceInvitedTeamArgs = {
filter?: InputMaybe<PendingWorkspaceCollaboratorsFilter>;
};
@ -4160,6 +4166,13 @@ export type WorkspaceDomainDeleteInput = {
workspaceId: Scalars['ID']['input'];
};
export enum WorkspaceFeatureName {
DomainBasedSecurityPolicies = 'domainBasedSecurityPolicies',
OidcSso = 'oidcSso',
Workspace = 'workspace',
WorkspaceDataRegionSpecificity = 'workspaceDataRegionSpecificity'
}
export type WorkspaceInviteCreateInput = {
/** Either this or userId must be filled */
email?: InputMaybe<Scalars['String']['input']>;