Skip to content

Commit

Permalink
722: create cards in a batch
Browse files Browse the repository at this point in the history
  • Loading branch information
sarahsporck committed Feb 24, 2023
1 parent 3edf84c commit 28f5e77
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 63 deletions.
4 changes: 2 additions & 2 deletions administration/src/cards/PdfFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function fillCodeArea(qrCode: PdfQrCode, x: number, y: number, size: number, pag

export async function generatePdf(
dynamicCodes: DynamicActivationCode[],
staticCodes: StaticVerificationCode[] | null,
staticCodes: StaticVerificationCode[],
region: Region,
pdfConfig: PdfConfig
) {
Expand All @@ -143,7 +143,7 @@ export async function generatePdf(
? await PDFDocument.load(await fetch(pdfConfig.templatePath).then(res => res.arrayBuffer()))
: null

if (staticCodes !== null && dynamicCodes.length !== staticCodes.length) {
if (staticCodes.length !== 0 && dynamicCodes.length !== staticCodes.length) {
throw new Error('Activation codes count does not match static codes count.')
}

Expand Down
63 changes: 25 additions & 38 deletions administration/src/cards/activation.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { DynamicActivationCode, StaticVerificationCode } from '../generated/card_pb'
import {
AddCardDocument,
AddCardMutation,
AddCardMutationVariables,
AddCardsDocument,
AddCardsMutation,
AddCardsMutationVariables,
CardGenerationModelInput,
CodeType,
Region,
Expand All @@ -11,42 +11,29 @@ import hashCardInfo from './hashCardInfo'
import { ApolloClient } from '@apollo/client'
import { uint8ArrayToBase64 } from '../util/base64'

export async function activateCard<T extends DynamicActivationCode | StaticVerificationCode>(
client: ApolloClient<object>,
activationCode: T,
region: Region,
codeType: T extends DynamicActivationCode ? CodeType.Dynamic : CodeType.Static
) {
const cardInfoHash = await hashCardInfo(activationCode.info!, activationCode.pepper)
const expirationDay = activationCode.info!.expirationDay
const totpSecret =
activationCode instanceof DynamicActivationCode ? uint8ArrayToBase64(activationCode.totpSecret) : null
const card: CardGenerationModelInput = {
cardExpirationDay: expirationDay ?? null, // JS number can represent integers up to 2^53, so it can represent all values of an uint32 (protobuf)
cardInfoHashBase64: uint8ArrayToBase64(cardInfoHash),
totpSecretBase64: totpSecret,
regionId: region.id,
codeType,
}

return await client.mutate<AddCardMutation, AddCardMutationVariables>({
mutation: AddCardDocument,
variables: { card },
})
}
type Codes = (DynamicActivationCode | StaticVerificationCode)[]

export async function activateCards<T extends DynamicActivationCode | StaticVerificationCode>(
client: ApolloClient<object>,
activationCodes: T[],
region: Region,
codeType: T extends DynamicActivationCode ? CodeType.Dynamic : CodeType.Static
) {
const results = await Promise.all(
activationCodes.map(async activationCode => activateCard(client, activationCode, region, codeType))
export async function activateCards(client: ApolloClient<object>, activationCodes: Codes, region: Region) {
const cards: CardGenerationModelInput[] = await Promise.all(
activationCodes.map(async code => {
const codeType = code instanceof DynamicActivationCode ? CodeType.Dynamic : CodeType.Static
const cardInfoHash = await hashCardInfo(code.info!, code.pepper)
const expirationDay = code.info!.expirationDay
const totpSecret = code instanceof DynamicActivationCode ? uint8ArrayToBase64(code.totpSecret) : null
return {
cardExpirationDay: expirationDay ?? null, // JS number can represent integers up to 2^53, so it can represent all values of an uint32 (protobuf)
cardInfoHashBase64: uint8ArrayToBase64(cardInfoHash),
totpSecretBase64: totpSecret,
regionId: region.id,
codeType,
}
})
)

const firstFailure = results.find(result => !result.data?.success)
if (firstFailure) {
throw Error(JSON.stringify(firstFailure))
const result = await client.mutate<AddCardsMutation, AddCardsMutationVariables>({
mutation: AddCardsDocument,
variables: { cards },
})
if (!result.data?.success) {
throw Error(JSON.stringify(result))
}
}
11 changes: 5 additions & 6 deletions administration/src/components/cards/CreateCardsController.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react'
import { useContext, useState } from 'react'
import { Spinner } from '@blueprintjs/core'
import { CardBlueprint } from '../../cards/CardBlueprint'
import CreateCardsForm from './CreateCardsForm'
Expand All @@ -9,8 +9,8 @@ import downloadDataUri from '../../util/downloadDataUri'
import { WhoAmIContext } from '../../WhoAmIProvider'
import { activateCards } from '../../cards/activation'
import { ProjectConfigContext } from '../../project-configs/ProjectConfigContext'
import { CodeType, Region } from '../../generated/graphql'
import { generatePdf } from '../../cards/PdfFactory'
import { Region } from '../../generated/graphql'

enum CardActivationState {
input,
Expand Down Expand Up @@ -51,13 +51,12 @@ const InnerCreateCardsController = ({ region }: { region: Region }) => {
? cardBlueprints.map(cardBlueprints => {
return cardBlueprints.generateStaticVerificationCode()
})
: null
: []

const pdfDataUri = await generatePdf(dynamicCodes, staticCodes, region, projectConfig.pdf)

await activateCards(client, dynamicCodes, region, CodeType.Dynamic)

if (staticCodes) await activateCards(client, staticCodes, region, CodeType.Static)
const codes = [...dynamicCodes, ...staticCodes]
await activateCards(client, codes, region)

downloadDataUri(pdfDataUri, 'berechtigungskarten.pdf')
setState(CardActivationState.finished)
Expand Down
4 changes: 2 additions & 2 deletions administration/src/graphql/verification/addCard.graphql
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
mutation addCard($card: CardGenerationModelInput!) {
success: addCard(card: $card)
mutation addCards($cards: [CardGenerationModelInput!]!) {
success: addCards(cards: $cards)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,31 @@ import java.util.Base64
@Suppress("unused")
class CardMutationService {
@GraphQLDescription("Stores a new digital EAK")
fun addCard(dfe: DataFetchingEnvironment, card: CardGenerationModel): Boolean {
fun addCards(dfe: DataFetchingEnvironment, cards: List<CardGenerationModel>): Boolean {
val jwtPayload = dfe.getContext<GraphQLContext>().enforceSignedIn()

transaction {
val user = AdministratorEntity.findById(jwtPayload.adminId) ?: throw UnauthorizedException()
val targetedRegionId = card.regionId
if (!Authorizer.mayCreateCardInRegion(user, targetedRegionId)) {
throw UnauthorizedException()
val user =
AdministratorEntity.findById(jwtPayload.adminId)
?: throw UnauthorizedException()
for (card in cards) {
val targetedRegionId = card.regionId
if (!Authorizer.mayCreateCardInRegion(user, targetedRegionId)) {
throw UnauthorizedException()
}
val totpSecret =
if (card.totpSecretBase64 != null)
Base64.getDecoder().decode(card.totpSecretBase64)
else null
CardRepository.insert(
Base64.getDecoder().decode(card.cardInfoHashBase64),
totpSecret,
card.cardExpirationDay,
card.regionId,
user.id.value,
card.codeType
)
}
val totpSecret = if (card.totpSecretBase64 != null) Base64.getDecoder().decode(card.totpSecretBase64) else null
CardRepository.insert(
Base64.getDecoder().decode(card.cardInfoHashBase64),
totpSecret,
card.cardExpirationDay,
card.regionId,
user.id.value,
card.codeType
)
}
return true
}
Expand Down
2 changes: 1 addition & 1 deletion specs/backend-api.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ type Coordinates {

type Mutation {
"Stores a new digital EAK"
addCard(card: CardGenerationModelInput!): Boolean!
addCards(cards: [CardGenerationModelInput!]!): Boolean!
"Stores a new application for an EAK"
addEakApplication(application: ApplicationInput!, regionId: Int!): Boolean!
"Changes an administrator's password"
Expand Down

0 comments on commit 28f5e77

Please sign in to comment.