From 997775e54bce90194b9707e5e16a477f369a2ab7 Mon Sep 17 00:00:00 2001 From: Faruk AYDIN Date: Tue, 19 Jul 2022 16:45:47 +0300 Subject: [PATCH] refactor: Adjust twitter authentication to use http client --- packages/backend/package.json | 1 + .../src/apps/twitter/authentication.ts | 110 ++++++++++++++---- yarn.lock | 5 + 3 files changed, 91 insertions(+), 25 deletions(-) diff --git a/packages/backend/package.json b/packages/backend/package.json index 69002543..9c36e63c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -52,6 +52,7 @@ "luxon": "2.3.1", "morgan": "^1.10.0", "nodemailer": "6.7.0", + "oauth-1.0a": "^2.2.6", "objection": "^3.0.0", "octokit": "^1.7.1", "pg": "^8.7.1", diff --git a/packages/backend/src/apps/twitter/authentication.ts b/packages/backend/src/apps/twitter/authentication.ts index 8d35ee01..22b8bf95 100644 --- a/packages/backend/src/apps/twitter/authentication.ts +++ b/packages/backend/src/apps/twitter/authentication.ts @@ -4,25 +4,37 @@ import type { IField, IJSONObject, } from '@automatisch/types'; -import TwitterApi, { TwitterApiTokens } from 'twitter-api-v2'; +import HttpClient from '../../helpers/http-client'; +import OAuth from 'oauth-1.0a'; +import crypto from 'crypto'; +import { URLSearchParams } from 'url'; export default class Authentication implements IAuthentication { appData: IApp; connectionData: IJSONObject; - client: TwitterApi; + + client: HttpClient; + oauthClient: OAuth; + + static baseUrl = 'https://api.twitter.com'; constructor(appData: IApp, connectionData: IJSONObject) { this.appData = appData; this.connectionData = connectionData; - - const clientParams = { - appKey: connectionData.consumerKey, - appSecret: connectionData.consumerSecret, - accessToken: connectionData.accessToken, - accessSecret: connectionData.accessSecret, - } as TwitterApiTokens; - - this.client = new TwitterApi(clientParams); + this.client = new HttpClient({ baseURL: Authentication.baseUrl }); + this.oauthClient = new OAuth({ + consumer: { + key: this.connectionData.consumerKey as string, + secret: this.connectionData.consumerSecret as string, + }, + signature_method: 'HMAC-SHA1', + hash_function(base_string, key) { + return crypto + .createHmac('sha1', key) + .update(base_string) + .digest('base64'); + }, + }); } async createAuthData() { @@ -31,35 +43,83 @@ export default class Authentication implements IAuthentication { ); const callbackUrl = appFields.value; - const authLink = await this.client.generateAuthLink(callbackUrl); - - return { - url: authLink.url, - accessToken: authLink.oauth_token, - accessSecret: authLink.oauth_token_secret, + const requestData = { + url: `${Authentication.baseUrl}/oauth/request_token`, + method: 'POST', + data: { oauth_callback: callbackUrl }, }; + + const authHeader = this.oauthClient.toHeader( + this.oauthClient.authorize(requestData) + ); + + try { + const response = await this.client.post(`/oauth/request_token`, null, { + headers: { ...authHeader }, + }); + + const responseData = Object.fromEntries( + new URLSearchParams(response.data) + ); + + return { + url: `${Authentication.baseUrl}/oauth/authorize?oauth_token=${responseData.oauth_token}`, + accessToken: responseData.oauth_token, + accessSecret: responseData.oauth_token_secret, + }; + } catch (error) { + const errorMessages = error.response.data.errors + .map((error: IJSONObject) => error.message) + .join(' '); + + throw new Error( + `Error occured while verifying credentials: ${errorMessages}` + ); + } } async verifyCredentials() { - const verifiedCredentials = await this.client.login( - this.connectionData.oauthVerifier as string + const verifiedCredentials = await this.client.post( + `/oauth/access_token?oauth_verifier=${this.connectionData.oauthVerifier}&oauth_token=${this.connectionData.accessToken}`, + null + ); + + const responseData = Object.fromEntries( + new URLSearchParams(verifiedCredentials.data) ); return { consumerKey: this.connectionData.consumerKey, consumerSecret: this.connectionData.consumerSecret, - accessToken: verifiedCredentials.accessToken, - accessSecret: verifiedCredentials.accessSecret, - userId: verifiedCredentials.userId, - screenName: verifiedCredentials.screenName, + accessToken: responseData.oauth_token, + accessSecret: responseData.oauth_token_secret, + userId: responseData.user_id, + screenName: responseData.screen_name, }; } async isStillVerified() { try { - await this.client.currentUser(); + const token = { + key: this.connectionData.accessToken as string, + secret: this.connectionData.accessSecret as string, + }; + + const requestData = { + url: `${Authentication.baseUrl}/1.1/account/verify_credentials.json`, + method: 'GET', + }; + + const authHeader = this.oauthClient.toHeader( + this.oauthClient.authorize(requestData, token) + ); + + await this.client.get(`/1.1/account/verify_credentials.json`, { + headers: { ...authHeader }, + }); + return true; - } catch { + } catch (error) { return false; } } diff --git a/yarn.lock b/yarn.lock index 2296e14f..977ec379 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14012,6 +14012,11 @@ nwsapi@^2.2.0: resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== +oauth-1.0a@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz#eadbccdb3bceea412d24586e6f39b2b412f0e491" + integrity sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ== + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"