2019-06-19 17:19:47 -07:00
|
|
|
import corsMiddleware from 'cors';
|
2024-03-02 04:06:47 +03:00
|
|
|
import graphqlUploadExpress from 'graphql-upload/graphqlUploadExpress.js';
|
|
|
|
|
import { ApolloServer } from '@apollo/server';
|
|
|
|
|
import { expressMiddleware } from '@apollo/server/express4';
|
|
|
|
|
import { ApolloServerPluginCacheControlDisabled } from '@apollo/server/plugin/disabled';
|
|
|
|
|
import express from 'express';
|
2025-07-10 04:25:09 +02:00
|
|
|
import { execute, subscribe, GraphQLError } from 'graphql';
|
2019-06-19 17:19:47 -07:00
|
|
|
import { SubscriptionServer } from 'subscriptions-transport-ws';
|
2023-01-06 23:39:02 +11:00
|
|
|
import { handleParseErrors, handleParseHeaders, handleParseSession } from '../middlewares';
|
2019-06-19 17:19:47 -07:00
|
|
|
import requiredParameter from '../requiredParameter';
|
|
|
|
|
import defaultLogger from '../logger';
|
|
|
|
|
import { ParseGraphQLSchema } from './ParseGraphQLSchema';
|
2020-10-25 15:06:58 -05:00
|
|
|
import ParseGraphQLController, { ParseGraphQLConfig } from '../Controllers/ParseGraphQLController';
|
2019-06-19 17:19:47 -07:00
|
|
|
|
2025-07-10 04:25:09 +02:00
|
|
|
|
|
|
|
|
const IntrospectionControlPlugin = (publicIntrospection) => ({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requestDidStart: (requestContext) => ({
|
|
|
|
|
|
|
|
|
|
didResolveOperation: async () => {
|
|
|
|
|
// If public introspection is enabled, we allow all introspection queries
|
|
|
|
|
if (publicIntrospection) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isMasterOrMaintenance = requestContext.contextValue.auth?.isMaster || requestContext.contextValue.auth?.isMaintenance
|
|
|
|
|
if (isMasterOrMaintenance) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Now we check if the query is an introspection query
|
|
|
|
|
// this check strategy should work in 99.99% cases
|
|
|
|
|
// we can have an issue if a user name a field or class __schemaSomething
|
|
|
|
|
// we want to avoid a full AST check
|
|
|
|
|
const isIntrospectionQuery =
|
|
|
|
|
requestContext.request.query?.includes('__schema')
|
|
|
|
|
|
|
|
|
|
if (isIntrospectionQuery) {
|
|
|
|
|
throw new GraphQLError('Introspection is not allowed', {
|
|
|
|
|
extensions: {
|
|
|
|
|
http: {
|
|
|
|
|
status: 403,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
2019-06-19 17:19:47 -07:00
|
|
|
class ParseGraphQLServer {
|
2019-07-25 20:46:25 +01:00
|
|
|
parseGraphQLController: ParseGraphQLController;
|
|
|
|
|
|
2019-06-19 17:19:47 -07:00
|
|
|
constructor(parseServer, config) {
|
2020-10-25 15:06:58 -05:00
|
|
|
this.parseServer = parseServer || requiredParameter('You must provide a parseServer instance!');
|
2019-06-19 17:19:47 -07:00
|
|
|
if (!config || !config.graphQLPath) {
|
|
|
|
|
requiredParameter('You must provide a config.graphQLPath!');
|
|
|
|
|
}
|
|
|
|
|
this.config = config;
|
2019-07-25 20:46:25 +01:00
|
|
|
this.parseGraphQLController = this.parseServer.config.parseGraphQLController;
|
2019-08-15 23:23:41 +02:00
|
|
|
this.log =
|
2020-10-25 15:06:58 -05:00
|
|
|
(this.parseServer.config && this.parseServer.config.loggerController) || defaultLogger;
|
2019-07-25 20:46:25 +01:00
|
|
|
this.parseGraphQLSchema = new ParseGraphQLSchema({
|
|
|
|
|
parseGraphQLController: this.parseGraphQLController,
|
|
|
|
|
databaseController: this.parseServer.config.databaseController,
|
2019-08-15 23:23:41 +02:00
|
|
|
log: this.log,
|
2019-07-25 20:46:25 +01:00
|
|
|
graphQLCustomTypeDefs: this.config.graphQLCustomTypeDefs,
|
2019-09-09 15:07:22 -07:00
|
|
|
appId: this.parseServer.config.appId,
|
2019-07-25 20:46:25 +01:00
|
|
|
});
|
2019-06-19 17:19:47 -07:00
|
|
|
}
|
|
|
|
|
|
2022-05-18 19:55:43 +02:00
|
|
|
async _getGraphQLOptions() {
|
2019-08-15 23:23:41 +02:00
|
|
|
try {
|
|
|
|
|
return {
|
|
|
|
|
schema: await this.parseGraphQLSchema.load(),
|
2024-03-02 04:06:47 +03:00
|
|
|
context: async ({ req, res }) => {
|
|
|
|
|
res.set('access-control-allow-origin', req.get('origin') || '*');
|
|
|
|
|
return {
|
|
|
|
|
info: req.info,
|
|
|
|
|
config: req.config,
|
|
|
|
|
auth: req.auth,
|
|
|
|
|
};
|
2019-10-02 06:47:56 +02:00
|
|
|
},
|
2019-08-15 23:23:41 +02:00
|
|
|
};
|
|
|
|
|
} catch (e) {
|
2020-10-25 15:06:58 -05:00
|
|
|
this.log.error(e.stack || (typeof e.toString === 'function' && e.toString()) || e);
|
2019-08-15 23:23:41 +02:00
|
|
|
throw e;
|
|
|
|
|
}
|
2019-06-19 17:19:47 -07:00
|
|
|
}
|
|
|
|
|
|
2022-05-18 19:55:43 +02:00
|
|
|
async _getServer() {
|
|
|
|
|
const schemaRef = this.parseGraphQLSchema.graphQLSchema;
|
|
|
|
|
const newSchemaRef = await this.parseGraphQLSchema.load();
|
|
|
|
|
if (schemaRef === newSchemaRef && this._server) {
|
|
|
|
|
return this._server;
|
|
|
|
|
}
|
2024-03-02 04:06:47 +03:00
|
|
|
const { schema, context } = await this._getGraphQLOptions();
|
|
|
|
|
const apollo = new ApolloServer({
|
|
|
|
|
csrfPrevention: {
|
|
|
|
|
// See https://www.apollographql.com/docs/router/configuration/csrf/
|
|
|
|
|
// needed since we use graphql upload
|
|
|
|
|
requestHeaders: ['X-Parse-Application-Id'],
|
|
|
|
|
},
|
2025-07-10 04:25:09 +02:00
|
|
|
introspection: this.config.graphQLPublicIntrospection,
|
|
|
|
|
plugins: [ApolloServerPluginCacheControlDisabled(), IntrospectionControlPlugin(this.config.graphQLPublicIntrospection)],
|
2024-03-02 04:06:47 +03:00
|
|
|
schema,
|
|
|
|
|
});
|
|
|
|
|
await apollo.start();
|
|
|
|
|
this._server = expressMiddleware(apollo, {
|
|
|
|
|
context,
|
|
|
|
|
});
|
2022-05-18 19:55:43 +02:00
|
|
|
return this._server;
|
|
|
|
|
}
|
|
|
|
|
|
2019-08-18 23:23:59 -07:00
|
|
|
_transformMaxUploadSizeToBytes(maxUploadSize) {
|
|
|
|
|
const unitMap = {
|
|
|
|
|
kb: 1,
|
|
|
|
|
mb: 2,
|
|
|
|
|
gb: 3,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
Number(maxUploadSize.slice(0, -2)) *
|
|
|
|
|
Math.pow(1024, unitMap[maxUploadSize.slice(-2).toLowerCase()])
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-14 20:16:31 +02:00
|
|
|
/**
|
|
|
|
|
* @static
|
|
|
|
|
* Allow developers to customize each request with inversion of control/dependency injection
|
|
|
|
|
*/
|
|
|
|
|
applyRequestContextMiddleware(api, options) {
|
|
|
|
|
if (options.requestContextMiddleware) {
|
|
|
|
|
if (typeof options.requestContextMiddleware !== 'function') {
|
|
|
|
|
throw new Error('requestContextMiddleware must be a function');
|
|
|
|
|
}
|
|
|
|
|
api.use(this.config.graphQLPath, options.requestContextMiddleware);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-19 17:19:47 -07:00
|
|
|
applyGraphQL(app) {
|
|
|
|
|
if (!app || !app.use) {
|
|
|
|
|
requiredParameter('You must provide an Express.js app instance!');
|
|
|
|
|
}
|
|
|
|
|
app.use(this.config.graphQLPath, corsMiddleware());
|
|
|
|
|
app.use(this.config.graphQLPath, handleParseHeaders);
|
2023-01-06 23:39:02 +11:00
|
|
|
app.use(this.config.graphQLPath, handleParseSession);
|
2025-10-14 20:16:31 +02:00
|
|
|
this.applyRequestContextMiddleware(app, this.parseServer.config);
|
2019-07-12 17:58:47 -03:00
|
|
|
app.use(this.config.graphQLPath, handleParseErrors);
|
2024-03-02 04:06:47 +03:00
|
|
|
app.use(
|
|
|
|
|
this.config.graphQLPath,
|
|
|
|
|
graphqlUploadExpress({
|
|
|
|
|
maxFileSize: this._transformMaxUploadSizeToBytes(
|
|
|
|
|
this.parseServer.config.maxUploadSize || '20mb'
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
app.use(this.config.graphQLPath, express.json(), async (req, res, next) => {
|
2022-05-18 19:55:43 +02:00
|
|
|
const server = await this._getServer();
|
2024-03-02 04:06:47 +03:00
|
|
|
return server(req, res, next);
|
2022-05-18 19:55:43 +02:00
|
|
|
});
|
2019-06-19 17:19:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applyPlayground(app) {
|
|
|
|
|
if (!app || !app.get) {
|
|
|
|
|
requiredParameter('You must provide an Express.js app instance!');
|
|
|
|
|
}
|
2024-03-02 04:06:47 +03:00
|
|
|
|
2019-06-19 17:19:47 -07:00
|
|
|
app.get(
|
|
|
|
|
this.config.playgroundPath ||
|
2025-07-10 04:25:09 +02:00
|
|
|
requiredParameter('You must provide a config.playgroundPath to applyPlayground!'),
|
2019-06-19 17:19:47 -07:00
|
|
|
(_req, res) => {
|
|
|
|
|
res.setHeader('Content-Type', 'text/html');
|
|
|
|
|
res.write(
|
2024-03-02 04:06:47 +03:00
|
|
|
`<div id="sandbox" style="position:absolute;top:0;right:0;bottom:0;left:0"></div>
|
|
|
|
|
<script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js"></script>
|
|
|
|
|
<script>
|
|
|
|
|
new window.EmbeddedSandbox({
|
|
|
|
|
target: "#sandbox",
|
|
|
|
|
endpointIsEditable: false,
|
2025-09-21 22:45:07 +07:00
|
|
|
initialEndpoint: ${JSON.stringify(this.config.graphQLPath)},
|
2024-03-02 04:06:47 +03:00
|
|
|
handleRequest: (endpointUrl, options) => {
|
|
|
|
|
return fetch(endpointUrl, {
|
|
|
|
|
...options,
|
|
|
|
|
headers: {
|
|
|
|
|
...options.headers,
|
2025-09-21 22:45:07 +07:00
|
|
|
'X-Parse-Application-Id': ${JSON.stringify(this.parseServer.config.appId)},
|
|
|
|
|
'X-Parse-Master-Key': ${JSON.stringify(this.parseServer.config.masterKey)},
|
2024-03-02 04:06:47 +03:00
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
// advanced options: https://www.apollographql.com/docs/studio/explorer/sandbox#embedding-sandbox
|
|
|
|
|
</script>`
|
2019-06-19 17:19:47 -07:00
|
|
|
);
|
|
|
|
|
res.end();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
createSubscriptions(server) {
|
|
|
|
|
SubscriptionServer.create(
|
|
|
|
|
{
|
|
|
|
|
execute,
|
|
|
|
|
subscribe,
|
|
|
|
|
onOperation: async (_message, params, webSocket) =>
|
2020-10-25 15:06:58 -05:00
|
|
|
Object.assign({}, params, await this._getGraphQLOptions(webSocket.upgradeReq)),
|
2019-06-19 17:19:47 -07:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
server,
|
|
|
|
|
path:
|
|
|
|
|
this.config.subscriptionsPath ||
|
2020-10-25 15:06:58 -05:00
|
|
|
requiredParameter('You must provide a config.subscriptionsPath to createSubscriptions!'),
|
2019-06-19 17:19:47 -07:00
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-07-25 20:46:25 +01:00
|
|
|
|
|
|
|
|
setGraphQLConfig(graphQLConfig: ParseGraphQLConfig): Promise {
|
|
|
|
|
return this.parseGraphQLController.updateGraphQLConfig(graphQLConfig);
|
|
|
|
|
}
|
2019-06-19 17:19:47 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { ParseGraphQLServer };
|