Skip to content

Commit

Permalink
[refactor] rewrite Election model & controller based on Web Crypto API
Browse files Browse the repository at this point in the history
[optimize] update Upstream packages

Signed-off-by: TechQuery <shiy2008@gmail.com>
  • Loading branch information
TechQuery committed Jan 22, 2025
1 parent aa692b4 commit 53c974e
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 41 deletions.
40 changes: 20 additions & 20 deletions pnpm-lock.yaml

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

69 changes: 52 additions & 17 deletions src/controller/Election.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import {
Authorized,
Body,
BodyParam,
CurrentUser,
ForbiddenError,
JsonController,
Param,
Post
} from 'routing-controllers';
import { ResponseSchema } from 'routing-controllers-openapi';
import { makeSHA } from 'web-utility';

import { dataSource, User, Voter, VoteTicket } from '../model';
import {
dataSource,
ElectionPublicKey,
User,
Voter,
VoteTicket,
VoteVerification
} from '../model';
import { lark, MemberBiDataTable } from '../utility';

@JsonController('/election')
export class ElectionController {
voterStore = dataSource.getRepository(Voter);
publicKeyStore = dataSource.getRepository(ElectionPublicKey);
algorithm = {
name: 'ECDSA',
namedCurve: 'P-384',
hash: { name: 'SHA-256' }
};

@Post('/:electionName/vote/ticket')
@Post('/:electionName/public-key')
@Authorized()
@ResponseSchema(VoteTicket)
async createVoteTicket(
@ResponseSchema(ElectionPublicKey)
async savePublicKey(
@CurrentUser() createdBy: User,
@Param('electionName') electionName: string
): Promise<VoteTicket> {
const { id, nickName, mobilePhone, email } = createdBy;
@Param('electionName') electionName: string,
@BodyParam('jsonWebKey') jsonWebKey: string
) {
const { id, nickName, mobilePhone } = createdBy;

const duplicatedVoter = await this.voterStore.findOneBy({
createdBy: { id },
Expand All @@ -43,15 +58,35 @@ export class ElectionController {
throw new ForbiddenError(
`${nickName} isn't a formal member who has the right to vote in ${electionName} election`
);
await this.voterStore.save({ electionName, createdBy });

const meta = [
nickName,
mobilePhone,
email,
const publicKey = await this.publicKeyStore.save({
electionName,
Math.random()
];
return { code: await makeSHA(meta + '') };
jsonWebKey
});
await this.voterStore.save({ createdBy, electionName });

return publicKey;
}

@Post('/:electionName/vote/verification')
@Authorized()
@ResponseSchema(VoteVerification)
async verifyVote(
@Param('electionName') electionName: string,
@Body() { publicKey, signature }: VoteTicket
) {
const key = await crypto.subtle.importKey(
'jwk',
JSON.parse(atob(publicKey)),
this.algorithm,
true,
['verify']
);
const verified = await crypto.subtle.verify(
this.algorithm,
key,
Buffer.from(signature, 'hex'),
new TextEncoder().encode(electionName)
);
return { electionName, publicKey, signature, verified };
}
}
28 changes: 26 additions & 2 deletions src/model/Election.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
import { IsString } from 'class-validator';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { Column, Entity } from 'typeorm';

import { Base } from './Base';
import { UserBase } from './User';

@Entity()
export class ElectionPublicKey extends Base {
@IsString()
@Column()
electionName: string;

@IsString()
@Column()
jsonWebKey: string;
}

@Entity()
export class Voter extends UserBase {
@IsString()
Expand All @@ -12,5 +24,17 @@ export class Voter extends UserBase {

export class VoteTicket {
@IsString()
code: string;
@IsOptional()
electionName: string;

@IsString()
publicKey: string;

@IsString()
signature: string;
}

export class VoteVerification extends VoteTicket {
@IsBoolean()
verified: boolean;
}
3 changes: 2 additions & 1 deletion src/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from './CheckEvent';
import { User } from './User';
import { ActivityLog, UserRank } from './ActivityLog';
import { Voter } from './Election';
import { ElectionPublicKey, Voter } from './Election';

export * from './Base';
export * from './CheckEvent';
Expand Down Expand Up @@ -39,6 +39,7 @@ const commonOptions: Pick<
UserActivityCheckInSummary,
ActivityAgendaCheckInSummary,
ActivityCheckInSummary,
ElectionPublicKey,
Voter
],
migrations: [`${isProduct ? '.tmp' : 'migration'}/*.ts`]
Expand Down
2 changes: 1 addition & 1 deletion type/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kaiyuanshe/kys-service",
"version": "1.0.0-rc.1",
"version": "1.0.0-rc.2",
"types": "index.d.ts",
"dependencies": {
"@types/jsonwebtoken": "^9.0.7",
Expand Down

0 comments on commit 53c974e

Please sign in to comment.