Π·Π΅Ρ€ΠΊΠ°Π»ΠΎ ΠΈΠ·
1
0
Π€ΠΎΡ€ΠΊΠ½ΡƒΡ‚ΡŒ 0
* UI cleanup on joint key select page

* Renamed next -> moveToNextStep per PR

* rename joint key retrieved to joint key upload

* Remove unnecessary text from joint key upload page

* Fix merge conflicts

* Read uploaded file

* Update nswag client with more complete context object

* Parse the key.json file and set it into the SubmitElectionRequest

* Validation

* Fix linting errors and add internationalization

* css cleanup

* fix more linting errors
This commit is contained in:
Lee Richardson 2022-02-02 10:37:53 -05:00 ΠΊΠΎΠΌΠΌΠΈΡ‚ ΠΏΡ€ΠΎΠΈΠ·Π²Ρ‘Π» GitHub
Π ΠΎΠ΄ΠΈΡ‚Π΅Π»ΡŒ cd13d80da3
ΠšΠΎΠΌΠΌΠΈΡ‚ e92d04d2ff
НС Π½Π°ΠΉΠ΄Π΅Π½ ΠΊΠ»ΡŽΡ‡, ΡΠΎΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ Π΄Π°Π½Π½ΠΎΠΉ подписи
Π˜Π΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΊΠ»ΡŽΡ‡Π° GPG: 4AEE18F83AFDEB23
10 ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ²: 172 Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΉ ΠΈ 136 ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΉ

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,6 @@
{
"commitment_hash": "4F642D946DB82268AF873F6359D85019CF9A3658B6655942AE7CF84F7382505B",
"crypto_base_hash": "B3B8CEA020B4C1CA796060D5B54ED24F20BEAB937F32C480D5648D3D38DC0882",
"crypto_extended_base_hash": "30CD485CBD768C6C2BB92867876D401509E94701B8D24F6CD89A28A1D0579862",
"elgamal_public_key": "B1CCFB49E3D0EA64FB0FE059291BE8C29AF358DFF7FCF8D546E1D846E120A7ECB9FB0BF4B1842F7FED78E57C3583A10154706194417D732CD88753BEB567267F004B6B14BF77AD60C552315072B6023A983241F902DD2B74B815F7F29ACC67D784858BDBB27D3865A50BAC6619E58B57B7758CFF6830A22E169F63A605164C7F1BA2CA790ACD5854CF8C980D01060EAADC484FC0F3554098581BB241FA60ACA932B8103BC8BFEC7D7F5779DEDB6837D084672ABB4BFA17D49C41DB8D6A5435827C1C1C86CA3EBA585181EEC64C76613D19CF4C97A617D0B19EF33A190777FCCE40C8667D9927005B9291B64E7FD0A589384B588717A6A5E632D3226BB8FC482369744881773BEC3B8EAAA37071B0797F5FA5E048A029693585AF68C006B19DE2B94E19334607ECBF69AE5D4C3FEDFC7B72D509D1139E58DDC907D1F43F9EEE90BAAB9D0C1D48E0E5D01EB259CAD7F7F24CE68AAD57E5C974032931C7EAFEBF1238F2FA8D5E5AEC297164E78E0E809E2F61B1228B7EF319837CE085E6DA47DF949027DC54C7A648BC0F2A6032184A9411966C48F528B4168B5633566CD481489CB1FAB52183658EB487423FB18A120E8B30832338A4CB72092734A4D01DB47AE0CC3C48FCA02CF432F5C334FC7C900C57BD4D506B8B84A7030863DC46463EC198734EC7D4CB8C7600CA2137EE36512BE1DAA076616C911A4BBE91B9E43F6745E7"
}

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -7,7 +7,7 @@ import routeIds from '../../routes/RouteIds';
import { createEnumStepper } from '../../utils/EnumStepper';
import WizardStep from '../WizardStep';
import {
JointKeyRetrievedStep,
JointKeyUploadStep,
JointKeySelectStep,
ManifestMenuStep,
ManifestPreviewStep,
@ -62,7 +62,7 @@ export const ElectionSetupWizard: React.FC = () => {
<JointKeySelectStep onNext={handleNext} onChanged={handleChanged} />
</WizardStep>
<WizardStep active={step === ElectionSetupStep.JointKeyRetrieved}>
<JointKeyRetrievedStep onNext={handleNext} />
<JointKeyUploadStep onNext={handleNext} onChanged={handleChanged} />
</WizardStep>
<WizardStep active={step === ElectionSetupStep.ManifestMenu}>
<ManifestMenuStep

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -5,6 +5,7 @@ import InfoIcon from '@mui/icons-material/Info';
import { useIntl } from 'react-intl';
import { SubmitElectionRequest } from '@electionguard/api-client';
import { Foundation as FoundationIcon } from '@mui/icons-material';
import FormattedButton from '../../FormattedButton';
import IconHeader from '../../IconHeader';
import { Message, MessageId } from '../../../lang';
@ -48,7 +49,10 @@ const BasicInfoStep: React.FC<SetupInstructionsStepProps> = ({
return (
<Container maxWidth="md" className={classes.root}>
<IconHeader title={new Message(MessageId.ElectionSetup_BasicInfo_Title)} />
<IconHeader
title={new Message(MessageId.ElectionSetup_BasicInfo_Title)}
Icon={FoundationIcon}
/>
<Container maxWidth="xs">
<form onSubmit={handleSubmit}>
<TextField

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -1,86 +0,0 @@
import { Box, Button, Container } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import { VpnKey as KeyIcon } from '@mui/icons-material';
import React from 'react';
import { FormattedMessage } from 'react-intl';
import IconHeader from '../../IconHeader';
import InternationalText from '../../InternationalText';
import { Message, MessageId } from '../../../lang';
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
height: '100%',
},
content: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
spaced: {
marginBottom: theme.spacing(2),
},
}));
export interface JointKeyRetrievedStepProps {
onNext: () => void;
}
/**
* Joint Key Select Step for Election Setup
*/
const JointKeyRetrievedStep: React.FC<JointKeyRetrievedStepProps> = ({ onNext }) => {
const classes = useStyles();
const onButtonClick = () => {
onNext();
};
return (
<Box
display="flex"
flexDirection="column"
justifyItems="center"
alignItems="center"
className={classes.root}
>
<Container maxWidth="md" className={classes.content}>
<IconHeader
title={
new Message(
MessageId.ElectionSetupJointKeyRetrievedTitle,
'Joint Key Retrieved'
)
}
Icon={KeyIcon}
/>
<Box display="flex" flexDirection="column" alignItems="center">
<InternationalText
className={classes.spaced}
variant="h5"
component="h2"
id={MessageId.ElectionSetupJointKeyRetrievedCTA}
/>
<InternationalText
className={classes.spaced}
id={MessageId.ElectionSetupJointKeyRetrievedDescription}
/>
<Button
className={classes.spaced}
variant="contained"
color="secondary"
onClick={onButtonClick}
>
<FormattedMessage id={MessageId.ElectionSetupJointKeyRetreivedNext} />
</Button>
</Box>
</Container>
</Box>
);
};
export default JointKeyRetrievedStep;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -8,7 +8,6 @@ import {
MenuItem,
Select,
SelectChangeEvent,
Typography,
} from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import { SaveAlt as SaveIcon } from '@mui/icons-material';
@ -17,10 +16,16 @@ import { FormattedMessage } from 'react-intl';
import { QueryClient, QueryClientProvider } from 'react-query';
// todo: Remove useGetJointKeys - import { useGetJointKeys } from '@electionguard/api-client';
import { Message, MessageId, loremIpsum } from '../../../lang';
import { Message, MessageId } from '../../../lang';
import IconHeader from '../../IconHeader';
const useStyles = makeStyles((theme) => ({
root: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
},
spaced: {
marginBottom: theme.spacing(2),
},
@ -79,36 +84,24 @@ const JointKeySelectStep: React.FC<JointKeySelectStepProps> = ({ onNext, onChang
};
return (
<Container maxWidth="md">
<Container maxWidth="md" className={classes.root}>
<QueryClientProvider client={queryClient}>
<Box display="flex" flexDirection="column">
<IconHeader
title={
new Message(
MessageId.ElectionSetupJointKeySelectTitle,
'Pull Guardian Keys'
)
}
title={new Message(MessageId.ElectionSetup_JointKeySelect_Title)}
Icon={SaveIcon}
/>
<Typography className={classes.spaced}>
<FormattedMessage
id={MessageId.ElectionSetupJointKeySelectDescription}
defaultMessage={loremIpsum}
/>
</Typography>
<Box display="flex" flexDirection="column" alignItems="start">
<FormControl className={classes.control}>
<InputLabel id="joint-key-select-label" className={classes.inputLabel}>
<FormattedMessage
id={MessageId.ElectionSetupJointKeySelectPrompt}
id={MessageId.ElectionSetup_JointKeySelect_Prompt}
defaultMessage="Select Key for Election"
/>
</InputLabel>
<Select
labelId="joint-key-select-label"
id="joint-key-select"
label="Hi there"
value={keyCeremony ? keyCeremony.key_name : ''}
onChange={onKeySelect}
>
@ -127,10 +120,7 @@ const JointKeySelectStep: React.FC<JointKeySelectStepProps> = ({ onNext, onChang
color="secondary"
onClick={onNextClick}
>
<FormattedMessage
id={MessageId.ElectionSetupJointKeySelectNext}
defaultMessage="Pull keys for selected election"
/>
<FormattedMessage id={MessageId.ElectionSetup_JointKeySelect_Next} />
</Button>
</Box>
</Box>

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,115 @@
import { Button, CircularProgress, Container } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import { VpnKey as KeyIcon } from '@mui/icons-material';
import React, { useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { CiphertextElectionContext, SubmitElectionRequest } from '@electionguard/api-client';
import IconHeader from '../../IconHeader';
import { Message, MessageId } from '../../../lang';
const useStyles = makeStyles((theme) => ({
content: {
paddingTop: theme.spacing(4),
paddingBottom: theme.spacing(4),
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
button: {
marginBottom: theme.spacing(2),
},
error: {
color: 'red',
marginBottom: theme.spacing(2),
},
}));
export interface JointKeyUploadStepProps {
onNext: () => void;
onChanged: (newSubmitElectionRequest: SubmitElectionRequest) => void;
}
/**
* Joint Key Select Step for Election Setup
*/
const JointKeyUploadStep: React.FC<JointKeyUploadStepProps> = ({ onNext, onChanged }) => {
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string>();
const classes = useStyles();
const intl = useIntl();
const setIntlError = (id: string) => {
const message = intl.formatMessage({
id,
});
setError(message);
};
const onFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e?.target?.files?.length) {
setUploading(true);
const file = e.target.files[0];
const text = await file.text();
try {
const keys = JSON.parse(text);
const context = {
commitment_hash: keys.commitment_hash,
crypto_base_hash: keys.crypto_base_hash,
crypto_extended_base_hash: keys.crypto_extended_base_hash,
elgamal_public_key: keys.elgamal_public_key,
} as CiphertextElectionContext;
const fileValid =
context.commitment_hash &&
context.crypto_base_hash &&
context.crypto_extended_base_hash &&
context.elgamal_public_key;
if (!fileValid) {
throw new Error();
}
onChanged({ context } as SubmitElectionRequest);
onNext();
} catch (ex) {
setIntlError(MessageId.ElectionSetup_JointKeyUpload_InvalidFile);
} finally {
setUploading(false);
}
} else {
setIntlError(MessageId.ElectionSetup_JointKeyUpload_NoFile);
}
};
return (
<Container maxWidth="md" className={classes.content}>
<IconHeader
title={new Message(MessageId.ElectionSetup_JointKeyUpload_Title)}
Icon={KeyIcon}
/>
<div className={classes.error}>{error}</div>
<Button
disabled={uploading}
color="secondary"
variant="contained"
component="label"
className={classes.button}
>
<FormattedMessage
id={MessageId.ElectionSetupUploadManifestUpload}
defaultMessage="Select Files to Upload"
/>
<input
id="manifest-upload"
accept="application/JSON"
type="file"
hidden
onChange={(e) => onFileUpload(e)}
/>
</Button>
{uploading && <CircularProgress size={24} variant="indeterminate" />}
</Container>
);
};
export default JointKeyUploadStep;

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -4,8 +4,8 @@ export type { SetupInstructionsStepProps } from './BasicInfoStep';
export { default as JointKeySelectStep } from './JointKeySelectStep';
export type { JointKeySelectStepProps } from './JointKeySelectStep';
export { default as JointKeyRetrievedStep } from './JointKeyRetrievedStep';
export type { JointKeyRetrievedStepProps } from './JointKeyRetrievedStep';
export { default as JointKeyUploadStep } from './JointKeyUploadStep';
export type { JointKeyUploadStepProps as JointKeyRetrievedStepProps } from './JointKeyUploadStep';
export { default as ManifestMenuStep } from './ManifestMenuStep';
export type { ManifestMenuStepProps } from './ManifestMenuStep';

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -31,15 +31,13 @@ enum MessageId {
ElectionSetupManifestMenuAbout = 'election_setup.manifest_menu.about',
ElectionSetupManifestMenuPrompt = 'election_setup.manifest_menu.prompt',
ElectionSetupJointKeyRetrievedTitle = 'election_setup.joint_key_retrieved.title',
ElectionSetupJointKeyRetrievedCTA = 'election_setup.joint_key_retrieved.cta',
ElectionSetupJointKeyRetrievedDescription = 'election_setup.joint_key_retrieved.description',
ElectionSetupJointKeyRetreivedNext = 'election_setup.joint_key_retrieved.next',
ElectionSetup_JointKeyUpload_Title = 'election_setup.joint_key_upload.title',
ElectionSetup_JointKeyUpload_NoFile = 'election_setup.joint_key_upload.no_file',
ElectionSetup_JointKeyUpload_InvalidFile = 'election_setup.joint_key_upload.invalid_file',
ElectionSetupJointKeySelectTitle = 'election_setup.joint_key_select.title',
ElectionSetupJointKeySelectDescription = 'election_setup.joint_key_select.description',
ElectionSetupJointKeySelectPrompt = 'election_setup.joint_key_select.prompt',
ElectionSetupJointKeySelectNext = 'election_setup.joint_key_select.next',
ElectionSetup_JointKeySelect_Title = 'election_setup.joint_key_select.title',
ElectionSetup_JointKeySelect_Prompt = 'election_setup.joint_key_select.prompt',
ElectionSetup_JointKeySelect_Next = 'election_setup.joint_key_select.next',
ElectionSetupManifestPreviewTitle = 'election_setup.manifest_preview.title',
ElectionSetupManifestPreviewPropertyName = 'election_setup.manifest_preview.property.name',

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -24,15 +24,13 @@
"election_setup.manifest_menu.title": "Add Election Manifest",
"election_setup.manifest_menu.prompt": "Select what you would like to do:",
"election_setup.joint_key_retrieved.title": "Joint Key Retrieved",
"election_setup.joint_key_retrieved.cta": "Create a new election with retrieved key",
"election_setup.joint_key_retrieved.description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis feugiat magna nec nibh congue, non pretium mauris feugiat. Nam commodo ultrices semper. Praesent hendrerit ut nibh nec mollis. Ut fermentum maximus nibh nec vulputate. Fusce ultricies, arcu quis faucibus egestas, ligula tellus placerat orci, sed scelerisque nisl mi eu nisi. Quisque pulvinar justo justo, non tristique enim pretium non. Cras eu lacus gravida, eleifend magna at, ultricies tortor.",
"election_setup.joint_key_retrieved.next": "Continue",
"election_setup.joint_key_upload.title": "Upload Joint Key",
"election_setup.joint_key_upload.no_file": "No file found. Please try again.",
"election_setup.joint_key_upload.invalid_file": "That file didn't look quite right. Please ensure it's the same key file that was produced by an election ceremony.",
"election_setup.joint_key_select.title": "Pull Guardian Keys",
"election_setup.joint_key_select.description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis feugiat magna nec nibh congue, non pretium mauris feugiat. Nam commodo ultrices semper. Praesent hendrerit ut nibh nec mollis. Ut fermentum maximus nibh nec vulputate. Fusce ultricies, arcu quis faucibus egestas, ligula tellus placerat orci, sed scelerisque nisl mi eu nisi. Quisque pulvinar justo justo, non tristique enim pretium non. Cras eu lacus gravida, eleifend magna at, ultricies tortor.",
"election_setup.joint_key_select.prompt": "Select Key for Election",
"election_setup.joint_key_select.next": "Pull keys for selected election",
"election_setup.joint_key_select.title": "Select Guardian Key",
"election_setup.joint_key_select.prompt": "Guardian Key",
"election_setup.joint_key_select.next": "Next Step",
"election_setup.manifest_preview.title": "Election Manifest Uploaded",
"election_setup.manifest_preview.property.name": "Name",

ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -2962,10 +2962,21 @@ export interface Body_login_for_access_token_api_v1_auth_login_post {
export interface CastBallotsRequest {
election_id?: string;
manifest?: any;
context?: any;
context?: CiphertextElectionContext;
ballots: any[];
}
/** The meta-data required for an election including keys, manifest, number of guardians, and quorum */
export interface CiphertextElectionContext {
number_of_guardians: number;
quorum: number;
elgamal_public_key: string;
commitment_hash: string;
manifest_hash: string;
crypto_base_hash: string;
crypto_extended_base_hash: string;
}
/** A Tally for a specific election. */
export interface CiphertextTally {
election_id: string;
@ -2999,7 +3010,7 @@ export interface CreateElectionRequest {
export interface DecryptBallotsWithSharesRequest {
encrypted_ballots: any[];
shares: { [key: string]: any[]; };
context?: any;
context: CiphertextElectionContext;
}
/** A request to decrypt a specific tally. Can optionally include the tally to decrypt. */
@ -3025,7 +3036,7 @@ export interface Election {
election_id: string;
key_name: string;
state: ElectionState;
context?: any;
context: CiphertextElectionContext;
manifest?: any;
}
@ -3192,7 +3203,7 @@ export interface MakeElectionContextRequest {
export interface MakeElectionContextResponse {
status?: App__api__v1__models__base__ResponseStatus;
message?: string;
context?: any;
context: CiphertextElectionContext;
}
/** A basic model object */
@ -3273,7 +3284,7 @@ export interface Settings {
export interface SpoilBallotsRequest {
election_id?: string;
manifest?: any;
context?: any;
context?: CiphertextElectionContext;
ballots: any[];
}
@ -3287,7 +3298,7 @@ export enum StorageMode {
export interface SubmitBallotsRequest {
election_id?: string;
manifest?: any;
context?: any;
context?: CiphertextElectionContext;
ballots: any[];
}
@ -3295,7 +3306,7 @@ export interface SubmitBallotsRequest {
export interface SubmitElectionRequest {
election_id: string;
key_name: string;
context?: any;
context: CiphertextElectionContext;
manifest?: any;
}
@ -3326,7 +3337,7 @@ export interface ValidateBallotRequest {
schema_override?: any;
ballot?: any;
manifest?: any;
context?: any;
context: CiphertextElectionContext;
}
/** A request to validate an Election Description. */