@credo-ts/core
Version:
<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1
104 lines (103 loc) • 5.66 kB
JavaScript
import { injectable } from "../../../plugins/index.mjs";
import __decorateMetadata from "../../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.mjs";
import __decorate from "../../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.mjs";
import { CREDENTIALS_CONTEXT_V2_URL } from "../constants.mjs";
import { omitUndefinedFields } from "../../w3c-di/proof-processing/normalization.mjs";
import { createProofVerificationIssue } from "../../w3c-di/W3cDataIntegrityError.mjs";
import "../../w3c-di/internal.mjs";
import { DI_SPEC_CONTEXT_HASHES } from "../jsonld/contexts/contextHashes.mjs";
import { DEFAULT_CONTEXTS } from "../jsonld/contexts/defaultContexts.mjs";
import jsonld_default from "../jsonld/jsonld.mjs";
import { JsonLdModuleConfig } from "../jsonld/JsonLdModuleConfig.mjs";
import { W3cV2CredentialsModuleConfig } from "../W3cV2CredentialsModuleConfig.mjs";
//#region src/modules/vc/data-integrity/W3cV2DataIntegrityContextValidator.ts
let W3cV2DataIntegrityContextValidator = class W3cV2DataIntegrityContextValidator {
constructor(jsonLdModuleConfig, w3cV2CredentialsModuleConfig) {
this.jsonLdModuleConfig = jsonLdModuleConfig;
this.w3cV2CredentialsModuleConfig = w3cV2CredentialsModuleConfig;
}
async validate(agentContext, inputDocument) {
const normalisedInputDocument = omitUndefinedFields(inputDocument);
const validatedDocument = { ...normalisedInputDocument };
const result = {
validated: false,
validatedDocument,
warnings: [],
errors: []
};
const contextValue = normaliseContext(validatedDocument["@context"]);
if (validatedDocument["@context"] === void 0 || validatedDocument["@context"] === null) {
result.errors.push(createProofVerificationIssue("Missing top-level @context in secured document", "Verification must not perform context injection (§2.4.2). Document must have explicit @context."));
result.validated = false;
result.validatedDocument = null;
return result;
}
const triggerErrors = [];
if (contextValue[0] !== "https://www.w3.org/ns/credentials/v2") triggerErrors.push(createProofVerificationIssue("@context does not start with the required VC 2.0 context", `Document @context must start with '${CREDENTIALS_CONTEXT_V2_URL}'`));
const nestedContextPaths = collectAllNestedContextPaths(validatedDocument);
if (nestedContextPaths.length > 0) triggerErrors.push(createProofVerificationIssue("Nested @context detected in document", `Nested @context found at path(s): ${nestedContextPaths.join(", ")}`));
for (const contextEntry of contextValue) {
if (typeof contextEntry !== "string") continue;
if (contextEntry === "https://www.w3.org/ns/credentials/v2") continue;
if (!(contextEntry in DI_SPEC_CONTEXT_HASHES)) continue;
const bundledContextIssue = this.verifyContextIsBundled(contextEntry);
if (bundledContextIssue) {
triggerErrors.push(bundledContextIssue);
break;
}
}
if (triggerErrors.length > 0) {
if (this.w3cV2CredentialsModuleConfig.recompactInvalidContexts) try {
result.validatedDocument = await jsonld_default.compact(normalisedInputDocument, [CREDENTIALS_CONTEXT_V2_URL], {
documentLoader: this.jsonLdModuleConfig.documentLoader(agentContext),
compactToRelative: false
});
result.warnings.push(...triggerErrors);
} catch (error) {
result.errors.push(createProofVerificationIssue("Context recompaction failed (§4.6 step 3.1)", error instanceof Error ? error.message : "JSON-LD compaction failed"));
}
else result.errors.push(...triggerErrors);
}
if (result.errors.length === 0) result.validated = true;
else {
result.validated = false;
result.validatedDocument = null;
}
return result;
}
/**
* Verifies a spec-pinned context URI (§2.4). The §2.4 hash-pinning mechanism exists for
* processors that don't vendor the canonical contexts and must instead fetch and hash-verify
* them over the network; Credo vendors all spec-required contexts, so a bundled copy is
* trusted as canonical and no network fetch or hash comparison is performed.
*/
verifyContextIsBundled(contextUri) {
if (hasBundledContext(contextUri)) return;
return createProofVerificationIssue("Unable to verify context hash (§2.4)", `Context '${contextUri}' is not available as a bundled context and cannot be hash-verified`);
}
};
W3cV2DataIntegrityContextValidator = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof JsonLdModuleConfig === "undefined" ? Object : JsonLdModuleConfig, typeof W3cV2CredentialsModuleConfig === "undefined" ? Object : W3cV2CredentialsModuleConfig])], W3cV2DataIntegrityContextValidator);
function normaliseContext(context) {
if (context === void 0 || context === null) return [];
return Array.isArray(context) ? context : [context];
}
function collectAllNestedContextPaths(value, path = []) {
if (value === null || typeof value !== "object") return [];
if (Array.isArray(value)) return value.flatMap((item, i) => collectAllNestedContextPaths(item, [...path, String(i)]));
const obj = value;
const paths = [];
for (const [key, entry] of Object.entries(obj)) {
const nextPath = [...path, key];
const isTopLevelContext = path.length === 0 && key === "@context";
if (key === "@context" && !isTopLevelContext) paths.push(nextPath.join("."));
paths.push(...collectAllNestedContextPaths(entry, nextPath));
}
return paths;
}
function hasBundledContext(contextUrl) {
if (contextUrl in DEFAULT_CONTEXTS) return true;
return contextUrl.split("#")[0] in DEFAULT_CONTEXTS;
}
//#endregion
export { W3cV2DataIntegrityContextValidator };
//# sourceMappingURL=W3cV2DataIntegrityContextValidator.mjs.map