UNPKG

@integromat/proto-oauth

Version:
278 lines (212 loc) 6.73 kB
'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'); } };