Skip to content

Commit

Permalink
Sanitize custom_stages and stages_enabled (#6770)
Browse files Browse the repository at this point in the history
* stages_enabled column fixes

* custom_stages fixes

* validate custom stages array in API

* fix client/API custom stages

* type fixes

* fix migration

* disallow single quotes in validation

* remove quote replacement
  • Loading branch information
timolegros authored Feb 16, 2024
1 parent 3f74acf commit 18bd067
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 21 deletions.
10 changes: 7 additions & 3 deletions libs/model/src/models/community.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export type CommunityAttributes = {
social_links?: string[];
ss58_prefix?: number;
stages_enabled?: boolean;
custom_stages?: string;
custom_stages?: string[];
custom_domain?: string;
block_explorer_ids?: string;
collapsed_on_homepage?: boolean;
Expand Down Expand Up @@ -120,10 +120,14 @@ export default (
active: { type: dataTypes.BOOLEAN },
stages_enabled: {
type: dataTypes.BOOLEAN,
allowNull: true,
allowNull: false,
defaultValue: true,
},
custom_stages: { type: dataTypes.STRING, allowNull: true },
custom_stages: {
type: dataTypes.ARRAY(dataTypes.TEXT),
allowNull: false,
defaultValue: [],
},
custom_domain: { type: dataTypes.STRING, allowNull: true },
block_explorer_ids: { type: dataTypes.STRING, allowNull: true },
collapsed_on_homepage: {
Expand Down
4 changes: 2 additions & 2 deletions packages/commonwealth/client/scripts/models/ChainInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class ChainInfo {
public description: string;
public socialLinks: string[];
public stagesEnabled: boolean;
public customStages: string;
public customStages: string[];
public customDomain: string;
public snapshot: string[];
public terms: string;
Expand Down Expand Up @@ -262,7 +262,7 @@ class ChainInfo {
social_links?: string[];
discord?: string;
stagesEnabled?: boolean;
customStages?: string;
customStages?: string[];
customDomain?: string;
terms?: string;
snapshot?: string[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ const CommunityProfileForm = () => {
description: values.communityDescription,
social_links: links.map((link) => link.value.trim()),
stagesEnabled: values.hasStagesEnabled,
customStages: values.customStages,
customStages: values.customStages
? JSON.parse(values.customStages)
: [],
iconUrl: values.communityProfileImageURL,
defaultOverview: values.defaultPage === DefaultPage.Overview,
});
Expand Down Expand Up @@ -154,9 +156,9 @@ const CommunityProfileForm = () => {
: DefaultPage.Discussions,
hasStagesEnabled: community.stagesEnabled,
customStages:
community.customStages === 'true' || !community.customStages
? ''
: community.customStages,
community.customStages.length > 0
? JSON.stringify(community.customStages)
: '',
communityBanner: community.communityBanner || '',
}}
validationSchema={communityProfileValidationSchema}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,13 @@ export const communityProfileValidationSchema = z.object({
(data) => {
if (data) {
try {
const validJSONStringArray = data.replace(/'/g, '"');
const parsedArray = JSON.parse(validJSONStringArray);
if (
if (data.includes("'")) return false;

const parsedArray = JSON.parse(data);
return !(
!Array.isArray(parsedArray) ||
!parsedArray.every((value) => typeof value === 'string')
) {
return false;
}
return true;
);
} catch {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ export const CommunityMetadataRows = ({
)}
<InputRow
title="Custom Stages"
value={customStages}
value={JSON.stringify(customStages)}
placeholder='["Temperature Check", "Consensus Check"]'
onChangeHandler={(v) => setCustomStages(v)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ export async function __updateCommunity(
if (hide_projects) community.hide_projects = hide_projects;
if (typeof stages_enabled === 'boolean')
community.stages_enabled = stages_enabled;
if (typeof custom_stages === 'string')
if (Array.isArray(custom_stages)) {
community.custom_stages = custom_stages;
}
if (typeof terms === 'string') community.terms = terms;
if (has_homepage) community.has_homepage = has_homepage;
if (default_page) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ async function setThreadStage(
// fetch available stages
let customStages = [];
try {
const communityStages = JSON.parse(community.custom_stages);
const communityStages = community.custom_stages;
if (Array.isArray(communityStages)) {
customStages = Array.from(communityStages)
.map((s) => s.toString())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use strict';

module.exports = {
up: async (queryInterface, Sequelize) => {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.addColumn(
'Communities',
'temp_stages_enabled',
{
type: Sequelize.BOOLEAN,
defaultValue: true,
},
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_stages_enabled = CASE
WHEN stages_enabled = 'true' THEN TRUE
ELSE FALSE
END;
`,
{
transaction,
},
);

await queryInterface.removeColumn('Communities', 'stages_enabled', {
transaction,
});
await queryInterface.renameColumn(
'Communities',
'temp_stages_enabled',
'stages_enabled',
{ transaction },
);

await queryInterface.sequelize.query(
`
ALTER TABLE "Communities"
ADD COLUMN temp_custom_stages TEXT[];
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"In Discussion","In Voting","Voting Ended"}'
WHERE id = 'shell-protocol' AND custom_stages = '["In Discussion","In Voting","Voting Ended"}';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"In Discussion", "Passed"}'
WHERE id = 'aavegotchi' AND custom_stages = '[In Discussion, Passed]';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"In Discussion", "Passed"}'
WHERE id = 'cerberus-zone' AND custom_stages = '["In Discussion, Passed"]';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"Temperature Check", "Discussion", "Voting", "Passed", "Failed"}'
WHERE id = 'dao-masons' AND custom_stages = '["Temperature Check" "Discussion" "Voting" "Passed" "Failed"]';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"Stage 1", "Stage 2", "Stage 3"}'
WHERE id = 'my-first-community' AND custom_stages = '"Stage 1", "Stage 2", "Stage 3"';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"In Discussion", "Voting", "Ended"}'
WHERE id = 'talis' AND custom_stages = '"In Discussion","Voting","Ended"';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"chat", "Stage 2", "Stage 3"}'
WHERE id = 'artrate' AND custom_stages = '["chat", "Stage 2", "Stage 3",…]';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = '{"Stage 1", "Stage 2", "Final Stage"}'
WHERE id = 'matic-token' AND custom_stages = '[ "Stage 1", "Stage 2", "Final Stage" ]';
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
UPDATE "Communities"
SET temp_custom_stages = CASE
WHEN custom_stages IS NULL OR custom_stages = '' OR custom_stages IN ('true', 'false')
THEN '{}'
ELSE (
SELECT ARRAY(
SELECT json_array_elements_text(custom_stages::json)
)
)
END
WHERE temp_custom_stages IS NULL;
`,
{ transaction },
);

await queryInterface.sequelize.query(
`
ALTER TABLE "Communities"
ALTER COLUMN "temp_custom_stages" SET DEFAULT '{}';
`,
{ transaction },
);

await queryInterface.removeColumn('Communities', 'custom_stages', {
transaction,
});

await queryInterface.renameColumn(
'Communities',
'temp_custom_stages',
'custom_stages',
{ transaction },
);
});
},

down: async (queryInterface, Sequelize) => {
// irreversible
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export async function createTestEntities() {
type: 'offchain',
base: 'ethereum',
// collapsed_on_homepage: true,
custom_stages: 'true',
custom_stages: [],
// stages_enabled: true,
// has_chain_events_listener: false,
icon_url:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export async function createTestEntities() {
type: 'offchain',
base: 'ethereum',
// collapsed_on_homepage: true,
custom_stages: 'true',
custom_stages: [],
// stages_enabled: true,
// has_chain_events_listener: false,
icon_url:
Expand Down

0 comments on commit 18bd067

Please sign in to comment.