From 69e42c8355380d39fa2c25da1bcfaa679f8e47ac Mon Sep 17 00:00:00 2001 From: Chinedu Daniel Date: Sun, 22 Apr 2018 19:33:09 +0100 Subject: [PATCH 1/2] Add routes to enable coinbase oauth functionality --- common/models/account.js | 187 ++++++++++++++++++++++++++++++++----- common/models/account.json | 33 ++++++- npm-debug.log | 48 ++++++++++ 3 files changed, 242 insertions(+), 26 deletions(-) create mode 100644 npm-debug.log diff --git a/common/models/account.js b/common/models/account.js index f2bf7ba..628f80e 100644 --- a/common/models/account.js +++ b/common/models/account.js @@ -20,6 +20,7 @@ import { measureMetric } from '../../lib/statsd'; const axios = require('axios') import web3 from '../../lib/web3' import { generateTwoFactorKey, verifyTwoFactorToken } from '../../lib/two-factor-auth'; +import redisClient from '../../server/boot/redisConnector'; const INVITE_ENABLED = true const DEFAULT_MAX_TTL = 31556926; // 1 year in seconds @@ -89,14 +90,22 @@ module.exports = function(Account) { invite_code: data.invite_code } - if (data.withGoogle) { - accountData.password = uuidv4() + if (data.oauth) accountData.password = uuidv4() + + if (data.oauth && data.oauth_provider === 'google') { accountData.google = { accessToken: data.accessToken, refreshToken: data.refreshToken, serverAuthCode: data.serverAuthCode } } + + if (data.oauth && data.oauth_provider === 'coinbase') { + accountData.coinbase = { + accessToken: data.accessToken, + refreshToken: data.refreshToken + } + } const instance = await Account.create(accountData).catch(e=>err=e) if (err) { err = new Error(err.message); @@ -375,7 +384,7 @@ module.exports = function(Account) { } this.exchangeAccounts.push({ id: uuidv4(), key, secret, name, passphrase, platform }) - + try { let account = await this.save() cb(null, account) @@ -569,7 +578,7 @@ module.exports = function(Account) { 'wallet': 'wallets', 'exchange-account': 'exchangeAccounts' } - + function verifyAccountOwner(owner={}, accountId, type) { const accountType = AccountTypes[type] const accounts = owner[accountType] || [] @@ -965,34 +974,110 @@ module.exports = function(Account) { } } + const oauthSignIn = async (email, cb) => { + const account = await Account.findOne({where: { email }}) + if (!account) { + const err = new Error('Account not found') + err.status = 404 + throw err + } else if (account.email !== email) { + const err = new Error('Unauthorized access attempt') + err.status = 401 + throw err + } + + let token + if (account.two_factor_enabled) { + token = { userId: account.id, twoFactorRequired: true } + } else { + token = await createAccessToken(account) + } + return cb(null, token) + } + + Account.coinbaseOauthProvider = async (code, req, cb) => { + const uri = `${req.protocol}://${req.get('host')}${req.originalUrl}`.split('?code=')[0] + let err; + + const { data } = await axios({ + method: 'POST', + url: 'https://api.coinbase.com/oauth/token', + data: { + code, + grant_type: 'authorization_code', + client_id: process.env.COINBASE_CLIENT_ID, + client_secret: process.env.COINBASE_CLIENT_SECRET, + redirect_uri: uri + } + }).catch(e => err=e) + + if (err) { + console.log(err.response.data) + return cb(err) + } + + const { data: userData } = await axios({ + method: 'GET', + url: 'https://api.coinbase.com/v2/user', + headers: { + Authorization: `Bearer ${data.access_token}` + } + }).catch(e=>err=e); + + if (err) { + console.log(err.response.data) + return cb(err) + } + + redisClient.hmset('coinbase-credentials', { + temp_code: code, + access_token: data.access_token, + refresh_token: data.refresh_token, + email: userData.data.email + }) + + cb() + } + + Account.fetchCoinbaseCredentials = (code, cb) => { + let err; + + if ( err ) { + return cb(err) + } + + return redisClient.hgetall('coinbase-credentials', function (err, obj) { + if (code !== obj.temp_code) { + err = new Error('Invalid coinbase code supplied') + err.status = 422 + return cb(err) + } + + return cb(err, obj) + }) + } + + Account.coinbaseSignIn = (data, cb) => { + try { + const { email } = data + return oauthSignIn(email, cb) + } catch (err) { + console.error('coinbaseSignIn err', err) + return cb(err) + } + } + Account.googleSignIn = async (data, cb) => { try { - const userInfo = await axios({ + const userInfo = await axios({ method: 'GET', url: 'https://www.googleapis.com/userinfo/v2/me', - headers: { + headers: { Authorization: `Bearer ${data.accessToken}` } }) - const account = await Account.findOne({where: {email: userInfo.data.email}}) - if (!account) { - const err = new Error('Account not found') - err.status = 404 - throw err - } else if (account.email !== userInfo.data.email) { - const err = new Error('Unauthorized access attempt') - err.status = 401 - throw err - } - - let token - if (account.two_factor_enabled) { - token = { userId: account.id, twoFactorRequired: true } - } else { - token = await createAccessToken(account) - } - return cb(null, token) + return oauthSignIn(userInfo.data.email, cb) } catch (err) { console.error('googleSignIn err', err) return cb(err) @@ -1117,6 +1202,58 @@ module.exports = function(Account) { description: 'Sign a user in via Google auth', }); + Account.remoteMethod('coinbaseSignIn', { + http: { + path: '/coinbase-signin', + verb: 'post', + }, + accepts: { + arg: 'data', + type: 'object', + http: { + source: 'body', + }, + description: 'Email for coinbase connect' + }, + returns: { + arg: 'accessToken', + type: 'object', + root: true + }, + description: 'Sign a user in via Coinbase auth', + }); + + Account.remoteMethod('coinbaseOauthProvider', { + http: { + path: '/oauth/coinbase', + verb: 'get', + }, + accepts: [ + { arg: 'code', type: 'string', http: { source: 'query' } }, + { arg: 'req', type: 'object', http: { source: 'req' } }, + ], + description: 'Handle coinbase oauth redirect with temporary code', + }); + + Account.remoteMethod('fetchCoinbaseCredentials', { + http: { + path: '/fetch-coinbase-credentials', + verb: 'get', + }, + accepts: { + arg: 'code', + type: 'string', + http: { + source: 'query', + }, + }, + returns: { + type: 'object', + root: true + }, + description: 'Fetch coinbase credentials if temporary code is valid' + }); + Account.remoteMethod('addAddress', { isStatic: false, http: { @@ -1542,4 +1679,4 @@ module.exports = function(Account) { }, description: ['Generates an access token for firebase'], }); -}; +}; \ No newline at end of file diff --git a/common/models/account.json b/common/models/account.json index e4f4d41..0a7962f 100644 --- a/common/models/account.json +++ b/common/models/account.json @@ -30,6 +30,16 @@ } } }, + "coinbase": { + "type": { + "accessToken": { + "type": "string" + }, + "refreshToken": { + "type": "string" + } + } + }, "description": { "type": "string" }, @@ -342,12 +352,33 @@ "permission": "ALLOW", "property": "currencyPreference" }, - { + { "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW", "property": "googleSignIn" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "coinbaseSignIn" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "fetchCoinbaseCredentials" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW", + "property": "coinbaseOauthProvider" } ], "methods": {} diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..9d5b01d --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,48 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/node', +1 verbose cli '/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/npm', +1 verbose cli 'run', +1 verbose cli 'start-dev' ] +2 info using npm@3.10.10 +3 info using node@v6.10.0 +4 verbose run-script [ 'prestart-dev', 'start-dev', 'poststart-dev' ] +5 info lifecycle tokens-api@1.0.0~prestart-dev: tokens-api@1.0.0 +6 silly lifecycle tokens-api@1.0.0~prestart-dev: no script for prestart-dev, continuing +7 info lifecycle tokens-api@1.0.0~start-dev: tokens-api@1.0.0 +8 verbose lifecycle tokens-api@1.0.0~start-dev: unsafe-perm in lifecycle true +9 verbose lifecycle tokens-api@1.0.0~start-dev: PATH: /Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/bin/node-gyp-bin:/Users/chinedudaniel/Workspace/tokens-api/node_modules/.bin:/usr/local/opt/qt/bin:/usr/local/opt/qt@5.7/bin:/usr/local/opt/qt/bin:/usr/local/Cellar/qt@5.5/5.5.1_1/bin:/Users/chinedudaniel/.rbenv/shims:/Users/chinedudaniel/.bin:/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/ImageMagick/bin:/Applications/Postgres.app/Contents/Versions/latest/bin:/Users/chinedudaniel/Library/Android/sdk/tools +10 verbose lifecycle tokens-api@1.0.0~start-dev: CWD: /Users/chinedudaniel/Workspace/tokens-api +11 silly lifecycle tokens-api@1.0.0~start-dev: Args: [ '-c', 'babel-watch server/server.js' ] +12 silly lifecycle tokens-api@1.0.0~start-dev: Returned: code: 1 signal: null +13 info lifecycle tokens-api@1.0.0~start-dev: Failed to exec start-dev script +14 verbose stack Error: tokens-api@1.0.0 start-dev: `babel-watch server/server.js` +14 verbose stack Exit status 1 +14 verbose stack at EventEmitter. (/Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/lib/utils/lifecycle.js:255:16) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at EventEmitter.emit (events.js:191:7) +14 verbose stack at ChildProcess. (/Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/lib/utils/spawn.js:40:14) +14 verbose stack at emitTwo (events.js:106:13) +14 verbose stack at ChildProcess.emit (events.js:191:7) +14 verbose stack at maybeClose (internal/child_process.js:877:16) +14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) +15 verbose pkgid tokens-api@1.0.0 +16 verbose cwd /Users/chinedudaniel/Workspace/tokens-api +17 error Darwin 16.7.0 +18 error argv "/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/node" "/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/npm" "run" "start-dev" +19 error node v6.10.0 +20 error npm v3.10.10 +21 error code ELIFECYCLE +22 error tokens-api@1.0.0 start-dev: `babel-watch server/server.js` +22 error Exit status 1 +23 error Failed at the tokens-api@1.0.0 start-dev script 'babel-watch server/server.js'. +23 error Make sure you have the latest version of node.js and npm installed. +23 error If you do, this is most likely a problem with the tokens-api package, +23 error not with npm itself. +23 error Tell the author that this fails on your system: +23 error babel-watch server/server.js +23 error You can get information on how to open an issue for this project with: +23 error npm bugs tokens-api +23 error Or if that isn't available, you can get their info via: +23 error npm owner ls tokens-api +23 error There is likely additional logging output above. +24 verbose exit [ 1, true ] From a9a6e4a7075a6a9db3277d88efcc21badb36a3f8 Mon Sep 17 00:00:00 2001 From: Chinedu Daniel Date: Tue, 1 May 2018 14:03:25 +0100 Subject: [PATCH 2/2] Address feedback comments on PR --- common/models/account.js | 4 ++-- npm-debug.log | 48 ---------------------------------------- 2 files changed, 2 insertions(+), 50 deletions(-) delete mode 100644 npm-debug.log diff --git a/common/models/account.js b/common/models/account.js index 628f80e..c7fada7 100644 --- a/common/models/account.js +++ b/common/models/account.js @@ -1029,7 +1029,7 @@ module.exports = function(Account) { return cb(err) } - redisClient.hmset('coinbase-credentials', { + redisClient.hmset(`coinbase-credential-${code}`, { temp_code: code, access_token: data.access_token, refresh_token: data.refresh_token, @@ -1046,7 +1046,7 @@ module.exports = function(Account) { return cb(err) } - return redisClient.hgetall('coinbase-credentials', function (err, obj) { + return redisClient.hgetall(`coinbase-credential-${code}`, function (err, obj) { if (code !== obj.temp_code) { err = new Error('Invalid coinbase code supplied') err.status = 422 diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index 9d5b01d..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,48 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ '/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/node', -1 verbose cli '/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/npm', -1 verbose cli 'run', -1 verbose cli 'start-dev' ] -2 info using npm@3.10.10 -3 info using node@v6.10.0 -4 verbose run-script [ 'prestart-dev', 'start-dev', 'poststart-dev' ] -5 info lifecycle tokens-api@1.0.0~prestart-dev: tokens-api@1.0.0 -6 silly lifecycle tokens-api@1.0.0~prestart-dev: no script for prestart-dev, continuing -7 info lifecycle tokens-api@1.0.0~start-dev: tokens-api@1.0.0 -8 verbose lifecycle tokens-api@1.0.0~start-dev: unsafe-perm in lifecycle true -9 verbose lifecycle tokens-api@1.0.0~start-dev: PATH: /Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/bin/node-gyp-bin:/Users/chinedudaniel/Workspace/tokens-api/node_modules/.bin:/usr/local/opt/qt/bin:/usr/local/opt/qt@5.7/bin:/usr/local/opt/qt/bin:/usr/local/Cellar/qt@5.5/5.5.1_1/bin:/Users/chinedudaniel/.rbenv/shims:/Users/chinedudaniel/.bin:/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/ImageMagick/bin:/Applications/Postgres.app/Contents/Versions/latest/bin:/Users/chinedudaniel/Library/Android/sdk/tools -10 verbose lifecycle tokens-api@1.0.0~start-dev: CWD: /Users/chinedudaniel/Workspace/tokens-api -11 silly lifecycle tokens-api@1.0.0~start-dev: Args: [ '-c', 'babel-watch server/server.js' ] -12 silly lifecycle tokens-api@1.0.0~start-dev: Returned: code: 1 signal: null -13 info lifecycle tokens-api@1.0.0~start-dev: Failed to exec start-dev script -14 verbose stack Error: tokens-api@1.0.0 start-dev: `babel-watch server/server.js` -14 verbose stack Exit status 1 -14 verbose stack at EventEmitter. (/Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/lib/utils/lifecycle.js:255:16) -14 verbose stack at emitTwo (events.js:106:13) -14 verbose stack at EventEmitter.emit (events.js:191:7) -14 verbose stack at ChildProcess. (/Users/chinedudaniel/.nvm/versions/node/v6.10.0/lib/node_modules/npm/lib/utils/spawn.js:40:14) -14 verbose stack at emitTwo (events.js:106:13) -14 verbose stack at ChildProcess.emit (events.js:191:7) -14 verbose stack at maybeClose (internal/child_process.js:877:16) -14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5) -15 verbose pkgid tokens-api@1.0.0 -16 verbose cwd /Users/chinedudaniel/Workspace/tokens-api -17 error Darwin 16.7.0 -18 error argv "/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/node" "/Users/chinedudaniel/.nvm/versions/node/v6.10.0/bin/npm" "run" "start-dev" -19 error node v6.10.0 -20 error npm v3.10.10 -21 error code ELIFECYCLE -22 error tokens-api@1.0.0 start-dev: `babel-watch server/server.js` -22 error Exit status 1 -23 error Failed at the tokens-api@1.0.0 start-dev script 'babel-watch server/server.js'. -23 error Make sure you have the latest version of node.js and npm installed. -23 error If you do, this is most likely a problem with the tokens-api package, -23 error not with npm itself. -23 error Tell the author that this fails on your system: -23 error babel-watch server/server.js -23 error You can get information on how to open an issue for this project with: -23 error npm bugs tokens-api -23 error Or if that isn't available, you can get their info via: -23 error npm owner ls tokens-api -23 error There is likely additional logging output above. -24 verbose exit [ 1, true ]