prisme-flow
Version:
prisme platform flow engine
266 lines (256 loc) • 8.93 kB
JavaScript
/**
* Created by prisme.io on 09/06/2017.
*/
module.exports = function (RED) {
'use strict'
var request = require('request')
var crypto = require('crypto')
var url = require('url')
function GoogleNode (n) {
RED.nodes.createNode(this, n)
this.displayName = n.displayName
}
RED.nodes.registerType('google-credentials', GoogleNode, {
credentials: {
displayName: {type: 'text'},
clientId: {type: 'text'},
clientSecret: {type: 'password'},
accessToken: {type: 'password'},
refreshToken: {type: 'password'},
expireTime: {type: 'password'}
}
})
GoogleNode.prototype.refreshToken = function (cb) {
var credentials = this.credentials
var node = this
// console.log("refreshing token: " + credentials.refreshToken);
if (!credentials.refreshToken) {
node.error(RED._('No refresh token to regain Google access'))
return cb(RED._('No refresh token to regain Google access'))
}
request.post({
url: 'https://accounts.google.com/o/oauth2/token',
json: true,
form: {
grant_type: 'refresh_token',
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
refresh_token: credentials.refreshToken
}
}, function (err, result, data) {
if (err) {
node.error(RED._('No refresh token to regain Google access', {err: err}))
return
}
if (data.error) {
node.error(RED._('No refresh token to regain Google accessr', {message: data.error.message}))
return
}
// console.log("refreshed: " + require('util').inspect(data));
credentials.accessToken = data.access_token
if (data.refresh_token) {
credentials.refreshToken = data.refresh_token
}
credentials.expiresIn = data.expires_in
credentials.expireTime =
data.expires_in + (new Date().getTime() / 1000)
credentials.tokenType = data.token_type
RED.nodes.addCredentials(node.id, credentials)
if (typeof cb !== undefined) {
cb()
}
})
}
GoogleNode.prototype.request = function (req, retries, cb) {
var node = this
if (typeof retries === 'function') {
cb = retries
retries = 1
}
if (typeof req !== 'object') {
req = { url: req }
}
req.method = req.method || 'GET'
if (!req.hasOwnProperty('json')) {
req.json = true
}
// always set access token to the latest ignoring any already present
req.auth = { bearer: this.credentials.accessToken }
// console.log(require('util').inspect(req));
if (!this.credentials.expireTime ||
this.credentials.expireTime < (new Date().getTime() / 1000)) {
if (retries === 0) {
node.error(RED._('too many refresh attempts, giving up'))
cb(RED._('too many refresh attempts, giving up'))
return
}
node.warn(RED._('trying to refresh token due to expiry'))
node.refreshToken(function (err) {
if (err) {
return
}
node.request(req, 0, cb)
})
return
}
request(req, function (err, result, data) {
if (err) {
// handled in callback
} else if (result.statusCode >= 400) {
data = {
error: {
code: result.statusCode,
message: result.body
}
}
} else if (data.error) {
if (data.error.code === 401 && retries > 0) {
retries--
node.warn(RED._('refreshing access token after 401 error'))
node.refreshToken(function (err) {
if (err) {
return cb(err, null)
}
return node.request(req, retries, cb)
})
}
}
cb(err, data)
})
}
RED.httpAdmin.get('/google-credentials/auth', function (req, res) {
if (!req.query.clientId || !req.query.clientSecret ||
!req.query.id || !req.query.callback) {
res.send(400)
return
}
var node_id = req.query.id
var callback = req.query.callback
var credentials = {
clientId: req.query.clientId,
clientSecret: req.query.clientSecret
}
var gPlusScopes = 'https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile'
var csrfToken = crypto.randomBytes(18).toString('base64').replace(/\//g, '-').replace(/\+/g, '_')
credentials.csrfToken = csrfToken
credentials.callback = callback
res.cookie('csrf', csrfToken)
res.redirect(url.format({
protocol: 'https',
hostname: 'accounts.google.com',
pathname: '/o/oauth2/auth',
query: {
response_type: 'code',
client_id: credentials.clientId,
state: node_id + ':' + csrfToken,
access_type: 'offline',
approval_prompt: 'force',
scope: 'profile https://www.googleapis.com/auth/calendar ' + gPlusScopes,
// TODO: include_granted_scopes: 'true', ?
redirect_uri: callback
}
}))
RED.nodes.addCredentials(node_id, credentials)
})
RED.httpAdmin.get('/google-credentials/auth/callback', function (req, res) {
if (req.query.error) {
return res.send(RED._('ERROR: __error__: __description__', {error: req.query.error, description: req.query.error_description}))
}
var state = req.query.state.split(':')
var node_id = state[0]
var credentials = RED.nodes.getCredentials(node_id)
if (!credentials || !credentials.clientId || !credentials.clientSecret) {
console.log('credentials not present?')
return res.send(RED._('ERROR: no credentials - should never happen'))
}
if (state[1] !== credentials.csrfToken) {
return res.status(401).send(
RED._('CSRF token mismatch, possible cross-site request forgery attempt.')
)
}
request.post({
url: 'https://accounts.google.com/o/oauth2/token',
json: true,
form: {
grant_type: 'authorization_code',
code: req.query.code,
client_id: credentials.clientId,
client_secret: credentials.clientSecret,
redirect_uri: credentials.callback
}
}, function (err, result, data) {
if (err) {
console.log('request error:' + err)
return res.send(RED._('Something went wrong in the authentication process. Please try again.'))
}
if (data.error) {
console.log('oauth error: ' + data.error)
return res.send(RED._('Something went wrong in the authentication process. Please try again.'))
}
credentials.accessToken = data.access_token
credentials.refreshToken = data.refresh_token
credentials.expiresIn = data.expires_in
credentials.expireTime =
data.expires_in + (new Date().getTime() / 1000)
credentials.tokenType = data.token_type
delete credentials.csrfToken
delete credentials.callback
RED.nodes.addCredentials(node_id, credentials)
request.get({
url: 'https://www.googleapis.com/plus/v1/people/me',
json: true,
auth: { bearer: credentials.accessToken }
}, function (err, result, data) {
if (err) {
console.log('fetching google profile failed: ' + err)
return res.send('auth worked but profile fetching failed')
}
if (data.error) {
console.log('fetching google profile failed: ' +
data.error.message)
return res.send(RED._('auth worked but profile fetching failed'))
}
credentials.displayName = data.displayName
RED.nodes.addCredentials(node_id, credentials)
res.send(RED._('Authorised - you can close this window and return to prisme-flow'))
})
})
})
function GoogleAPINode (n) {
RED.nodes.createNode(this, n)
}
RED.nodes.registerType('google-api-config', GoogleAPINode, {
credentials: {
key: { type: 'password' }
}
})
GoogleAPINode.prototype.request = function (req, cb) {
if (typeof req !== 'object') {
req = { url: req }
}
req.method = req.method || 'GET'
if (!req.hasOwnProperty('json')) {
req.json = true
}
if (!req.qs) {
req.qs = {}
}
req.qs.key = this.credentials.key
return request(req, function (err, result, data) {
if (err) {
return cb(err.toString(), null)
}
if (result.statusCode >= 400) {
return cb(RED._('HTTP Error __statusCode__', {statusCode: result.statusCode}), data)
}
if (data && data.status !== 'OK') {
var error = RED._('HTTP Error __statusCode__', {status: data.status})
if (data.error_message) {
error += ': ' + data.error_message
}
return cb(error, data)
}
return cb(null, data)
})
}
}