Skip to content
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

Production deploy #4086

Merged
merged 11 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ FILE_API_KEY_NEXUS=👻
FILE_API_KEY_BARNET=👻
FILE_API_KEY_LAMBETH=👻
FILE_API_KEY_SOUTHWARK=👻
FILE_API_KEY_EPSOM_EWELL=👻
FILE_API_KEY_MEDWAY=👻

# Editor
EDITOR_URL_EXT=http://localhost:3000
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ hasura.planx.uk/.env.test
/playwright-report/
/playwright/.cache/
api.planx.uk/tmp/
.python-version
__pycache__

# Ignore certificate files
**/*.chain
Expand Down
2 changes: 2 additions & 0 deletions api.planx.uk/.env.test.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ FILE_API_KEY_NEXUS=👻
FILE_API_KEY_BARNET=👻
FILE_API_KEY_LAMBETH=👻
FILE_API_KEY_SOUTHWARK=👻
FILE_API_KEY_EPSOM_EWELL=👻
FILE_API_KEY_MEDWAY=👻

# Editor
EDITOR_URL_EXT=example.com
Expand Down
8 changes: 8 additions & 0 deletions api.planx.uk/modules/auth/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ export const useFilePermission: RequestHandler = (req, _res, next): void => {
isEqual(
req.headers["api-key"] as string,
process.env.FILE_API_KEY_SOUTHWARK!,
) ||
isEqual(
req.headers["api-key"] as string,
process.env.FILE_API_KEY_MEDWAY!,
) ||
isEqual(
req.headers["api-key"] as string,
process.env.FILE_API_KEY_EPSOM_EWELL!,
);
if (!isAuthenticated) return next({ status: 401, message: "Unauthorised" });
return next();
Expand Down
2 changes: 2 additions & 0 deletions api.planx.uk/modules/sendEmail/docs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ paths:
post:
tags: [send email]
summary: Send an email
security:
- hasuraAuth: []
parameters:
- name: template
in: path
Expand Down
10 changes: 6 additions & 4 deletions doc/how-to/aws/how-to-setup-aws-s3-submissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ Once a council has confirmed they're cleared from their IT dept to use this meth

2. Create 2x tokens for sending secure requests to the Power Automate webhook and add both encrypted values to:
- `team_integrations.production_power_automate_api_key` & `team_integrations.staging_power_automate_api_key` via the production Hasura console
- See `how-to-generate-a-secret` for how to properly generate tokens and encrypt values
- See `how-to-generate-a-secret` and `how-to-add-a-team-secret` for how to properly generate tokens and encrypt values

3. Create 2x tokens for downloading files from the PlanX S3 Bucket and add values to:
- Root `.env.example` & `.env`, API's `.env.test` & `.env.test.example` as `FILE_API_KEY_{TEAM_SLUG}`
- In addition to committing these changes, also manually sync to AWS `pizza-secrets` via `scripts/push-secrets.sh`
- Root `docker-compose.yml`
- API's `modules/auth/middleware.ts` function `isAuthenticated`
- Pulumi's `infrastructure/application/index.ts` list of `apiService` "environment" variables
- Run `pulumi config set file-api-key-{team_slug} --stack {stack}` 2x for each staging & production stacks
- Encrypt the values using _our_ encrypt scripts (again see `how-to-generate-a-secret`) and add to `team_integrations.production_file_api_key` & `team_integrations.staging_file_api_key` via the production Hasura console. Please note these values are _not_ currently read, but suitable for a potential future refactor (just a bit tricky because file API keys are issued to a mix of _teams_ and _systems_ (eg BOPS & Idox)).
- Run `pulumi config set file-api-key-{team_slug} {your-new-secret} --secret --stack {stack}` once for each staging & production stacks, making sure that the secret you used for the root `.env` is the STAGING secret.
- Encrypt the values using _our_ encrypt scripts (see [`how-to-add-a-team-secret.md`](https://github.com/theopensystemslab/planx-new/blob/main/doc/how-to/secrets/how-to-add-a-team-secret.md)) and add to `team_integrations.production_file_api_key` & `team_integrations.staging_file_api_key` via the production Hasura console. Please note these values are _not_ currently read, but suitable for a potential future refactor (just a bit tricky because file API keys are issued to a mix of _teams_ and _systems_ (eg BOPS & Idox)).

4. Securely share tokens back to council contact via onetimesecret or similar
4. Securely share all four raw tokens back to council contact via onetimesecret or similar:
- The two Power Automate API keys
- The two file API keys
1 change: 1 addition & 0 deletions doc/how-to/secrets/how-to-add-a-team-secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This guide will demonstrate how to -

> [!NOTE]
> The `stack_name` should be either `production` or `staging`, depending on which environment the secret is for (e.g. `staging_govpay_secret`).
> I.e. the staging encryption key needs to be used for staging secrets (even though they need to get set on the production database)

### Encrypt the secret
1. In `/scripts/encrypt`, run the encryption script using the encryption key and raw secret that you obtained in the previous steps: `pnpm encrypt <encryption-key> <secret>`.
Expand Down
2 changes: 1 addition & 1 deletion doc/how-to/secrets/how-to-generate-a-secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
2. Secrets must meet the following criteria -
* Have a minimum length of 32 characters
* All characters should be alphanumeric
3. Follow [the current process for adding secrets](https://github.com/theopensystemslab/planx-new/blob/main/doc/how-to/how-to-add-a-secret.md) to the application
3. Follow [the current process for adding secrets](https://github.com/theopensystemslab/planx-new/blob/main/doc/how-to/secrets/how-to-add-a-secret.md) to the application

## Principles
- Staging and Production environments should not share secrets
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ services:
FILE_API_KEY_BARNET: ${FILE_API_KEY_BARNET}
FILE_API_KEY_LAMBETH: ${FILE_API_KEY_LAMBETH}
FILE_API_KEY_SOUTHWARK: ${FILE_API_KEY_SOUTHWARK}
FILE_API_KEY_EPSOM_EWELL: ${FILE_API_KEY_EPSOM_EWELL}
FILE_API_KEY_MEDWAY: ${FILE_API_KEY_MEDWAY}
FILE_API_KEY_NEXUS: ${FILE_API_KEY_NEXUS}
FILE_API_KEY: ${FILE_API_KEY}
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import InputRow from "ui/shared/InputRow";

import { Option } from "../../shared";
import type { Group } from "../model";
import { OptionEditor } from "./OptionsEditor";
import ChecklistOptionsEditor from "./OptionsEditor";

export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
return (
Expand Down Expand Up @@ -70,7 +70,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
}) as Option
}
newValueLabel="add new option"
Editor={OptionEditor}
Editor={ChecklistOptionsEditor}
editorExtraProps={{
groupIndex,
showValueField: !!formik.values.fn,
Expand Down Expand Up @@ -142,7 +142,7 @@ export const Options: React.FC<{ formik: FormikHookReturn }> = ({ formik }) => {
},
}) as Option
}
Editor={OptionEditor}
Editor={ChecklistOptionsEditor}
editorExtraProps={{ showValueField: !!formik.values.fn }}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,104 +1,46 @@
import {
BaseOptionsEditor,
BaseOptionsEditorProps,
} from "@planx/components/shared/BaseOptionsEditor";
import React from "react";
import ImgInput from "ui/editor/ImgInput/ImgInput";
import SimpleMenu from "ui/editor/SimpleMenu";
import Input from "ui/shared/Input/Input";
import InputRow from "ui/shared/InputRow";
import InputRowItem from "ui/shared/InputRowItem";

import { FlagsSelect } from "../../shared/FlagsSelect";
import { OptionEditorProps } from "../types";
export type ChecklistOptionsEditorProps = BaseOptionsEditorProps & {
index: number;
groupIndex?: number;
groups?: Array<string>;
onMoveToGroup?: (itemIndex: number, groupIndex: number) => void;
showValueField?: boolean;
};

export const OptionEditor: React.FC<OptionEditorProps> = (props) => {
const ChecklistOptionsEditor: React.FC<ChecklistOptionsEditorProps> = ({
value,
onChange,
showValueField = false,
groups,
onMoveToGroup,
index,
}) => {
return (
<div style={{ width: "100%" }}>
<InputRow>
{props.value.id ? (
<input type="hidden" value={props.value.id} readOnly />
) : null}
<InputRowItem width="200%">
<Input
required
format="bold"
multiline
value={props.value.data.text || ""}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
text: ev.target.value,
},
});
}}
placeholder="Option"
/>
</InputRowItem>

<ImgInput
img={props.value.data.img}
onChange={(img) => {
props.onChange({
...props.value,
data: {
...props.value.data,
img,
},
});
}}
<BaseOptionsEditor
value={value}
onChange={onChange}
showValueField={showValueField}
>
{typeof index !== "undefined" && groups && onMoveToGroup && (
<SimpleMenu
items={groups.map((group, groupIndex) => ({
label: `Move to ${group || `group ${groupIndex}`}`,
onClick: () => {
if (onMoveToGroup && typeof index === "number")
onMoveToGroup(index, groupIndex);
},
disabled: groupIndex === groupIndex,
}))}
/>

{typeof props.index !== "undefined" &&
props.groups &&
props.onMoveToGroup && (
<SimpleMenu
items={props.groups.map((group, groupIndex) => ({
label: `Move to ${group || `group ${groupIndex}`}`,
onClick: () => {
props.onMoveToGroup &&
typeof props.index === "number" &&
props.onMoveToGroup(props.index, groupIndex);
},
disabled: groupIndex === props.groupIndex,
}))}
/>
)}
</InputRow>

{props.showValueField && (
<InputRow>
<Input
format="data"
value={props.value.data.val || ""}
placeholder="Data Value"
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
val: ev.target.value,
},
});
}}
/>
</InputRow>
)}

<FlagsSelect
value={
Array.isArray(props.value.data.flag)
? props.value.data.flag
: [props.value.data.flag]
}
onChange={(ev) => {
props.onChange({
...props.value,
data: {
...props.value.data,
flag: ev,
},
});
}}
/>
</div>
</BaseOptionsEditor>
);
};

export default ChecklistOptionsEditor;
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe("Checklist Component - Grouped Layout", () => {
text="home type?"
handleSubmit={handleSubmit}
groupedOptions={groupedOptions}
/>,
/>
);

await user.click(screen.getByText("Section 1"));
Expand All @@ -46,7 +46,7 @@ describe("Checklist Component - Grouped Layout", () => {
handleSubmit={handleSubmit}
previouslySubmittedData={{ answers: ["S1_Option1", "S3_Option1"] }}
groupedOptions={groupedOptions}
/>,
/>
);

expect(screen.getByTestId("group-0-expanded")).toBeTruthy();
Expand All @@ -67,7 +67,7 @@ describe("Checklist Component - Grouped Layout", () => {
description=""
text="home type?"
groupedOptions={groupedOptions}
/>,
/>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
Expand All @@ -83,7 +83,7 @@ describe("Checklist Component - Grouped Layout", () => {
text="home type?"
handleSubmit={handleSubmit}
groupedOptions={groupedOptions}
/>,
/>
);
const [section1Button, section2Button, section3Button] =
screen.getAllByRole("button");
Expand Down Expand Up @@ -140,7 +140,7 @@ describe("Checklist Component - Basic & Images Layout", () => {
text="home type?"
handleSubmit={handleSubmit}
options={options[type]}
/>,
/>
);

expect(screen.getByRole("heading")).toHaveTextContent("home type?");
Expand Down Expand Up @@ -177,7 +177,7 @@ describe("Checklist Component - Basic & Images Layout", () => {
handleSubmit={handleSubmit}
previouslySubmittedData={{ answers: ["flat_id", "house_id"] }}
options={options[type]}
/>,
/>
);

await user.click(screen.getByTestId("continue-button"));
Expand All @@ -194,7 +194,7 @@ describe("Checklist Component - Basic & Images Layout", () => {
description=""
text="home type?"
options={options[type]}
/>,
/>
);
const results = await axe(container);
expect(results).toHaveNoViolations();
Expand All @@ -210,7 +210,7 @@ describe("Checklist Component - Basic & Images Layout", () => {
text="home type?"
handleSubmit={handleSubmit}
options={options[type]}
/>,
/>
);

await user.tab();
Expand Down
9 changes: 0 additions & 9 deletions editor.planx.uk/src/@planx/components/Checklist/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,5 @@ export interface ChecklistProps extends Checklist {
} & BaseNodeData;
};
}
export interface OptionEditorProps {
index: number;
value: Option;
onChange: (newVal: Option) => void;
groupIndex?: number;
groups?: Array<string>;
onMoveToGroup?: (itemIndex: number, groupIndex: number) => void;
showValueField?: boolean;
}

export type Props = PublicProps<Checklist>;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const FaceBox = ({
px: 0,
width: "100%",
textTransform: "none",
[`&.${toggleButtonClasses.selected}`]: {
[`&.${toggleButtonClasses.selected} > div`]: {
borderColor: (theme) => theme.palette.primary.dark,
background: (theme) => theme.palette.background.paper,
},
Expand Down
6 changes: 6 additions & 0 deletions editor.planx.uk/src/@planx/components/List/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { ICONS } from "../shared/icons";
import { EditorProps } from "../shared/types";
import { List, parseContent, validationSchema } from "./model";
import { ProposedAdvertisements } from "./schemas/Adverts";
import { ExistingBuildingsCIL } from "./schemas/CIL/ExistingCIL";
import { MezzanineCIL } from "./schemas/CIL/MezzanineCIL";
import { UnoccupiedBuildingsCIL } from "./schemas/CIL/UnoccupiedCIL";
import { NonResidentialFloorspace } from "./schemas/Floorspace";
import { BuildingDetailsGLA } from "./schemas/GLA/BuildingDetails";
import { CommunalSpaceGLA } from "./schemas/GLA/CommunalSpace";
Expand Down Expand Up @@ -69,6 +72,9 @@ export const SCHEMAS = [
{ name: "Proposed advertisements", schema: ProposedAdvertisements },
{ name: "Parking details", schema: Parking },
{ name: "Parking details (GLA)", schema: ParkingGLA },
{ name: "Existing buildings (CIL)", schema: ExistingBuildingsCIL },
{ name: "Unoccupied buildings (CIL)", schema: UnoccupiedBuildingsCIL },
{ name: "Mezzanine floors (CIL)", schema: MezzanineCIL },
{ name: "Trees", schema: Trees },
{ name: "Trees (Map first)", schema: TreesMapFirst },
];
Expand Down
Loading
Loading