diff --git a/packages/server/graphql/public/permissions.ts b/packages/server/graphql/public/permissions.ts index edf2d4ccce6..c74d9bc023c 100644 --- a/packages/server/graphql/public/permissions.ts +++ b/packages/server/graphql/public/permissions.ts @@ -51,10 +51,16 @@ const permissionMap: PermissionMap = { verifyEmail: rateLimit({perMinute: 50, perHour: 100}), addApprovedOrganizationDomains: or( isSuperUser, - and(isViewerBillingLeader('args.orgId'), isOrgTier('args.orgId', 'enterprise')) + and( + isViewerBillingLeader<'Mutation.addApprovedOrganizationDomains'>('args.orgId'), + isOrgTier<'Mutation.addApprovedOrganizationDomains'>('args.orgId', 'enterprise') + ) ), - removeApprovedOrganizationDomains: or(isSuperUser, isViewerBillingLeader('args.orgId')), - uploadIdPMetadata: isViewerOnOrg('args.orgId'), + removeApprovedOrganizationDomains: or( + isSuperUser, + isViewerBillingLeader<'Mutation.removeApprovedOrganizationDomains'>('args.orgId') + ), + uploadIdPMetadata: isViewerOnOrg<'Mutation.uploadIdPMetadata'>('args.orgId'), updateTemplateCategory: isViewerOnTeam(getTeamIdFromArgTemplateId) }, Query: { @@ -63,7 +69,10 @@ const permissionMap: PermissionMap = { SAMLIdP: rateLimit({perMinute: 120, perHour: 3600}) }, Organization: { - saml: and(isViewerBillingLeader('source.id'), isOrgTier('source.id', 'enterprise')) + saml: and( + isViewerBillingLeader<'Organization.saml'>('source.id'), + isOrgTier<'Organization.saml'>('source.id', 'enterprise') + ) }, User: { domains: or(isSuperUser, isUserViewer) diff --git a/packages/server/graphql/public/rules/getResolverDotPath.ts b/packages/server/graphql/public/rules/getResolverDotPath.ts index cac815882a0..05e9098ff08 100644 --- a/packages/server/graphql/public/rules/getResolverDotPath.ts +++ b/packages/server/graphql/public/rules/getResolverDotPath.ts @@ -1,9 +1,39 @@ +import {FirstParam} from '../../../../client/types/generics' +import {Resolvers} from '../resolverTypes' + export const getResolverDotPath = ( - dotPath: ResolverDotPath, + dotPath: `${'source' | 'args'}.${string}`, source: Record, args: Record ) => { return dotPath.split('.').reduce((val: any, key) => val?.[key], {source, args}) } -export type ResolverDotPath = `source.${string}` | `args.${string}` +type SecondParam = T extends (arg1: any, arg2: infer A, ...args: any[]) => any ? A : never + +type ParseParent = T extends `${infer Parent extends string}.${string}` ? Parent : never +type ParseChild = T extends `${string}.${infer Child extends string}` ? Child : never + +type ExtractTypeof = '__isTypeOf' extends keyof NonNullable + ? NonNullable['__isTypeOf'] + : never +type ExtractParent = FirstParam>> + +type Source = + ParseParent extends keyof Resolvers + ? ExtractParent> extends never + ? never + : keyof ExtractParent> & string + : never + +type ExtractChild = TChild extends keyof TOp + ? NonNullable + : never + +type Arg = + ParseParent extends keyof Resolvers + ? keyof SecondParam]>, ParseChild>> & + string + : never + +export type ResolverDotPath = `source.${Source}` | `args.${Arg}` diff --git a/packages/server/graphql/public/rules/isOrgTier.ts b/packages/server/graphql/public/rules/isOrgTier.ts index 06e10388dac..7fb5a80eb6e 100644 --- a/packages/server/graphql/public/rules/isOrgTier.ts +++ b/packages/server/graphql/public/rules/isOrgTier.ts @@ -3,7 +3,7 @@ import {GQLContext} from '../../graphql' import {TierEnum} from '../resolverTypes' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isOrgTier = (orgIdDotPath: ResolverDotPath, requiredTier: TierEnum) => +export const isOrgTier = (orgIdDotPath: ResolverDotPath, requiredTier: TierEnum) => rule(`isViewerOnOrg-${orgIdDotPath}-${requiredTier}`, {cache: 'strict'})( async (source, args, {dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args) diff --git a/packages/server/graphql/public/rules/isViewerBillingLeader.ts b/packages/server/graphql/public/rules/isViewerBillingLeader.ts index 28708113671..99391ad1baf 100644 --- a/packages/server/graphql/public/rules/isViewerBillingLeader.ts +++ b/packages/server/graphql/public/rules/isViewerBillingLeader.ts @@ -3,7 +3,7 @@ import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isViewerBillingLeader = (orgIdDotPath: ResolverDotPath) => +export const isViewerBillingLeader = (orgIdDotPath: ResolverDotPath) => rule(`isViewerBillingLeader-${orgIdDotPath}`, {cache: 'strict'})( async (source, args, {authToken, dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args) diff --git a/packages/server/graphql/public/rules/isViewerOnOrg.ts b/packages/server/graphql/public/rules/isViewerOnOrg.ts index 01b0cbab18f..15532d23a86 100644 --- a/packages/server/graphql/public/rules/isViewerOnOrg.ts +++ b/packages/server/graphql/public/rules/isViewerOnOrg.ts @@ -3,7 +3,7 @@ import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isViewerOnOrg = (orgIdDotPath: ResolverDotPath) => +export const isViewerOnOrg = (orgIdDotPath: ResolverDotPath) => rule(`isViewerOnOrg-${orgIdDotPath}`, {cache: 'strict'})( async (source, args, {authToken, dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args)