🔀 Merge branch 'RicardoE105-feature/github-oauth' into oauth-support

This commit is contained in:
Jan Oberhauser 2020-02-09 15:40:18 -08:00
commit 6e6ece1bcb
6 changed files with 136 additions and 45 deletions

View file

@ -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'));
});

View file

@ -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;
},

View file

@ -41,18 +41,21 @@
<font-awesome-icon icon="question-circle" />
</el-tooltip>
</div>
<el-row v-for="parameter in credentialProperties" :key="parameter.name" class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
<div slot="content" v-html="parameter.description"></div>
<font-awesome-icon icon="question-circle"/>
</el-tooltip>
</el-col>
<el-col :span="18">
<parameter-input :parameter="parameter" :value="propertyValue[parameter.name]" :path="parameter.name" :isCredential="true" @valueChanged="valueChanged" />
</el-col>
</el-row>
<div v-for="parameter in credentialProperties" :key="parameter.name">
<el-row v-if="displayNodeParameter(parameter)" class="parameter-wrapper">
<el-col :span="6" class="parameter-name">
{{parameter.displayName}}:
<el-tooltip placement="top" class="parameter-info" v-if="parameter.description" effect="light">
<div slot="content" v-html="parameter.description"></div>
<font-awesome-icon icon="question-circle"/>
</el-tooltip>
</el-col>
<el-col :span="18">
<parameter-input :parameter="parameter" :value="propertyValue[parameter.name]" :path="parameter.name" :isCredential="true" @valueChanged="valueChanged" />
</el-col>
</el-row>
</div>
<el-row class="nodes-access-wrapper">
<el-col :span="6" class="headline">
@ -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;

View file

@ -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',

View file

@ -1,3 +1,5 @@
import { OptionsWithUri } from 'request';
import {
IExecuteFunctions,
IHookFunctions,
@ -17,16 +19,12 @@ import {
* @returns {Promise<any>}
*/
export async function githubApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise<any> { // 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

View file

@ -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',