Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webmecanik Zapier OAuth2 branded version #2

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
27297b4
Oauth2 for webmecanik
kuzmany Jan 27, 2020
1454730
Add base url to connectionLabel
kuzmany Jan 28, 2020
f49d595
Add help text to clientId and clientSecret
kuzmany Jan 28, 2020
2a4fbc4
Unit test reworked
kuzmany Jan 28, 2020
f452472
Revert back results.should.not.be.empty()
kuzmany Jan 29, 2020
730d769
Add refresh_token to refreshAccessToken method
kuzmany Feb 14, 2020
290bfbd
Rewrite refreshAccessToken
kuzmany Feb 14, 2020
dcbc8dd
Improve comments
kuzmany Feb 14, 2020
8da4294
Change test hook
kuzmany Feb 14, 2020
a8c2985
add refresh_token to store procedure
kuzmany Feb 14, 2020
dcbf17e
Update README.md
kuzmany Feb 14, 2020
6897a80
auth fix again
kuzmany Feb 14, 2020
9b600e1
Bump version to 3.0.0
kuzmany Feb 24, 2020
794052a
test: fails auth on invalid url
kuzmany Mar 26, 2020
76aec60
Fix issue based on validation of project
kuzmany May 1, 2020
7014468
Update .gitignore
kuzmany May 1, 2020
4d20b99
Add docs and Webmecanik Automation branding
kuzmany May 1, 2020
f83fd43
Custom branding
kuzmany May 1, 2020
d113103
Bump to version 1.0.0
kuzmany May 1, 2020
8480186
Add support for segmentChanged
kuzmany Oct 26, 2021
1009715
Fix Abstract import
kuzmany Oct 25, 2021
171de10
Bump to version 1.1.0
kuzmany Dec 14, 2021
8a912cd
Bump to version 1.1.2 - fix segment change load contact custom fields
kuzmany Mar 21, 2023
30ff44b
Increase limit for tags to 500
kuzmany May 3, 2023
191466c
1.1.3
kuzmany May 3, 2023
8435ead
Updated contact.js to handle datetime fields in a more consistent man…
kuzmany Mar 11, 2024
fa117fe
Updated version to 1.1.5 and fixed segmentChanged.js to properly acce…
kuzmany Mar 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .environment.dist
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
TEST_BASE_URL="http://..."
TEST_BASIC_AUTH_USERNAME=""
TEST_BASIC_AUTH_PASSWORD=""
BASE_URL=""
ACCESS_TOKEN=
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ docs
node_modules
*.log
.environment
.idea
.zapierapprc
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 3.0.0

* OAuth2 authentication support

## 2.1.6

* Minimal Node version bumped from 8 to 10.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ If you'd like to help with development, read the [Zapier tutorial](https://githu

There are functional tests covering the basic functionality.

1. Create `.environment` file in the root of this file and copy there content from `.environment.dist`, fill in the auth details.
2. Run `zapier test`.
1. Get access_token of Oauth2 of your Mautic API. You can get it manually by https://developer.mautic.org/#oauth-2 or by https://github.com/mautic/api-library (or by API tester)
<img src="https://user-images.githubusercontent.com/462477/74517536-8c4bb980-4f12-11ea-8b1a-9922806ba25f.png" width="300" />
2. Create `.environment` file in the root of this file and copy there content from `.environment.dist`, fill in the BASE_URL and ACCESS_TOKEN generated above.
3. Run `zapier test`.
120 changes: 101 additions & 19 deletions authentication.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,112 @@
const test = (z, bundle) => {
// Normally you want to make a request to an endpoint that is either specifically designed to test auth, or one that
// every user will have access to, such as an account or profile endpoint like /me.
// In this example, we'll hit httpbin, which validates the Authorization Header against the arguments passed in the URL path
const getAccessToken = (z, bundle) => {
const promise = z.request(bundle.inputData.baseUrl+'/oauth/v2/token', {
method: 'POST',
body: {
// extra data pulled from the users query string
accountDomain: bundle.cleanedRequest.querystring.accountDomain,
code: bundle.inputData.code,
client_id: bundle.inputData.clientId,
client_secret: bundle.inputData.clientSecret,
redirect_uri: bundle.inputData.redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
});

// Needs to return `access_token` and refresh_token
return promise.then(response => {
if (response.status !== 200) {
throw new Error('Unable to fetch access token: ' + response.content);
}

const result = JSON.parse(response.content);
return {
access_token: result.access_token,
refresh_token: result.refresh_token
};
});
};

const refreshAccessToken = (z, bundle) => {
const promise = z.request(bundle.authData.baseUrl+'/oauth/v2/token', {
method: 'POST',
body: {
refresh_token: bundle.authData.refresh_token,
client_id: bundle.authData.clientId,
client_secret: bundle.authData.clientSecret,
grant_type: 'refresh_token'
},
headers: {
'content-type': 'application/x-www-form-urlencoded'
}
});

// Needs to return `access_token` and refresh_token
return promise.then(response => {
if (response.status !== 200) {
throw new Error('Unable to fetch access token: ' + response.content);
}

const result = JSON.parse(response.content);
return {
access_token: result.access_token,
refresh_token: result.refresh_token
};
});
};


const testAuth = (z , bundle ) => {
// Normally you want to make a request to an endpoint that is either specifically designed to test auth, or one that every user will have access to, such as an account or profile endpoint like /me.
const promise = z.request({
method: 'GET',
url: bundle.authData.baseUrl+'/api/contacts/list/fields',
});

// This method can return any truthy value to indicate the credentials are valid.
// Raise an error to show
return z.request({
url: bundle.authData.baseUrl+'/api/contacts?limit=1&minimal=1',
}).then((response) => {
return promise.then(response => {

if (typeof response.json === 'undefined') {
throw new Error('The URL you provided ('+bundle.authData.baseUrl+') is not the base URL of a Mautic instance');
}
if (typeof response.json === 'undefined') {
throw new Error('The URL you provided ('+bundle.inputData.baseUrl+') is not the base URL of a Webmecanik Automation instance');
}

return response;
});
return response;
});
};


module.exports = {
type: 'basic',
type: 'oauth2',
fields: [
{key: 'baseUrl', type: 'string', required: true, helpText: 'The root URL of your Mautic installation starting with https://. E.g. https://my.mautic.net.'}
{key: 'clientId', type: 'string', required: true, helpText: 'Your Client ID (Public Key) is available in Webmecanik Automation > Settings > API Credentials > OAuth 2 ([docs](https://support.webmecanik.com/hc/en-us/articles/115004193669-How-to-connect-Zapier-to-my-Automation-account-))'},
{key: 'clientSecret', type: 'string', required: true, helpText: 'Your Client Secret (Secret Key) is available in Webmecanik Automation > Settings > API Credentials > OAuth 2 ([docs](https://support.webmecanik.com/hc/en-us/articles/115004193669-How-to-connect-Zapier-to-my-Automation-account-))'},
{key: 'baseUrl', type: 'string', required: true, helpText: 'The root URL of your Webmecanik Automation installation starting with https://. E.g. https://my.automation.webmecanik.com'}
],
connectionLabel: '{{bundle.authData.username}}',

// The test method allows Zapier to verify that the credentials a user provides are valid. We'll execute this
// method whenver a user connects their account for the first time.
test: test
oauth2Config: {
// Step 1 of the OAuth flow; specify where to send the user to authenticate with Webmecanik Automation API.
// Zapier generates the state and redirect_uri, you are responsible for providing the rest.
authorizeUrl: {
url: '{{bundle.inputData.baseUrl}}/oauth/v2/authorize',
params: {
client_id: '{{bundle.inputData.clientId}}',
state: '{{bundle.inputData.state}}',
redirect_uri: '{{bundle.inputData.redirect_uri}}',
response_type: 'code'
}
},
// Step 2 of the OAuth flow; Exchange a code for an access token.
// This could also use the request shorthand.
getAccessToken: getAccessToken,
// The access token expires after a pre-defined amount of time, these method refresh it
refreshAccessToken: refreshAccessToken,
// Zapier to automatically invoke `refreshAccessToken` on a 401 response
autoRefresh: true
},
// The test method allows Zapier to verify that the access token is valid
test: testAuth,
// assuming "baseUrl of Webmecanik Automation"
connectionLabel: '{{baseUrl}}',
};
13 changes: 9 additions & 4 deletions entities/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Contact = function(z, bundle) {
// Fill in the core fields we want to provide
coreFields.forEach((field) => {
var type = typeof dirtyContact[field.key];
if (type !== 'undefined' && (type === 'string' || type === 'number')) {
if (dirtyContact[field.key] == null || (type !== 'undefined' && (type === 'string' || type === 'number'))) {
contact[field.key] = dirtyContact[field.key];
}
});
Expand All @@ -41,11 +41,16 @@ Contact = function(z, bundle) {

// The API response has also 'all' fields which are in different format.
if (field && typeof field.alias !== 'undefined') {
contact[field.alias] = field.value;
}
if (field.type === 'datetime' && field.value) {
const dateTime = new Date(field.value);
contact[field.alias] = dateTime.toISOString();
} else {
contact[field.alias] = field.value;
}
}
}
}

// Flatten the owner info
if (dirtyContact.owner && typeof dirtyContact.owner === 'object' && dirtyContact.owner.id) {
contact.ownedBy = dirtyContact.owner.id;
Expand Down
2 changes: 1 addition & 1 deletion entities/email.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Abstract = require('./abstract');
const Abstract = require('./Abstract');

module.exports = function(z, bundle) {
this.singular = 'email';
Expand Down
2 changes: 1 addition & 1 deletion entities/form.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Abstract = require('./abstract');
const Abstract = require('./Abstract');

Form = function(z, bundle) {
this.singular = 'form';
Expand Down
2 changes: 1 addition & 1 deletion entities/page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Abstract = require('./abstract');
const Abstract = require('./Abstract');

module.exports = function(z, bundle) {
this.singular = 'page';
Expand Down
14 changes: 11 additions & 3 deletions entities/tag.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Abstract = require('./abstract');
const Abstract = require('./Abstract');

Tag = function(z, bundle) {
this.singular = 'tag';
Expand All @@ -16,7 +16,7 @@ Tag = function(z, bundle) {
this.cleanTags = (dirtyTags) => {
const cleanTags = []
const usedTags = []

if (dirtyTags) {
for (var key in dirtyTags) {
var tag = dirtyTags[key].tag;
Expand All @@ -31,7 +31,15 @@ Tag = function(z, bundle) {
return cleanTags;
}

this.getList = (params) => this.abstract.getList(params);
this.getList = (params) => {
if (!params) {
params = {}
}

params.limit = 500;

return this.abstract.getList(params);
};
};

module.exports = Tag;
2 changes: 1 addition & 1 deletion entities/user.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const Abstract = require('./abstract');
const Abstract = require('./Abstract');

module.exports = function(z, bundle) {
this.singular = 'user';
Expand Down
Loading