Skip to content

Commit

Permalink
Merge pull request #242 from aion-dk/tech-1654-expire_voter_sessions
Browse files Browse the repository at this point in the history
[Tech-1654] expire voter sessions
  • Loading branch information
av-martin authored Apr 27, 2023
2 parents eea1ce5 + 3d7acfe commit 03b7775
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 14 deletions.
36 changes: 35 additions & 1 deletion lib/av_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ export class AVClient implements IAVClient {

authorizationResponse = await coordinator.authorizeProofOfElectionCodes(this.keyPair.publicKey, this.proofOfElectionCodes, this.votingRoundReference)
} else {
throw new InvalidConfigError('Unknown authorization mode of voter authorizer')
throw new InvalidConfigError(`Unknown authorization mode of voter authorizer: '${authorizationMode}'`)
}

const { authToken } = authorizationResponse.data;
Expand Down Expand Up @@ -264,6 +264,40 @@ export class AVClient implements IAVClient {
return this.createVoterRegistration();
}

public async expireVoterSessions(votingRoundReference: string): Promise<AxiosResponse> {
const {
url: vaUrl,
contextUuid: vaUuid,
authorizationMode,
} = this.getLatestConfig().items.voterAuthorizerConfig.content.voterAuthorizer;
const latestConfigAddress = this.getLatestConfig().items.latestConfigItem.address;
const coordinator = new VoterAuthorizationCoordinator(vaUrl, vaUuid);

let authorizationResponse: AxiosResponse

switch (authorizationMode) {
case "proof-of-election-codes":
authorizationResponse = await coordinator.authorizeProofOfElectionCodes(
this.keyPair.publicKey,
this.proofOfElectionCodes,
votingRoundReference,
"expire");
break;
case "proof-of-identity":
throw new InvalidStateError("voter_authorizer.expire_voter.proof_of_identity_not_supported");
default:
throw new InvalidConfigError(`Unknown authorization mode of voter authorizer: '${authorizationMode}'`);
}

const { authToken } = authorizationResponse.data;
const decodedAuthToken = jwtDecode<JwtPayload>(authToken);

if(decodedAuthToken === null)
throw new InvalidTokenError('Auth token could not be decoded');

return await this.bulletinBoard.expireVoterSessions(authToken, latestConfigAddress);
}

/**
* Should be called after {@link AVClient.validateAccessCode | validateAccessCode}.
*
Expand Down
25 changes: 25 additions & 0 deletions lib/av_client/connectors/bulletin_board.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ export class BulletinBoard {
return response;
}

async expireVoterSessions(authToken: string, parentAddress: string): Promise<AxiosResponse> {
const response = await this.backend.post('voting/expirations', {
expToken: authToken,
parentAddress
}).catch(error => {
const response = error.response as AxiosResponse<BulletinBoardData>;
if (error.request && !response) {
throw new NetworkError('Network error. Could not connect to Bulletin Board.');
}

if ([403, 500].includes(response.status) && response.data) {
if (!response.data.error || !response.data.error.code || !response.data.error.description) {
throw new UnsupportedServerReplyError(`Unsupported Bulletin Board server error message: ${JSON.stringify(error.response.data)}`)
}

const errorMessage = response.data.error.description;
throw new BulletinBoardError(errorMessage);
}

throw error;
});

return response;
}

submitVotes(signedBallotCryptogramsItem): Promise<AxiosResponse> {
return this.backend.post('voting/votes', {vote: signedBallotCryptogramsItem}, {
headers: {
Expand Down
9 changes: 5 additions & 4 deletions lib/av_client/connectors/voter_authorization_coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default class VoterAuthorizationCoordinator {
}

/**
*
* @param opaqueVoterId Gets
* @returns
*
* @param opaqueVoterId Gets
* @returns
*/
createSession(opaqueVoterId: string, email: string): Promise<AxiosResponse> {
return this.backend.post('create_session', {
Expand Down Expand Up @@ -57,8 +57,9 @@ export default class VoterAuthorizationCoordinator {
})
}

authorizeProofOfElectionCodes(publicKey: string, proof: ProofOfElectionCodes, votingRoundReference: string): Promise<AxiosResponse> {
authorizeProofOfElectionCodes(publicKey: string, proof: ProofOfElectionCodes, votingRoundReference: string, scope = "register"): Promise<AxiosResponse> {
return this.backend.post('authorize_proof', {
scope: scope,
electionContextUuid: this.electionContextUuid,
voterPublicKey: proof.mainKeyPair.publicKey, // This public key is used by the VA to find the voter to authorize.
sessionPublicKey: publicKey, // This public key is used for the auth token
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aion-dk/js-client",
"version": "3.0.2",
"version": "3.1.0",
"license": "MIT",
"description": "Assembly Voting JS client",
"main": "dist/lib/av_client.js",
Expand Down
79 changes: 71 additions & 8 deletions test/bulletin_board.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ import {
NetworkError,
UnsupportedServerReplyError
} from '../lib/av_client/errors';
import {expect} from "chai";

describe('BulletinBoard#registerVoter', () => {
let bulletinBoard: BulletinBoard;
let bulletinBoard: BulletinBoard;

beforeEach(() => {
bulletinBoard = new BulletinBoard(bulletinBoardHost + 'us');
});
beforeEach(() => {
bulletinBoard = new BulletinBoard(bulletinBoardHost + 'us');
});

afterEach(() => {
nock.cleanAll();
});
afterEach(() => {
nock.cleanAll();
});

describe('BulletinBoard#registerVoter', () => {
context('public key already registered on Bulletin Board', () => {
it('returns an error', async () => {
nock(bulletinBoardHost).post('/us/voting/registrations')
Expand Down Expand Up @@ -61,3 +62,65 @@ describe('BulletinBoard#registerVoter', () => {
});
});
});


describe('BulletinBoard#expireVoterSessions', () => {

context('nothing goes wrong', ()=>{
it('returns a response', async () => {
nock(bulletinBoardHost).post('/us/voting/expirations')
.reply(200, { expiredSessions: 0 });

expect(
await bulletinBoard.expireVoterSessions('authToken', 'parent_address')
).to.be.a("object");
});

it('the response data indicates how many sessions were expired', async () => {
nock(bulletinBoardHost).post('/us/voting/expirations')
.reply(200, { expiredSessions: 0 });

const response = await bulletinBoard.expireVoterSessions('authToken', 'parent_address');
expect(response.data).to.eql({expiredSessions: 0});
});
})

context('public key already registered on Bulletin Board', () => {
it('returns an error', async () => {
nock(bulletinBoardHost).post('/us/voting/expirations')
.reply(403, { error: { code: 13, description: 'Public key error' }});

await expectError(
bulletinBoard.expireVoterSessions('authToken', 'parent_address'),
BulletinBoardError,
'Public key error'
);
});
});

context('Bulletin Board returns unknown error', () => {
it('returns an error', async () => {
nock(bulletinBoardHost).post('/us/voting/expirations')
.reply(500, { foo: 'bar' });

await expectError(
bulletinBoard.expireVoterSessions('authToken', 'parent_address'),
UnsupportedServerReplyError,
'Unsupported Bulletin Board server error message: {"foo":"bar"}'
);
});
});

context('Bulletin Board becomes unreachable', () => {
it('returns an error', async () => {
nock(bulletinBoardHost).post('/us/voting/expirations')
.replyWithError('Some network error');

await expectError(
bulletinBoard.expireVoterSessions('authToken', 'parent_address'),
NetworkError,
'Network error. Could not connect to Bulletin Board.'
);
});
});
});

0 comments on commit 03b7775

Please sign in to comment.