diff --git a/packages/backend/src/apps/discord/authentication.ts b/packages/backend/src/apps/discord/authentication.ts new file mode 100644 index 00000000..a644c89a --- /dev/null +++ b/packages/backend/src/apps/discord/authentication.ts @@ -0,0 +1,91 @@ +import { URLSearchParams } from 'url'; +import DiscordApi from 'discord.js'; +import axios, { AxiosInstance } from 'axios'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + client?: any + connectionData: any + appData: any + scope: string[] = ['identify', 'email'] + httpClient: AxiosInstance = axios.create({ + baseURL: 'https://discord.com/api/' + }) + + constructor(connectionData: any) { + this.connectionData = connectionData; + this.appData = App.findOneByKey('discord'); + } + + get oauthRedirectUrl() { + return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; + } + + async createAuthData() { + const searchParams = new URLSearchParams({ + client_id: this.connectionData.consumerKey, + redirect_uri: this.oauthRedirectUrl, + response_type: 'code', + scope: this.scope.join(' '), + }); + + const url = `https://discord.com/api/oauth2/authorize?${searchParams.toString()}`; + + return { url }; + } + + async verifyCredentials() { + const params = new URLSearchParams({ + client_id: this.connectionData.consumerKey, + redirect_uri: this.oauthRedirectUrl, + response_type: 'code', + scope: this.scope.join(' '), + client_secret: this.connectionData.consumerSecret, + code: this.connectionData.oauthVerifier, + grant_type: 'authorization_code', + }); + const { data: verifiedCredentials }: any = await this.httpClient.post('/oauth2/token', params.toString()); + + const { + access_token: accessToken, + refresh_token: refreshToken, + expires_in: expiresIn, + scope: scope, + token_type: tokenType, + } = verifiedCredentials; + + const { data: user }: any = await this.httpClient.get('/users/@me', { + headers: { + Authorization: `${tokenType} ${accessToken}`, + }, + }); + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken, + refreshToken, + expiresIn, + scope, + tokenType, + userId: user.id, + screenName: user.username, + email: user.email, + }; + } + + async isStillVerified() { + try { + await this.httpClient.get('/users/@me', { + headers: { + Authorization: `${this.connectionData.tokenType} ${this.connectionData.accessToken}`, + }, + }); + + return true; + } catch { + return false + } + } +} diff --git a/packages/backend/src/apps/discord/index.ts b/packages/backend/src/apps/discord/index.ts index e0cb2e3c..f098a1dc 100644 --- a/packages/backend/src/apps/discord/index.ts +++ b/packages/backend/src/apps/discord/index.ts @@ -1,91 +1,9 @@ -import { URLSearchParams } from 'url'; -import DiscordApi from 'discord.js'; -import axios, { AxiosInstance } from 'axios'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication'; export default class Discord { - client?: any - connectionData: any - appData: any - scope: string[] = ['identify', 'email'] - httpClient: AxiosInstance = axios.create({ - baseURL: 'https://discord.com/api/' - }) + authenticationClient: any constructor(connectionData: any) { - this.connectionData = connectionData; - this.appData = App.findOneByKey('discord'); - } - - get oauthRedirectUrl() { - return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; - } - - async createAuthData() { - const searchParams = new URLSearchParams({ - client_id: this.connectionData.consumerKey, - redirect_uri: this.oauthRedirectUrl, - response_type: 'code', - scope: this.scope.join(' '), - }); - - const url = `https://discord.com/api/oauth2/authorize?${searchParams.toString()}`; - - return { url }; - } - - async verifyCredentials() { - const params = new URLSearchParams({ - client_id: this.connectionData.consumerKey, - redirect_uri: this.oauthRedirectUrl, - response_type: 'code', - scope: this.scope.join(' '), - client_secret: this.connectionData.consumerSecret, - code: this.connectionData.oauthVerifier, - grant_type: 'authorization_code', - }); - const { data: verifiedCredentials }: any = await this.httpClient.post('/oauth2/token', params.toString()); - - const { - access_token: accessToken, - refresh_token: refreshToken, - expires_in: expiresIn, - scope: scope, - token_type: tokenType, - } = verifiedCredentials; - - const { data: user }: any = await this.httpClient.get('/users/@me', { - headers: { - Authorization: `${tokenType} ${accessToken}`, - }, - }); - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken, - refreshToken, - expiresIn, - scope, - tokenType, - userId: user.id, - screenName: user.username, - email: user.email, - }; - } - - async isStillVerified() { - try { - await this.httpClient.get('/users/@me', { - headers: { - Authorization: `${this.connectionData.tokenType} ${this.connectionData.accessToken}`, - }, - }); - - return true; - } catch { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/firebase/authentication.ts b/packages/backend/src/apps/firebase/authentication.ts new file mode 100644 index 00000000..cacf56a4 --- /dev/null +++ b/packages/backend/src/apps/firebase/authentication.ts @@ -0,0 +1,78 @@ +import { google, google as GoogleApi } from 'googleapis'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + oauthClient: any + connectionData: any + appData: any + + scopes: string[] = [ + 'https://www.googleapis.com/auth/datastore', + 'https://www.googleapis.com/auth/firebase', + 'https://www.googleapis.com/auth/user.emails.read', + 'profile', + ] + + constructor(connectionData: any) { + this.appData = App.findOneByKey('firebase'); + this.connectionData = connectionData; + + this.oauthClient = new GoogleApi.auth.OAuth2( + connectionData.consumerKey, + connectionData.consumerSecret, + this.oauthRedirectUrl, + ); + + GoogleApi.options({ auth: this.oauthClient }); + } + + get oauthRedirectUrl() { + return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; + } + + async createAuthData() { + const url = this.oauthClient.generateAuthUrl({ + access_type: 'offline', + scope: this.scopes + }); + + return { url }; + } + + async verifyCredentials() { + const { tokens } = await this.oauthClient.getToken(this.connectionData.oauthVerifier); + this.oauthClient.setCredentials(tokens); + + const people = GoogleApi.people('v1'); + + const { data } = await people.people.get({ + resourceName: 'people/me', + personFields: 'emailAddresses', + }); + + const { emailAddresses, resourceName: userId } = data; + const primaryEmailAddress = emailAddresses.find(emailAddress => emailAddress.metadata.primary); + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken: tokens.access_token, + refreshToken: tokens.refresh_token, + tokenType: tokens.token_type, + expiryDate: tokens.expiry_date, + scope: tokens.scope, + screenName: primaryEmailAddress.value, + userId, + } + } + + async isStillVerified() { + try { + await this.oauthClient.getTokenInfo(this.connectionData.accessToken); + return true; + } catch { + return false + } + } +} diff --git a/packages/backend/src/apps/firebase/index.ts b/packages/backend/src/apps/firebase/index.ts index 88f6b385..110fe945 100644 --- a/packages/backend/src/apps/firebase/index.ts +++ b/packages/backend/src/apps/firebase/index.ts @@ -1,78 +1,9 @@ -import { google, google as GoogleApi } from 'googleapis'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication' export default class Firebase { - oauthClient: any - connectionData: any - appData: any - - scopes: string[] = [ - 'https://www.googleapis.com/auth/datastore', - 'https://www.googleapis.com/auth/firebase', - 'https://www.googleapis.com/auth/user.emails.read', - 'profile', - ] + authenticationClient: any constructor(connectionData: any) { - this.appData = App.findOneByKey('firebase'); - this.connectionData = connectionData; - - this.oauthClient = new GoogleApi.auth.OAuth2( - connectionData.consumerKey, - connectionData.consumerSecret, - this.oauthRedirectUrl, - ); - - GoogleApi.options({ auth: this.oauthClient }); - } - - get oauthRedirectUrl() { - return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; - } - - async createAuthData() { - const url = this.oauthClient.generateAuthUrl({ - access_type: 'offline', - scope: this.scopes - }); - - return { url }; - } - - async verifyCredentials() { - const { tokens } = await this.oauthClient.getToken(this.connectionData.oauthVerifier); - this.oauthClient.setCredentials(tokens); - - const people = GoogleApi.people('v1'); - - const { data } = await people.people.get({ - resourceName: 'people/me', - personFields: 'emailAddresses', - }); - - const { emailAddresses, resourceName: userId } = data; - const primaryEmailAddress = emailAddresses.find(emailAddress => emailAddress.metadata.primary); - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken: tokens.access_token, - refreshToken: tokens.refresh_token, - tokenType: tokens.token_type, - expiryDate: tokens.expiry_date, - scope: tokens.scope, - screenName: primaryEmailAddress.value, - userId, - } - } - - async isStillVerified() { - try { - await this.oauthClient.getTokenInfo(this.connectionData.accessToken); - return true; - } catch { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/flickr/authentication.ts b/packages/backend/src/apps/flickr/authentication.ts new file mode 100644 index 00000000..dc094626 --- /dev/null +++ b/packages/backend/src/apps/flickr/authentication.ts @@ -0,0 +1,68 @@ +import FlickrApi from 'flickr-sdk'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + oauthClient: any + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.oauthClient = new FlickrApi.OAuth(connectionData.consumerKey, connectionData.consumerSecret); + + if (connectionData.accessToken && connectionData.accessSecret) { + this.client = new FlickrApi( + FlickrApi.OAuth.createPlugin( + connectionData.consumerKey, + connectionData.consumerSecret, + connectionData.accessToken, + connectionData.accessSecret, + ) + ); + } + + this.connectionData = connectionData; + this.appData = App.findOneByKey('flickr'); + } + + async createAuthData() { + const appFields = this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl') + const callbackUrl = appFields.value; + + const oauthData = (await this.oauthClient.request(callbackUrl)).body; + const url = await this.oauthClient.authorizeUrl(oauthData.oauth_token, 'delete'); + + return { + accessToken: oauthData.oauth_token, + accessSecret: oauthData.oauth_token_secret, + url: url, + }; + } + + async verifyCredentials() { + const verifiedCredentials = (await this.oauthClient.verify( + this.connectionData.accessToken, + this.connectionData.oauthVerifier, + this.connectionData.accessSecret + )).body; + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken: verifiedCredentials.oauth_token, + accessSecret: verifiedCredentials.oauth_token_secret, + userId: verifiedCredentials.user_nsid, + screenName: verifiedCredentials.fullname + } + } + + async isStillVerified() { + try { + await this.client.test.login(); + return true; + } catch { + return false; + } + } +} diff --git a/packages/backend/src/apps/flickr/index.ts b/packages/backend/src/apps/flickr/index.ts index badc6522..91a129b9 100644 --- a/packages/backend/src/apps/flickr/index.ts +++ b/packages/backend/src/apps/flickr/index.ts @@ -1,68 +1,9 @@ -import FlickrApi from 'flickr-sdk'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication' export default class Flickr { - oauthClient: any - client: any - connectionData: any - appData: any + authenticationClient: any constructor(connectionData: any) { - this.oauthClient = new FlickrApi.OAuth(connectionData.consumerKey, connectionData.consumerSecret); - - if (connectionData.accessToken && connectionData.accessSecret) { - this.client = new FlickrApi( - FlickrApi.OAuth.createPlugin( - connectionData.consumerKey, - connectionData.consumerSecret, - connectionData.accessToken, - connectionData.accessSecret, - ) - ); - } - - this.connectionData = connectionData; - this.appData = App.findOneByKey('flickr'); - } - - async createAuthData() { - const appFields = this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl') - const callbackUrl = appFields.value; - - const oauthData = (await this.oauthClient.request(callbackUrl)).body; - const url = await this.oauthClient.authorizeUrl(oauthData.oauth_token, 'delete'); - - return { - accessToken: oauthData.oauth_token, - accessSecret: oauthData.oauth_token_secret, - url: url, - }; - } - - async verifyCredentials() { - const verifiedCredentials = (await this.oauthClient.verify( - this.connectionData.accessToken, - this.connectionData.oauthVerifier, - this.connectionData.accessSecret - )).body; - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken: verifiedCredentials.oauth_token, - accessSecret: verifiedCredentials.oauth_token_secret, - userId: verifiedCredentials.user_nsid, - screenName: verifiedCredentials.fullname - } - } - - async isStillVerified() { - try { - await this.client.test.login(); - return true; - } catch { - return false; - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/github/authentication.ts b/packages/backend/src/apps/github/authentication.ts new file mode 100644 index 00000000..2d492016 --- /dev/null +++ b/packages/backend/src/apps/github/authentication.ts @@ -0,0 +1,77 @@ +import { + getWebFlowAuthorizationUrl, + exchangeWebFlowCode, + checkToken, +} from '@octokit/oauth-methods'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + connectionData: any + appData: any + scopes: string[] = ['repo'] + + constructor(connectionData: any) { + this.connectionData = connectionData; + this.appData = App.findOneByKey('github'); + } + + get oauthRedirectUrl() { + return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; + } + + async createAuthData() { + const { url } = await getWebFlowAuthorizationUrl({ + clientType: "oauth-app", + clientId: this.connectionData.consumerKey, + redirectUrl: this.oauthRedirectUrl, + scopes: this.scopes, + }); + + return { + url: url, + }; + } + + async verifyCredentials() { + const { data, authentication } = await exchangeWebFlowCode({ + clientType: "oauth-app", + clientId: this.connectionData.consumerKey, + clientSecret: this.connectionData.consumerSecret, + code: this.connectionData.oauthVerifier, + }); + + this.connectionData.accessToken = data.access_token; + + const tokenInfo = await this.getTokenInfo(); + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken: data.access_token, + scope: data.scope, + tokenType: data.token_type, + userId: tokenInfo.data.user.id, + screenName: tokenInfo.data.user.login, + } + } + + async getTokenInfo() { + return checkToken({ + clientType: "oauth-app", + clientId: this.connectionData.consumerKey, + clientSecret: this.connectionData.consumerSecret, + token: this.connectionData.accessToken, + }); + } + + async isStillVerified() { + try { + await this.getTokenInfo(); + + return true; + } catch { + return false; + } + } +} diff --git a/packages/backend/src/apps/github/index.ts b/packages/backend/src/apps/github/index.ts index 9c2a1e51..a2d12eec 100644 --- a/packages/backend/src/apps/github/index.ts +++ b/packages/backend/src/apps/github/index.ts @@ -1,77 +1,9 @@ -import { - getWebFlowAuthorizationUrl, - exchangeWebFlowCode, - checkToken, -} from '@octokit/oauth-methods'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication'; export default class Github { - connectionData: any - appData: any - scopes: string[] = ['repo'] + authenticationClient: any constructor(connectionData: any) { - this.connectionData = connectionData; - this.appData = App.findOneByKey('github'); - } - - get oauthRedirectUrl() { - return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; - } - - async createAuthData() { - const { url } = await getWebFlowAuthorizationUrl({ - clientType: "oauth-app", - clientId: this.connectionData.consumerKey, - redirectUrl: this.oauthRedirectUrl, - scopes: this.scopes, - }); - - return { - url: url, - }; - } - - async verifyCredentials() { - const { data, authentication } = await exchangeWebFlowCode({ - clientType: "oauth-app", - clientId: this.connectionData.consumerKey, - clientSecret: this.connectionData.consumerSecret, - code: this.connectionData.oauthVerifier, - }); - - this.connectionData.accessToken = data.access_token; - - const tokenInfo = await this.getTokenInfo(); - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken: data.access_token, - scope: data.scope, - tokenType: data.token_type, - userId: tokenInfo.data.user.id, - screenName: tokenInfo.data.user.login, - } - } - - async getTokenInfo() { - return checkToken({ - clientType: "oauth-app", - clientId: this.connectionData.consumerKey, - clientSecret: this.connectionData.consumerSecret, - token: this.connectionData.accessToken, - }); - } - - async isStillVerified() { - try { - await this.getTokenInfo(); - - return true; - } catch { - return false; - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/postgresql/authentication.ts b/packages/backend/src/apps/postgresql/authentication.ts new file mode 100644 index 00000000..ecdf2dd9 --- /dev/null +++ b/packages/backend/src/apps/postgresql/authentication.ts @@ -0,0 +1,38 @@ +import { Client } from 'pg'; +import App from '../../models/app'; + +export default class Authentication { + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.client = new Client({ + host: connectionData.host, + port: connectionData.port, + database: connectionData.database, + user: connectionData.username, + password: connectionData.password, + }) + + this.connectionData = connectionData; + this.appData = App.findOneByKey('postgresql'); + } + + async verifyCredentials() { + await this.client.connect() + + return { + screenName: this.connectionData.database + } + } + + async isStillVerified() { + try { + await this.client.connect() + return true; + } catch(error) { + return false + } + } +} diff --git a/packages/backend/src/apps/postgresql/index.ts b/packages/backend/src/apps/postgresql/index.ts index f1229abb..33ecbc6d 100644 --- a/packages/backend/src/apps/postgresql/index.ts +++ b/packages/backend/src/apps/postgresql/index.ts @@ -1,38 +1,9 @@ -import { Client } from 'pg'; -import App from '../../models/app'; +import Authentication from './authentication' export default class PostgreSQL { - client: any - connectionData: any - appData: any + authenticationClient: any; constructor(connectionData: any) { - this.client = new Client({ - host: connectionData.host, - port: connectionData.port, - database: connectionData.database, - user: connectionData.username, - password: connectionData.password, - }) - - this.connectionData = connectionData; - this.appData = App.findOneByKey('postgresql'); - } - - async verifyCredentials() { - await this.client.connect() - - return { - screenName: this.connectionData.database - } - } - - async isStillVerified() { - try { - await this.client.connect() - return true; - } catch(error) { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/smtp/authentication.ts b/packages/backend/src/apps/smtp/authentication.ts new file mode 100644 index 00000000..884005ed --- /dev/null +++ b/packages/backend/src/apps/smtp/authentication.ts @@ -0,0 +1,40 @@ +import nodemailer from 'nodemailer'; +import App from '../../models/app'; + +export default class Authentication { + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.client = nodemailer.createTransport({ + host: connectionData.host, + port: connectionData.port, + secure: connectionData.useTls, + auth: { + user: connectionData.username, + pass: connectionData.password, + }, + }); + + this.connectionData = connectionData; + this.appData = App.findOneByKey('smtp'); + } + + async verifyCredentials() { + await this.client.verify() + + return { + screenName: this.connectionData.username + } + } + + async isStillVerified() { + try { + await this.client.verify() + return true; + } catch(error) { + return false + } + } +} diff --git a/packages/backend/src/apps/smtp/index.ts b/packages/backend/src/apps/smtp/index.ts index 5e65587b..66ee7253 100644 --- a/packages/backend/src/apps/smtp/index.ts +++ b/packages/backend/src/apps/smtp/index.ts @@ -1,40 +1,9 @@ -import nodemailer from 'nodemailer'; -import App from '../../models/app'; +import Authentication from './authentication'; export default class SMTP { - client: any - connectionData: any - appData: any + authenticationClient: any constructor(connectionData: any) { - this.client = nodemailer.createTransport({ - host: connectionData.host, - port: connectionData.port, - secure: connectionData.useTls, - auth: { - user: connectionData.username, - pass: connectionData.password, - }, - }); - - this.connectionData = connectionData; - this.appData = App.findOneByKey('smtp'); - } - - async verifyCredentials() { - await this.client.verify() - - return { - screenName: this.connectionData.username - } - } - - async isStillVerified() { - try { - await this.client.verify() - return true; - } catch(error) { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/twilio/authentication.ts b/packages/backend/src/apps/twilio/authentication.ts new file mode 100644 index 00000000..9af70584 --- /dev/null +++ b/packages/backend/src/apps/twilio/authentication.ts @@ -0,0 +1,37 @@ +import TwilioApi from 'twilio'; +import App from '../../models/app'; + +export default class Authentication { + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.client = TwilioApi(connectionData.accountSid, connectionData.authToken); + + this.connectionData = connectionData; + this.appData = App.findOneByKey('twilio'); + } + + async verifyCredentials() { + await this.verify(); + + return { + screenName: this.connectionData.accountSid + } + } + + async verify() { + try { + await this.client.keys.list({ limit: 1 }) + return true; + } catch(error) { + // Test credentials throw HTTP 403 and thus, we need to have an exception. + return error?.status === 403; + } + } + + async isStillVerified() { + return this.verify(); + } +} diff --git a/packages/backend/src/apps/twilio/index.ts b/packages/backend/src/apps/twilio/index.ts index 163946be..509776d4 100644 --- a/packages/backend/src/apps/twilio/index.ts +++ b/packages/backend/src/apps/twilio/index.ts @@ -1,37 +1,9 @@ -import TwilioApi from 'twilio'; -import App from '../../models/app'; +import Authentication from './authentication'; export default class Twilio { - client: any - connectionData: any - appData: any + authenticationClient: any; constructor(connectionData: any) { - this.client = TwilioApi(connectionData.accountSid, connectionData.authToken); - - this.connectionData = connectionData; - this.appData = App.findOneByKey('twilio'); - } - - async verifyCredentials() { - await this.verify(); - - return { - screenName: this.connectionData.accountSid - } - } - - async verify() { - try { - await this.client.keys.list({ limit: 1 }) - return true; - } catch(error) { - // Test credentials throw HTTP 403 and thus, we need to have an exception. - return error?.status === 403; - } - } - - async isStillVerified() { - return this.verify(); + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/twitch/authentication.ts b/packages/backend/src/apps/twitch/authentication.ts new file mode 100644 index 00000000..a35325ea --- /dev/null +++ b/packages/backend/src/apps/twitch/authentication.ts @@ -0,0 +1,96 @@ +import TwitchApi, { ApiVersions } from 'twitch-js'; +import fetchUtil from 'twitch-js/lib/utils/fetch'; +import App from '../../models/app'; +import Field from '../../types/field'; + +type TwitchTokenResponse = { + accessToken: string; + refreshToken: string; + expiresIn: string; + tokenType: string; +}; + +export default class Authentication { + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.connectionData = connectionData; + this.appData = App.findOneByKey('twitch'); + + if (this.clientOptions.token) { + this.client = new TwitchApi(this.clientOptions); + } + } + + get clientOptions() { + return { + token: this.connectionData.accessToken, + clientId: this.connectionData.consumerKey, + log: { enabled: true } + }; + } + + get oauthRedirectUrl() { + return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; + } + + async createAuthData() { + const { url } = await fetchUtil('https://id.twitch.tv/oauth2/authorize', { + search: { + client_id: this.connectionData.consumerKey, + redirect_uri: this.oauthRedirectUrl, + response_type: 'code', + scope: 'user:read:email', + } + }); + + return { url }; + } + + async verifyCredentials() { + const verifiedCredentials = await fetchUtil('https://id.twitch.tv/oauth2/token', { + method: 'post', + search: { + client_id: this.connectionData.consumerKey, + client_secret: this.connectionData.consumerSecret, + code: this.connectionData.oauthVerifier, + grant_type: 'authorization_code', + redirect_uri: this.oauthRedirectUrl + } + }) as TwitchTokenResponse; + + this.connectionData.accessToken = verifiedCredentials.accessToken; + + const { api } = new TwitchApi(this.clientOptions); + + const { data } = await api.get('users'); + const [user] = data; + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken: verifiedCredentials.accessToken, + refreshToken: verifiedCredentials.refreshToken, + expiresIn: verifiedCredentials.expiresIn, + tokenType: verifiedCredentials.tokenType, + userId: user.id, + screenName: user.displayName, + } + } + + async isStillVerified() { + try { + await fetchUtil('https://id.twitch.tv/oauth2/userinfo', { + headers: { + Authorization: `Bearer ${this.connectionData.accessToken}`, + }, + }); + + return true; + } catch (err) { + return false + } + } +} diff --git a/packages/backend/src/apps/twitch/index.ts b/packages/backend/src/apps/twitch/index.ts index 8c75c1dd..38ba9dbb 100644 --- a/packages/backend/src/apps/twitch/index.ts +++ b/packages/backend/src/apps/twitch/index.ts @@ -1,96 +1,9 @@ -import TwitchApi, { ApiVersions } from 'twitch-js'; -import fetchUtil from 'twitch-js/lib/utils/fetch'; -import App from '../../models/app'; -import Field from '../../types/field'; - -type TwitchTokenResponse = { - accessToken: string; - refreshToken: string; - expiresIn: string; - tokenType: string; -}; +import Authentication from './authentication'; export default class Twitch { - client: any - connectionData: any - appData: any + authenticationClient: any constructor(connectionData: any) { - this.connectionData = connectionData; - this.appData = App.findOneByKey('twitch'); - - if (this.clientOptions.token) { - this.client = new TwitchApi(this.clientOptions); - } - } - - get clientOptions() { - return { - token: this.connectionData.accessToken, - clientId: this.connectionData.consumerKey, - log: { enabled: true } - }; - } - - get oauthRedirectUrl() { - return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; - } - - async createAuthData() { - const { url } = await fetchUtil('https://id.twitch.tv/oauth2/authorize', { - search: { - client_id: this.connectionData.consumerKey, - redirect_uri: this.oauthRedirectUrl, - response_type: 'code', - scope: 'user:read:email', - } - }); - - return { url }; - } - - async verifyCredentials() { - const verifiedCredentials = await fetchUtil('https://id.twitch.tv/oauth2/token', { - method: 'post', - search: { - client_id: this.connectionData.consumerKey, - client_secret: this.connectionData.consumerSecret, - code: this.connectionData.oauthVerifier, - grant_type: 'authorization_code', - redirect_uri: this.oauthRedirectUrl - } - }) as TwitchTokenResponse; - - this.connectionData.accessToken = verifiedCredentials.accessToken; - - const { api } = new TwitchApi(this.clientOptions); - - const { data } = await api.get('users'); - const [user] = data; - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken: verifiedCredentials.accessToken, - refreshToken: verifiedCredentials.refreshToken, - expiresIn: verifiedCredentials.expiresIn, - tokenType: verifiedCredentials.tokenType, - userId: user.id, - screenName: user.displayName, - } - } - - async isStillVerified() { - try { - await fetchUtil('https://id.twitch.tv/oauth2/userinfo', { - headers: { - Authorization: `Bearer ${this.connectionData.accessToken}`, - }, - }); - - return true; - } catch (err) { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/twitter/authentication.ts b/packages/backend/src/apps/twitter/authentication.ts new file mode 100644 index 00000000..49b9a3d3 --- /dev/null +++ b/packages/backend/src/apps/twitter/authentication.ts @@ -0,0 +1,56 @@ +import TwitterApi from 'twitter-api-v2'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + client: any + connectionData: any + appData: any + + constructor(connectionData: any) { + this.client = new TwitterApi({ + appKey: connectionData.consumerKey, + appSecret: connectionData.consumerSecret, + accessToken: connectionData.accessToken, + accessSecret: connectionData.accessSecret + }); + + this.connectionData = connectionData; + this.appData = App.findOneByKey('twitter'); + } + + async createAuthData() { + const appFields = this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl') + const callbackUrl = appFields.value; + + const authLink = await this.client.generateAuthLink(callbackUrl); + + return { + url: authLink.url, + accessToken: authLink.oauth_token, + accessSecret: authLink.oauth_token_secret, + } + } + + async verifyCredentials() { + const verifiedCredentials = await this.client.login(this.connectionData.oauthVerifier) + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken: verifiedCredentials.accessToken, + accessSecret: verifiedCredentials.accessSecret, + userId: verifiedCredentials.userId, + screenName: verifiedCredentials.screenName + } + } + + async isStillVerified() { + try { + await this.client.currentUser(); + return true; + } catch { + return false + } + } +} diff --git a/packages/backend/src/apps/twitter/index.ts b/packages/backend/src/apps/twitter/index.ts index 9416dc28..f3c10d21 100644 --- a/packages/backend/src/apps/twitter/index.ts +++ b/packages/backend/src/apps/twitter/index.ts @@ -1,56 +1,9 @@ -import TwitterApi from 'twitter-api-v2'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication'; export default class Twitter { - client: any - connectionData: any - appData: any + authenticationClient: any constructor(connectionData: any) { - this.client = new TwitterApi({ - appKey: connectionData.consumerKey, - appSecret: connectionData.consumerSecret, - accessToken: connectionData.accessToken, - accessSecret: connectionData.accessSecret - }); - - this.connectionData = connectionData; - this.appData = App.findOneByKey('twitter'); - } - - async createAuthData() { - const appFields = this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl') - const callbackUrl = appFields.value; - - const authLink = await this.client.generateAuthLink(callbackUrl); - - return { - url: authLink.url, - accessToken: authLink.oauth_token, - accessSecret: authLink.oauth_token_secret, - } - } - - async verifyCredentials() { - const verifiedCredentials = await this.client.login(this.connectionData.oauthVerifier) - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken: verifiedCredentials.accessToken, - accessSecret: verifiedCredentials.accessSecret, - userId: verifiedCredentials.userId, - screenName: verifiedCredentials.screenName - } - } - - async isStillVerified() { - try { - await this.client.currentUser(); - return true; - } catch { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/apps/typeform/authentication.ts b/packages/backend/src/apps/typeform/authentication.ts new file mode 100644 index 00000000..8579b057 --- /dev/null +++ b/packages/backend/src/apps/typeform/authentication.ts @@ -0,0 +1,93 @@ +import { URLSearchParams } from 'url'; +import axios, { AxiosInstance } from + 'axios'; +import App from '../../models/app'; +import Field from '../../types/field'; + +export default class Authentication { + client?: any + connectionData: any + appData: any + scope: string[] = [ + 'forms:read', + 'forms:write', + 'webhooks:read', + 'webhooks:write', + 'responses:read', + 'accounts:read', + 'workspaces:read', + ] + httpClient: AxiosInstance = axios.create({ + baseURL: 'https://api.typeform.com' + }) + + constructor(connectionData: any) { + this.connectionData = connectionData; + this.appData = App.findOneByKey('typeform'); + } + + get oauthRedirectUrl() { + return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; + } + + async createAuthData() { + const searchParams = new URLSearchParams({ + client_id: this.connectionData.consumerKey, + redirect_uri: this.oauthRedirectUrl, + scope: this.scope.join(' '), + }); + + const url = `https://api.typeform.com/oauth/authorize?${searchParams.toString()}`; + + return { url }; + } + + async verifyCredentials() { + const params = new URLSearchParams({ + grant_type: 'authorization_code', + code: this.connectionData.oauthVerifier, + client_id: this.connectionData.consumerKey, + client_secret: this.connectionData.consumerSecret, + redirect_uri: this.oauthRedirectUrl, + }); + + const { data: verifiedCredentials }: any = await this.httpClient.post('/oauth/token', params.toString()); + + const { + access_token: accessToken, + expires_in: expiresIn, + token_type: tokenType, + } = verifiedCredentials; + + const { data: user }: any = await this.httpClient.get('/me', { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + return { + consumerKey: this.connectionData.consumerKey, + consumerSecret: this.connectionData.consumerSecret, + accessToken, + expiresIn, + tokenType, + userId: user.user_id, + screenName: user.alias, + email: user.email, + }; + } + + async isStillVerified() { + try { + await this.httpClient.get('/me', { + headers: { + Authorization: `Bearer ${this.connectionData.accessToken}`, + }, + }); + + return true; + } catch { + return false + } + } +} diff --git a/packages/backend/src/apps/typeform/index.ts b/packages/backend/src/apps/typeform/index.ts index 11b13bb3..6ad9b40e 100644 --- a/packages/backend/src/apps/typeform/index.ts +++ b/packages/backend/src/apps/typeform/index.ts @@ -1,93 +1,9 @@ -import { URLSearchParams } from 'url'; -import axios, { AxiosInstance } from - 'axios'; -import App from '../../models/app'; -import Field from '../../types/field'; +import Authentication from './authentication'; export default class Typeform { - client?: any - connectionData: any - appData: any - scope: string[] = [ - 'forms:read', - 'forms:write', - 'webhooks:read', - 'webhooks:write', - 'responses:read', - 'accounts:read', - 'workspaces:read', - ] - httpClient: AxiosInstance = axios.create({ - baseURL: 'https://api.typeform.com' - }) + authenticationClient: any; constructor(connectionData: any) { - this.connectionData = connectionData; - this.appData = App.findOneByKey('typeform'); - } - - get oauthRedirectUrl() { - return this.appData.fields.find((field: Field) => field.key == 'oAuthRedirectUrl').value; - } - - async createAuthData() { - const searchParams = new URLSearchParams({ - client_id: this.connectionData.consumerKey, - redirect_uri: this.oauthRedirectUrl, - scope: this.scope.join(' '), - }); - - const url = `https://api.typeform.com/oauth/authorize?${searchParams.toString()}`; - - return { url }; - } - - async verifyCredentials() { - const params = new URLSearchParams({ - grant_type: 'authorization_code', - code: this.connectionData.oauthVerifier, - client_id: this.connectionData.consumerKey, - client_secret: this.connectionData.consumerSecret, - redirect_uri: this.oauthRedirectUrl, - }); - - const { data: verifiedCredentials }: any = await this.httpClient.post('/oauth/token', params.toString()); - - const { - access_token: accessToken, - expires_in: expiresIn, - token_type: tokenType, - } = verifiedCredentials; - - const { data: user }: any = await this.httpClient.get('/me', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }); - - return { - consumerKey: this.connectionData.consumerKey, - consumerSecret: this.connectionData.consumerSecret, - accessToken, - expiresIn, - tokenType, - userId: user.user_id, - screenName: user.alias, - email: user.email, - }; - } - - async isStillVerified() { - try { - await this.httpClient.get('/me', { - headers: { - Authorization: `Bearer ${this.connectionData.accessToken}`, - }, - }); - - return true; - } catch { - return false - } + this.authenticationClient = new Authentication(connectionData); } } diff --git a/packages/backend/src/graphql/mutations/create-auth-data.ts b/packages/backend/src/graphql/mutations/create-auth-data.ts index ccf7ed99..7648c4e3 100644 --- a/packages/backend/src/graphql/mutations/create-auth-data.ts +++ b/packages/backend/src/graphql/mutations/create-auth-data.ts @@ -19,7 +19,7 @@ const createAuthDataResolver = async (params: Params, req: RequestWithCurrentUse consumerSecret: connection.data.consumerSecret }); - const authLink = await appInstance.createAuthData(); + const authLink = await appInstance.authenticationClient.createAuthData(); await connection.$query().patch({ data: { diff --git a/packages/backend/src/graphql/mutations/verify-connection.ts b/packages/backend/src/graphql/mutations/verify-connection.ts index ef2dae01..0692e46d 100644 --- a/packages/backend/src/graphql/mutations/verify-connection.ts +++ b/packages/backend/src/graphql/mutations/verify-connection.ts @@ -15,7 +15,7 @@ const verifyConnectionResolver = async (params: Params, req: RequestWithCurrentU const appClass = (await import(`../../apps/${connection.key}`)).default; const appInstance = new appClass(connection.data) - const verifiedCredentials = await appInstance.verifyCredentials(); + const verifiedCredentials = await appInstance.authenticationClient.verifyCredentials(); connection = await connection.$query().patchAndFetch({ data: { diff --git a/packages/backend/src/graphql/queries/test-connection.ts b/packages/backend/src/graphql/queries/test-connection.ts index e545d7e9..b2cac07c 100644 --- a/packages/backend/src/graphql/queries/test-connection.ts +++ b/packages/backend/src/graphql/queries/test-connection.ts @@ -16,7 +16,7 @@ const testConnectionResolver = async (params: Params, req: RequestWithCurrentUse const appClass = (await import(`../../apps/${connection.key}`)).default; const appInstance = new appClass(connection.data); - const isStillVerified = await appInstance.isStillVerified(); + const isStillVerified = await appInstance.authenticationClient.isStillVerified(); connection = await connection.$query().patchAndFetch({ verified: isStillVerified