Skip to content

Commit

Permalink
rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
jessicamcinchak committed Jan 30, 2024
2 parents 8def9ce + 9054e70 commit 474f5a6
Show file tree
Hide file tree
Showing 16 changed files with 2,262 additions and 53 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# API
API_PORT=7002
API_URL_EXT=http://localhost:${API_PORT}
ENCRYPTION_KEY=👻

# JWT_SECRET must be at least 32 characters
JWT_SECRET=👻
Expand Down
54 changes: 29 additions & 25 deletions api.planx.uk/modules/send/bops/bops.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,13 @@ describe(`sending an application to BOPS`, () => {
});

queryMock.mockQuery({
name: "GetStagingBopsSubmissionURL",
name: "GetStagingIntegrations",
data: {
teams: [
{
integrations: {
bopsSubmissionURL: submissionURL,
bopsSecret: null,
},
},
],
Expand All @@ -72,15 +73,9 @@ describe(`sending an application to BOPS`, () => {
});

queryMock.mockQuery({
name: "GetStagingBopsSubmissionURL",
name: "GetStagingIntegrations",
data: {
teams: [
{
integrations: {
bopsSubmissionURL: null,
},
},
],
teams: [],
},
variables: {
slug: "unsupported-team",
Expand Down Expand Up @@ -128,9 +123,11 @@ describe(`sending an application to BOPS`, () => {
.post("/bops/unsupported-team")
.set({ Authorization: process.env.HASURA_PLANX_API_KEY! })
.send({ payload: { sessionId: "123" } })
.expect(400)
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/not enabled for this local authority/);
expect(res.body.error).toMatch(
/No team matching "unsupported-team" found/,
);
});
});

Expand Down Expand Up @@ -187,12 +184,15 @@ describe(`sending an application to BOPS v2`, () => {
});

queryMock.mockQuery({
name: "GetStagingBopsSubmissionURL",
name: "GetStagingIntegrations",
data: {
teams: [
{
integrations: {
bopsSubmissionURL: submissionURL,
// Decodes to "abc123"
bopsSecret:
"668514b455572ca39c46e49396506b29:6c6df14c7fd405dbf380533ba0dcae39",
},
},
],
Expand All @@ -203,15 +203,9 @@ describe(`sending an application to BOPS v2`, () => {
});

queryMock.mockQuery({
name: "GetStagingBopsSubmissionURL",
name: "GetStagingIntegrations",
data: {
teams: [
{
integrations: {
bopsSubmissionURL: null,
},
},
],
teams: [],
},
variables: {
slug: "unsupported-team",
Expand All @@ -220,9 +214,14 @@ describe(`sending an application to BOPS v2`, () => {
});

it("successfully proxies request and returns hasura id", async () => {
nock(`${submissionURL}/api/v2/planning_applications`).post("").reply(200, {
application: "0000123",
});
const expectedHeaders = { authorization: "Bearer abc123" };
const nockScope = nock(`${submissionURL}/api/v2/planning_applications`, {
reqheaders: expectedHeaders,
})
.post("")
.reply(200, {
application: "0000123",
});

await supertest(app)
.post("/bops-v2/southwark")
Expand All @@ -233,6 +232,9 @@ describe(`sending an application to BOPS v2`, () => {
expect(res.body).toEqual({
application: { id: 22, bopsResponse: { application: "0000123" } },
});

// Check nock was called with expected headers
expect(nockScope.isDone()).toBe(true);
});
});

Expand All @@ -259,9 +261,11 @@ describe(`sending an application to BOPS v2`, () => {
.post("/bops-v2/unsupported-team")
.set({ Authorization: process.env.HASURA_PLANX_API_KEY! })
.send({ payload: { sessionId: "123" } })
.expect(400)
.expect(500)
.then((res) => {
expect(res.body.error).toMatch(/not enabled for this local authority/);
expect(res.body.error).toMatch(
/No team matching "unsupported-team" found/,
);
});
});

Expand Down
37 changes: 11 additions & 26 deletions api.planx.uk/modules/send/bops/bops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,12 @@ const sendToBOPS = async (req: Request, res: Response, next: NextFunction) => {
const localAuthority = req.params.localAuthority;
const env =
process.env.APP_ENVIRONMENT === "production" ? "production" : "staging";
const bopsSubmissionURL = await $api.team.getBopsSubmissionURL(
localAuthority,

const { bopsSubmissionURL, bopsToken } = await $api.team.getIntegrations({
slug: localAuthority,
encryptionKey: process.env.ENCRYPTION_KEY!,
env,
);
const isSupported = Boolean(bopsSubmissionURL);
if (!isSupported) {
return next(
new ServerError({
status: 400,
message: `Back-office Planning System (BOPS) is not enabled for this local authority (${localAuthority})`,
}),
);
}
});
const target = `${bopsSubmissionURL}/api/v1/planning_applications`;
const exportData = await $api.export.bopsPayload(payload?.sessionId);

Expand All @@ -69,7 +62,7 @@ const sendToBOPS = async (req: Request, res: Response, next: NextFunction) => {
adapter: "http",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.BOPS_API_TOKEN}`,
Authorization: `Bearer ${bopsToken || process.env.BOPS_API_TOKEN}`,
},
data: exportData,
})
Expand Down Expand Up @@ -181,19 +174,11 @@ const sendToBOPSV2 = async (
const localAuthority = req.params.localAuthority;
const env =
process.env.APP_ENVIRONMENT === "production" ? "production" : "staging";
const bopsSubmissionURL = await $api.team.getBopsSubmissionURL(
localAuthority,
const { bopsSubmissionURL, bopsToken } = await $api.team.getIntegrations({
slug: localAuthority,
encryptionKey: process.env.ENCRYPTION_KEY!,
env,
);
const isSupported = Boolean(bopsSubmissionURL);
if (!isSupported) {
return next(
new ServerError({
status: 400,
message: `Back-office Planning System (BOPS) is not enabled for this local authority (${localAuthority})`,
}),
);
}
});
const target = `${bopsSubmissionURL}/api/v2/planning_applications`;
const exportData = await $api.export.digitalPlanningDataPayload(
payload?.sessionId,
Expand All @@ -206,7 +191,7 @@ const sendToBOPSV2 = async (
adapter: "http",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.BOPS_API_TOKEN}`,
Authorization: `Bearer ${bopsToken || process.env.BOPS_API_TOKEN}`,
},
data: exportData,
})
Expand Down
1 change: 1 addition & 0 deletions api.planx.uk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"date-fns": "^2.29.3",
"dompurify": "^3.0.6",
"express": "^4.18.2",
"express-async-errors": "^3.1.1",
"express-jwt": "^8.4.1",
"express-pino-logger": "^7.0.0",
"express-rate-limit": "^7.1.5",
Expand Down
11 changes: 11 additions & 0 deletions api.planx.uk/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api.planx.uk/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import "isomorphic-fetch";
import "express-async-errors";
import { json, urlencoded } from "body-parser";
import assert from "assert";
import cookieParser from "cookie-parser";
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ services:
ORDNANCE_SURVEY_API_KEY: ${ORDNANCE_SURVEY_API_KEY}
MINIO_PORT: ${MINIO_PORT}
CORS_ALLOWLIST: ${EDITOR_URL_EXT}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
# Local authority config
# Lambeth
GOV_UK_PAY_TOKEN_LAMBETH: ${GOV_UK_PAY_TOKEN_LAMBETH}
Expand Down
6 changes: 4 additions & 2 deletions hasura.planx.uk/metadata/tables.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1345,9 +1345,11 @@
permission:
columns:
- id
- team_id
- staging_bops_submission_url
- production_bops_secret
- production_bops_submission_url
- staging_bops_secret
- staging_bops_submission_url
- team_id
filter: {}
- table:
name: team_members
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ALTER TABLE "public"."team_integrations" DROP COLUMN "staging_bops_secret";
ALTER TABLE "public"."team_integrations" DROP COLUMN "production_bops_secret";

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
alter table "public"."team_integrations" add column "staging_bops_secret" text
null;

comment on column "public"."team_integrations"."staging_bops_secret" is E'Secret required for BOPS submission in the format <API_KEY>:<INITIALIZATION_VECTOR>';

alter table "public"."team_integrations" add column "production_bops_secret" text
null;

comment on column "public"."team_integrations"."production_bops_secret" is E'Secret required for BOPS submission in the format <API_KEY>:<INITIALIZATION_VECTOR>';
2 changes: 2 additions & 0 deletions infrastructure/application/Pulumi.production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ config:
application:bops-api-token:
secure: AAABALRtSjQMRjERtkHs6WBOI7MBMw4bFOuHO8qLLPe9Pfg8Nlbp61XalZA1pmXsRt/T+59fGcdqbKA5WRkOZVibhFo=
application:cloudflare-zone-id: a9b9933f28e786ec4cfd4bb596f5a519
application:encryption-key:
secure: AAABAPhLlb8bwYKCvJW3vOnM8e9yEx7wW/EUWJQWsF2nEej7Zs0/zIm5hoJq1ltDZ6D4YU1WmAxyrgh1vSQTatJNl55q56gNWwrpZTZamg/9IH5nPo+PzOrkWxBErzC+
application:file-api-key:
secure: AAABAGyTfLujGho+V0tEhFXQRET5FjYK6txyaFTB3gY/VaKzq8yNlocJTAM5nt8mBhF6T+AeQD2GxW63
application:file-api-key-nexus:
Expand Down
2 changes: 2 additions & 0 deletions infrastructure/application/Pulumi.staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ config:
secure: AAABAJx2KnnFeuEmwRB5VyE790TeYxiKmWFuVXVY8Lb7+HDNRYND8Pfd9d61zjwPzi9Jf2ZlT0OH++1MXYafhdQO28Y=
application:cloudflare-zone-id:
secure: AAABAPZz/bzFCZEZd+jzPpYP4HXAOLYQmLGf2YLQE2YPfMBUtDC83KCo2l2DJ4AL4OKL+jFFx8wrrJc6DDwXJQ==
application:encryption-key:
secure: AAABAHgpGq29S4PSqVz8QRkZdj3CrlCHWfp+qXZ0adpb1u/o1YWtvmn3Z0+XLO0gItZlRS6NbLvlCa4wk96Ia1oCcdOkv4jrVG05WOBSmRprqYwa7FWWXvegMS6HhiXV
application:file-api-key:
secure: AAABAN0LjLOgxCkr5ZqQLn6FkZPcrPlvNG4fbNZ02W2qC1VVYVee/3aToZQuXuokVwnIPNbbe2w=
application:file-api-key-nexus:
Expand Down
4 changes: 4 additions & 0 deletions infrastructure/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,10 @@ export = async () => {
name: "ORDNANCE_SURVEY_API_KEY",
value: config.requireSecret("ordnance-survey-api-key"),
},
{
name: "ENCRYPTION_KEY",
value: config.requireSecret("encryption-key"),
},
generateCORSAllowList(CUSTOM_DOMAINS, DOMAIN),
...generateTeamSecrets(config, env),
],
Expand Down
33 changes: 33 additions & 0 deletions scripts/encrypt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { encrypt } from "@opensystemslab/planx-core";

/**
* Encrypt a secret
* Currently used to generate secure secrets for the team_integrations table
* e.g converting plain text 3rd-part API keys (such as BOPS tokens) to encrypted strings
*
* @param secret - The secret to be encrypted.
* @param encryptionKey - The encryption key - a 32-byte string
* @returns The encrypted secret and initialization vector in the format ${secret}:${iv}
* @example pnpm encode <secret> <encryptionKey>
*/
function main() {
try {
if (process.argv.length < 4) {
console.error("Usage: pnpm encode <secret> <encryptionKey>");
process.exit(1);
}

const secret = process.argv[2];
const encryptionKey = process.argv[3];
const encrypted = encrypt(secret, encryptionKey);

console.log("Success!");
console.log(encrypted);
} catch (error) {
console.log("Error!")
console.error(error);
process.exit(1);
}
}

main();
17 changes: 17 additions & 0 deletions scripts/encrypt/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "encrypt",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"encrypt": "ts-node index.ts"
},
"keywords": [],
"dependencies": {
"@opensystemslab/planx-core": "git+https://github.com/theopensystemslab/planx-core#02c3999",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"author": "",
"license": "ISC"
}
Loading

0 comments on commit 474f5a6

Please sign in to comment.