diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 365bb1765b..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..ed67e96279 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[] { + const 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 0b4c8452ab..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,16 +19,12 @@ 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 authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string; - const options = { + const options: OptionsWithUri = { method, headers: { - 'Authorization': `token ${credentials.accessToken}`, - 'User-Agent': credentials.user, + 'User-Agent': 'n8n', }, body, qs: query, @@ -35,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 6174a1bd47..81befb410c 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: 'githubOAuth2Api', + 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',