apollo-federation-file-upload icon indicating copy to clipboard operation
apollo-federation-file-upload copied to clipboard

Ability to add request headers to GQL request on file upload

Open dominik-myszkowski opened this issue 2 years ago • 9 comments

🚀 Feature Request

Description

  • Whenever a file upload via apollo-federation-file-upload occurs, there are set request headers and one can not add anything to that header (request from Gateway to subgraph). willSendRequest method will execute but all the headers added in that method (in case of file upload) will be ignored. There are many issues arising from that problem, for example I can not pass authentication token from Gateway to subgraph for authenticating user.

Implementation details

  • There should be an option (object props) to pass file upload headers OR file upload request should add headers added in willSendRequest

Potential caveats

  • none

Acceptance criteria

Additional context and visual reference

dominik-myszkowski avatar Nov 29 '22 13:11 dominik-myszkowski

I'm having the same issue, I can't figure out why the headers set in willSendRequest are ignored. Any clue?

superlevure avatar Jan 07 '23 12:01 superlevure

Actually, this was fixed by https://github.com/profusion/apollo-federation-file-upload/commit/962583d88afa8843a0e0d4cce71696417d8aec50 but not released yet ?

superlevure avatar Jan 09 '23 18:01 superlevure

Same problem here. I can see that the gateway sets the headers based on the context via the willSendRequest method but the headers don't propagate to the corresponding service with subgraph. Any solutions pls? Right now I can't authenticate user accessing upload resolver implemeted by the microservice :/

BobbiSixkiller avatar Jan 20 '23 09:01 BobbiSixkiller

@superlevure please have you somehow managed to get it to set the headers? If so could you please provide a workaround?

BobbiSixkiller avatar Jan 21 '23 10:01 BobbiSixkiller

For anyone having this issue, my workaround for now is simply constructing the headers object based on context value received via GraphQLDataSourceProcessOptions args. Somehow the request?.http?.headers returns undefined so I relied on the context, where my authenticated user is set. So basically I tweaked the processFiles method like this

private async processFiles(
	args: GraphQLDataSourceProcessOptions,
	fileVariables: FileVariablesTuple[]
): ProcessResult {
	const { context, request } = args;
	const form = new FormData();

	const variables = cloneDeep(request.variables || {});
	fileVariables.forEach(([variableName]: FileVariablesTuple): void => {
		set(variables, variableName, null);
	});

	const operations = JSON.stringify({
		query: request.query,
		variables,
	});

	form.append("operations", operations);

	const fileMap: { [key: string]: string[] } = {};

	const resolvedFiles: FileUpload[] = await Promise.all(
		fileVariables.map(
			async (
				[variableName, file]: FileVariablesTuple,
				i: number
			): Promise<FileUpload> => {
				const fileUpload: FileUpload = await file;
				fileMap[i] = [`variables.${variableName}`];
				return fileUpload;
			}
		)
	);

	// This must come before the file contents append bellow
	form.append("map", JSON.stringify(fileMap));
	await this.addDataHandler(form, resolvedFiles);

	// This must happen before constructing the request headers
	// otherwise any custom headers set in willSendRequest are ignored
	if (this.willSendRequest) {
		await this.willSendRequest(args);
	}

	console.log(context.user, context.locale);

	const headers = {
		// ...Object.fromEntries(request?.http?.headers || []),
		...Object.fromEntries([["user", JSON.stringify(context[user])]]),
		...form.getHeaders(),
	};

	console.log(headers);

	Object.assign(headers, form.getHeaders() || {});

	const httpRequest = {
		headers,
		method: "POST",
		url: this.url,
	};

	const options = {
		...httpRequest,
		body: form,
	};

	// NOTE: there is currently a type mismatch related to Headers in @apollo/gateway and apollo-server-env:
	//
	//  >> you should ensure that you pass "plain" objects rather than Headers or Request objects,
	//  >> as the newer version has slightly different logic about how to recognize Headers and
	//  >> Request objects.
	//
	// see:
	// - https://github.com/apollographql/federation/pull/1906
	// - https://github.com/profusion/apollo-federation-file-upload/issues/52#issuecomment-1148946002
	request.http = httpRequest as Exclude<typeof request.http, undefined>;

	let httpResponse: Response | undefined;

	try {
		httpResponse = await this.fetcher(this.url, options);

		const body = await this.parseBody(httpResponse);

		if (!isObject(body)) {
			throw new Error(`Expected JSON response body, but received: ${body}`);
		}
		const response = {
			...body,
			http: httpResponse,
		};

		if (typeof this.didReceiveResponse === "function") {
			return this.didReceiveResponse({ context, request, response });
		}

		return response;
	} catch (error) {
		this.didEncounterError(error, options, httpResponse);
		throw error;
	}
}`

BobbiSixkiller avatar Jan 22 '23 10:01 BobbiSixkiller

Ran into this problem too; it seems like the code here is the culprit as it never passes the headers down to the Request constructor. This will cause lots of issues with subgraphs that need access to e.g. Authorization or apollo-require-preflight headers to function correctly.

As mentioned above there seems to be a commit that fixes this - can this be released soon?

           request.http = {
                headers,
                method: 'POST',
                url: this.url,
            };
            if (this.willSendRequest) {
                yield this.willSendRequest(args);
            }
            const options = Object.assign(Object.assign({}, request.http), { 
                // Apollo types are not up-to-date, make TS happy
                body: form });
            const httpRequest = new apollo_server_env_1.Request(request.http.url, options);

wabrit avatar Jan 24 '23 10:01 wabrit

will this be released soon? 💯

paulbijancoch avatar Feb 06 '23 15:02 paulbijancoch

https://medium.com/profusion-engineering/file-uploads-graphql-and-apollo-federation-c5a878707f4c

g7fernandes avatar Feb 16 '23 14:02 g7fernandes

Any idea, I got stucked on this. Upload works fine, yet headers are not overwritten.

KMoscIszko avatar Jun 26 '23 19:06 KMoscIszko