UNPKG

@govtechsg/open-attestation

Version:
155 lines (154 loc) 9.86 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { documentLoaders, expand } from "@govtechsg/jsonld"; import fetch from "cross-fetch"; var getId = function (objectOrString) { if (typeof objectOrString === "string") { return objectOrString; } return objectOrString.id; }; /* Based on https://tools.ietf.org/html/rfc3339#section-5.6 */ var dateFullYear = /[0-9]{4}/; var dateMonth = /(0[1-9]|1[0-2])/; var dateMDay = /([12]\d|0[1-9]|3[01])/; var timeHour = /([01][0-9]|2[0-3])/; var timeMinute = /[0-5][0-9]/; var timeSecond = /([0-5][0-9]|60)/; var timeSecFrac = /(\.[0-9]+)?/; var timeNumOffset = new RegExp("[-+]".concat(timeHour.source, ":").concat(timeMinute.source)); var timeOffset = new RegExp("([zZ]|".concat(timeNumOffset.source, ")")); var partialTime = new RegExp("".concat(timeHour.source, ":").concat(timeMinute.source, ":").concat(timeSecond.source).concat(timeSecFrac.source)); var fullDate = new RegExp("".concat(dateFullYear.source, "-").concat(dateMonth.source, "-").concat(dateMDay.source)); var fullTime = new RegExp("".concat(partialTime.source).concat(timeOffset.source)); var rfc3339 = new RegExp("".concat(fullDate.source, "[ tT]").concat(fullTime.source)); var isValidRFC3339 = function (str) { return rfc3339.test(str); }; /* Based on https://tools.ietf.org/html/rfc3986 and https://github.com/ajv-validator/ajv/search?q=uri&unscoped_q=uri */ var uri = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i; var rfc3986 = new RegExp(uri); var isValidRFC3986 = function (str) { return rfc3986.test(str); }; var preloadedContextList = [ "https://www.w3.org/2018/credentials/v1", "https://www.w3.org/2018/credentials/examples/v1", "https://schemata.openattestation.com/com/openattestation/1.0/DrivingLicenceCredential.json", "https://schemata.openattestation.com/com/openattestation/1.0/OpenAttestation.v3.json", "https://schemata.openattestation.com/com/openattestation/1.0/CustomContext.json", ]; var contexts = new Map(); var nodeDocumentLoader = documentLoaders.xhr ? documentLoaders.xhr() : documentLoaders.node(); var preload = true; var documentLoader = function (url) { return __awaiter(void 0, void 0, void 0, function () { var _i, preloadedContextList_1, url_1, promise, promise; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: if (preload) { preload = false; for (_i = 0, preloadedContextList_1 = preloadedContextList; _i < preloadedContextList_1.length; _i++) { url_1 = preloadedContextList_1[_i]; contexts.set(url_1, fetch(url_1, { headers: { accept: "application/json, application/ld+json" } }).then(function (res) { return res.json(); })); } } if (!contexts.get(url)) return [3 /*break*/, 2]; promise = contexts.get(url); _a = { contextUrl: undefined }; return [4 /*yield*/, promise]; case 1: return [2 /*return*/, (_a.document = _b.sent(), _a.documentUrl = url, _a)]; case 2: promise = nodeDocumentLoader(url); contexts.set(url, promise.then(function (_a) { var document = _a.document; return document; })); return [2 /*return*/, promise]; } }); }); }; export function validateW3C(credential) { return __awaiter(this, void 0, void 0, function () { var issuerId; return __generator(this, function (_a) { switch (_a.label) { case 0: // ensure first context is 'https://www.w3.org/2018/credentials/v1' as it's mandatory, see https://www.w3.org/TR/vc-data-model/#contexts if (!Array.isArray(credential["@context"]) || (Array.isArray(credential["@context"]) && credential["@context"][0] !== "https://www.w3.org/2018/credentials/v1")) { throw new Error("https://www.w3.org/2018/credentials/v1 needs to be first in the list of contexts"); } issuerId = getId(credential.issuer); if (!isValidRFC3986(issuerId)) { throw new Error("Property 'issuer' id must be a valid RFC 3986 URI"); } // ensure issuanceDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#issuance-date if (!isValidRFC3339(credential.issuanceDate)) { throw new Error("Property 'issuanceDate' must be a valid RFC 3339 date"); } // ensure expirationDate is a valid RFC3339 date, see https://www.w3.org/TR/vc-data-model/#expiration if (credential.expirationDate && !isValidRFC3339(credential.expirationDate)) { throw new Error("Property 'expirationDate' must be a valid RFC 3339 date"); } // https://www.w3.org/TR/vc-data-model/#types if (!credential.type || !Array.isArray(credential.type)) { throw new Error("Property 'type' must exist and be an array"); } if (!credential.type.includes("VerifiableCredential")) { throw new Error("Property 'type' must have VerifiableCredential as one of the items"); } return [4 /*yield*/, expand(credential, { expansionMap: function (info) { if (info.unmappedProperty) { throw new Error("\"The property ".concat(info.activeProperty ? "".concat(info.activeProperty, ".") : "").concat(info.unmappedProperty, " in the input was not defined in the context\"")); } }, documentLoader: documentLoader, })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); }