Skip to content
This repository has been archived by the owner on Jul 12, 2019. It is now read-only.

Commit

Permalink
Merge pull request #112 from zapier/z.dehydrateFile
Browse files Browse the repository at this point in the history
Introduce z.dehydrateFile
  • Loading branch information
eliangcs committed Sep 25, 2018
2 parents 1d9b848 + fa8d91c commit 1e69c35
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 56 deletions.
2 changes: 2 additions & 0 deletions src/app-middlewares/before/z-object.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _ = require('lodash');

const createAppRequestClient = require('../../tools/create-app-request-client');
const createDehydrator = require('../../tools/create-dehydrator');
const createFileDehydrator = require('../../tools/create-file-dehydrator');
const createFileStasher = require('../../tools/create-file-stasher');
const createJSONtool = require('../../tools/create-json-tool');
const createStoreKeyTool = require('../../tools/create-storekey-tool');
Expand All @@ -22,6 +23,7 @@ const injectZObject = input => {
JSON: createJSONtool(),
hash: hashing.hashify,
dehydrate: createDehydrator(input),
dehydrateFile: createFileDehydrator(input),
stashFile: createFileStasher(input),
cursor: createStoreKeyTool(input),
errors
Expand Down
5 changes: 4 additions & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ const SAFE_LOG_KEYS = [
'timestamp'
];

const DEFAULT_FILE_HYDRATOR_NAME = 'zapierDefaultFileHydrator';

module.exports = {
IS_TESTING,
KILL_MIN_LIMIT,
Expand All @@ -62,5 +64,6 @@ module.exports = {
DEFAULT_LOGGING_HTTP_ENDPOINT,
DEFAULT_LOGGING_HTTP_API_KEY,
SENSITIVE_KEYS,
SAFE_LOG_KEYS
SAFE_LOG_KEYS,
DEFAULT_FILE_HYDRATOR_NAME
};
5 changes: 3 additions & 2 deletions src/tools/create-app-request-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,9 @@ const createAppRequestClient = (input, options) => {

if (app.authentication) {
if (
app.authentication.type === 'oauth2' &&
_.get(app, 'authentication.oauth2Config.autoRefresh')
app.authentication.type === 'session' ||
(app.authentication.type === 'oauth2' &&
_.get(app, 'authentication.oauth2Config.autoRefresh'))
) {
httpOriginalAfters.push(throwForStaleAuth);
}
Expand Down
30 changes: 1 addition & 29 deletions src/tools/create-dehydrator.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
'use strict';

const crypto = require('crypto');

const _ = require('lodash');

const resolveMethodPath = require('./resolve-method-path');

const MAX_PAYLOAD_SIZE = 2048; // most urls cannot be larger than 2,083

const wrapHydrate = payload => {
payload = JSON.stringify(payload);

if (process.env._ZAPIER_ONE_TIME_SECRET) {
payload = new Buffer(payload).toString('base64');

const signature = Buffer.from(
crypto
.createHmac('sha1', process.env._ZAPIER_ONE_TIME_SECRET)
.update(payload)
.digest()
).toString('base64');

payload += ':' + signature;
}

return 'hydrate|||' + payload + '|||hydrate';
};
const wrapHydrate = require('./wrap-hydrate');

const createDehydrator = input => {
const app = _.get(input, '_zapier.app');
Expand All @@ -37,12 +15,6 @@ const createDehydrator = input => {
'Oops! You passed a full `bundle` - really you should pass what you want under `inputData`!'
);
}
const payloadSize = JSON.stringify(inputData).length;
if (payloadSize > MAX_PAYLOAD_SIZE) {
throw new Error(
`Oops! You passed too much data (${payloadSize} bytes) to your dehydration function - try slimming it down under ${MAX_PAYLOAD_SIZE} bytes (usually by just passing the needed IDs).`
);
}
return wrapHydrate({
type: 'method',
method: resolveMethodPath(app, func),
Expand Down
58 changes: 58 additions & 0 deletions src/tools/create-file-dehydrator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use strict';

const _ = require('lodash');

const { DEFAULT_FILE_HYDRATOR_NAME } = require('../constants');
const { DehydrateError } = require('../errors');
const resolveMethodPath = require('./resolve-method-path');
const wrapHydrate = require('./wrap-hydrate');

const createFileDehydrator = input => {
const app = _.get(input, '_zapier.app');

const dehydrateFileFromRequest = (url, request, meta) => {
url = url || undefined;
request = request || undefined;
meta = meta || undefined;
if (!url && !request) {
throw new DehydrateError(
'You must provide either url or request arguments!'
);
}
return wrapHydrate({
type: 'file',
method: `hydrators.${DEFAULT_FILE_HYDRATOR_NAME}`,
bundle: { url, request, meta }
});
};

const dehydrateFileFromFunc = (func, inputData) => {
inputData = inputData || {};
if (inputData.inputData) {
throw new DehydrateError(
'Oops! You passed a full `bundle` - really you should pass what you want under `inputData`!'
);
}
return wrapHydrate({
type: 'file',
method: resolveMethodPath(app, func),
// inputData vs. bundle is a legacy oddity
bundle: _.omit(inputData, 'environment') // don't leak the environment
});
};

return (...args) => {
const arg0 = args[0];
if (_.isFunction(arg0)) {
return dehydrateFileFromFunc.apply(this, args);
}
if (arg0 && typeof arg0 !== 'string') {
throw new DehydrateError(
`First argument must be either null, a URL (string), or a hydrator function! We got ${typeof arg0}.`
);
}
return dehydrateFileFromRequest.apply(this, args);
};
};

module.exports = createFileDehydrator;
18 changes: 18 additions & 0 deletions src/tools/default-file-hydrator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
'use strict';

const defaultFileHydrator = (z, bundle) => {
const request = bundle.inputData.request || {};
request.url = bundle.inputData.url || request.url;
request.raw = true;

const filePromise = z.request(request);
const meta = bundle.inputData.meta || {};
return z.stashFile(
filePromise,
meta.knownLength,
meta.filename,
meta.contentType
);
};

module.exports = defaultFileHydrator;
11 changes: 11 additions & 0 deletions src/tools/schema.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
'use strict';

const _ = require('lodash');

const { DEFAULT_FILE_HYDRATOR_NAME } = require('../constants');
const cleaner = require('./cleaner');
const dataTools = require('./data');
const defaultFileHydrator = require('./default-file-hydrator');
const schemaTools = require('./schema-tools');
const zapierSchema = require('zapier-platform-schema');

Expand Down Expand Up @@ -98,6 +101,12 @@ const copyPropertiesFromResource = (type, action, appRaw) => {
return action;
};

const injectDefaultFileHydrator = appRaw => {
appRaw.hydrators = appRaw.hydrators || {};
appRaw.hydrators[DEFAULT_FILE_HYDRATOR_NAME] = defaultFileHydrator;
return appRaw;
};

const compileApp = appRaw => {
appRaw = dataTools.deepCopy(appRaw);
appRaw = schemaTools.findSourceRequireFunctions(appRaw);
Expand Down Expand Up @@ -159,6 +168,8 @@ const compileApp = appRaw => {
);
});

injectDefaultFileHydrator(appRaw);

return appRaw;
};

Expand Down
36 changes: 36 additions & 0 deletions src/tools/wrap-hydrate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const crypto = require('crypto');

const { DehydrateError } = require('../errors');

const MAX_PAYLOAD_SIZE = 2048;

const wrapHydrate = payload => {
payload = JSON.stringify(payload);

if (payload.length > MAX_PAYLOAD_SIZE) {
throw new DehydrateError(
`Oops! You passed too much data (${
payload.length
} bytes) to your dehydration function - try slimming it down under ${MAX_PAYLOAD_SIZE} bytes (usually by just passing the needed IDs).`
);
}

if (process.env._ZAPIER_ONE_TIME_SECRET) {
payload = new Buffer(payload).toString('base64');

const signature = Buffer.from(
crypto
.createHmac('sha1', process.env._ZAPIER_ONE_TIME_SECRET)
.update(payload)
.digest()
).toString('base64');

payload += ':' + signature;
}

return 'hydrate|||' + payload + '|||hydrate';
};

module.exports = wrapHydrate;
32 changes: 32 additions & 0 deletions test/create-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,38 @@ describe('create-app', () => {
done();
});
});

it('should be applied to session auth app on z.request in functions', done => {
const sessionAuthAppDefinition = dataTools.deepCopy(appDefinition);
sessionAuthAppDefinition.authentication = {
type: 'session',
test: {},
sessionConfig: {
perform: {} // stub, not needed for this test
}
};
const sessionAuthApp = createApp(sessionAuthAppDefinition);

const event = {
command: 'execute',
bundle: {
inputData: {
options: {
url: 'http://zapier-httpbin.herokuapp.com/status/401'
}
}
},
method: 'resources.executeRequestAsFunc.list.operation.perform'
};
sessionAuthApp(createInput(sessionAuthAppDefinition, event, testLogger))
.then(() => {
done('expected an error, got success');
})
.catch(error => {
error.name.should.eql('RefreshAuthError');
done();
});
});
});

describe('inputFields', () => {
Expand Down
Loading

0 comments on commit 1e69c35

Please sign in to comment.