UNPKG

@dbp-topics/greenlight

Version:

[GitHub Repository](https://github.com/digital-blueprint/greenlight-frontend) | [npmjs package](https://www.npmjs.com/package/@dbp-topics/greenlight) | [Unpkg CDN](https://unpkg.com/browse/@dbp-topics/greenlight/) | [Greenlight Bundle](https://gitlab.tugr

222 lines (201 loc) 8.09 kB
import {importHCert, fetchTrustData, trustAnchorProd, trustAnchorTest} from './utils.js'; import { validateHCertRules, ValueSets, BusinessRules, decodeValueSets, decodeJSONBusinessRules, RuleValidationResult, getValidUntil, } from './rules'; import {name as pkgName} from './../../package.json'; import * as commonUtils from '@dbp-toolkit/common/utils'; import {createInstance} from '../i18n.js'; import {withDate} from './utils.js'; export class RegionResult { constructor() { /** @type {string} */ this.country = null; /** @type {string} */ this.region = null; /** @type {boolean} Wether the HCERT is valid according to the regional rules */ this.isValid = false; /** @type {string} Contains an error message if isValid === false */ this.error = null; /** @type {Date|null} */ this.validUntil = null; } } export class ValidationResult { constructor() { /** @type {boolean} Wether the HCERT itself + signature is valid */ this.isValid = false; /** @type {string} Contains an error message if isValid === false*/ this.error = null; /** @type {string} */ this.firstname = null; /** @type {string} */ this.firstname_t = null; /** @type {string} */ this.lastname = null; /** @type {string} */ this.lastname_t = null; /** @type {string} */ this.dob = null; /** @type {Object<string, RegionResult>} Contains a result for each region as long as isValid === true*/ this.regions = {}; } } export class Validator { /** * @param {Date} trustDate - The date used to verify the trust data, the HCERT signature and to select the rules * @param {boolean} production */ constructor(trustDate, production = true) { let dir = production ? 'prod' : 'test'; this._trustAnchor = production ? trustAnchorProd : trustAnchorTest; this._baseUrl = commonUtils.getAssetURL(pkgName, 'dgc-trust/' + dir); this._verifier = null; /** @type {BusinessRules} */ this._businessRules = null; /** @type {ValueSets} */ this._valueSets = null; this._loaded = false; this._trustDate = trustDate; } async _ensureData() { // Does all the one time setup if not already done // XXX: this is racy if called concurrently if (this._loaded === true) return; let hcert = await importHCert(); let trustData = await fetchTrustData(this._baseUrl); this._verifier = withDate(this._trustDate, () => { return new hcert.VerifierTrustList( this._trustAnchor, trustData['trustlist'], trustData['trustlistsig'] ); }); this._businessRules = await decodeJSONBusinessRules( hcert, trustData, this._trustAnchor, this._trustDate ); this._valueSets = await decodeValueSets( hcert, trustData, this._trustAnchor, this._trustDate ); this._loaded = true; } /** * Validate the HCERT for a given Date, usually the current date. * * Returns a ValidationResult or throws if validation wasn't possible. * * @param {string} cert * @param {Date} date * @param {string} [lang] * @param {string} [country] * @param {string[]} [regions] * @returns {ValidationResult} */ async validate(cert, date, lang = 'en', country = 'AT', regions = ['ET']) { await this._ensureData(); let i18n = createInstance(); i18n.changeLanguage(lang); // Iterate through all errors and use the description in the language we prefere the most let getTranslatedErrors = (errors) => { let languages = i18n.languages + ['en']; let translated = []; for (let error of errors) { let text = 'unknown'; let prio = -1; for (let [ln, desc] of Object.entries(error)) { let thisPrio = languages.indexOf(ln); if (prio === -1) { text = desc; prio = thisPrio; } else if (thisPrio !== -1 && thisPrio < prio) { text = desc; prio = thisPrio; } } translated.push(text); } translated.sort(); return translated; }; // Verify that the signature is correct and decode the HCERT. // This also verifies if the signature is still valid, so mock the date let hcertData = withDate(this._trustDate, () => { return this._verifier.verify(cert); }); let result = new ValidationResult(); if (hcertData.isValid) { let greenCertificate = hcertData.greenCertificate; result.isValid = true; result.firstname = greenCertificate.nam.gn ?? ''; result.lastname = greenCertificate.nam.fn ?? ''; result.firstname_t = greenCertificate.nam.gnt ?? ''; result.lastname_t = greenCertificate.nam.fnt ?? ''; result.dob = greenCertificate.dob ?? ''; for (let region of regions) { let regionResult = new RegionResult(); regionResult.country = country; regionResult.region = region; let businessRules = this._businessRules.filter(country, region); /** @type {RuleValidationResult} */ let res = validateHCertRules( greenCertificate, businessRules, this._valueSets, date, this._trustDate ); if (res.isValid) { regionResult.isValid = true; // according to the rules, returns null if it never becomes invalid let validUntil = getValidUntil( greenCertificate, businessRules, this._valueSets, date ); let isFullDate = (date) => { // https://github.com/ehn-dcc-development/hcert-kotlin/pull/64 return date && date.includes('T'); }; // If anything regarding the certificate stops being valid earlier // than the rules then it takes precedence let meta = hcertData.metaInformation; if (isFullDate(meta.certificateValidUntil)) { let certificateValidUntil = new Date(meta.certificateValidUntil); if (validUntil === null || certificateValidUntil < validUntil) { validUntil = certificateValidUntil; } } if (isFullDate(meta.expirationTime)) { let expirationTime = new Date(meta.expirationTime); if (validUntil === null || expirationTime < validUntil) { validUntil = expirationTime; } } regionResult.validUntil = validUntil; } else { regionResult.isValid = false; regionResult.error = i18n.t('hcert.cert-not-valid-error', { error: getTranslatedErrors(res.errors).join('\n'), }); } result.regions[region] = regionResult; } } else { result.isValid = false; result.error = i18n.t('hcert.cert-validation-failed-error', {error: hcertData.error}); } return result; } }