Skip to content

Commit 269f4ce

Browse files
committed
feat: graphql introspection option
1 parent 505a37a commit 269f4ce

File tree

8 files changed

+213
-42
lines changed

8 files changed

+213
-42
lines changed

spec/ParseGraphQLServer.spec.js

Lines changed: 142 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('ParseGraphQLServer', () => {
5050

5151
beforeEach(async () => {
5252
parseServer = await global.reconfigureServer({
53+
maintenanceKey: 'test2',
5354
maxUploadSize: '1kb',
5455
});
5556
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
@@ -88,8 +89,8 @@ describe('ParseGraphQLServer', () => {
8889

8990
it('should initialize parseGraphQLSchema with a log controller', async () => {
9091
const loggerAdapter = {
91-
log: () => {},
92-
error: () => {},
92+
log: () => { },
93+
error: () => { },
9394
};
9495
const parseServer = await global.reconfigureServer({
9596
loggerAdapter,
@@ -124,10 +125,10 @@ describe('ParseGraphQLServer', () => {
124125
info: new Object(),
125126
config: new Object(),
126127
auth: new Object(),
127-
get: () => {},
128+
get: () => { },
128129
};
129130
const res = {
130-
set: () => {},
131+
set: () => { },
131132
};
132133

133134
it_id('0696675e-060f-414f-bc77-9d57f31807f5')(it)('should return schema and context with req\'s info, config and auth', async () => {
@@ -431,7 +432,7 @@ describe('ParseGraphQLServer', () => {
431432
objects.push(object1, object2, object3, object4);
432433
}
433434

434-
async function createGQLFromParseServer(_parseServer) {
435+
async function createGQLFromParseServer(_parseServer, parseGraphQLServerOptions) {
435436
if (parseLiveQueryServer) {
436437
await parseLiveQueryServer.server.close();
437438
}
@@ -448,6 +449,7 @@ describe('ParseGraphQLServer', () => {
448449
graphQLPath: '/graphql',
449450
playgroundPath: '/playground',
450451
subscriptionsPath: '/subscriptions',
452+
...parseGraphQLServerOptions,
451453
});
452454
parseGraphQLServer.applyGraphQL(expressApp);
453455
parseGraphQLServer.applyPlayground(expressApp);
@@ -456,7 +458,7 @@ describe('ParseGraphQLServer', () => {
456458
}
457459

458460
beforeEach(async () => {
459-
await createGQLFromParseServer(parseServer);
461+
await createGQLFromParseServer(parseServer);
460462

461463
const subscriptionClient = new SubscriptionClient(
462464
'ws://localhost:13377/subscriptions',
@@ -488,8 +490,8 @@ describe('ParseGraphQLServer', () => {
488490
},
489491
},
490492
});
491-
spyOn(console, 'warn').and.callFake(() => {});
492-
spyOn(console, 'error').and.callFake(() => {});
493+
spyOn(console, 'warn').and.callFake(() => { });
494+
spyOn(console, 'error').and.callFake(() => { });
493495
});
494496

495497
afterEach(async () => {
@@ -605,6 +607,96 @@ describe('ParseGraphQLServer', () => {
605607
]);
606608
};
607609

610+
describe('Introspection', () => {
611+
it('should have public introspection disabled by default without master key', async () => {
612+
613+
try {
614+
await apolloClient.query({
615+
query: gql`
616+
query Introspection {
617+
__schema {
618+
types {
619+
name
620+
}
621+
}
622+
}
623+
`,
624+
})
625+
626+
fail('should have thrown an error');
627+
628+
} catch (e) {
629+
expect(e.message).toEqual('Response not successful: Received status code 403');
630+
expect(e.networkError.result.errors[0].message).toEqual('Introspection is not allowed');
631+
}
632+
});
633+
634+
it('should always work with master key', async () => {
635+
const introspection =
636+
await apolloClient.query({
637+
query: gql`
638+
query Introspection {
639+
__schema {
640+
types {
641+
name
642+
}
643+
}
644+
}
645+
`,
646+
context: {
647+
headers: {
648+
'X-Parse-Master-Key': 'test',
649+
},
650+
}
651+
},)
652+
expect(introspection.data).toBeDefined();
653+
expect(introspection.errors).not.toBeDefined();
654+
});
655+
656+
it('should always work with maintenance key', async () => {
657+
const introspection =
658+
await apolloClient.query({
659+
query: gql`
660+
query Introspection {
661+
__schema {
662+
types {
663+
name
664+
}
665+
}
666+
}
667+
`,
668+
context: {
669+
headers: {
670+
'X-Parse-Maintenance-Key': 'test2',
671+
},
672+
}
673+
},)
674+
expect(introspection.data).toBeDefined();
675+
expect(introspection.errors).not.toBeDefined();
676+
});
677+
678+
it('should have public introspection enabled if enabled', async () => {
679+
680+
const parseServer = await reconfigureServer();
681+
await createGQLFromParseServer(parseServer, { graphQLPublicIntrospection: true });
682+
683+
const introspection =
684+
await apolloClient.query({
685+
query: gql`
686+
query Introspection {
687+
__schema {
688+
types {
689+
name
690+
}
691+
}
692+
}
693+
`,
694+
})
695+
expect(introspection.data).toBeDefined();
696+
});
697+
});
698+
699+
608700
describe('Default Types', () => {
609701
it('should have Object scalar type', async () => {
610702
const objectType = (
@@ -749,6 +841,11 @@ describe('ParseGraphQLServer', () => {
749841
}
750842
}
751843
`,
844+
context: {
845+
headers: {
846+
'X-Parse-Master-Key': 'test',
847+
},
848+
}
752849
})
753850
).data['__schema'].types.map(type => type.name);
754851

@@ -780,6 +877,11 @@ describe('ParseGraphQLServer', () => {
780877
}
781878
}
782879
`,
880+
context: {
881+
headers: {
882+
'X-Parse-Master-Key': 'test',
883+
},
884+
}
783885
})
784886
).data['__schema'].types.map(type => type.name);
785887

@@ -864,7 +966,7 @@ describe('ParseGraphQLServer', () => {
864966
});
865967

866968
it('should have clientMutationId in call function input', async () => {
867-
Parse.Cloud.define('hello', () => {});
969+
Parse.Cloud.define('hello', () => { });
868970

869971
const callFunctionInputFields = (
870972
await apolloClient.query({
@@ -886,7 +988,7 @@ describe('ParseGraphQLServer', () => {
886988
});
887989

888990
it('should have clientMutationId in call function payload', async () => {
889-
Parse.Cloud.define('hello', () => {});
991+
Parse.Cloud.define('hello', () => { });
890992

891993
const callFunctionPayloadFields = (
892994
await apolloClient.query({
@@ -1312,6 +1414,11 @@ describe('ParseGraphQLServer', () => {
13121414
}
13131415
}
13141416
`,
1417+
context: {
1418+
headers: {
1419+
'X-Parse-Master-Key': 'test',
1420+
},
1421+
}
13151422
})
13161423
).data['__schema'].types.map(type => type.name);
13171424

@@ -6553,7 +6660,7 @@ describe('ParseGraphQLServer', () => {
65536660
);
65546661
expect(
65556662
(await deleteObject(object4.className, object4.id)).data.delete[
6556-
object4.className.charAt(0).toLowerCase() + object4.className.slice(1)
6663+
object4.className.charAt(0).toLowerCase() + object4.className.slice(1)
65576664
]
65586665
).toEqual({ objectId: object4.id, __typename: 'PublicClass' });
65596666
await expectAsync(object4.fetch({ useMasterKey: true })).toBeRejectedWith(
@@ -7447,9 +7554,9 @@ describe('ParseGraphQLServer', () => {
74477554
it('should send reset password', async () => {
74487555
const clientMutationId = uuidv4();
74497556
const emailAdapter = {
7450-
sendVerificationEmail: () => {},
7557+
sendVerificationEmail: () => { },
74517558
sendPasswordResetEmail: () => Promise.resolve(),
7452-
sendMail: () => {},
7559+
sendMail: () => { },
74537560
};
74547561
parseServer = await global.reconfigureServer({
74557562
appName: 'test',
@@ -7488,11 +7595,11 @@ describe('ParseGraphQLServer', () => {
74887595
const clientMutationId = uuidv4();
74897596
let resetPasswordToken;
74907597
const emailAdapter = {
7491-
sendVerificationEmail: () => {},
7598+
sendVerificationEmail: () => { },
74927599
sendPasswordResetEmail: ({ link }) => {
74937600
resetPasswordToken = link.split('token=')[1].split('&')[0];
74947601
},
7495-
sendMail: () => {},
7602+
sendMail: () => { },
74967603
};
74977604
parseServer = await global.reconfigureServer({
74987605
appName: 'test',
@@ -7558,9 +7665,9 @@ describe('ParseGraphQLServer', () => {
75587665
it('should send verification email again', async () => {
75597666
const clientMutationId = uuidv4();
75607667
const emailAdapter = {
7561-
sendVerificationEmail: () => {},
7668+
sendVerificationEmail: () => { },
75627669
sendPasswordResetEmail: () => Promise.resolve(),
7563-
sendMail: () => {},
7670+
sendMail: () => { },
75647671
};
75657672
parseServer = await global.reconfigureServer({
75667673
appName: 'test',
@@ -11157,25 +11264,25 @@ describe('ParseGraphQLServer', () => {
1115711264
},
1115811265
});
1115911266
const SomeClassType = new GraphQLObjectType({
11160-
name: 'SomeClass',
11161-
fields: {
11162-
nameUpperCase: {
11163-
type: new GraphQLNonNull(GraphQLString),
11164-
resolve: p => p.name.toUpperCase(),
11165-
},
11166-
type: { type: TypeEnum },
11167-
language: {
11168-
type: new GraphQLEnumType({
11169-
name: 'LanguageEnum',
11170-
values: {
11171-
fr: { value: 'fr' },
11172-
en: { value: 'en' },
11173-
},
11174-
}),
11175-
resolve: () => 'fr',
11176-
},
11267+
name: 'SomeClass',
11268+
fields: {
11269+
nameUpperCase: {
11270+
type: new GraphQLNonNull(GraphQLString),
11271+
resolve: p => p.name.toUpperCase(),
1117711272
},
11178-
}),
11273+
type: { type: TypeEnum },
11274+
language: {
11275+
type: new GraphQLEnumType({
11276+
name: 'LanguageEnum',
11277+
values: {
11278+
fr: { value: 'fr' },
11279+
en: { value: 'en' },
11280+
},
11281+
}),
11282+
resolve: () => 'fr',
11283+
},
11284+
},
11285+
}),
1117911286
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
1118011287
graphQLPath: '/graphql',
1118111288
graphQLCustomTypeDefs: new GraphQLSchema({

spec/SecurityCheckGroups.spec.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('Security Check Groups', () => {
3333
config.security.enableCheckLog = false;
3434
config.allowClientClassCreation = false;
3535
config.enableInsecureAuthAdapters = false;
36+
config.graphQLPublicIntrospection = false;
3637
await reconfigureServer(config);
3738

3839
const group = new CheckGroupServerConfig();
@@ -41,12 +42,14 @@ describe('Security Check Groups', () => {
4142
expect(group.checks()[1].checkState()).toBe(CheckState.success);
4243
expect(group.checks()[2].checkState()).toBe(CheckState.success);
4344
expect(group.checks()[4].checkState()).toBe(CheckState.success);
45+
expect(group.checks()[5].checkState()).toBe(CheckState.success);
4446
});
4547

4648
it('checks fail correctly', async () => {
4749
config.masterKey = 'insecure';
4850
config.security.enableCheckLog = true;
4951
config.allowClientClassCreation = true;
52+
config.graphQLPublicIntrospection = true;
5053
await reconfigureServer(config);
5154

5255
const group = new CheckGroupServerConfig();
@@ -55,6 +58,7 @@ describe('Security Check Groups', () => {
5558
expect(group.checks()[1].checkState()).toBe(CheckState.fail);
5659
expect(group.checks()[2].checkState()).toBe(CheckState.fail);
5760
expect(group.checks()[4].checkState()).toBe(CheckState.fail);
61+
expect(group.checks()[5].checkState()).toBe(CheckState.fail);
5862
});
5963
});
6064

0 commit comments

Comments
 (0)