mirror of
https://github.com/covidpass-org/covidpass.git
synced 2025-02-22 06:27:49 +01:00
Merge branch 'main' of https://github.com/billylo1/covidpass into main
This commit is contained in:
commit
8befe6af76
@ -27,18 +27,17 @@ function urlParse(text: string, links: link[]): JSX.Element[] {
|
||||
function Faq(): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'index', 'faq']);
|
||||
const questionList = [
|
||||
{description: 'Which version of iOS does this support?', answer: 'iOS 13.7 is the minimum at the moment. We are looking for adjustments for older iOS versions, but it will take a bit of time.'},
|
||||
{description: 'I\'m having issues with adding it to my iPhone 6.', answer: 'Unfortunately, the iPhone 6 supports up to iOS 12 while the minimum requirement for our app is iOS 13.7 however, we are looking for ways around this to make it more accessible. In the meantime you can try it on a computer or another device and save it as either a wallet card or a photo - if you save it as a card, you can then email it to your iPhone and you will be able to import it into Apple Wallet that way.'},
|
||||
{description: 'Which version of iOS does this support?', answer: 'The new enhanced QR code requires iOS 15+.'},
|
||||
{description: 'I\'m having issues with adding it to my iPhone 6.', answer: 'Unfortunately, the minimum requirement for the new QR code is iOS 15 which runs on iPhone 6s and newer devices.'},
|
||||
{description: "What are the supported browsers?", answer: 'For iPhones, only Safari is supported for importing to your Apple Wallet. For any other devices, we recommend that you save it as photo using your browser of choice. Browsers built internally into mobile apps (e.g. Facebook, Twitter, Instagram) are known to have issues.'},
|
||||
{description: "How is my private information handled?", answer: 'Your proof-of-vaccination PDF (and most of the information in it) never leaves your device, and does NOT get sent to our server - the only information we send and store is non-personally-identifiable information such as vaccine type, date, and which organization gave you the vaccine. We share your concern about personal data being stored and lost, which is why we chose not to store or send any of it to our servers so there is no chance of it being lost or leaked.'},
|
||||
{description: 'Do you have plans for Android support?', answer: 'Yes. We are working with Google to gain access to the APIs required. Meanwhile, you can also use this tool to download an Apple Wallet pass and import that into Google Pay Wallet using apps such as Pass2Pay or simply save it as a photo.'},
|
||||
{description: 'I have a Red/White OHIP card. Can I still use this tool?', answer: 'Yes you can! Just call the Provincial Vaccine Contact Centre at 1-833-943-3900. The call centre agent can email you a copy of the receipt.'},
|
||||
{description: 'I do not have a health card. Can I still use this tool?', answer: 'First contact your local public health unit to verify your identity and receive a COVIDcovid ID/Personal Access Code. You can then call the Provincial Vaccine Contact Centre at 1-833-943-3900 to get an email copy of your receipt.'},
|
||||
{description: 'I\'m seeing an error message saying “Failed byte range verification." What do I do?', answer: 'If you see this error then please try re-downloading your receipt from the provincial proof-of-vaccination portal and trying again. We have received reports from some people that this has resolved the problem for them.'},
|
||||
{description: 'What does the colour of the Apple Wallet pass mean?', answer: 'Dose 1 is shown as Orange; dose 2+ in green for easy differentiation without reading the text. For the Janssen (Johnson & Johnson) vaccine, dose 1 is shown as green.'},
|
||||
{description: 'Should I use the official provincial apps when they come out on 22nd October?', answer: 'YES. Once the official QR code from the province is available, please come back to this site and you will be able to generate a new Apple Wallet pass which contains that new QR code'},
|
||||
{description: 'Why isn\'t the new Apple Wallet pass green/orange?', answer: 'The official verifier from Ontario will decide which one is accepted. Color coding is no longer used for our Apple Wallet tool.'},
|
||||
{description: 'How is the data on my vaccination receipt processed?', answer: 'Inside your local web browser, it checks the receipt for a digital signature from the provincial proof-of-vaccination system. If present, the receipt data is converted into Apple\'s format and then added into your iOS Wallet app.'},
|
||||
{description: 'How can organizations validate this QR code?', answer: 'You can use our verifier app at verifier.vaccine-ontario.ca to verify these passes quickly if you are a business - you should also be able to use any normal QR code scanner to scan this code and it will take you to a verification site which tells you whether the receipt is valid or not'},
|
||||
{description: 'How can organizations validate this QR code?', answer: 'Verify Ontario app is your official tool. For devices that cannot run the official tool, you can also use our web-based tool at verifier.vaccine-ontario.ca'},
|
||||
{description: 'Can I use the same iPhone to store passes for my entire family?', answer: 'Yes.'},
|
||||
{description: 'Is this free and non-commercial?', answer: 'Similar to VaxHuntersCanada, there are no commercial interests. Just volunteers trying to do our part to help the community.'},
|
||||
{description: 'How about support for other provinces?', answer: 'We will be investigating BC and Québec support shortly. If you are interested in contributing, please email us at grassroots@vaccine-ontario.ca'},
|
||||
|
@ -1 +0,0 @@
|
||||
nb
|
@ -1,6 +0,0 @@
|
||||
title: CovidPass
|
||||
subtitle: Legg til EUs digitale COVID -sertifikater i favorittlommebokappene dine.
|
||||
privacyPolicy: Privacy Policy
|
||||
donate: Sponsor
|
||||
gitHub: GitHub
|
||||
imprint: Avtrykk
|
@ -1,20 +0,0 @@
|
||||
noFileOrQrCode: Vennligst skann en QR-kode, eller velg en fil
|
||||
signatureFailed: Feil under signering av pass på server
|
||||
decodingFailed: Kunne ikke dekode nyttelast for QR-kode
|
||||
invalidColor: Ugyldig farge
|
||||
certificateData: Kunne ikke lese sertifikatdata
|
||||
nameMissing: Kunne ikke lese navnet
|
||||
dobMissing: Kunne ikke lese fødselsdatoen
|
||||
invalidMedicalProduct: Ugyldig medisinsk produkt
|
||||
invalidCountryCode: Ugyldig landskode
|
||||
invalidManufacturer: Ugyldig produsent
|
||||
invalidFileType: Ugyldig filtype
|
||||
couldNotDecode: Kunne ikke dekode QR-koden fra filen
|
||||
couldNotFindQrCode: Kunne ikke finne QR-koden i den medfølgende filen
|
||||
invalidQrCode: Ugyldig QR-kode
|
||||
certificateType: Fant ingen gyldig sertifikattype
|
||||
invalidTestResult: Ugyldig testresultat
|
||||
invalidTestType: Ugyldig testtype
|
||||
noCameraAccess: Fikk ikke tilgang til kameraet. Kontroller tillatelser under Innstillinger > Safari > Kamera.
|
||||
noCameraFound: Fant ikke kamera.
|
||||
safariSupportOnly: På iOS, vennligst bruk Safari nettleseren.
|
@ -1,27 +0,0 @@
|
||||
heading: Informasjon i henhold til § 5 TMG
|
||||
contact: Kontakt
|
||||
euDisputeResolution: EU Konfliktløsning
|
||||
euDisputeResolutionParagraph: |
|
||||
Europakommisjonen tilbyr en plattform for online konfliktløsning (OS) https://ec.europa.eu/consumers/odr.
|
||||
Du finner e-postadressen vår i avtrykket ovenfor.
|
||||
consumerDisputeResolution: Forbrukerkonfliktløsning
|
||||
consumerDisputeResolutionParagraph: Vi er ikke villige eller forpliktet til å delta i konfliktløsningssaker for et forbrukernemnd.
|
||||
liabilityForContents: Ansvar for innholdet
|
||||
liabilityForContentsParagraph: |
|
||||
Som tjenesteleverandør er vi ansvarlig for vårt eget innhold på disse sidene i henhold til § 7 avsnitt 1 TMG under de generelle lovene.
|
||||
I henhold til §§ 8 til 10 TMG er vi ikke forpliktet til å overvåke overført eller lagret informasjon eller å undersøke omstendigheter som indikerer ulovlig aktivitet.
|
||||
Forpliktelser til å fjerne eller blokkere bruk av informasjon i henhold til de generelle lovene forblir upåvirket.
|
||||
Ansvar i denne forbindelse er imidlertid bare mulig fra det tidspunkt et konkret brudd på loven blir kjent.
|
||||
Hvis vi blir klar over slike overtredelser, fjerner vi det relevante innholdet umiddelbart.
|
||||
liabilityForLinks: Ansvar for lenker
|
||||
liabilityForLinksParagraph: |
|
||||
Vårt tilbud inneholder lenker til eksterne nettsteder til tredjeparter, hvis innhold vi ikke har innflytelse på.
|
||||
Derfor kan vi ikke påta oss noe ansvar for dette eksterne innholdet.
|
||||
Den respektive leverandøren eller operatøren av nettstedene er alltid ansvarlig for innholdet på de koblede nettstedene.
|
||||
De koblede sidene ble sjekket for mulige lovbrudd på tidspunktet for koblingen.
|
||||
Ulovlig innhold var ikke gjenkjennelig på tidspunktet for koblingen.
|
||||
En permanent kontroll av innholdet på de lenkete sidene er imidlertid ikke rimelig uten konkrete bevis på brudd på loven.
|
||||
Hvis vi blir klar over brudd, vil vi fjerne slike lenker umiddelbart.
|
||||
credits: Kreditere
|
||||
creditsSource: Med utdrag fra https://www.e-recht24.de/impressum-generator.html
|
||||
creditsTranslation: Oversatt med https://www.DeepL.com/Translator (free version)
|
@ -1,29 +0,0 @@
|
||||
iosHint: På iOS, vennligst bruk Safari nettleseren.
|
||||
errorClose: Lukk
|
||||
selectCertificate: Velg Sertifikat
|
||||
selectCertificateDescription: |
|
||||
Skann QR-koden på sertifikatet ditt, eller velg et skjermbilde eller en PDF med QR-koden.
|
||||
Vær oppmerksom på at det ikke støttes å velge en fil direkte fra kameraet.
|
||||
stopCamera: Stopp Kamera
|
||||
startCamera: Start Kamera
|
||||
openFile: Velg Fil
|
||||
foundQrCode: Fant QR-kode!
|
||||
pickColor: Velg en farge
|
||||
pickColorDescription: Velg en bakgrunnsfarge for passet ditt.
|
||||
colorWhite: hvit
|
||||
colorBlack: svart
|
||||
colorGrey: grå
|
||||
colorGreen: grønn
|
||||
colorIndigo: mørkeblå
|
||||
colorBlue: blå
|
||||
colorPurple: lilla
|
||||
colorTeal: blågrønn
|
||||
addToWallet: Legg til i Lommebok
|
||||
dataPrivacyDescription: |
|
||||
Personvern er av spesiell betydning ved behandling av helserelaterte data.
|
||||
For å ta en informert beslutning, vennligst les
|
||||
iAcceptThe: Jeg godtar
|
||||
privacyPolicy: Personvernerklæring
|
||||
createdOnDevice: Laget på enheten din
|
||||
openSourceTransparent: Åpen kildekode og gjennomsiktig
|
||||
hostedInEU: Driftet i EU
|
@ -1,57 +0,0 @@
|
||||
gdprNotice: |
|
||||
Personvernerklæringen vår er basert på vilkårene som brukes av den europeiske lovgiveren
|
||||
for vedtakelsen av General Data Protection Regulation (GDPR).
|
||||
generalInfo: Generell informasjon
|
||||
generalInfoProcess: |
|
||||
Hele prosessen med å generere passfilen skjer lokalt i nettleseren din.
|
||||
For signeringstrinnet sendes bare en hash representasjon av dataene dine til serveren.
|
||||
generalInfoStoring: Dine data lagres ikke utover den aktive nettlesersessionen, og nettsten bruker ikke informasjonskapsler.
|
||||
generalInfoThirdParties: Ingen data blir sendt til tredjeparter.
|
||||
generalInfoHttps: Vi overfører dataene dine sikkert over https.
|
||||
generalInfoLocation: Serveren vår ligger i Nuremberg, Germany.
|
||||
generalInfoGitHub: Kildekoden til dette nettstedet er tilgjengelig på
|
||||
generalInfoLockScreen: Som standard er Apple Wallet pass tilgjengelig fra låseskjermen. Dette kan endres i
|
||||
settings: innstillinger
|
||||
generalInfoProvider: |
|
||||
Serverleverandøren behandler data for å tilby dette nettstedet.
|
||||
For å bedre forstå hvilke tiltak de tar for å beskytte dataene dine, vennligst les også deres
|
||||
privacyPolicy: personvernerklæring
|
||||
andThe: og
|
||||
dataPrivacyFaq: vanlige spørsmål om personvern
|
||||
contact: Kontakt
|
||||
email: Epost
|
||||
website: Webside
|
||||
process: Forenklet forklaring av prosessen
|
||||
processFirst: Først skjer følgende trinn lokalt i nettleseren din
|
||||
processSecond: Så skjer følgende trinn på serveren vår
|
||||
processThird: Til slutt skjer følgende trinn lokalt i nettleseren din
|
||||
processRecognizing: Gjenkjenne og trekke ut QR-kodedataene fra det valgte sertifikatet
|
||||
processDecoding: Dekoding av dine personlige og helserelaterte data fra QR-kode
|
||||
processAssembling: Montering av en ufullstendig pass-fil ut av dataene dine
|
||||
processGenerating: Generere en fil som inneholder hashes av dataene som er lagret i passfilen
|
||||
processSending: Sender bare filen som inneholder hasjene til serveren vår
|
||||
processReceiving: Motta og sjekke hashene som ble generert lokalt
|
||||
processSigning: Signerer filen som inneholder hasjene
|
||||
processSendingBack: Sender signaturen tilbake
|
||||
processCompleting: Montering av den signerte passfilen fra den ufullstendige filen generert lokalt og signaturen
|
||||
processSaving: Lagrer filen på enheten din
|
||||
locallyProcessedData: Lokalt behandlede data
|
||||
the:
|
||||
schema: Skjema for digitalt Covid -sertifikat
|
||||
specification: inneholder en detaljert spesifikasjon av hvilke data som kan finnes i QR -koden og vil bli behandlet i nettleseren din.
|
||||
serverProvider: Serverleverandør
|
||||
serverProviderIs: Serverleverandøren vår er
|
||||
logFiles: Følgende data kan samles inn og lagres i serverloggfilene
|
||||
logFilesBrowser: Nettlesertypene og versjonene som brukes
|
||||
logFilesOs: Operativsystemet som brukes av tilgangssystemet
|
||||
logFilesReferrer: Nettstedet som et tilgangssystem kommer til nettstedet vårt fra (såkalte henvisninger)
|
||||
logFilesTime: Dato og klokkeslett for tilgang
|
||||
logFilesIpAddress: De pseudonymiserte IP-adressene
|
||||
rights: Dine rettigheter
|
||||
rightsGranted: I samsvar med GDPR har du følgende rettigheter
|
||||
rightsAccess: Rett til tilgang til dataene dine; Du har rett til å vite hvilke data som er samlet om deg og hvordan de ble behandlet.
|
||||
rightsErasure: Rett til å bli glemt; Sletting av dine personlige data.
|
||||
rightsRectification: Rett til å rette opp; Du har rett til å korrigere unøyaktige data.
|
||||
rightsPortability: Rett til dataportabilitet; Du har rett til å overføre dataene dine fra et behandlingssystem til et annet.
|
||||
thirdParties: Tredjeparter knyttet til
|
||||
appleSync: Apple kan synkronisere passene dine via iCloud
|
@ -1 +0,0 @@
|
||||
nb
|
@ -1 +0,0 @@
|
||||
no
|
@ -147,7 +147,9 @@ export class PassData {
|
||||
|
||||
// Update our pass name if this is an SHC pass
|
||||
if (payload.rawData.length > 0) {
|
||||
const newPassTitle = `${Constants.NAME}, ${payload.shcReceipt.cardOrigin}`;
|
||||
// const newPassTitle = `${Constants.NAME}, ${payload.shcReceipt.cardOrigin}`;
|
||||
const newPassTitle = `${Constants.NAME}`; // hot patch for production for now... string too long to fit in pass
|
||||
|
||||
this.logoText = newPassTitle;
|
||||
this.organizationName = newPassTitle;
|
||||
this.description = newPassTitle;
|
||||
|
@ -109,6 +109,7 @@ export class PassPhotoCommon {
|
||||
// validation since SHCs are self-validating. This entire registration process could
|
||||
// be turned off for SHCs and there would be no harm to the card creation process
|
||||
registrationPayload = generateSHCRegisterPayload(payloadBody.shcReceipt);
|
||||
registrationPayload.serialNumber = payload.serialNumber; // serial number is needed as it's the firestore document id
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -193,9 +193,26 @@ function processSHCReceipt(receipt: SHCReceipt, generic: PassDictionary) {
|
||||
generic.primaryFields.push(
|
||||
{
|
||||
key: "name",
|
||||
label: "Name",
|
||||
value: receipt.name
|
||||
label: "",
|
||||
value: `${receipt.name} (${receipt.dateOfBirth})`
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
for (let i = 0; i < receipt.vaccinations.length; i++) {
|
||||
generic.secondaryFields.push(
|
||||
{
|
||||
key: "vaccine",
|
||||
label: "Vaccine",
|
||||
value: receipt.vaccinations[i].vaccineName
|
||||
},
|
||||
{
|
||||
key: "dov",
|
||||
label: "Vacc. Date",
|
||||
value: receipt.vaccinations[i].vaccinationDate
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
190
src/process.ts
190
src/process.ts
@ -1,12 +1,11 @@
|
||||
import {PayloadBody, Receipt, HashTable} from "./payload";
|
||||
import {PayloadBody} from "./payload";
|
||||
import * as PdfJS from 'pdfjs-dist/legacy/build/pdf'
|
||||
import {QRCode} from "jsqr";
|
||||
import { getCertificatesInfoFromPDF } from "@ninja-labs/verify-pdf"; // ES6
|
||||
import * as Sentry from '@sentry/react';
|
||||
import * as Decode from './decode';
|
||||
import {getScannedJWS, verifyJWS, decodeJWS} from "./shc";
|
||||
|
||||
import { PDFPageProxy, TextItem } from 'pdfjs-dist/types/src/display/api';
|
||||
import { PDFPageProxy } from 'pdfjs-dist/types/src/display/api';
|
||||
|
||||
// import {PNG} from 'pngjs'
|
||||
// import {decodeData} from "./decode";
|
||||
@ -22,19 +21,9 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
|
||||
switch (file.type) {
|
||||
case 'application/pdf':
|
||||
|
||||
///////// DELETE THE FOLLOWING CODE ON OCT 22ND //////////////////////
|
||||
const receiptType = await detectReceiptType(fileBuffer);
|
||||
console.log(`receiptType = ${receiptType}`);
|
||||
// receipt type is needed to decide if digital signature checking is needed
|
||||
if (receiptType == 'ON') {
|
||||
// Bail out immediately, special case
|
||||
const receipts = await loadPDF(fileBuffer);
|
||||
return {receipts, rawData: '', shcReceipt: null};
|
||||
} else {
|
||||
///////// END OCT 22ND DELETE BLOCK //////////////////////
|
||||
imageData = await getImageDataFromPdf(fileBuffer);
|
||||
}
|
||||
imageData = await getImageDataFromPdf(fileBuffer);
|
||||
break;
|
||||
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/webp':
|
||||
@ -42,6 +31,7 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
|
||||
console.log(`image ${file.type}`);
|
||||
imageData = [await getImageDataFromImage(file)];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error('invalidFileType')
|
||||
}
|
||||
@ -50,174 +40,6 @@ export async function getPayloadBodyFromFile(file: File): Promise<PayloadBody> {
|
||||
return processSHC(imageData);
|
||||
}
|
||||
|
||||
async function detectReceiptType(fileBuffer : ArrayBuffer): Promise<string> {
|
||||
|
||||
console.log('detectPDFTypeAndProcess');
|
||||
|
||||
const typedArray = new Uint8Array(fileBuffer);
|
||||
let loadingTask = PdfJS.getDocument(typedArray);
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
const docMetadata = await pdfDocument.getMetadata();
|
||||
|
||||
// Explicitly try to detect an ON PDF based on the headers in the PDF
|
||||
//console.log(`PDF details: metadata=${JSON.stringify(docMetadata)}`);
|
||||
|
||||
// The Ontario proof-of-vaccination receipts have several fixed unchanging pieces of metadata that we use for detection
|
||||
if (docMetadata.info['IsSignaturesPresent'] &&
|
||||
(docMetadata.info['Producer'] == 'PDFKit') &&
|
||||
(docMetadata.info['PDFFormatVersion'] == '1.7') &&
|
||||
(docMetadata.info['Title'] == 'COVID-19 vaccination receipt / Récépissé de vaccination contre la COVID-19')
|
||||
) {
|
||||
return Promise.resolve('ON');
|
||||
} else {
|
||||
// If it's not an exact match to an ON proof-of-vaccination PDF, it will never pass validation anyways so treat it as an SHC PDF
|
||||
return Promise.resolve('SHC');
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPDF(fileBuffer : ArrayBuffer): Promise<HashTable<Receipt>> {
|
||||
|
||||
try {
|
||||
|
||||
const certs = getCertificatesInfoFromPDF(fileBuffer);
|
||||
|
||||
const result = certs[0];
|
||||
const refcert = '-----BEGIN CERTIFICATE-----\r\n'+
|
||||
'MIIHNTCCBh2gAwIBAgIQanhJa+fBXT8GQ8QG/t9p4TANBgkqhkiG9w0BAQsFADCB\r\n'+
|
||||
'ujELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsT\r\n'+
|
||||
'H1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAy\r\n'+
|
||||
'MDE0IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEuMCwG\r\n'+
|
||||
'A1UEAxMlRW50cnVzdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEwxTTAeFw0y\r\n'+
|
||||
'MTA1MjAxMzQxNTBaFw0yMjA2MTkxMzQxNDlaMIHTMQswCQYDVQQGEwJDQTEQMA4G\r\n'+
|
||||
'A1UECBMHT250YXJpbzEQMA4GA1UEBxMHVG9yb250bzETMBEGCysGAQQBgjc8AgED\r\n'+
|
||||
'EwJDQTEYMBYGCysGAQQBgjc8AgECEwdPbnRhcmlvMRcwFQYDVQQKEw5PbnRhcmlv\r\n'+
|
||||
'IEhlYWx0aDEaMBgGA1UEDxMRR292ZXJubWVudCBFbnRpdHkxEzARBgNVBAUTCjE4\r\n'+
|
||||
'LTA0LTIwMTkxJzAlBgNVBAMTHmNvdmlkMTlzaWduZXIub250YXJpb2hlYWx0aC5j\r\n'+
|
||||
'YTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2bD+Ng1RNYCNVVtEQ3\r\n'+
|
||||
'zg8JKFvRWFFPIF/UTXGg3iArK1tKr1xtjx6OdFtwosHyo+3ksPRicc4KeuV6/QMF\r\n'+
|
||||
'qiVJ5IOy9TSVImJsmONgFyEiak0dGYG5SeHiWwyaUvkniWd7U3wWEl4nOZuLAYu4\r\n'+
|
||||
'8ZLot8p8Q/UaNvAoNsRDv6YDGjL2yGHaXxi3Bb6XTQTLcevuEQeM6g1LtKyisZfB\r\n'+
|
||||
'Q8TKThBq99EojwHfXIhddxbPKLeXvWJgK1TcL17UFIwx6ig74s0LyYqEPm8Oa8qR\r\n'+
|
||||
'+IesFUT9Liv7xhV+tU52wmNfDi4znmLvs5Cmh/vmcHKyhEbxhYqciWJocACth5ij\r\n'+
|
||||
'E3kCAwEAAaOCAxowggMWMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFoW3zt+jaHS\r\n'+
|
||||
'pm1EV5hU4XD+mwO5MB8GA1UdIwQYMBaAFMP30LUqMK2vDZEhcDlU3byJcMc6MGgG\r\n'+
|
||||
'CCsGAQUFBwEBBFwwWjAjBggrBgEFBQcwAYYXaHR0cDovL29jc3AuZW50cnVzdC5u\r\n'+
|
||||
'ZXQwMwYIKwYBBQUHMAKGJ2h0dHA6Ly9haWEuZW50cnVzdC5uZXQvbDFtLWNoYWlu\r\n'+
|
||||
'MjU2LmNlcjAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmVudHJ1c3QubmV0\r\n'+
|
||||
'L2xldmVsMW0uY3JsMCkGA1UdEQQiMCCCHmNvdmlkMTlzaWduZXIub250YXJpb2hl\r\n'+
|
||||
'YWx0aC5jYTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG\r\n'+
|
||||
'AQUFBwMCMEsGA1UdIAREMEIwNwYKYIZIAYb6bAoBAjApMCcGCCsGAQUFBwIBFhto\r\n'+
|
||||
'dHRwczovL3d3dy5lbnRydXN0Lm5ldC9ycGEwBwYFZ4EMAQEwggF+BgorBgEEAdZ5\r\n'+
|
||||
'AgQCBIIBbgSCAWoBaAB3AFYUBpov18Ls0/XhvUSyPsdGdrm8mRFcwO+UmFXWidDd\r\n'+
|
||||
'AAABeYoCz+MAAAQDAEgwRgIhAKGKAoZMzwkh/3sZXq6vtEYhoYHfZzsjh9jqZvfS\r\n'+
|
||||
'xQVZAiEAmJu/ftbkNFBr8751Z9wA2dpI0Qt+LoeL1TJQ833Kdg4AdQDfpV6raIJP\r\n'+
|
||||
'H2yt7rhfTj5a6s2iEqRqXo47EsAgRFwqcwAAAXmKAs/cAAAEAwBGMEQCICsD/Vj+\r\n'+
|
||||
'ypZeHhesMyv/TkS5ftQjqyIaAFTL/02Gtem4AiBcWdPQspH3vfzZr4LO9z4u5jTg\r\n'+
|
||||
'Psfm5PZr66tI7yASrAB2AEalVet1+pEgMLWiiWn0830RLEF0vv1JuIWr8vxw/m1H\r\n'+
|
||||
'AAABeYoC0WkAAAQDAEcwRQIgTL5F11+7KhQ60jnODm9AkyvXRLY32Mj6tgudRAXO\r\n'+
|
||||
'y7UCIQDd/dU+Ax1y15yiAA5xM+bWJ7T+Ztd99SD1lw/o8fEmOjANBgkqhkiG9w0B\r\n'+
|
||||
'AQsFAAOCAQEAlpV3RoNvnhDgd2iFSF39wytf1R6/0u5FdL7eIkYNfnkqXu9Ux9cO\r\n'+
|
||||
'/OeaGAFMSzaDPA8Xt9A0HqkEbh1pr7UmZVqBwDr4a7gczvt7+HFJRn//Q2fwhmaw\r\n'+
|
||||
'vXTLLxcAPQF00G6ySsc9MUbsArh6AVhMf9tSXgNaTDj3X3UyYDfR+G8H9eVG/LPp\r\n'+
|
||||
'34QV/8uvPUFXGj6MjdQysx6YG+K3mae0GEVpODEl4MiceEFZ7v4CPA6pFNadijRF\r\n'+
|
||||
'6tdXky2psuo7VXfnE2WIlahKr56x+8R6To5pcWglKTywTqvCbnKRRVZhXXYo3Awd\r\n'+
|
||||
'8h9+TbL3ACHDqA4fi5sAbZ7nMXp8RK4o5A==\r\n'+
|
||||
'-----END CERTIFICATE-----';
|
||||
|
||||
const pdfCert = result.pemCertificate.trim();
|
||||
//const pdfOrg = result.issuedBy.organizationName;
|
||||
const issuedpemCertificate = (pdfCert == refcert.trim());
|
||||
|
||||
//console.log(`pdf is signed by this cert ${result.pemCertificate.trim()}`);
|
||||
//console.log(issuedpemCertificate);
|
||||
//console.log(`PDF is signed by ${result.issuedBy.organizationName}, issued to ${result.issuedTo.commonName}`);
|
||||
|
||||
if (issuedpemCertificate) {
|
||||
//console.log('getting receipt details inside PDF');
|
||||
const receipt = await getPdfDetails(fileBuffer);
|
||||
// console.log(JSON.stringify(receipt, null, 2));
|
||||
return Promise.resolve(receipt);
|
||||
|
||||
} else {
|
||||
/* We don't need to track these anymore - this is all going away in a week anyways
|
||||
// According to the Sentry docs, this can be up to 8KB in size
|
||||
// https://develop.sentry.dev/sdk/data-handling/#variable-size
|
||||
Sentry.setContext("certificate", {
|
||||
pdfCert: pdfCert,
|
||||
pdfOrg: pdfOrg,
|
||||
});
|
||||
Sentry.captureMessage('Certificate validation failed');
|
||||
*/
|
||||
console.error('invalid certificate');
|
||||
return Promise.reject(`invalid certificate + ${JSON.stringify(result)}`);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
|
||||
if (e.message.includes('Failed to locate ByteRange') ||
|
||||
e.message.includes(' ASN.1') ||
|
||||
e.message.includes('Failed byte range verification') ||
|
||||
e.message.includes('parse DER') ||
|
||||
e.message.includes('8, 16, 24, or 32 bits')) {
|
||||
e.message = 'Sorry. Selected PDF file is not digitally signed. Please download official copy from Step 1 and retry. Thanks.'
|
||||
} else {
|
||||
if (!e.message.includes('cancelled')) {
|
||||
console.error(e);
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
}
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function getPdfDetails(fileBuffer: ArrayBuffer): Promise<HashTable<Receipt>> {
|
||||
|
||||
try {
|
||||
const typedArray = new Uint8Array(fileBuffer);
|
||||
let loadingTask = PdfJS.getDocument(typedArray);
|
||||
|
||||
const pdfDocument = await loadingTask.promise;
|
||||
// Load all dose numbers
|
||||
const receiptObj = {};
|
||||
|
||||
for (let pages = 1; pages <= pdfDocument.numPages; pages++){
|
||||
const pdfPage = await pdfDocument.getPage(pages);
|
||||
const content = await pdfPage.getTextContent();
|
||||
const numItems = content.items.length;
|
||||
let name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization;
|
||||
|
||||
for (let i = 0; i < numItems; i++) {
|
||||
let item = content.items[i] as TextItem;
|
||||
const value = item.str;
|
||||
if (value.includes('Name / Nom'))
|
||||
name = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Date:')) {
|
||||
vaccinationDate = (content.items[i+1] as TextItem).str;
|
||||
vaccinationDate = vaccinationDate.split(',')[0];
|
||||
}
|
||||
if (value.includes('Product name')) {
|
||||
vaccineName = (content.items[i+1] as TextItem).str;
|
||||
}
|
||||
if (value.includes('Date of birth'))
|
||||
dateOfBirth = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('Authorized organization'))
|
||||
organization = (content.items[i+1] as TextItem).str;
|
||||
if (value.includes('You have received'))
|
||||
numDoses = Number(value.split(' ')[3]);
|
||||
}
|
||||
receiptObj[numDoses] = new Receipt(name, vaccinationDate, vaccineName, dateOfBirth, numDoses, organization);
|
||||
//console.log(receiptObj[numDoses]);
|
||||
}
|
||||
|
||||
return Promise.resolve(receiptObj);
|
||||
} catch (e) {
|
||||
if (e && e.message && !e.message.includes('cancelled')) {
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function getImageDataFromPdfPage(pdfPage: PDFPageProxy): Promise<ImageData> {
|
||||
|
||||
const pdfScale = 2;
|
||||
@ -350,7 +172,7 @@ async function processSHC(allImageData : ImageData[]) : Promise<PayloadBody> {
|
||||
// If we got here, no SHC was detected and successfully decoded.
|
||||
// The vast majority of our processed things right now are ON proof-of-vaccination PDFs, not SHC docs, so assume anything
|
||||
// that blew up here was a malformed ON proof-of-vaccination and create an appropriate error message for that
|
||||
return Promise.reject(new Error('No valid ON proof-of-vaccination digital signature found! Please make sure you download the PDF directly from covid19.ontariohealth.ca, Save as Files on your iPhone, and do NOT save/print it as a PDF!'));
|
||||
return Promise.reject(new Error('No SHC QR code found! Please try taking another picture of the SHC you wish to import'));
|
||||
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user