From e3e71fc8e29347ef7958d72d24c4662eb7f71438 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Fri, 12 May 2023 19:39:17 -0700 Subject: [PATCH] #389 - Replaced verified with verified per env --- requests/Switcher API.postman_collection.json | 19 ++-- src/api-docs/paths/path-config.js | 8 +- src/api-docs/schemas/config.js | 7 +- src/client/relay/index.js | 18 +++- src/client/resolvers.js | 2 +- src/models/config.js | 11 ++- src/routers/config.js | 6 +- src/services/config.js | 14 +-- tests/config-relay.test.js | 93 ++++++++++++------- tests/relay.test.js | 4 +- 10 files changed, 113 insertions(+), 69 deletions(-) diff --git a/requests/Switcher API.postman_collection.json b/requests/Switcher API.postman_collection.json index d5fe71b..d7a2f6c 100644 --- a/requests/Switcher API.postman_collection.json +++ b/requests/Switcher API.postman_collection.json @@ -3786,7 +3786,7 @@ "variable": [ { "key": "config", - "value": "5e0eceb66f4f994eac9007b2" + "value": "5f504cc2aaea6121c4a35bfc" } ] } @@ -3815,7 +3815,7 @@ } }, "url": { - "raw": "{{url}}/config/relay/verify/:config?code=", + "raw": "{{url}}/config/relay/verify/:config/:environment", "host": [ "{{url}}" ], @@ -3823,18 +3823,17 @@ "config", "relay", "verify", - ":config" - ], - "query": [ - { - "key": "code", - "value": "" - } + ":config", + ":environment" ], "variable": [ { "key": "config", - "value": "5e0eceb66f4f994eac9007b2" + "value": "5f504cc2aaea6121c4a35bfc" + }, + { + "key": "environment", + "value": "default" } ] } diff --git a/src/api-docs/paths/path-config.js b/src/api-docs/paths/path-config.js index 90da70a..4029934 100644 --- a/src/api-docs/paths/path-config.js +++ b/src/api-docs/paths/path-config.js @@ -302,18 +302,18 @@ export default { } } }, - '/config/relay/verify/{id}': { + '/config/relay/verify/{id}/{env}': { patch: { tags: ['Config'], - description: 'Verify Config Relay ownership based on given verification code', + description: 'Verify Config Relay ownership', security: [{ bearerAuth: [] }], parameters: [ pathParameter('id', 'Config ID', true), - queryParameter('code', 'Verification code', true, 'string') + pathParameter('env', 'Environment name', true) ], responses: { '200': { - description: 'Config Relay verification code generated', + description: 'Verify Config Relay using [GET] endpoint/verify', content: { 'application/json': { schema: { diff --git a/src/api-docs/schemas/config.js b/src/api-docs/schemas/config.js index 4e83f7f..627df82 100644 --- a/src/api-docs/schemas/config.js +++ b/src/api-docs/schemas/config.js @@ -48,8 +48,11 @@ export const relay = { description: 'Generated string used to verify Relay endpoint ownership' }, verified: { - type: 'boolean', - description: 'Valid when true (default: false)' + type: 'object', + additionalProperties: { + type: 'boolean' + }, + description: 'Defines when Relay endpoint is verified' } } }; diff --git a/src/client/relay/index.js b/src/client/relay/index.js index e89b37f..e25d2c7 100644 --- a/src/client/relay/index.js +++ b/src/client/relay/index.js @@ -30,6 +30,15 @@ export async function resolveValidation(relay, entry, environment) { }; } +export async function resolveVerification(relay, environment) { + const endpoint = relay.endpoint.get(environment)?.replace(/\/$/, ''); + const url = `${endpoint?.substring(0, endpoint.lastIndexOf('/'))}/verify`; + const header = createHeader(relay.auth_prefix, relay.auth_token.get(environment)); + const response = await get(url, `?code=${relay.verification_code}`, header); + + return response.data?.code; +} + async function post(url, data, headers) { try { return await axios.post(url, data, headers); @@ -68,9 +77,12 @@ function createHeader(auth_prefix, auth_token, environment) { headers['Content-Type'] = 'application/json'; - if (auth_token && environment in auth_token && - auth_prefix && auth_token[environment]) { - headers['Authorization'] = `${auth_prefix} ${auth_token[environment]}`; + if (environment) { + if (auth_token && environment in auth_token && auth_prefix) { + headers['Authorization'] = `${auth_prefix} ${auth_token[environment]}`; + } + } else if (auth_token && auth_prefix) { + headers['Authorization'] = `${auth_prefix} ${auth_token}`; } return { diff --git a/src/client/resolvers.js b/src/client/resolvers.js index 3e2548d..8e9d061 100644 --- a/src/client/resolvers.js +++ b/src/client/resolvers.js @@ -174,7 +174,7 @@ async function resolveRelay(config, environment, entry, response) { try { if (config.relay?.activated[environment]) { isRelayValid(config.relay); - isRelayVerified(config.relay); + isRelayVerified(config.relay, environment); if (config.relay.type === RelayTypes.NOTIFICATION) { resolveNotification(config.relay, entry, environment); diff --git a/src/models/config.js b/src/models/config.js index 0c0ab13..1d6a31f 100644 --- a/src/models/config.js +++ b/src/models/config.js @@ -102,8 +102,9 @@ const configSchema = new mongoose.Schema({ type: String }, verified: { - type: Boolean, - default: false + type: Map, + of: Boolean, + default: new Map() } } }, { @@ -156,11 +157,11 @@ async function recordConfigHistory(config, modifiedField) { } function hasRelayEndpointUpdates(config, modifiedField) { - const hasUpdate = modifiedField.filter(field => field.indexOf('relay.endpoint') >= 0); + const hasUpdate = modifiedField.filter(field => field.indexOf('relay.endpoint.') >= 0); if (hasUpdate.length) { - config.relay.verified = false; - config.relay.verification_code = undefined; + const environment = hasUpdate[0].split('.')[2]; + config.relay.verified.set(environment, false); } } diff --git a/src/routers/config.js b/src/routers/config.js index eed69e4..8c96694 100644 --- a/src/routers/config.js +++ b/src/routers/config.js @@ -221,12 +221,12 @@ router.patch('/config/relay/verificationCode/:id', auth, [ } }); -router.patch('/config/relay/verify/:id', auth, [ +router.patch('/config/relay/verify/:id/:env', auth, [ check('id').isMongoId(), - query('code').isAscii() + check('env').isLength({ min: 1 }) ], validate, async (req, res) => { try { - const result = await Services.verifyRelay(req.params.id, req.query.code, req.admin); + const result = await Services.verifyRelay(req.params.id, req.params.env, req.admin); res.send({ status: result }); } catch (e) { responseException(res, e, 500); diff --git a/src/services/config.js b/src/services/config.js index 6eb98db..021f3cd 100644 --- a/src/services/config.js +++ b/src/services/config.js @@ -10,6 +10,7 @@ import { checkSwitcher } from '../external/switcher-api-facade'; import { BadRequestError, NotFoundError } from '../exceptions'; import { checkEnvironmentStatusChange_v2 } from '../middleware/validators'; import { getComponentById, getComponents } from './component'; +import { resolveVerification } from '../client/relay'; async function verifyAddComponentInput(configId, admin) { const config = await getConfigById(configId); @@ -251,6 +252,7 @@ export async function removeRelay(id, env, admin) { config.relay.activated.delete(env); config.relay.endpoint.delete(env); config.relay.auth_token.delete(env); + config.relay.verified.delete(env); } else { config.relay = {}; } @@ -268,17 +270,17 @@ export async function getRelayVerificationCode(id, admin) { config.updatedBy = admin.email; config.relay.verification_code = randomUUID(); - config.relay.verified = false; return config.save(); } -export async function verifyRelay(id, code, admin) { +export async function verifyRelay(id, env, admin) { let config = await getConfigById(id); config = await verifyOwnership(admin, config, config.domain, ActionTypes.UPDATE, RouterTypes.CONFIG); - if (!config.relay.verified && Object.is(config.relay.verification_code, code)) { - config.relay.verified = true; + const code = await resolveVerification(config.relay, env); + if (!config.relay.verified?.get(env) && Object.is(config.relay.verification_code, code)) { + config.relay.verified.set(env, true); await config.save(); return 'verified'; } @@ -299,12 +301,12 @@ export function isRelayValid(relay) { throw new BadRequestError('HTTPS required'); } -export function isRelayVerified(relay) { +export function isRelayVerified(relay, environment) { const bypass = process.env.RELAY_BYPASS_VERIFICATION === 'true' || false; if (bypass) return; - if (!relay.verified) + if (!relay.verified[environment]) throw new BadRequestError('Relay not verified'); } \ No newline at end of file diff --git a/tests/config-relay.test.js b/tests/config-relay.test.js index 1f737b1..6a0d8df 100644 --- a/tests/config-relay.test.js +++ b/tests/config-relay.test.js @@ -1,5 +1,7 @@ import mongoose from 'mongoose'; import request from 'supertest'; +import sinon from 'sinon'; +import axios from 'axios'; import app from '../src/app'; import { Config } from '../src/models/config'; import { @@ -8,6 +10,7 @@ import { domainId, configId1, } from './fixtures/db_api'; +import { EnvType } from '../src/models/environment'; afterAll(async () => { await new Promise(resolve => setTimeout(resolve, 1000)); @@ -83,10 +86,10 @@ describe('Testing relay verification', () => { // That // Config has a verified Relay let config = await Config.findById(configId1).exec(); - config.relay.verified = true; + config.relay.verified.set(EnvType.DEFAULT, true); config.relay.verification_code = '123'; await config.save(); - expect(config.relay.verified).toBe(true); + expect(config.relay.verified.get(EnvType.DEFAULT)).toBe(true); // Test: change endpoint bodyRelay.endpoint.default = 'https://localhost:8080'; @@ -98,8 +101,8 @@ describe('Testing relay verification', () => { .send(bodyRelay).expect(200); config = await Config.findById(configId1).exec(); - expect(config.relay.verified).toBe(false); - expect(config.relay.verification_code).toBe(undefined); + expect(config.relay.verified.get(EnvType.DEFAULT)).toBe(false); + expect(config.relay.verification_code).toBe('123'); }); test('CONFIG_RELAY_SUITE - Should NOT reset Relay verified flag when changing anything but endpoint', async () => { @@ -115,10 +118,10 @@ describe('Testing relay verification', () => { // That // Config has a verified Relay let config = await Config.findById(configId1).exec(); - config.relay.verified = true; + config.relay.verified.set(EnvType.DEFAULT, true); config.relay.verification_code = '123'; await config.save(); - expect(config.relay.verified).toBe(true); + expect(config.relay.verified.get(EnvType.DEFAULT)).toBe(true); // Test: change endpoint bodyRelay.method = 'GET'; @@ -130,7 +133,7 @@ describe('Testing relay verification', () => { .send(bodyRelay).expect(200); config = await Config.findById(configId1).exec(); - expect(config.relay.verified).toBe(true); + expect(config.relay.verified.get(EnvType.DEFAULT)).toBe(true); expect(config.relay.verification_code).toBe('123'); }); @@ -163,11 +166,11 @@ describe('Testing relay association', () => { // DB validation - document updated const config = await Config.findById(configId1).lean().exec(); - expect(config.relay.verified).toEqual(false); + expect(config.relay.verified[EnvType.DEFAULT]).toEqual(false); expect(config.relay.verification_code).toEqual(undefined); - expect(config.relay.activated['default']).toEqual(true); - expect(config.relay.endpoint['default']).toBe('http://localhost:3001'); - expect(config.relay.auth_token['default']).toEqual('123'); + expect(config.relay.activated[EnvType.DEFAULT]).toEqual(true); + expect(config.relay.endpoint[EnvType.DEFAULT]).toBe('http://localhost:3001'); + expect(config.relay.auth_token[EnvType.DEFAULT]).toEqual('123'); }); test('CONFIG_RELAY_SUITE - Should NOT configure new Relay - Config not found', async () => { @@ -331,6 +334,17 @@ describe('Testing relay association', () => { }); test('CONFIG_RELAY_SUITE - Should remove all Relays', async () => { + // Given - adding relay to be removed later on + await request(app) + .patch(`/config/updateRelay/${configId1}`) + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send({ + activated: { default: true }, + endpoint: { default: 'http://localhost:7000' }, + auth_token: { default: 'abcd' } + }).expect(200); + + // Test await request(app) .patch(`/config/removeRelay/${configId1}/default`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) @@ -367,61 +381,74 @@ describe('Testing relay association', () => { test('CONFIG_RELAY_SUITE - Should verify code', async () => { // Given + // Adding relay + await request(app) + .patch(`/config/updateRelay/${configId1}`) + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send({ + activated: { default: true }, + endpoint: { default: 'http://localhost:7000/relay' }, + auth_token: { default: 'abcd' }, + auth_prefix: 'Bearer' + }).expect(200); + // Request verification code let response = await request(app) .patch(`/config/relay/verificationCode/${configId1}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) .send().expect(200); + // Relay verification + const axiosStub = sinon.stub(axios, 'get'); + const mockRelayService = { data: { code: response.body.code } }; + axiosStub.returns(Promise.resolve(mockRelayService)); + // Test response = await request(app) - .patch(`/config/relay/verify/${configId1}?code=${response.body.code}`) + .patch(`/config/relay/verify/${configId1}/${EnvType.DEFAULT}?code=${response.body.code}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) .send().expect(200); + axiosStub.restore(); expect(response.body.status).toBe('verified'); }); test('CONFIG_RELAY_SUITE - Should NOT verify code - Config not found', async () => { // Given // Request verification code - const response = await request(app) + await request(app) .patch(`/config/relay/verificationCode/${configId1}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) .send().expect(200); // Test await request(app) - .patch(`/config/relay/verify/${new mongoose.Types.ObjectId()}?code=${response.body.code}`) + .patch(`/config/relay/verify/${new mongoose.Types.ObjectId()}/${EnvType.DEFAULT}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) .send().expect(404); }); test('CONFIG_RELAY_SUITE - Should NOT verify code - Invalid code', async () => { - await request(app) - .patch(`/config/relay/verify/${configId1}?code=`) - .set('Authorization', `Bearer ${adminMasterAccountToken}`) - .send().expect(422); - }); - - test('CONFIG_RELAY_SUITE - Should NOT verify code - Relay already verified', async () => { // Given - // Request verification code - let response = await request(app) - .patch(`/config/relay/verificationCode/${configId1}`) - .set('Authorization', `Bearer ${adminMasterAccountToken}`) - .send().expect(200); - - // That - // Is already verified + // Adding relay await request(app) - .patch(`/config/relay/verify/${configId1}?code=${response.body.code}`) + .patch(`/config/updateRelay/${configId1}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) - .send().expect(200); + .send({ + activated: { default: true }, + endpoint: { default: 'http://localhost:7000/relay' }, + auth_token: { default: 'abcd' }, + auth_prefix: 'Bearer' + }).expect(200); + + // Relay verification + const axiosStub = sinon.stub(axios, 'get'); + const mockRelayService = { data: { code: 'INVALID' } }; + axiosStub.returns(Promise.resolve(mockRelayService)); // Test - response = await request(app) - .patch(`/config/relay/verify/${configId1}?code=${response.body.code}`) + const response = await request(app) + .patch(`/config/relay/verify/${configId1}/${EnvType.DEFAULT}`) .set('Authorization', `Bearer ${adminMasterAccountToken}`) .send().expect(200); diff --git a/tests/relay.test.js b/tests/relay.test.js index f6fb792..a216deb 100644 --- a/tests/relay.test.js +++ b/tests/relay.test.js @@ -466,10 +466,10 @@ describe('Testing Switcher Relay Verification', () => { // Config has a verified Relay const config = await Config.findById(configId).exec(); - config.relay.verified = true; + config.relay.verified.set(EnvType.DEFAULT, true); config.relay.verification_code = '123'; await config.save(); - expect(config.relay.verified).toBe(true); + expect(config.relay.verified.get(EnvType.DEFAULT)).toBe(true); // Test const req = await request(app)