-
Notifications
You must be signed in to change notification settings - Fork 7.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide scopes for custom node operations #3164
Changes from 8 commits
55fb52f
8644860
94631f6
d70a5da
1af98bb
2c3d03b
0e439c1
fdcf0a7
e8e1e60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,6 +168,7 @@ import { ExecutionEntity } from './databases/entities/ExecutionEntity'; | |
import { SharedWorkflow } from './databases/entities/SharedWorkflow'; | ||
import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants'; | ||
import { credentialsController } from './api/credentials.api'; | ||
import { oauth2CredentialController } from './api/oauth2Credential.api'; | ||
import { getInstanceBaseUrl, isEmailSetUp } from './UserManagement/UserManagementHelper'; | ||
|
||
require('body-parser-xml')(bodyParser); | ||
|
@@ -1932,6 +1933,8 @@ class App { | |
// OAuth2-Credential/Auth | ||
// ---------------------------------------- | ||
|
||
this.app.use(`/${this.restEndpoint}/oauth2-credential`, oauth2CredentialController); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In future we can move There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should 😊 can you create a Tech Debt ticket for it? |
||
|
||
// Authorize OAuth Data | ||
this.app.get( | ||
`/${this.restEndpoint}/oauth2-credential/auth`, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* eslint-disable import/no-cycle */ | ||
import express from 'express'; | ||
import { UserSettings } from 'n8n-core'; | ||
import { LoggerProxy } from 'n8n-workflow'; | ||
import { ResponseHelper } from '..'; | ||
import { RESPONSE_ERROR_MESSAGES } from '../constants'; | ||
import { CredentialsHelper } from '../CredentialsHelper'; | ||
import { getLogger } from '../Logger'; | ||
import { OAuthRequest } from '../requests'; | ||
|
||
export const oauth2CredentialController = express.Router(); | ||
|
||
/** | ||
* Initialize Logger if needed | ||
*/ | ||
oauth2CredentialController.use((req, res, next) => { | ||
try { | ||
LoggerProxy.getInstance(); | ||
} catch (error) { | ||
LoggerProxy.init(getLogger()); | ||
} | ||
next(); | ||
}); | ||
|
||
/** | ||
* GET /oauth2-credential/scopes | ||
*/ | ||
oauth2CredentialController.get( | ||
'/scopes', | ||
ResponseHelper.send(async (req: OAuthRequest.OAuth2Credential.Scopes): Promise<string[]> => { | ||
const { credentialType: type } = req.query; | ||
|
||
if (!type) { | ||
LoggerProxy.debug( | ||
'Request for OAuth2 credential scopes failed because of missing credential type in query string', | ||
); | ||
|
||
throw new ResponseHelper.ResponseError( | ||
RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL_TYPE, | ||
undefined, | ||
400, | ||
); | ||
} | ||
|
||
if (!type.endsWith('OAuth2Api')) { | ||
LoggerProxy.debug( | ||
'Request for OAuth2 credential scopes failed because requested credential type is not OAuth2', | ||
); | ||
|
||
throw new ResponseHelper.ResponseError( | ||
RESPONSE_ERROR_MESSAGES.CREDENTIAL_TYPE_NOT_OAUTH2, | ||
undefined, | ||
400, | ||
); | ||
} | ||
|
||
const encryptionKey = await UserSettings.getEncryptionKey(); | ||
|
||
return new CredentialsHelper(encryptionKey).getScopes(type); | ||
}), | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import express from 'express'; | ||
|
||
import * as utils from './shared/utils'; | ||
import * as testDb from './shared/testDb'; | ||
import { RESPONSE_ERROR_MESSAGES } from '../../src/constants'; | ||
|
||
import type { Role } from '../../src/databases/entities/Role'; | ||
import { CredentialTypes, LoadNodesAndCredentials } from '../../src'; | ||
|
||
const SCOPES_ENDPOINT = '/oauth2-credential/scopes'; | ||
|
||
let app: express.Application; | ||
let testDbName = ''; | ||
let globalOwnerRole: Role; | ||
|
||
beforeAll(async () => { | ||
app = utils.initTestServer({ endpointGroups: ['oauth2-credential'], applyAuth: true }); | ||
const initResult = await testDb.init(); | ||
testDbName = initResult.testDbName; | ||
|
||
utils.initConfigFile(); | ||
|
||
globalOwnerRole = await testDb.getGlobalOwnerRole(); | ||
utils.initTestLogger(); | ||
|
||
const loadNodesAndCredentials = LoadNodesAndCredentials(); | ||
await loadNodesAndCredentials.init(); | ||
|
||
const credentialTypes = CredentialTypes(); | ||
await credentialTypes.init(loadNodesAndCredentials.credentialTypes); | ||
}); | ||
|
||
afterAll(async () => { | ||
await testDb.terminate(testDbName); | ||
}); | ||
|
||
describe('OAuth2 scopes', () => { | ||
beforeEach(async () => { | ||
await testDb.truncate(['User'], testDbName); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should return scopes - comma-delimited`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent | ||
.get(SCOPES_ENDPOINT) | ||
.query({ credentialType: 'twistOAuth2Api' }); | ||
|
||
expect(response.statusCode).toBe(200); | ||
|
||
const scopes = response.body.data; | ||
const TWIST_OAUTH2_API_SCOPES_TOTAL = 6; | ||
|
||
expect(Array.isArray(scopes)).toBe(true); | ||
expect(scopes.length).toBe(TWIST_OAUTH2_API_SCOPES_TOTAL); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should return scopes - whitespace-delimited`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent | ||
.get(SCOPES_ENDPOINT) | ||
.query({ credentialType: 'dropboxOAuth2Api' }); | ||
|
||
expect(response.statusCode).toBe(200); | ||
|
||
const scopes = response.body.data; | ||
const DROPBOX_OAUTH2_API_SCOPES_TOTAL = 4; | ||
|
||
expect(Array.isArray(scopes)).toBe(true); | ||
expect(scopes.length).toBe(DROPBOX_OAUTH2_API_SCOPES_TOTAL); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should return scope - non-delimited`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent | ||
.get(SCOPES_ENDPOINT) | ||
.query({ credentialType: 'harvestOAuth2Api' }); | ||
|
||
expect(response.statusCode).toBe(200); | ||
|
||
const scopes = response.body.data; | ||
|
||
expect(Array.isArray(scopes)).toBe(true); | ||
expect(scopes.length).toBe(1); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should fail with missing credential type`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent.get(SCOPES_ENDPOINT); | ||
|
||
expect(response.statusCode).toBe(400); | ||
expect(response.body.message).toBe(RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL_TYPE); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should fail with non-OAuth2 credential type`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent | ||
.get(SCOPES_ENDPOINT) | ||
.query({ credentialType: 'disqusApi' }); | ||
|
||
expect(response.statusCode).toBe(400); | ||
expect(response.body.message).toBe(RESPONSE_ERROR_MESSAGES.CREDENTIAL_TYPE_NOT_OAUTH2); | ||
}); | ||
|
||
test(`GET ${SCOPES_ENDPOINT} should fail with wrong OAuth2 credential type`, async () => { | ||
const ownerShell = await testDb.createUserShell(globalOwnerRole); | ||
const authOwnerShellAgent = utils.createAgent(app, { auth: true, user: ownerShell }); | ||
|
||
const response = await authOwnerShellAgent | ||
.get(SCOPES_ENDPOINT) | ||
.query({ credentialType: 'wrongOAuth2Api' }); | ||
|
||
expect(response.statusCode).toBe(500); | ||
expect(response.body.message).toBe(`${RESPONSE_ERROR_MESSAGES.NO_CREDENTIAL}: wrongOAuth2Api`); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not ideal, but larger refactoring would seem out of scope.