Skip to content

Commit

Permalink
fix(proxy): file upload via multipart/form-data or application/octet-…
Browse files Browse the repository at this point in the history
…stream (#2708)

- fixing /proxy to allow file upload with `Content-Type:
multipart/form-data`
- adding support for uploading file in proxy with `Content-Type:
application/octet-stream`
- fixing node client to upload file via `nango.proxy()`

## Issue ticket number and link

https://linear.app/nango/issue/NAN-1473/enable-file-upload-with-nango-proxy

## Checklist before requesting a review (skip if just adding/editing
APIs & templates)
- [ ] I added tests, otherwise the reason is: 
- [ ] I added observability, otherwise the reason is:
- [ ] I added analytics, otherwise the reason is:
  • Loading branch information
TBonnin authored Sep 12, 2024
1 parent 7be0220 commit 1653a4a
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 11 deletions.
13 changes: 6 additions & 7 deletions packages/node-client/bin/proxy-multi-part-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@ const args = process.argv.slice(2);

const nango = new Nango({ host: 'http://localhost:3003', secretKey: args[0] });

const pdf = fs.readFileSync('./test.pdf', 'utf8');
const filePath = './test.pdf';
const fileName = filePath.split('/').pop();
const buffer = fs.readFileSync(filePath);
const formData = new FormData();
formData.append('content', pdf);

console.log(formData);
formData.append('file', new Blob([buffer]), fileName);

nango
.proxy({
providerConfigKey: 'unauthenticated',
connectionId: 'u',
baseUrlOverride: 'http://localhost:3009',
method: 'POST',
endpoint: '/',
endpoint: '/upload/multipart',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
'Nango-Proxy-Content-Type': 'multipart/form-data'
'Content-Type': 'multipart/form-data'
}
})
.then((response) => {
Expand Down
29 changes: 29 additions & 0 deletions packages/node-client/bin/proxy-octet-stream.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Nango } from '../dist/index.js';
import fs from 'fs';
const args = process.argv.slice(2);

const nango = new Nango({ host: 'http://localhost:3003', secretKey: args[0] });

const filePath = './test.pdf';
const fileName = filePath.split('/').pop();
const buffer = fs.readFileSync(filePath);

nango
.proxy({
providerConfigKey: 'unauthenticated',
connectionId: 'u',
baseUrlOverride: 'http://localhost:3009',
method: 'POST',
endpoint: '/upload/octet-stream',
data: buffer,
headers: {
'Content-Type': 'application/octet-stream',
'X-File-Name': fileName
}
})
.then((response) => {
console.log(response?.data);
})
.catch((err) => {
console.log(err.response?.data || err.message);
});
4 changes: 4 additions & 0 deletions packages/node-client/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,10 @@ export class Nango {
...customPrefixedHeaders
};

if (customHeaders?.['Content-Type']) {
headers['Content-Type'] = customHeaders['Content-Type'];
}

if (retries) {
headers['Retries'] = retries;
}
Expand Down
7 changes: 6 additions & 1 deletion packages/server/lib/controllers/proxy.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import url from 'url';
import querystring from 'querystring';
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import { backOff } from 'exponential-backoff';
import type { HTTP_VERB, UserProvidedProxyConfiguration, InternalProxyConfiguration, ApplicationConstructedProxyConfiguration } from '@nangohq/shared';
import type { HTTP_VERB, UserProvidedProxyConfiguration, InternalProxyConfiguration, ApplicationConstructedProxyConfiguration, File } from '@nangohq/shared';
import { NangoError, LogActionEnum, errorManager, ErrorSourceEnum, proxyService, connectionService, configService, featureFlags } from '@nangohq/shared';
import { metrics, getLogger, axiosInstance as axios, getHeaders } from '@nangohq/utils';
import { logContextGetter } from '@nangohq/logs';
Expand Down Expand Up @@ -65,13 +65,18 @@ class ProxyController {

const rawBodyFlag = await featureFlags.isEnabled('proxy:rawbody', 'global', false);
const data = rawBodyFlag ? req.rawBody : req.body;
let files: File[] = [];
if (Array.isArray(req.files)) {
files = req.files as File[];
}

const externalConfig: UserProvidedProxyConfiguration = {
endpoint,
providerConfigKey,
connectionId,
retries: retries ? Number(retries) : 0,
data,
files,
headers,
baseUrlOverride,
decompress: decompress === 'true' ? true : false,
Expand Down
8 changes: 5 additions & 3 deletions packages/server/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,18 @@ if (isTest) {
webAuth = apiAuth;
}

const bodyLimit = '75mb';
router.use(
express.json({
limit: '75mb',
limit: bodyLimit,
verify: (req: Request, _, buf) => {
req.rawBody = buf.toString();
}
})
);
router.use(bodyParser.raw({ type: 'text/xml' }));
router.use(express.urlencoded({ extended: true }));
router.use(bodyParser.raw({ type: 'application/octet-stream', limit: bodyLimit }));
router.use(bodyParser.raw({ type: 'text/xml', limit: bodyLimit }));
router.use(express.urlencoded({ extended: true, limit: bodyLimit }));

const upload = multer({ storage: multer.memoryStorage() });

Expand Down
13 changes: 13 additions & 0 deletions packages/shared/lib/models/Proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,25 @@ import type { BasicApiCredentials, ApiKeyCredentials, AppCredentials, TbaCredent
import type { Connection } from './Connection.js';
import type { Template as ProviderTemplate } from '@nangohq/types';

export interface File {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
size: number;
destination: string;
filename: string;
path: string;
buffer: Buffer;
}

interface BaseProxyConfiguration {
providerConfigKey: string;
connectionId: string;
endpoint: string;
retries?: number;
data?: unknown;
files?: File[];
headers?: Record<string, string>;
params?: string | Record<string, string | number>;
paramsSerializer?: ParamsSerializerOptions;
Expand Down
6 changes: 6 additions & 0 deletions packages/shared/lib/services/proxy.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ class ProxyService {
Object.keys(data as any).forEach((key) => {
formData.append(key, (data as any)[key]);
});
for (const file of externalConfig.files || []) {
formData.append(file.fieldname, file.buffer, {
filename: file.originalname,
contentType: file.mimetype
});
}

data = formData;
}
Expand Down

0 comments on commit 1653a4a

Please sign in to comment.