@integromat/proto-oauth
Version:
Integromat OAuth Proto-Classes
278 lines (212 loc) • 6.73 kB
JavaScript
'use strict'
const Client = require('./oauth2_client');
const debug = require('@integromat/debug')('imt:proto:oauth2');
const { IMTOAuthAccount } = require('@integromat/proto');
global.IMTOAuth2Account = class IMTOAuth2Account extends IMTOAuthAccount {
/**
*
*/
constructor(options) {
super();
this.options = options || {};
}
/**
* Composes the Redirects object based on the environment and other conditions
* @return {{redirectUri: string, localRedirectUri: string, makeRedirectUri: string}}
*/
get redirects() {
const wrapWithRedirectBase = (uri) => {
if (!this.environment.oauthRedirectBase) return uri;
return this.environment.oauthRedirectBase.replace('{{accountName}}', this.name);
};
// If using the new format of specifying redirects, use this one
if (this.environment.redirects) {
const localRedirectUri = wrapWithRedirectBase(`https://${this.environment.host}/oauth/cb/${this.name}`); // Local Address is always based on host
const redirectUri = wrapWithRedirectBase(`https://${this.environment.redirects.integromat}/oauth/cb/${this.name}`); // Redirect URI has to always point at Integromat
const makeRedirectUri = wrapWithRedirectBase(`https://${this.environment.redirects.make}/oauth/cb/${this.name}`); // Make Redirect URI has to always point at Make
return {
redirectUri,
localRedirectUri,
makeRedirectUri
};
}
// Otherwise, keep the legacy standard
const redirectUri = wrapWithRedirectBase(`https://${this.environment.host}/oauth/cb/${this.name}`);
// We're polyfilling the object to make it backward compatible with instances that use new versions of apps but old configuration
return {
redirectUri,
localRedirectUri: redirectUri,
makeRedirectUri: redirectUri
};
}
/**
*
*/
initialize(done) {
this.options.clientId = this.data.consumerKey || this.data.clientId || this.common.consumerKey|| this.common.clientId;
this.options.clientSecret = this.data.consumerSecret || this.data.clientSecret || this.common.consumerSecret || this.common.clientSecret;
this.options.redirectUri = this.options.redirectUri || this.redirects.redirectUri;
this.client = new Client(this.options);
done();
}
/**
*
*/
authorize(scope, done) {
let params = {
redirect_uri: this.options.redirectUri,
state: this.client.state,
client_id: this.options.clientId,
response_type: 'code'
};
scope = this.scope.concat(scope);
scope = scope.filter((e, i, s) => s.indexOf(e) === i) // remove duplicates
if (scope.length) params.scope = scope.join(this.options.scopeSeparator);
if (this.options.authorizeParams) Object.assign(params, this.options.authorizeParams);
if (this.options.addAuthorizeParameters) Object.assign(params, this.options.addAuthorizeParameters);
IMTOAuthAccount.createToken(params.state, {
account: this.id,
scope,
expires: this.environment.currentDate.getTime() + (900 * 1000) // 15 minutes
}, (err) => {
if (err) return done(err);
done(null, this.client.getAuthorizeUrl(params));
});
}
/**
*
*/
callback(request, done) {
if (this.isAccessDenied(request)) return done(new Error('Access Denied.'));
this.client.getAccessToken(request.query.code, (err, response, body) => {
let error = this.getResponseError(err, response);
if (error) return done(error);
if ('string' === typeof body) {
body = require('querystring').parse(body);
}
this.saveTokens(body);
this.saveExpire(body);
this.saveScope(body, done);
});
}
/**
*
*/
test(done) {
this.refreshToken(err => {
if (err) return done(err, false);
this.getUserInfo((err, response, body) => {
if (err) return done(err, false);
this.saveMetadata(response, body);
done(null, true);
});
});
}
/**
*
*/
getTokenFromRequest(request) {
return request.query.state;
}
/**
*
*/
isAccessDenied(request) {
return request.query.error && request.query.error === 'access_denied';
}
/**
* Saves accepted scope.
*/
saveScope(body, done) {
if (!Array.isArray(this.acceptedScope)) return done();
for (let i = 0, l = this.acceptedScope.length; i < l; i++) {
if (this.scope.indexOf(this.acceptedScope[i]) === -1) {
this.scope.push(this.acceptedScope[i]);
}
}
done();
}
/**
*
*/
saveTokens(body) {
this.data.accessToken = body.access_token;
if (body.refresh_token) this.data.refreshToken = body.refresh_token;
}
/**
*
*/
get(url, done) {
if (!this.data.accessToken) return done(new Error('No access token specified.'));
this.client.get(url, this.data.accessToken, (err, response, body) => {
let error = this.getResponseError(err, response);
if (error) return done(error);
done(null, response, body);
});
}
/**
*
*/
post(url, body, done) {
if (!this.data.accessToken) return done(new Error('No access token specified.'));
if (body) body = JSON.stringify(body);
let accessToken = this.data.accessToken;
let headers = {};
this.client.post(url, accessToken, (err, response, body) => {
let error = this.getResponseError(err, response);
if (error) return done(error);
done(null, response, body);
});
}
/**
*
*/
refreshToken(done) {
if (!this.options.refreshToken) return done();
if (!this.data.refreshToken) return done(new Error('No refresh token specified.'));
this.client.getRefreshToken(this.data.refreshToken, (err, response, body) => {
let error = this.getResponseError(err, response);
if (error) return done(error);
this.saveTokens(body);
done(null, body);
});
}
/**
*
*/
shouldValidate() {
if (!this.data.expire) return false;
this.data.expire = new Date(this.data.expire);
const timeout = this.environment && this.environment.integromat && this.environment.integromat >= 2 ? 5 * 60 * 1000 : this.scenario.timeout;
return this.data.expire.getTime() <= this.environment.currentDate.getTime() + timeout;
}
/**
*
*/
validateWithRefreshToken(done) {
if (this.data.expire) this.data.expire = new Date(this.data.expire);
if (this.data.expire && (this.data.expire.getTime() - this.scenario.timeout > this.environment.currentDate.getTime())) {
return done(null, false);
}
this.options.refreshToken = true;
this.refreshToken((err, body) => {
if (err) return done(err);
this.saveExpire(body);
done(null, true);
});
}
/**
*
*/
getResponseError(err, response) {
if (!err && response.statusCode < 300) return false;
if (err instanceof Error) return err;
let msg;
try {
msg = JSON.stringify(response.body.error || response.body);
} catch(err) {
msg = response.body;
}
return new Error(msg || 'Unexpected error');
}
};