import { response as Response } from 'express'; import nock from 'nock'; import { parse as parseQs } from 'querystring'; import { Container } from 'typedi'; import { OAuth2CredentialController } from '@/controllers/oauth/oauth2-credential.controller'; import { CredentialsHelper } from '@/credentials-helper'; import type { CredentialsEntity } from '@/databases/entities/credentials-entity'; import type { User } from '@/databases/entities/user'; import { saveCredential } from '@test-integration/db/credentials'; import { createOwner } from '@test-integration/db/users'; import * as testDb from '@test-integration/test-db'; import type { SuperAgentTest } from '@test-integration/types'; import { setupTestServer } from '@test-integration/utils'; describe('OAuth2 API', () => { const testServer = setupTestServer({ endpointGroups: ['oauth2'] }); let owner: User; let ownerAgent: SuperAgentTest; let credential: CredentialsEntity; const credentialData = { clientId: 'client_id', clientSecret: 'client_secret', authUrl: 'https://test.domain/oauth2/auth', accessTokenUrl: 'https://test.domain/oauth2/token', authQueryParameters: 'access_type=offline', }; CredentialsHelper.prototype.applyDefaultsAndOverwrites = (_, decryptedDataOriginal) => decryptedDataOriginal; beforeAll(async () => { owner = await createOwner(); ownerAgent = testServer.authAgentFor(owner); }); beforeEach(async () => { await testDb.truncate(['SharedCredentials', 'Credentials']); credential = await saveCredential( { name: 'Test', type: 'testOAuth2Api', data: credentialData, }, { user: owner, role: 'credential:owner', }, ); }); it('should return a valid auth URL when the auth flow is initiated', async () => { const controller = Container.get(OAuth2CredentialController); const csrfSpy = jest.spyOn(controller, 'createCsrfState').mockClear(); const response = await ownerAgent .get('/oauth2-credential/auth') .query({ id: credential.id }) .expect(200); const authUrl = new URL(response.body.data); expect(authUrl.hostname).toBe('test.domain'); expect(authUrl.pathname).toBe('/oauth2/auth'); expect(csrfSpy).toHaveBeenCalled(); const [_, state] = csrfSpy.mock.results[0].value; expect(parseQs(authUrl.search.slice(1))).toEqual({ access_type: 'offline', client_id: 'client_id', redirect_uri: 'http://localhost:5678/rest/oauth2-credential/callback', response_type: 'code', state, scope: 'openid', }); }); it('should handle a valid callback without auth', async () => { const controller = Container.get(OAuth2CredentialController); const csrfSpy = jest.spyOn(controller, 'createCsrfState').mockClear(); const renderSpy = (Response.render = jest.fn(function () { this.end(); })); await ownerAgent.get('/oauth2-credential/auth').query({ id: credential.id }).expect(200); const [_, state] = csrfSpy.mock.results[0].value; nock('https://test.domain').post('/oauth2/token').reply(200, { access_token: 'updated_token' }); await testServer.authlessAgent .get('/oauth2-credential/callback') .query({ code: 'auth_code', state }) .expect(200); expect(renderSpy).toHaveBeenCalledWith('oauth-callback'); const updatedCredential = await Container.get(CredentialsHelper).getCredentials( credential, credential.type, ); expect(updatedCredential.getData()).toEqual({ ...credentialData, oauthTokenData: { access_token: 'updated_token' }, }); }); });