diff --git a/server/api.ts b/server/api.ts index 5af976e4a..606953b13 100644 --- a/server/api.ts +++ b/server/api.ts @@ -28,6 +28,7 @@ import { Passport } from 'passport'; import { kyselyUserFindById } from './graphql/datasources/userKyselyPersistence.js'; import resolvers, { type Context } from './graphql/resolvers.js'; import typeDefs from './graphql/schema.js'; +import { makeGqlServices } from './graphql/services.js'; import { authSchemaWrapper } from './graphql/utils/authorization.js'; import { getOrgIdFromPath } from './graphql/utils/orgIdFromPath.js'; import { buildPassportContext } from './graphql/utils/passportContext.js'; @@ -430,34 +431,3 @@ export default async function makeApiServer(deps: Dependencies) { function pickStatus(safeErrors: NonEmptyArray) { return safeErrors[0].status; } - -function makeGqlServices(deps: Dependencies) { - return { - ...safePick(deps, [ - 'ApiKeyService', - 'DataWarehouse', - 'DerivedFieldsService', - 'getItemTypeEventuallyConsistent', - 'getEnabledRulesForItemTypeEventuallyConsistent', - 'ItemInvestigationService', - 'ModerationConfigService', - 'ManualReviewToolService', - 'HMAHashBankService', - 'NcmecService', - 'OrgSettingsService', - 'PartialItemsService', - 'ReportingService', - 'RuleEvaluator', - 'SignalsService', - 'SigningKeyPairService', - 'Tracer', - 'UserManagementService', - 'UserStatisticsService', - 'UserHistoryQueries', - 'UserStrikeService', - 'SSOService', - ]), - }; -} - -export type GQLServices = ReturnType; diff --git a/server/graphql/resolvers.ts b/server/graphql/resolvers.ts index 2e06a6963..e40746dba 100644 --- a/server/graphql/resolvers.ts +++ b/server/graphql/resolvers.ts @@ -1,7 +1,6 @@ import { mergeResolvers } from '@graphql-tools/merge'; import { type GraphQLFieldResolver } from 'graphql'; -import { type GQLServices } from '../api.js'; import { type DataSources } from '../iocContainer/index.js'; import { MIN_PASSWORD_LENGTH, @@ -41,6 +40,7 @@ import { resolvers as signalResolvers } from './modules/signal.js'; import { resolvers as spotTestResolvers } from './modules/spotTest.js'; import { resolvers as textBankResolvers } from './modules/textBank.js'; import { resolvers as userResolvers } from './modules/user.js'; +import { type GQLServices } from './services.js'; import { forbiddenError, unauthenticatedError } from './utils/errors.js'; import { gqlErrorResult, gqlSuccessResult } from './utils/gqlResult.js'; import { type PassportGqlContext } from './utils/passportContext.js'; diff --git a/server/graphql/services.ts b/server/graphql/services.ts new file mode 100644 index 000000000..e1d2e13cb --- /dev/null +++ b/server/graphql/services.ts @@ -0,0 +1,39 @@ +import { type Dependencies } from '../iocContainer/index.js'; +import { safePick } from '../utils/misc.js'; + +/** + * The slice of the IoC container that GraphQL resolvers can see, exposed on the + * resolver context as `services`. + * + * This is deliberately an allowlist rather than the whole container: resolvers + * get these keys and nothing else, which is what stops one reaching straight + * for `Scylla` or `KyselyPg` instead of going through a service. + */ +export function makeGqlServices(deps: Dependencies) { + return safePick(deps, [ + 'ApiKeyService', + 'DataWarehouse', + 'DerivedFieldsService', + 'getItemTypeEventuallyConsistent', + 'getEnabledRulesForItemTypeEventuallyConsistent', + 'ItemInvestigationService', + 'ModerationConfigService', + 'ManualReviewToolService', + 'HMAHashBankService', + 'NcmecService', + 'OrgSettingsService', + 'PartialItemsService', + 'ReportingService', + 'RuleEvaluator', + 'SignalsService', + 'SigningKeyPairService', + 'Tracer', + 'UserManagementService', + 'UserStatisticsService', + 'UserHistoryQueries', + 'UserStrikeService', + 'SSOService', + ]); +} + +export type GQLServices = ReturnType; diff --git a/server/iocContainer/index.ts b/server/iocContainer/index.ts index 4ce8a390a..41f7a8a25 100644 --- a/server/iocContainer/index.ts +++ b/server/iocContainer/index.ts @@ -108,6 +108,7 @@ import { import makeHmaService, { HashBankService, type HashBank, + type HmaService, } from '../services/hmaService/index.js'; import { ItemInvestigationService } from '../services/itemInvestigationService/index.js'; import { @@ -363,6 +364,7 @@ export interface Dependencies { ReportingRuleExecutionLogger: ReportingRuleExecutionLogger; // Core business logic services + HMAHashBankService: HmaService; ActionPublisher: ActionPublisher; RuleEngine: RuleEngine; RuleEvaluator: RuleEvaluator; diff --git a/server/iocContainer/services/gqlDataSources.ts b/server/iocContainer/services/gqlDataSources.ts index 4f4e04010..5ac7afaec 100644 --- a/server/iocContainer/services/gqlDataSources.ts +++ b/server/iocContainer/services/gqlDataSources.ts @@ -22,12 +22,9 @@ import makeRuleAPI, { import makeUserAPI, { type UserAPI, } from '../../graphql/datasources/UserApi.js'; -import { type HmaService } from '../../services/hmaService/index.js'; import { type Dependencies } from '../index.js'; import { register } from '../utils.js'; -// HMA service will be registered in main IoC container to avoid circular dependencies - declare module '../index.js' { interface Dependencies { // GraphQL Api Data Sources @@ -40,7 +37,6 @@ declare module '../index.js' { RuleAPIDataSource: RuleAPI; UserAPIDataSource: UserAPI; DataSources: DataSources; - HMAHashBankService: HmaService; } } @@ -57,8 +53,6 @@ export function registerGqlDataSources(bottle: Bottle) { register(bottle, 'RuleAPIDataSource', makeRuleAPI); register(bottle, 'UserAPIDataSource', makeUserAPI); - // HMA Service will be registered in main IoC container - // Master dataSource service. Exists so that we can easily propagate the type // of this whole dataSources object to all the places we need to reference the // GraphQL context's type.