From 96741460e38d6b8f5c2a4d90b4cf02af44768265 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Sun, 9 Feb 2020 10:11:15 -0500 Subject: [PATCH 1/2] :sparkles: Github OAuth support --- packages/cli/src/Server.ts | 2 +- .../nodes/Github/GenericFunctions.ts | 23 ++++++---- .../nodes-base/nodes/Github/Github.node.ts | 43 ++++++++++++++++--- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 365bb1765b..560636029b 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -979,7 +979,7 @@ class App { // Save the credentials in DB await Db.collections.Credentials!.update(state.cid, newCredentialsData); - res.sendFile(pathResolve('templates/oauth-callback.html')); + res.sendFile(pathResolve('../templates/oauth-callback.html')); }); diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index 0b4c8452ab..b851eefe50 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -17,17 +17,24 @@ import { * @returns {Promise} */ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any - const credentials = this.getCredentials('githubApi'); - if (credentials === undefined) { - throw new Error('No credentials got returned!'); + const githubApiCredentials = this.getCredentials('githubApi'); + const oAuth2ApiCrendetials = this.getCredentials('oAuth2Api'); + let headers = {} + if (githubApiCredentials !== undefined) { + headers = { + Authorization: `token ${githubApiCredentials.accessToken}`, + 'User-Agent': githubApiCredentials.user, + }; + } else { + const { access_token } = oAuth2ApiCrendetials!.oauthTokenData as IDataObject; + headers = { + Authorization: `token ${access_token}`, + 'User-Agent': 'Node js', + }; } - const options = { method, - headers: { - 'Authorization': `token ${credentials.accessToken}`, - 'User-Agent': credentials.user, - }, + headers, body, qs: query, uri: `https://api.github.com${endpoint}`, diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index 6174a1bd47..a8588b0cdd 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -33,9 +33,44 @@ export class Github implements INodeType { { name: 'githubApi', required: true, - } + displayOptions: { + show: { + authentication: [ + 'accessToken', + ], + }, + }, + }, + { + name: 'oAuth2Api', + required: true, + displayOptions: { + show: { + authentication: [ + 'oauth2', + ], + }, + }, + }, ], properties: [ + { + displayName: 'Authentication', + name: 'authentication', + type: 'options', + options: [ + { + name: 'Access Token', + value: 'accessToken', + }, + { + name: 'OAuth2', + value: 'oauth2', + }, + ], + default: 'accessToken', + description: 'The resource to operate on.', + }, { displayName: 'Resource', name: 'resource', @@ -1088,12 +1123,6 @@ export class Github implements INodeType { const items = this.getInputData(); const returnData: IDataObject[] = []; - const credentials = this.getCredentials('githubApi'); - - if (credentials === undefined) { - throw new Error('No credentials got returned!'); - } - // Operations which overwrite the returned data const overwriteDataOperations = [ 'file:create', From c1574a73ac0b2a6cdea7ca60869012096fca4229 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 9 Feb 2020 15:39:14 -0800 Subject: [PATCH 2/2] :zap: Improved Github Oauth support and generic Oauth functionality --- packages/cli/src/Server.ts | 2 +- .../src/components/CredentialsEdit.vue | 59 +++++++++++++------ .../src/components/CredentialsInput.vue | 34 +++++++---- .../GithubOAuth2Api.credentials.ts | 20 +++++++ .../nodes/Github/GenericFunctions.ts | 36 +++++------ .../nodes-base/nodes/Github/Github.node.ts | 2 +- 6 files changed, 104 insertions(+), 49 deletions(-) diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 560636029b..fdc9e94e4e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -979,7 +979,7 @@ class App { // Save the credentials in DB await Db.collections.Credentials!.update(state.cid, newCredentialsData); - res.sendFile(pathResolve('../templates/oauth-callback.html')); + res.sendFile(pathResolve(__dirname, '../../templates/oauth-callback.html')); }); diff --git a/packages/editor-ui/src/components/CredentialsEdit.vue b/packages/editor-ui/src/components/CredentialsEdit.vue index 782e615a33..187953ef52 100644 --- a/packages/editor-ui/src/components/CredentialsEdit.vue +++ b/packages/editor-ui/src/components/CredentialsEdit.vue @@ -34,11 +34,11 @@ import CredentialsInput from '@/components/CredentialsInput.vue'; import { ICredentialsCreatedEvent, ICredentialsDecryptedResponse, - INodeProperties, } from '@/Interface'; import { ICredentialType, + INodeProperties, } from 'n8n-workflow'; import mixins from 'vue-typed-mixins'; @@ -172,6 +172,42 @@ export default mixins( }, }, methods: { + mergeCredentialProperties (mainProperties: INodeProperties[], addProperties: INodeProperties[]): void { + let existingIndex: number; + for (const property of addProperties) { + existingIndex = mainProperties.findIndex(element => element.name === property.name); + + if (existingIndex === -1) { + // Property does not exist yet, so add + mainProperties.push(property); + } else { + // Property exists already, so overwrite + mainProperties[existingIndex] = property; + } + } + }, + getCredentialProperties (name: string): INodeProperties[] { + let credentialsData = this.$store.getters.credentialType(name); + + if (credentialsData === null) { + throw new Error(`Could not find credentials of type: ${name}`); + } + + if (credentialsData.extends === undefined) { + return credentialsData.properties; + } + + const combineProperties = [] as INodeProperties[]; + for (const credentialsTypeName of credentialsData.extends) { + const mergeCredentialProperties = this.getCredentialProperties(credentialsTypeName); + this.mergeCredentialProperties(combineProperties, mergeCredentialProperties); + } + + // The properties defined on the parent credentials take presidence + this.mergeCredentialProperties(combineProperties, credentialsData.properties); + + return combineProperties; + }, getCredentialTypeData (name: string): ICredentialType | null { let credentialData = this.$store.getters.credentialType(name); @@ -179,25 +215,14 @@ export default mixins( return credentialData; } + // TODO: The credential-extend-resolve-logic is currently not needed in the backend + // as the whole credential data gets saved with the defaults. That logic should, + // however, probably also get improved in the future. + // Credentials extends another one. So get the properties of the one it // extends and add them. credentialData = JSON.parse(JSON.stringify(credentialData)); - let existingIndex: number; - for (const credentialTypeName of credentialData.extends) { - const data = this.$store.getters.credentialType(credentialTypeName); - - for (const property of data.properties) { - existingIndex = credentialData.properties.findIndex(element => element.name === property.name); - - if (existingIndex === -1) { - // Property does not exist yet, so add - credentialData.properties.push(property); - } else { - // Property exists already, so overwrite - credentialData.properties[existingIndex] = property; - } - } - } + credentialData.properties = this.getCredentialProperties(credentialData.name); return credentialData; }, diff --git a/packages/editor-ui/src/components/CredentialsInput.vue b/packages/editor-ui/src/components/CredentialsInput.vue index 708f72631e..76aa1c6672 100644 --- a/packages/editor-ui/src/components/CredentialsInput.vue +++ b/packages/editor-ui/src/components/CredentialsInput.vue @@ -41,18 +41,21 @@ - - - {{parameter.displayName}}: - -
- -
-
- - - -
+
+ + + {{parameter.displayName}}: + +
+ +
+
+ + + +
+
+ @@ -239,6 +242,13 @@ export default mixins( return result; }, + displayNodeParameter (parameter: INodeProperties): boolean { + if (parameter.type === 'hidden') { + return false; + } + + return true; + }, async oAuth2CredentialAuthorize () { let url; diff --git a/packages/nodes-base/credentials/GithubOAuth2Api.credentials.ts b/packages/nodes-base/credentials/GithubOAuth2Api.credentials.ts index 0bf12444f2..9466ce447b 100644 --- a/packages/nodes-base/credentials/GithubOAuth2Api.credentials.ts +++ b/packages/nodes-base/credentials/GithubOAuth2Api.credentials.ts @@ -11,6 +11,26 @@ export class GithubOAuth2Api implements ICredentialType { ]; displayName = 'Github OAuth2 API'; properties = [ + { + displayName: 'Authorization URL', + name: 'authUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://github.com/login/oauth/authorize', + required: true, + }, + { + displayName: 'Access Token URL', + name: 'accessTokenUrl', + type: 'hidden' as NodePropertyTypes, + default: 'https://github.com/login/oauth/access_token', + required: true, + }, + { + displayName: 'Scope', + name: 'scope', + type: 'hidden' as NodePropertyTypes, + default: 'repo,admin:repo_hook,admin:org,admin:org_hook,gist,notifications,user,write:packages,read:packages,delete:packages,worfklow', + }, { displayName: 'Auth URI Query Parameters', name: 'authQueryParameters', diff --git a/packages/nodes-base/nodes/Github/GenericFunctions.ts b/packages/nodes-base/nodes/Github/GenericFunctions.ts index b851eefe50..97cc69aba4 100644 --- a/packages/nodes-base/nodes/Github/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Github/GenericFunctions.ts @@ -1,3 +1,5 @@ +import { OptionsWithUri } from 'request'; + import { IExecuteFunctions, IHookFunctions, @@ -17,24 +19,13 @@ import { * @returns {Promise} */ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any - const githubApiCredentials = this.getCredentials('githubApi'); - const oAuth2ApiCrendetials = this.getCredentials('oAuth2Api'); - let headers = {} - if (githubApiCredentials !== undefined) { - headers = { - Authorization: `token ${githubApiCredentials.accessToken}`, - 'User-Agent': githubApiCredentials.user, - }; - } else { - const { access_token } = oAuth2ApiCrendetials!.oauthTokenData as IDataObject; - headers = { - Authorization: `token ${access_token}`, - 'User-Agent': 'Node js', - }; - } - const options = { + const authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string; + + const options: OptionsWithUri = { method, - headers, + headers: { + 'User-Agent': 'n8n', + }, body, qs: query, uri: `https://api.github.com${endpoint}`, @@ -42,7 +33,16 @@ export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, }; try { - return await this.helpers.request(options); + if (authenticationMethod === 'accessToken') { + const credentials = this.getCredentials('githubApi'); + if (credentials === undefined) { + throw new Error('No credentials got returned!'); + } + options.headers!.Authorization = `token ${credentials.accessToken}`; + return await this.helpers.request(options); + } else { + return await this.helpers.requestOAuth.call(this, 'githubOAuth2Api', options); + } } catch (error) { if (error.statusCode === 401) { // Return a clear error diff --git a/packages/nodes-base/nodes/Github/Github.node.ts b/packages/nodes-base/nodes/Github/Github.node.ts index a8588b0cdd..81befb410c 100644 --- a/packages/nodes-base/nodes/Github/Github.node.ts +++ b/packages/nodes-base/nodes/Github/Github.node.ts @@ -42,7 +42,7 @@ export class Github implements INodeType { }, }, { - name: 'oAuth2Api', + name: 'githubOAuth2Api', required: true, displayOptions: { show: {