-
Notifications
You must be signed in to change notification settings - Fork 19
Added oauth2 to authenticate and added documentation for it. #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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'); | ||
|
|
||
|
|
@@ -217,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', | ||
|
|
@@ -517,7 +524,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 +607,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', | ||
omkarsk98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "accessTokenUrl": 'oauth/token', | ||
omkarsk98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| const defaultIDs = { | ||
| groupID: options.group_id, | ||
| userID: options.user_id, | ||
|
|
@@ -614,28 +627,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 = (() => { | ||
| const generateState = () => { | ||
| this.state = crypto.randomBytes(20).toString('hex'); | ||
| return this.state; | ||
| } | ||
|
|
||
| this.getAuthorizationUrl = () => { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i would probably do something like adding state as an argument to this method |
||
| if (!useOauth2) return ""; | ||
| return oauth2.getAuthorizeUrl({ | ||
| redirect_uri, | ||
| scope: '', | ||
| state: generateState(), | ||
| response_type: 'code' | ||
| }); | ||
| } | ||
|
|
||
| const verifyState = state => { | ||
| if (!useOauth2) return true; | ||
| return state === this.state; | ||
| } | ||
|
|
||
| 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 \`getAccessToken()\` to register the access token.`); | ||
| if(this.accessToken) return resolve(this.accessToken); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah sorry, I realize now this might not work either. What we have to remember is that with the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @keriwarr Yaa I thought about this. With this, our clients will need to use the
Also, as per you, will this library be used for a project of multiple users? |
||
| return oauth2.getOAuthAccessToken(this.authCode, { | ||
| code: this.authCode, | ||
| redirect_uri, | ||
| grant_type: 'authorization_code' | ||
| }, (err, accessToken) => { | ||
| if (err) { | ||
| return reject(err); | ||
| } | ||
| oauth2.useAuthorizationHeaderforGET(true); | ||
keriwarr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| this.accessToken = accessToken; | ||
| return resolve(true); | ||
| }) | ||
| }) | ||
| } | ||
|
|
||
|
|
||
| const accessTokenPromise = () => { | ||
omkarsk98 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| 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(); | ||
| } | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this code would really benefit from having a clear separation between the client credentials code and the authorization code code. maybe something like a switch statement over the new
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Called by default while initializing so as to acquire a token right away. This is just a single case. We cant get the token right away in case of authorization_code |
||
|
|
||
| const generateEndpointMethod = getEndpointMethodGenerator( | ||
| logger, | ||
| accessTokenPromise, | ||
| useOauth2 ? getAccessTokenFromAuthCode : accessTokenPromise, | ||
| defaultIDs, | ||
| oauth2 | ||
| ); | ||
|
|
@@ -645,8 +709,16 @@ class Splitwise { | |
| R.values(METHODS).forEach((method) => { | ||
| this[method.methodName] = generateEndpointMethod(method); | ||
| }); | ||
|
|
||
| this.getAccessToken = () => accessTokenPromise; | ||
| if (useOauth2) { | ||
| this.getAccessToken = (code, state) => { | ||
| if (!verifyState(state)) | ||
| return Promise.reject(`State verification failed: ${state}`); | ||
| this.authCode = code; | ||
| return getAccessTokenFromAuthCode(); | ||
| } | ||
| } | ||
| else | ||
| this.getAccessToken = () => accessTokenPromise; | ||
|
||
| } | ||
|
|
||
| // Bonus utility method for easily making transactions from one person to one person | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.