From a8785f9a4dd1908fdc18123bea12efec13a828dc Mon Sep 17 00:00:00 2001 From: programmer Date: Sun, 10 Jul 2022 16:37:47 -0700 Subject: [PATCH 1/4] Added oauth2 to authenticate --- src/splitwise.js | 86 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 10 deletions(-) diff --git a/src/splitwise.js b/src/splitwise.js index 1548b46..93e8800 100644 --- a/src/splitwise.js +++ b/src/splitwise.js @@ -1,5 +1,6 @@ const { OAuth2 } = require('oauth'); const querystring = require('querystring'); +const crypto = require("crypto"); const { promisify } = require('es6-promisify'); const validate = require('validate.js'); @@ -517,7 +518,7 @@ const getEndpointMethodGenerator = (logger, accessTokenPromise, defaultIDs, oaut let url = `${endpoint}/${id}`; // Get the access token - let resultPromise = accessTokenPromise; + let resultPromise = accessTokenPromise(); resultPromise.then( () => { @@ -600,7 +601,13 @@ const getEndpointMethodGenerator = (logger, accessTokenPromise, defaultIDs, oaut */ class Splitwise { constructor(options = {}) { - const { consumerKey, consumerSecret, accessToken } = options; + + const { consumerKey, consumerSecret, accessToken, useOauth2 = false, redirect_uri = null } = options; + const SPLITWISE_ENDPOINTS = { + "baseUrl": 'https://secure.splitwise.com/', + "authorizeUrl": 'oauth/authorize', + "accessTokenUrl": 'oauth/token', + } const defaultIDs = { groupID: options.group_id, userID: options.user_id, @@ -614,28 +621,79 @@ class Splitwise { logger({ level: LOG_LEVELS.ERROR, message }); throw new Error(message); } + // check if no redirect url supplied to useOauth2 + if (useOauth2 && !redirect_uri) { + const message = 'Redirect url required for OAuth2'; + logger({ level: LOG_LEVELS.ERROR, message }); + throw new Error(message); + } const oauth2 = new OAuth2( consumerKey, consumerSecret, - 'https://secure.splitwise.com/', - null, - 'oauth/token', + SPLITWISE_ENDPOINTS.baseUrl, + useOauth2 ? SPLITWISE_ENDPOINTS.authorizeUrl : null, + SPLITWISE_ENDPOINTS.accessTokenUrl, null ); - const accessTokenPromise = (() => { + this.generateState = () => { + this.state = crypto.randomBytes(20).toString('hex'); + return this.state; + } + + this.getAuthorizationUrl = () => { + if (!useOauth2) return ""; + return oauth2.getAuthorizeUrl({ + redirect_uri, + scope: '', + state: this.generateState(), + response_type: 'code' + }); + } + + this.verifyState = state => { + if (!useOauth2) return true; + return state === this.state; + } + + this.getAccessTokenFromAuthCode = () => { + return new Promise((resolve, reject) => { + if(!this.authCode) + return reject(`No auth code generated yet. Visit ${this.getAuthorizationUrl()} and login. You need a callback url in your project as mentioned in callback url in your registered splitwise app. Then call \`getAccessTokenFromAuthCode()\` to register the access token.`); + if(this.accessToken) return resolve(this.accessToken); + return oauth2.getOAuthAccessToken(this.authCode, { + code: this.authCode, + redirect_uri, + grant_type: 'authorization_code' + }, (err, accessToken) => { + if (err) { + return reject(err); + } + oauth2.useAuthorizationHeaderforGET(true); + this.accessToken = accessToken; + return resolve(true); + }) + }) + } + + + const accessTokenPromise = () => { if (accessToken) { logger({ message: 'using provided access token' }); return Promise.resolve(accessToken); } logger({ message: 'making request for access token' }); return getAccessTokenPromise(logger, oauth2); - })(); + }; + if (!useOauth2) { + // earlier an IIFE. But now, this is called only in case of client_credentials + accessTokenPromise(); + } const generateEndpointMethod = getEndpointMethodGenerator( logger, - accessTokenPromise, + useOauth2 ? this.getAccessTokenFromAuthCode : accessTokenPromise, defaultIDs, oauth2 ); @@ -645,8 +703,16 @@ class Splitwise { R.values(METHODS).forEach((method) => { this[method.methodName] = generateEndpointMethod(method); }); - - this.getAccessToken = () => accessTokenPromise; + if (useOauth2) { + this.getAccessToken = (code, state) => { + if (!this.verifyState(state)) + return Promise.reject(`State verification failed: ${state}`); + this.authCode = code; + return this.getAccessTokenFromAuthCode(); + } + } + else + this.getAccessToken = () => accessTokenPromise; } // Bonus utility method for easily making transactions from one person to one person From 5de1775bf351970fdfd57a72c0e3d835a52de5fd Mon Sep 17 00:00:00 2001 From: programmer Date: Sun, 10 Jul 2022 22:30:06 -0700 Subject: [PATCH 2/4] Documented oauth2 in readme --- README.md | 86 +++++++++++++++++++++++++++++++++++------------- src/splitwise.js | 22 ++++++++----- 2 files changed, 77 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7edfbb3..37dc94e 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,44 @@ Promise.all([ ) ``` +## Using Oauth2.0 + +#### We strongly recommend using this beacuse of higher level of security. Reference to working of [oauth2](https://oauth.net/getting-started/). + +In this example, we create login using oauth2.0. +1. Initialise the splitwise instance with `useOauth2:true`. You will alse need a `redirect_uri` for oauth2. This will be the same as the one you used while creating your app here https://secure.splitwise.com/oauth_clients. + +``` +const Splitwise = require('splitwise'); +const sw = Splitwise({ + consumerKey: 'your key here', + consumerSecret: 'your secret here', + useOauth2: true, + redirect_uri: 'your redirect_uri' +}); +``` +2. Get the authorization url before calling any APIs using `getAuthorizationUrl()`. +``` +const authUrl = sw.getAuthorizationUrl(); +``` +This url will contain a login page returned by splitwise. This url will also contain the `redirect_uri`. This should match with the one you used while registering your app. After the user logs in, splitwise will call your `callback` url that you mentioned while creating your app. This will contain a state and a code in the query params. + +3. Get the access token from the auth code using your callback API. Use the returned code and state to get the access token. +``` +app.get('/callback', (req, res) => { + return sw.getAccessToken(req.query.code, req.query.state) + .then(accessToken => { + return res.json({status: 200, accessToken}); + }) + .catch(err => { + return res.json({status: 500, error: err}); + }) +}); +``` + +Now your access token is registered and it will be used while calling any APIs. You can call library APIs after this like the examples below. + + ## API Reference ### `const sw = Splitwise({...})` @@ -73,6 +111,8 @@ This is the entry point to the package. All of the other methods are in the form |-|-|-| | `consumerKey` | **yes** | Obtained by registering your application | | `consumerSecret` | **yes** | Obtained by registering your application | +| `useOauth2` | no | Whether to use oauth2.0 to authenticate | +| `redirect_uri` | no | Required for `useOauth2`. | | `accessToken` | no | Re-use an existing access token | | `logger` | no | Will be called with info and error messages | | `logLevel` | no | Set to `'error'` to only see error messages | @@ -171,29 +211,29 @@ sw.verbResource({ Without further ado, here is the list of all available methods. In order to see the specifics of which parameters should be passed in, and which data can be expected in response, please refer to the [official API documentation](http://dev.splitwise.com/), or click on the method in question. - [`sw.test()`](http://dev.splitwise.com/dokuwiki/doku.php?id=test) - - [`sw.getCurrencies()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_currencies) - - [`sw.getCategories()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_categories) - - [`sw.parseSentence()`](http://dev.splitwise.com/dokuwiki/doku.php?id=parse_sentence) - - [`sw.getCurrentUser()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_current_user) - - [`sw.getUser()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_user) - - [`sw.updateUser()`](http://dev.splitwise.com/dokuwiki/doku.php?id=update_user) - - [`sw.getGroups()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_groups) - - [`sw.getGroup()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_group) - - [`sw.createGroup()`](http://dev.splitwise.com/dokuwiki/doku.php?id=create_group) - - [`sw.deleteGroup()`](http://dev.splitwise.com/dokuwiki/doku.php?id=delete_group) - - [`sw.addUserToGroup()`](http://dev.splitwise.com/dokuwiki/doku.php?id=add_user_to_group) - - [`sw.removeUserFromGroup()`](http://dev.splitwise.com/dokuwiki/doku.php?id=remove_user_from_group) - - [`sw.getExpenses()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_expenses) - - [`sw.getExpense()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_expense) - - [`sw.createExpense()`](http://dev.splitwise.com/dokuwiki/doku.php?id=create_expense) - - [`sw.updateExpense()`](http://dev.splitwise.com/dokuwiki/doku.php?id=update_expense) - - [`sw.deleteExpense()`](http://dev.splitwise.com/dokuwiki/doku.php?id=delete_expense) - - [`sw.getFriends()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_friends) - - [`sw.getFriend()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_friend) - - [`sw.createFriend()`](http://dev.splitwise.com/dokuwiki/doku.php?id=create_friend) - - [`sw.createFriends()`](http://dev.splitwise.com/dokuwiki/doku.php?id=create_friends) - - [`sw.deleteFriend()`](http://dev.splitwise.com/dokuwiki/doku.php?id=delete_friend) - - [`sw.getNotifications()`](http://dev.splitwise.com/dokuwiki/doku.php?id=get_notifications) + - [`sw.getCurrencies()`](https://dev.splitwise.com/#tag/other/paths/~1get_currencies/get) + - [`sw.getCategories()`](https://dev.splitwise.com/#tag/other/paths/~1get_categories/get) + - [`sw.parseSentence()`](https://dev.splitwise.com/#tag/other/paths/~1parse_sentence/post) + - [`sw.getCurrentUser()`](https://dev.splitwise.com/#tag/users/paths/~1get_current_user/get) + - [`sw.getUser()`](https://dev.splitwise.com/#tag/users/paths/~1get_user~1{id}/get) + - [`sw.updateUser()`](https://dev.splitwise.com/#tag/users/paths/~1update_user~1{id}/post) + - [`sw.getGroups()`](https://dev.splitwise.com/#tag/groups/paths/~1get_groups/get) + - [`sw.getGroup()`](https://dev.splitwise.com/#tag/groups/paths/~1get_group~1{id}/get) + - [`sw.createGroup()`](https://dev.splitwise.com/#tag/groups/paths/~1create_group/post) + - [`sw.deleteGroup()`](https://dev.splitwise.com/#tag/groups/paths/~1delete_group~1{id}/post) + - [`sw.addUserToGroup()`](https://dev.splitwise.com/#tag/groups/paths/~1add_user_to_group/post) + - [`sw.removeUserFromGroup()`](https://dev.splitwise.com/#tag/groups/paths/~1remove_user_from_group/post) + - [`sw.getExpenses()`](https://dev.splitwise.com/#tag/expenses/paths/~1get_expenses/get) + - [`sw.getExpense()`](https://dev.splitwise.com/#tag/expenses/paths/~1get_expense~1{id}/get) + - [`sw.createExpense()`](https://dev.splitwise.com/#tag/expenses/paths/~1create_expense/post) + - [`sw.updateExpense()`](https://dev.splitwise.com/#tag/expenses/paths/~1update_expense~1{id}/post) + - [`sw.deleteExpense()`](https://dev.splitwise.com/#tag/expenses/paths/~1delete_expense~1{id}/post) + - [`sw.getFriends()`](https://dev.splitwise.com/#tag/friends/paths/~1get_friends/get) + - [`sw.getFriend()`](https://dev.splitwise.com/#tag/friends/paths/~1get_friend~1{id}/get) + - [`sw.createFriend()`](https://dev.splitwise.com/#tag/friends/paths/~1create_friend/post) + - [`sw.createFriends()`](https://dev.splitwise.com/#tag/friends/paths/~1create_friends/post) + - [`sw.deleteFriend()`](https://dev.splitwise.com/#tag/friends/paths/~1delete_friend~1{id}/post) + - [`sw.getNotifications()`](https://dev.splitwise.com/#tag/notifications/paths/~1get_notifications/get) - `sw.getMainData()` **NOTE**: Splitwise makes some important notes about their API that booleans and nested parameters don't work. You won't need to worry about this. That is, instead of calling: diff --git a/src/splitwise.js b/src/splitwise.js index 93e8800..5452c72 100644 --- a/src/splitwise.js +++ b/src/splitwise.js @@ -218,6 +218,12 @@ const METHODS = { propName: PROP_NAMES.NOTIFICATIONS, paramNames: ['updated_after', 'limit'], }, + CREATE_COMMENT: { + endpoint: 'create_comment', + methodName: 'createComment', + verb: METHOD_VERBS.POST, + paramNames: ['expense_id', 'content'], + }, GET_MAIN_DATA: { endpoint: 'get_main_data', methodName: 'getMainData', @@ -637,7 +643,7 @@ class Splitwise { null ); - this.generateState = () => { + const generateState = () => { this.state = crypto.randomBytes(20).toString('hex'); return this.state; } @@ -647,20 +653,20 @@ class Splitwise { return oauth2.getAuthorizeUrl({ redirect_uri, scope: '', - state: this.generateState(), + state: generateState(), response_type: 'code' }); } - this.verifyState = state => { + const verifyState = state => { if (!useOauth2) return true; return state === this.state; } - this.getAccessTokenFromAuthCode = () => { + const getAccessTokenFromAuthCode = () => { return new Promise((resolve, reject) => { if(!this.authCode) - return reject(`No auth code generated yet. Visit ${this.getAuthorizationUrl()} and login. You need a callback url in your project as mentioned in callback url in your registered splitwise app. Then call \`getAccessTokenFromAuthCode()\` to register the access token.`); + return reject(`No auth code generated yet. Visit ${this.getAuthorizationUrl()} and login. You need a callback url in your project as mentioned in callback url in your registered splitwise app. Then call \`getAccessToken()\` to register the access token.`); if(this.accessToken) return resolve(this.accessToken); return oauth2.getOAuthAccessToken(this.authCode, { code: this.authCode, @@ -693,7 +699,7 @@ class Splitwise { const generateEndpointMethod = getEndpointMethodGenerator( logger, - useOauth2 ? this.getAccessTokenFromAuthCode : accessTokenPromise, + useOauth2 ? getAccessTokenFromAuthCode : accessTokenPromise, defaultIDs, oauth2 ); @@ -705,10 +711,10 @@ class Splitwise { }); if (useOauth2) { this.getAccessToken = (code, state) => { - if (!this.verifyState(state)) + if (!verifyState(state)) return Promise.reject(`State verification failed: ${state}`); this.authCode = code; - return this.getAccessTokenFromAuthCode(); + return getAccessTokenFromAuthCode(); } } else From c412a3e0a74ce7bb0dcec219b3039213e2472410 Mon Sep 17 00:00:00 2001 From: programmer Date: Mon, 18 Jul 2022 19:02:10 -0700 Subject: [PATCH 3/4] Updated as per PR comments --- README.md | 12 ++++++------ src/splitwise.js | 37 +++++++++++++++++++------------------ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 37dc94e..c3844e2 100644 --- a/README.md +++ b/README.md @@ -59,19 +59,19 @@ Promise.all([ ) ``` -## Using Oauth2.0 +## Using Authorisation code #### We strongly recommend using this beacuse of higher level of security. Reference to working of [oauth2](https://oauth.net/getting-started/). -In this example, we create login using oauth2.0. -1. Initialise the splitwise instance with `useOauth2:true`. You will alse need a `redirect_uri` for oauth2. This will be the same as the one you used while creating your app here https://secure.splitwise.com/oauth_clients. +In this example, we create login using authorization code +1. Initialise the splitwise instance with `grant_type:authorization_code`. You will alse need a `redirect_uri` for this. This will be the same as the one you used while creating your app here https://secure.splitwise.com/oauth_clients. ``` const Splitwise = require('splitwise'); const sw = Splitwise({ consumerKey: 'your key here', consumerSecret: 'your secret here', - useOauth2: true, + grant_type: 'authorization_code', redirect_uri: 'your redirect_uri' }); ``` @@ -111,8 +111,8 @@ This is the entry point to the package. All of the other methods are in the form |-|-|-| | `consumerKey` | **yes** | Obtained by registering your application | | `consumerSecret` | **yes** | Obtained by registering your application | -| `useOauth2` | no | Whether to use oauth2.0 to authenticate | -| `redirect_uri` | no | Required for `useOauth2`. | +| `grant_type` | no | `authorization_code` or `client_credentials`(default) | +| `redirect_uri` | no | Required for `authorization_code`. | | `accessToken` | no | Re-use an existing access token | | `logger` | no | Will be called with info and error messages | | `logLevel` | no | Set to `'error'` to only see error messages | diff --git a/src/splitwise.js b/src/splitwise.js index 5452c72..5485199 100644 --- a/src/splitwise.js +++ b/src/splitwise.js @@ -608,11 +608,11 @@ const getEndpointMethodGenerator = (logger, accessTokenPromise, defaultIDs, oaut class Splitwise { constructor(options = {}) { - const { consumerKey, consumerSecret, accessToken, useOauth2 = false, redirect_uri = null } = options; + const { consumerKey, consumerSecret, accessToken, grant_type = "client_credentials", redirect_uri = null, isAuthorizationCode = grant_type === "authorization_code" } = options; const SPLITWISE_ENDPOINTS = { - "baseUrl": 'https://secure.splitwise.com/', - "authorizeUrl": 'oauth/authorize', - "accessTokenUrl": 'oauth/token', + "path": 'https://secure.splitwise.com/', + "authorizePath": 'oauth/authorize', + "accessTokenPath": 'oauth/token', } const defaultIDs = { groupID: options.group_id, @@ -627,8 +627,8 @@ class Splitwise { logger({ level: LOG_LEVELS.ERROR, message }); throw new Error(message); } - // check if no redirect url supplied to useOauth2 - if (useOauth2 && !redirect_uri) { + // check if no redirect url supplied to isAuthorizationCode + if (isAuthorizationCode && !redirect_uri) { const message = 'Redirect url required for OAuth2'; logger({ level: LOG_LEVELS.ERROR, message }); throw new Error(message); @@ -637,9 +637,9 @@ class Splitwise { const oauth2 = new OAuth2( consumerKey, consumerSecret, - SPLITWISE_ENDPOINTS.baseUrl, - useOauth2 ? SPLITWISE_ENDPOINTS.authorizeUrl : null, - SPLITWISE_ENDPOINTS.accessTokenUrl, + SPLITWISE_ENDPOINTS.path, + isAuthorizationCode ? SPLITWISE_ENDPOINTS.authorizePath : null, + SPLITWISE_ENDPOINTS.accessTokenPath, null ); @@ -649,7 +649,7 @@ class Splitwise { } this.getAuthorizationUrl = () => { - if (!useOauth2) return ""; + if (!isAuthorizationCode) return ""; return oauth2.getAuthorizeUrl({ redirect_uri, scope: '', @@ -659,7 +659,7 @@ class Splitwise { } const verifyState = state => { - if (!useOauth2) return true; + if (!isAuthorizationCode) return true; return state === this.state; } @@ -676,6 +676,7 @@ class Splitwise { if (err) { return reject(err); } + // useAuthorizationHeaderforGET attaches auth in header. Else get requests throw 401 in case of authorization_code for missing access token. oauth2.useAuthorizationHeaderforGET(true); this.accessToken = accessToken; return resolve(true); @@ -684,7 +685,7 @@ class Splitwise { } - const accessTokenPromise = () => { + const getAccessTokenWithClientCredentials = () => { if (accessToken) { logger({ message: 'using provided access token' }); return Promise.resolve(accessToken); @@ -692,14 +693,14 @@ class Splitwise { logger({ message: 'making request for access token' }); return getAccessTokenPromise(logger, oauth2); }; - if (!useOauth2) { - // earlier an IIFE. But now, this is called only in case of client_credentials - accessTokenPromise(); + if (!isAuthorizationCode) { + // earlier an IIFE. But now, this is called only in case of client_credentials. Called by default while initialising so as to acquire a token right away. + getAccessTokenWithClientCredentials(); } const generateEndpointMethod = getEndpointMethodGenerator( logger, - useOauth2 ? getAccessTokenFromAuthCode : accessTokenPromise, + isAuthorizationCode ? getAccessTokenFromAuthCode : getAccessTokenWithClientCredentials, defaultIDs, oauth2 ); @@ -709,7 +710,7 @@ class Splitwise { R.values(METHODS).forEach((method) => { this[method.methodName] = generateEndpointMethod(method); }); - if (useOauth2) { + if (isAuthorizationCode) { this.getAccessToken = (code, state) => { if (!verifyState(state)) return Promise.reject(`State verification failed: ${state}`); @@ -718,7 +719,7 @@ class Splitwise { } } else - this.getAccessToken = () => accessTokenPromise; + this.getAccessToken = () => getAccessTokenWithClientCredentials; } // Bonus utility method for easily making transactions from one person to one person From 2dbfdd2c1c0a6d1780f6017efc148c63dee93681 Mon Sep 17 00:00:00 2001 From: programmer Date: Tue, 2 Aug 2022 02:56:04 -0700 Subject: [PATCH 4/4] State verification upto the client. Returning access token --- README.md | 10 ++++++++++ src/splitwise.js | 26 ++++++++------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c3844e2..131c6fe 100644 --- a/README.md +++ b/README.md @@ -73,8 +73,11 @@ const sw = Splitwise({ consumerSecret: 'your secret here', grant_type: 'authorization_code', redirect_uri: 'your redirect_uri' + state: 'your state' // OPTIONAL. But we strongly recomend to have a state parameter and verify it later on as mentioned below }); +// Your state can be any random string or a securely generated crypto string like this `crypto.randomBytes(20).toString('hex')` ``` + 2. Get the authorization url before calling any APIs using `getAuthorizationUrl()`. ``` const authUrl = sw.getAuthorizationUrl(); @@ -84,6 +87,11 @@ This url will contain a login page returned by splitwise. This url will also con 3. Get the access token from the auth code using your callback API. Use the returned code and state to get the access token. ``` app.get('/callback', (req, res) => { + // OPTIONAL: if you want to check the state parameter, you can do so here. We strongly recomend to do this. + if(req.query.state !== CONFIG.state) { + // CONFIG.state if the state you supplied in the splitwise constructor + return res.json({ status: 401, message: "Invalid state" }); + } return sw.getAccessToken(req.query.code, req.query.state) .then(accessToken => { return res.json({status: 200, accessToken}); @@ -113,6 +121,7 @@ This is the entry point to the package. All of the other methods are in the form | `consumerSecret` | **yes** | Obtained by registering your application | | `grant_type` | no | `authorization_code` or `client_credentials`(default) | | `redirect_uri` | no | Required for `authorization_code`. | +| `state` | no | Strongly recommended for `authorization_code`. | | `accessToken` | no | Re-use an existing access token | | `logger` | no | Will be called with info and error messages | | `logLevel` | no | Set to `'error'` to only see error messages | @@ -179,6 +188,7 @@ const sw = Splitwise({ }) // do stuff with `sw` ``` +To get the Access Token in case of `grant_type=authorization_code`, you can only fetch it after you authorize the app with the authorization url. You will see an error message as `No auth code generated yet.` in this case. ### `sw.createDebt({...})` The endpoint for creating debts is a little awkward to use. If you are in the common scenario of needing to create a simple debt between two individuals, this method will do just that. diff --git a/src/splitwise.js b/src/splitwise.js index 5485199..c1b3b61 100644 --- a/src/splitwise.js +++ b/src/splitwise.js @@ -608,7 +608,7 @@ const getEndpointMethodGenerator = (logger, accessTokenPromise, defaultIDs, oaut class Splitwise { constructor(options = {}) { - const { consumerKey, consumerSecret, accessToken, grant_type = "client_credentials", redirect_uri = null, isAuthorizationCode = grant_type === "authorization_code" } = options; + const { consumerKey, consumerSecret, accessToken, grant_type = "client_credentials", redirect_uri = null, isAuthorizationCode = grant_type === "authorization_code", state = null } = options; const SPLITWISE_ENDPOINTS = { "path": 'https://secure.splitwise.com/', "authorizePath": 'oauth/authorize', @@ -633,7 +633,7 @@ class Splitwise { logger({ level: LOG_LEVELS.ERROR, message }); throw new Error(message); } - + const oauth2 = new OAuth2( consumerKey, consumerSecret, @@ -643,26 +643,16 @@ class Splitwise { null ); - const generateState = () => { - this.state = crypto.randomBytes(20).toString('hex'); - return this.state; - } - this.getAuthorizationUrl = () => { if (!isAuthorizationCode) return ""; return oauth2.getAuthorizeUrl({ redirect_uri, scope: '', - state: generateState(), + state, response_type: 'code' }); } - const verifyState = state => { - if (!isAuthorizationCode) return true; - return state === this.state; - } - const getAccessTokenFromAuthCode = () => { return new Promise((resolve, reject) => { if(!this.authCode) @@ -679,7 +669,7 @@ class Splitwise { // useAuthorizationHeaderforGET attaches auth in header. Else get requests throw 401 in case of authorization_code for missing access token. oauth2.useAuthorizationHeaderforGET(true); this.accessToken = accessToken; - return resolve(true); + return resolve(accessToken); }) }) } @@ -710,16 +700,16 @@ class Splitwise { R.values(METHODS).forEach((method) => { this[method.methodName] = generateEndpointMethod(method); }); + + // Seperate methods for authorization code and client credentials if (isAuthorizationCode) { - this.getAccessToken = (code, state) => { - if (!verifyState(state)) - return Promise.reject(`State verification failed: ${state}`); + this.getAccessToken = code => { this.authCode = code; return getAccessTokenFromAuthCode(); } } else - this.getAccessToken = () => getAccessTokenWithClientCredentials; + this.getAccessToken = getAccessTokenWithClientCredentials; } // Bonus utility method for easily making transactions from one person to one person