diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 877114c..520db11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1201,8 +1201,8 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} - fast-uri@3.0.5: - resolution: {integrity: sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==} + fast-uri@3.0.6: + resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} fast-xml-parser@4.5.1: resolution: {integrity: sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==} @@ -1922,8 +1922,8 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - napi-build-utils@1.0.2: - resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} @@ -2197,8 +2197,8 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - prebuild-install@7.1.2: - resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} hasBin: true @@ -2646,11 +2646,11 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tldts-core@6.1.73: - resolution: {integrity: sha512-k1g5eX87vxu3g//6XMn62y4qjayu4cYby/PF7Ksnh4F4uUK1Z1ze/mJ4a+y5OjdJ+cXRp+YTInZhH+FGdUWy1w==} + tldts-core@6.1.74: + resolution: {integrity: sha512-gTwtY6L2GfuxiL4CWpLknv9JDYYqBvKCk/BT5uAaAvCA0s6pzX7lr2IrkQZSUlnSjRHIjTl8ZwKCVXJ7XNRWYw==} - tldts@6.1.73: - resolution: {integrity: sha512-/h4bVmuEMm57c2uCiAf1Q9mlQk7cA22m+1Bu0K92vUUtTVT9D4mOFWD9r4WQuTULcG9eeZtNKhLl0Il1LdKGog==} + tldts@6.1.74: + resolution: {integrity: sha512-O5vTZ1UmmEmrLl/59U9igitnSMlprALLaLgbv//dEvjobPT9vyURhHXKMCDLEhn3qxZFIkb9PwAfNYV0Ol7RPQ==} hasBin: true to-regex-range@5.0.1: @@ -3647,7 +3647,7 @@ snapshots: ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.0.5 + fast-uri: 3.0.6 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4303,7 +4303,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-uri@3.0.5: {} + fast-uri@3.0.6: {} fast-xml-parser@4.5.1: dependencies: @@ -5158,7 +5158,7 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - napi-build-utils@1.0.2: {} + napi-build-utils@2.0.0: {} negotiator@0.6.3: {} @@ -5433,14 +5433,14 @@ snapshots: dependencies: xtend: 4.0.2 - prebuild-install@7.1.2: + prebuild-install@7.1.3: dependencies: detect-libc: 2.0.3 expand-template: 2.0.3 github-from-package: 0.0.0 minimist: 1.2.8 mkdirp-classic: 0.5.3 - napi-build-utils: 1.0.2 + napi-build-utils: 2.0.0 node-abi: 3.73.0 pump: 3.0.2 rc: 1.2.8 @@ -5836,7 +5836,7 @@ snapshots: dependencies: bindings: 1.5.0 node-addon-api: 7.1.1 - prebuild-install: 7.1.2 + prebuild-install: 7.1.3 tar: 6.2.1 optionalDependencies: node-gyp: 8.4.1 @@ -5984,11 +5984,11 @@ snapshots: through@2.3.8: {} - tldts-core@6.1.73: {} + tldts-core@6.1.74: {} - tldts@6.1.73: + tldts@6.1.74: dependencies: - tldts-core: 6.1.73 + tldts-core: 6.1.74 to-regex-range@5.0.1: dependencies: @@ -6003,7 +6003,7 @@ snapshots: tough-cookie@5.1.0: dependencies: - tldts: 6.1.73 + tldts: 6.1.74 tr46@5.0.0: dependencies: diff --git a/src/controller/Election.ts b/src/controller/Election.ts index 5ec9f33..b6f1e22 100644 --- a/src/controller/Election.ts +++ b/src/controller/Election.ts @@ -1,5 +1,7 @@ import { Authorized, + Body, + BodyParam, CurrentUser, ForbiddenError, JsonController, @@ -7,23 +9,36 @@ import { 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 { - 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 }, @@ -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 }; } } diff --git a/src/model/Election.ts b/src/model/Election.ts index 9be636b..0626213 100644 --- a/src/model/Election.ts +++ b/src/model/Election.ts @@ -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() @@ -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; } diff --git a/src/model/index.ts b/src/model/index.ts index 55c8633..cf79030 100644 --- a/src/model/index.ts +++ b/src/model/index.ts @@ -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'; @@ -39,6 +39,7 @@ const commonOptions: Pick< UserActivityCheckInSummary, ActivityAgendaCheckInSummary, ActivityCheckInSummary, + ElectionPublicKey, Voter ], migrations: [`${isProduct ? '.tmp' : 'migration'}/*.ts`] diff --git a/type/package.json b/type/package.json index 45db46a..8578fb5 100644 --- a/type/package.json +++ b/type/package.json @@ -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",