@ucanto/validator
Advanced tools
Comparing version 4.0.2 to 4.0.3
@@ -44,3 +44,3 @@ export function capability<A extends API.Ability, R extends API.URI<`${string}:`>, C extends API.Caveats = {}>(descriptor: API.Descriptor<A, R, C>): API.TheCapabilityParser<API.CapabilityMatch<A, R, C>>; | ||
unknown: API.Capability<API.Ability, `${string}:${string}`, unknown>[]; | ||
errors: (API.DelegationError | MatchError)[]; | ||
errors: (MatchError | API.DelegationError)[]; | ||
matches: (DerivedMatch<T, M> | M)[]; | ||
@@ -50,3 +50,4 @@ }; | ||
} | ||
import { DelegationError as MatchError } from "./error.js"; | ||
export {}; | ||
//# sourceMappingURL=capability.d.ts.map |
@@ -42,3 +42,3 @@ /** | ||
name: "InvalidClaim"; | ||
causes: (API.InvalidCapability | API.DelegationError | API.EscalatedDelegation)[]; | ||
causes: (API.InvalidCapability | API.EscalatedDelegation | API.DelegationError)[]; | ||
context: object; | ||
@@ -48,3 +48,3 @@ /** | ||
*/ | ||
get cause(): API.InvalidCapability | API.DelegationError | API.EscalatedDelegation; | ||
get cause(): API.InvalidCapability | API.EscalatedDelegation | API.DelegationError; | ||
} | ||
@@ -77,2 +77,12 @@ /** | ||
} | ||
export class DIDKeyResolutionError extends Failure { | ||
/** | ||
* @param {API.UCAN.DID} did | ||
* @param {API.Unauthorized} [cause] | ||
*/ | ||
constructor(did: API.UCAN.DID, cause?: API.Unauthorized | undefined); | ||
name: "DIDKeyResolutionError"; | ||
did: `did:${string}:${string}`; | ||
cause: API.Unauthorized | undefined; | ||
} | ||
/** | ||
@@ -154,2 +164,9 @@ * @implements {API.InvalidAudience} | ||
get validAt(): number; | ||
toJSON(): { | ||
error: true; | ||
name: "NotValidBefore"; | ||
message: string; | ||
validAt: number; | ||
stack: string | undefined; | ||
}; | ||
} | ||
@@ -156,0 +173,0 @@ export function indent(message: string, indent?: string): string; |
export { capability } from "./capability.js"; | ||
export * from "./schema.js"; | ||
export * as Schema from "./schema.js"; | ||
export function access<A extends API.Ability, R extends API.URI<`${string}:`>, URI extends R, C extends API.Caveats>(invocation: API.Invocation<API.Capability<A, URI, API.InferCaveats<C>>>, { canIssue, principal, resolve, capability }: API.ValidationOptions<API.ParsedCapability<A, R, API.InferCaveats<C>>>): Promise<API.Result<Authorization<API.ParsedCapability<A, R, API.InferCaveats<C>>>, API.Unauthorized>>; | ||
export function authorize<C extends { | ||
can: API.Ability; | ||
with: API.URI<`${string}:`>; | ||
nb?: {} | undefined; | ||
}, Match extends API.Match<any, API.UnknownMatch>>(match: Match, config: Required<API.ValidationOptions<C>>): Promise<API.Result<Authorization<API.ParsedCapability>, API.InvalidClaim>>; | ||
export function access<A extends API.Ability, R extends API.URI<`${string}:`>, URI extends R, C extends API.Caveats>(invocation: API.Invocation<API.Capability<A, URI, API.InferCaveats<C>>>, { capability, ...config }: API.ValidationOptions<API.ParsedCapability<A, R, API.InferCaveats<C>>>): Promise<API.Result<Authorization<API.ParsedCapability<A, R, API.InferCaveats<C>>>, API.Unauthorized>>; | ||
export function claim<A extends API.Ability, R extends API.URI<`${string}:`>, C extends API.Caveats>(capability: API.CapabilityParser<API.Match<API.ParsedCapability<A, R, API.InferCaveats<C>>, API.UnknownMatch>>, proofs: API.Proof[], { authority, principal, resolveDIDKey, canIssue, resolve, }: API.ClaimOptions): Promise<API.Result<Authorization<API.ParsedCapability<A, R, API.InferCaveats<C>>>, API.Unauthorized>>; | ||
export function authorize<Match extends API.Match<any, API.UnknownMatch>>(match: Match, config: Required<API.ClaimOptions>): Promise<API.Result<Authorization<API.ParsedCapability>, API.InvalidClaim>>; | ||
import { Failure } from "./error.js"; | ||
import { UnavailableProof } from "./error.js"; | ||
import { MalformedCapability } from "./error.js"; | ||
import { DIDKeyResolutionError } from "./error.js"; | ||
import * as Schema from "./schema.js"; | ||
import * as API from "@ucanto/interface"; | ||
@@ -43,8 +41,24 @@ /** | ||
/** | ||
* @param {API.InvalidCapability | API.InvalidProof | API.InvalidClaim} cause | ||
* @param {{ | ||
* capability: API.CapabilityParser | ||
* delegationErrors: API.DelegationError[] | ||
* unknownCapabilities: API.Capability[] | ||
* invalidProofs: API.InvalidProof[] | ||
* failedProofs: API.InvalidClaim[] | ||
* }} cause | ||
*/ | ||
constructor(cause: API.InvalidCapability | API.InvalidProof | API.InvalidClaim); | ||
constructor({ capability, delegationErrors, unknownCapabilities, invalidProofs, failedProofs, }: { | ||
capability: API.CapabilityParser; | ||
delegationErrors: API.DelegationError[]; | ||
unknownCapabilities: API.Capability[]; | ||
invalidProofs: API.InvalidProof[]; | ||
failedProofs: API.InvalidClaim[]; | ||
}); | ||
/** @type {"Unauthorized"} */ | ||
name: "Unauthorized"; | ||
cause: API.InvalidCapability | API.InvalidProof | API.InvalidClaim; | ||
capability: API.CapabilityParser<API.Match<unknown, API.UnknownMatch>>; | ||
delegationErrors: API.DelegationError[]; | ||
unknownCapabilities: API.Capability<API.Ability, `${string}:${string}`, any>[]; | ||
invalidProofs: API.InvalidProof[]; | ||
failedProofs: API.InvalidClaim[]; | ||
toJSON(): { | ||
@@ -54,3 +68,2 @@ error: true; | ||
message: string; | ||
cause: API.InvalidCapability | API.InvalidProof | API.InvalidClaim; | ||
stack: string | undefined; | ||
@@ -67,3 +80,3 @@ }; | ||
* delegationErrors: API.DelegationError[] | ||
* unknownCapaibilities: API.Capability[] | ||
* unknownCapabilities: API.Capability[] | ||
* invalidProofs: ProofError[] | ||
@@ -76,3 +89,3 @@ * failedProofs: API.InvalidClaim[] | ||
delegationErrors: API.DelegationError[]; | ||
unknownCapaibilities: API.Capability[]; | ||
unknownCapabilities: API.Capability[]; | ||
invalidProofs: ProofError[]; | ||
@@ -84,3 +97,3 @@ failedProofs: API.InvalidClaim[]; | ||
delegationErrors: API.DelegationError[]; | ||
unknownCapaibilities: API.Capability[]; | ||
unknownCapabilities: API.Capability[]; | ||
invalidProofs: ProofError[]; | ||
@@ -92,6 +105,6 @@ failedProofs: API.InvalidClaim[]; | ||
get issuer(): API.Principal<`did:${string}:${string}`>; | ||
get capability(): any; | ||
get delegation(): API.Delegation<API.Capabilities>; | ||
} | ||
import { InvalidAudience } from "./error.js"; | ||
import { capability } from "./capability.js"; | ||
import { DelegationError } from "./error.js"; | ||
@@ -109,3 +122,3 @@ declare class ProofError extends Failure { | ||
} | ||
export { Failure, UnavailableProof, MalformedCapability, InvalidAudience }; | ||
export { Failure, UnavailableProof, MalformedCapability, DIDKeyResolutionError as DIDResolutionError, Schema, InvalidAudience }; | ||
//# sourceMappingURL=lib.d.ts.map |
{ | ||
"name": "@ucanto/validator", | ||
"description": "UCAN RPC validators", | ||
"version": "4.0.2", | ||
"version": "4.0.3", | ||
"keywords": [ | ||
@@ -24,4 +24,4 @@ "UCAN", | ||
"@ipld/dag-cbor": "^8.0.0", | ||
"@ucanto/core": "^4.0.2", | ||
"@ucanto/interface": "^4.0.2", | ||
"@ucanto/core": "^4.0.3", | ||
"@ucanto/interface": "^4.0.3", | ||
"multiformats": "^10.0.2" | ||
@@ -33,4 +33,4 @@ }, | ||
"@types/mocha": "^9.1.0", | ||
"@ucanto/client": "^4.0.2", | ||
"@ucanto/principal": "^4.0.2", | ||
"@ucanto/client": "^4.0.3", | ||
"@ucanto/principal": "^4.0.3", | ||
"c8": "^7.11.0", | ||
@@ -37,0 +37,0 @@ "chai": "^4.3.6", |
import * as API from '@ucanto/interface' | ||
import { the } from './util.js' | ||
import { isLink } from 'multiformats/link' | ||
@@ -100,3 +101,3 @@ /** | ||
describe() { | ||
return [`Signature is invalid`].join('\n') | ||
return [`Proof ${this.delegation.cid} signature is invalid`].join('\n') | ||
} | ||
@@ -121,5 +122,5 @@ } | ||
return [ | ||
`Linked proof '${this.link}' is not included nor could be resolved`, | ||
`Linked proof '${this.link}' is not included and could not be resolved`, | ||
...(this.cause | ||
? [li(`Provided resolve failed: ${this.cause.message}`)] | ||
? [li(`Proof resolution failed with: ${this.cause.message}`)] | ||
: []), | ||
@@ -130,2 +131,21 @@ ].join('\n') | ||
export class DIDKeyResolutionError extends Failure { | ||
/** | ||
* @param {API.UCAN.DID} did | ||
* @param {API.Unauthorized} [cause] | ||
*/ | ||
constructor(did, cause) { | ||
super() | ||
this.name = the('DIDKeyResolutionError') | ||
this.did = did | ||
this.cause = cause | ||
} | ||
describe() { | ||
return [ | ||
`Unable to resolve '${this.did}' key`, | ||
...(this.cause ? [li(`Resolution failed: ${this.cause.message}`)] : []), | ||
].join('\n') | ||
} | ||
} | ||
/** | ||
@@ -146,3 +166,3 @@ * @implements {API.InvalidAudience} | ||
describe() { | ||
return `Delegates to '${this.delegation.audience.did()}' instead of '${this.audience.did()}'` | ||
return `Delegation audience is '${this.delegation.audience.did()}' instead of '${this.audience.did()}'` | ||
} | ||
@@ -195,2 +215,3 @@ toJSON() { | ||
} | ||
/* c8 ignore next 3 */ | ||
describe() { | ||
@@ -211,3 +232,5 @@ return `Encountered unknown capability: ${format(this.capability)}` | ||
describe() { | ||
return `Expired on ${new Date(this.delegation.expiration * 1000)}` | ||
return `Proof ${this.delegation.cid} has expired on ${new Date( | ||
this.delegation.expiration * 1000 | ||
)}` | ||
} | ||
@@ -239,3 +262,5 @@ get expiredAt() { | ||
describe() { | ||
return `Not valid before ${new Date(this.delegation.notBefore * 1000)}` | ||
return `Proof ${this.delegation.cid} is not valid before ${new Date( | ||
this.delegation.notBefore * 1000 | ||
)}` | ||
} | ||
@@ -245,2 +270,12 @@ get validAt() { | ||
} | ||
toJSON() { | ||
const { error, name, validAt, message, stack } = this | ||
return { | ||
error, | ||
name, | ||
message, | ||
validAt, | ||
stack, | ||
} | ||
} | ||
} | ||
@@ -256,5 +291,5 @@ | ||
capability, | ||
(key, value) => { | ||
(_key, value) => { | ||
/* c8 ignore next 2 */ | ||
if (value && value.asCID === value) { | ||
if (isLink(value)) { | ||
return value.toString() | ||
@@ -261,0 +296,0 @@ } else { |
303
src/lib.js
import * as API from '@ucanto/interface' | ||
import { isDelegation, UCAN } from '@ucanto/core' | ||
import { capability } from './capability.js' | ||
import * as Schema from './schema.js' | ||
import { | ||
@@ -12,10 +14,17 @@ UnavailableProof, | ||
MalformedCapability, | ||
DIDKeyResolutionError, | ||
li, | ||
} from './error.js' | ||
export { Failure, UnavailableProof, MalformedCapability } | ||
export { | ||
Failure, | ||
UnavailableProof, | ||
MalformedCapability, | ||
DIDKeyResolutionError as DIDResolutionError, | ||
} | ||
export { capability } from './capability.js' | ||
import { DID } from './schema.js' | ||
export * from './schema.js' | ||
export * as Schema from './schema.js' | ||
export { Schema } | ||
@@ -28,3 +37,10 @@ /** | ||
/** | ||
* @param {Required<API.ProofResolver>} config | ||
* | ||
* @param {UCAN.DID} did | ||
* @returns {API.DIDKeyResolutionError} | ||
*/ | ||
const failDIDKeyResolution = did => new DIDKeyResolutionError(did) | ||
/** | ||
* @param {Required<API.ClaimOptions>} config | ||
* @param {API.Match<unknown, API.Match>} match | ||
@@ -54,10 +70,10 @@ */ | ||
/** | ||
* @param {API.Delegation} delegation | ||
* @param {API.Proof[]} proofs | ||
* @param {Required<API.ProofResolver>} config | ||
*/ | ||
const resolveProofs = async (delegation, config) => { | ||
const resolveProofs = async (proofs, config) => { | ||
/** @type {API.Result<API.Delegation, API.UnavailableProof>[]} */ | ||
const proofs = [] | ||
const delegations = [] | ||
const promises = [] | ||
for (const [index, proof] of delegation.proofs.entries()) { | ||
for (const [index, proof] of proofs.entries()) { | ||
if (!isDelegation(proof)) { | ||
@@ -67,5 +83,5 @@ promises.push( | ||
try { | ||
proofs[index] = await config.resolve(proof) | ||
delegations[index] = await config.resolve(proof) | ||
} catch (error) { | ||
proofs[index] = new UnavailableProof( | ||
delegations[index] = new UnavailableProof( | ||
proof, | ||
@@ -79,3 +95,3 @@ /** @type {Error} */ (error) | ||
} else { | ||
proofs[index] = proof | ||
delegations[index] = proof | ||
} | ||
@@ -85,3 +101,3 @@ } | ||
await Promise.all(promises) | ||
return proofs | ||
return delegations | ||
} | ||
@@ -91,3 +107,3 @@ | ||
* @param {API.Source} from | ||
* @param {Required<API.ProofResolver>} config | ||
* @param {Required<API.ClaimOptions>} config | ||
* @return {Promise<{sources:API.Source[], errors:ProofError[]}>} | ||
@@ -99,3 +115,3 @@ */ | ||
// resolve all the proofs that can be side-loaded | ||
const proofs = await resolveProofs(delegation, config) | ||
const proofs = await resolveProofs(delegation.proofs, config) | ||
for (const [index, proof] of proofs.entries()) { | ||
@@ -106,3 +122,3 @@ // if proof can not be side-loaded save a proof errors. | ||
} else { | ||
// If proof does not delegate to a matchig audience save an proof error. | ||
// If proof does not delegate to a matching audience save an proof error. | ||
if (delegation.issuer.did() !== proof.audience.did()) { | ||
@@ -118,3 +134,3 @@ errors.push( | ||
// If proof is not valid (expired, not active yet or has incorrect | ||
// signature) save a correspondig proof error. | ||
// signature) save a corresponding proof error. | ||
const validation = await validate(proof, config) | ||
@@ -148,2 +164,7 @@ if (validation.error) { | ||
/** | ||
* Finds a valid path in a proof chain of the given `invocation` by exploring | ||
* every possible option. On success an `Authorization` object is returned that | ||
* illustrates the valid path. If no valid path is found `Unauthorized` error | ||
* is returned detailing all explored paths and where they proved to fail. | ||
* | ||
* @template {API.Ability} A | ||
@@ -154,36 +175,87 @@ * @template {API.URI} R | ||
* @param {API.Invocation<API.Capability<A, URI, API.InferCaveats<C>>>} invocation | ||
* @param {API.ValidationOptions<API.ParsedCapability<A, R, API.InferCaveats<C>>>} config | ||
* @param {API.ValidationOptions<API.ParsedCapability<A, R, API.InferCaveats<C>>>} options | ||
* @returns {Promise<API.Result<Authorization<API.ParsedCapability<A, R, API.InferCaveats<C>>>, API.Unauthorized>>} | ||
*/ | ||
export const access = async ( | ||
invocation, | ||
{ canIssue = isSelfIssued, principal, resolve = unavailable, capability } | ||
export const access = async (invocation, { capability, ...config }) => | ||
claim(capability, [invocation], config) | ||
/** | ||
* Attempts to find a valid proof chain for the claimed `capability` given set | ||
* of `proofs`. On success an `Authorization` object with detailed proof chain | ||
* is returned and on failure `Unauthorized` error is returned with details on | ||
* paths explored and why they have failed. | ||
* | ||
* @template {API.Ability} A | ||
* @template {API.URI} R | ||
* @template {API.Caveats} C | ||
* @param {API.CapabilityParser<API.Match<API.ParsedCapability<A, R, API.InferCaveats<C>>>>} capability | ||
* @param {API.Proof[]} proofs | ||
* @param {API.ClaimOptions} config | ||
* @returns {Promise<API.Result<Authorization<API.ParsedCapability<A, R, API.InferCaveats<C>>>, API.Unauthorized>>} | ||
*/ | ||
export const claim = async ( | ||
capability, | ||
proofs, | ||
{ | ||
authority, | ||
principal, | ||
resolveDIDKey = failDIDKeyResolution, | ||
canIssue = isSelfIssued, | ||
resolve = unavailable, | ||
} | ||
) => { | ||
const config = { canIssue, resolve, principal, capability } | ||
const config = { | ||
canIssue, | ||
resolve, | ||
principal, | ||
capability, | ||
authority, | ||
resolveDIDKey, | ||
} | ||
const claim = capability.match({ | ||
capability: invocation.capabilities[0], | ||
delegation: invocation, | ||
index: 0, | ||
}) | ||
const invalidProofs = [] | ||
if (claim.error) { | ||
return new Unauthorized(claim) | ||
/** @type {API.Source[]} */ | ||
const sources = [] | ||
for (const proof of await resolveProofs(proofs, config)) { | ||
const delegation = proof.error ? proof : await validate(proof, config) | ||
if (!delegation.error) { | ||
for (const [index, capability] of delegation.capabilities.entries()) { | ||
sources.push({ | ||
capability, | ||
delegation, | ||
index, | ||
}) | ||
} | ||
} else { | ||
invalidProofs.push(delegation) | ||
} | ||
} | ||
const check = await validate(invocation, config) | ||
if (check.error) { | ||
return new Unauthorized(check) | ||
} | ||
// look for the matching capability | ||
const selection = capability.select(sources) | ||
const match = claim.prune(config) | ||
if (match == null) { | ||
return new Authorization(claim, []) | ||
} else { | ||
const result = await authorize(match, config) | ||
if (result.error) { | ||
return new Unauthorized(result) | ||
const { errors: delegationErrors, unknown: unknownCapabilities } = selection | ||
const failedProofs = [] | ||
for (const matched of selection.matches) { | ||
const selector = matched.prune(config) | ||
if (selector == null) { | ||
return new Authorization(matched, []) | ||
} else { | ||
return new Authorization(claim, [result]) | ||
const result = await authorize(selector, config) | ||
if (result.error) { | ||
failedProofs.push(result) | ||
} else { | ||
return new Authorization(matched, [result]) | ||
} | ||
} | ||
} | ||
return new Unauthorized({ | ||
capability, | ||
delegationErrors, | ||
unknownCapabilities, | ||
invalidProofs, | ||
failedProofs, | ||
}) | ||
} | ||
@@ -217,8 +289,7 @@ | ||
/** | ||
* Verifies whether any of the delegated proofs grant give capabality. | ||
* Verifies whether any of the delegated proofs grant give capability. | ||
* | ||
* @template {API.ParsedCapability} C | ||
* @template {API.Match} Match | ||
* @param {Match} match | ||
* @param {Required<API.ValidationOptions<C>>} config | ||
* @param {Required<API.ClaimOptions>} config | ||
* @returns {Promise<API.Result<Authorization<API.ParsedCapability>, API.InvalidClaim>>} | ||
@@ -232,3 +303,3 @@ */ | ||
const selection = match.select(sources) | ||
const { errors: delegationErrors, unknown: unknownCapaibilities } = selection | ||
const { errors: delegationErrors, unknown: unknownCapabilities } = selection | ||
@@ -240,3 +311,3 @@ const failedProofs = [] | ||
// @ts-expect-error - it may not be a parsed capability but rather a | ||
// group of capabilites but we can deal with that in the future. | ||
// group of capabilities but we can deal with that in the future. | ||
return new Authorization(matched, []) | ||
@@ -249,3 +320,3 @@ } else { | ||
// @ts-expect-error - it may not be a parsed capability but rather a | ||
// group of capabilites but we can deal with that in the future. | ||
// group of capabilities but we can deal with that in the future. | ||
return new Authorization(matched, [result]) | ||
@@ -259,3 +330,3 @@ } | ||
delegationErrors, | ||
unknownCapaibilities, | ||
unknownCapabilities, | ||
invalidProofs, | ||
@@ -281,3 +352,3 @@ failedProofs, | ||
return [ | ||
`Can not derive from prf:${this.index} - ${this.proof} because:`, | ||
`Capability can not be derived from prf:${this.index} - ${this.proof} because:`, | ||
li(this.cause.message), | ||
@@ -296,3 +367,3 @@ ].join(`\n`) | ||
* delegationErrors: API.DelegationError[] | ||
* unknownCapaibilities: API.Capability[] | ||
* unknownCapabilities: API.Capability[] | ||
* invalidProofs: ProofError[] | ||
@@ -311,5 +382,2 @@ * failedProofs: API.InvalidClaim[] | ||
} | ||
get capability() { | ||
return this.info.match.value | ||
} | ||
get delegation() { | ||
@@ -325,3 +393,3 @@ return this.info.match.source[0].delegation | ||
const unknown = this.info.unknownCapaibilities.map(c => | ||
const unknown = this.info.unknownCapabilities.map(c => | ||
li(JSON.stringify(c)) | ||
@@ -331,3 +399,3 @@ ) | ||
return [ | ||
`Claimed capability ${this.info.match} is invalid`, | ||
`Capability ${this.info.match} is not authorized because:`, | ||
li(`Capability can not be (self) issued by '${this.issuer.did()}'`), | ||
@@ -345,18 +413,52 @@ ...(errors.length > 0 ? errors : [li(`Delegated capability not found`)]), | ||
*/ | ||
class Unauthorized extends Failure { | ||
/** | ||
* @param {API.InvalidCapability | API.InvalidProof | API.InvalidClaim} cause | ||
* @param {{ | ||
* capability: API.CapabilityParser | ||
* delegationErrors: API.DelegationError[] | ||
* unknownCapabilities: API.Capability[] | ||
* invalidProofs: API.InvalidProof[] | ||
* failedProofs: API.InvalidClaim[] | ||
* }} cause | ||
*/ | ||
constructor(cause) { | ||
constructor({ | ||
capability, | ||
delegationErrors, | ||
unknownCapabilities, | ||
invalidProofs, | ||
failedProofs, | ||
}) { | ||
super() | ||
/** @type {"Unauthorized"} */ | ||
this.name = 'Unauthorized' | ||
this.cause = cause | ||
this.capability = capability | ||
this.delegationErrors = delegationErrors | ||
this.unknownCapabilities = unknownCapabilities | ||
this.invalidProofs = invalidProofs | ||
this.failedProofs = failedProofs | ||
} | ||
get message() { | ||
return this.cause.message | ||
describe() { | ||
const errors = [ | ||
...this.failedProofs.map(error => li(error.message)), | ||
...this.delegationErrors.map(error => li(error.message)), | ||
...this.invalidProofs.map(error => li(error.message)), | ||
] | ||
const unknown = this.unknownCapabilities.map(c => li(JSON.stringify(c))) | ||
return [ | ||
`Claim ${this.capability} is not authorized`, | ||
...(errors.length > 0 | ||
? errors | ||
: [li(`No matching delegated capability found`)]), | ||
...(unknown.length > 0 | ||
? [li(`Encountered unknown capabilities\n${unknown.join('\n')}`)] | ||
: []), | ||
].join('\n') | ||
} | ||
toJSON() { | ||
const { error, name, message, cause, stack } = this | ||
return { error, name, message, cause, stack } | ||
const { error, name, message, stack } = this | ||
return { error, name, message, stack } | ||
} | ||
@@ -368,4 +470,4 @@ } | ||
* @param {T} delegation | ||
* @param {API.PrincipalOptions} config | ||
* @returns {Promise<API.Result<T, API.InvalidProof>>} | ||
* @param {Required<API.ClaimOptions>} config | ||
* @returns {Promise<API.Result<T, API.InvalidProof|API.DIDKeyResolutionError>>} | ||
*/ | ||
@@ -391,12 +493,79 @@ const validate = async (delegation, config) => { | ||
* @param {T} delegation | ||
* @param {API.PrincipalOptions} config | ||
* @returns {Promise<API.Result<T, API.InvalidSignature>>} | ||
* @param {Required<API.ClaimOptions>} config | ||
* @returns {Promise<API.Result<T, API.InvalidSignature|API.DIDKeyResolutionError>>} | ||
*/ | ||
const verifySignature = async (delegation, { principal }) => { | ||
const issuer = principal.parse(delegation.issuer.did()) | ||
const verifySignature = async (delegation, config) => { | ||
const did = delegation.issuer.did() | ||
const issuer = await resolveVerifier(did, delegation, config) | ||
if (issuer.error) { | ||
return issuer | ||
} | ||
const valid = await UCAN.verifySignature(delegation.data, issuer) | ||
return valid ? delegation : new InvalidSignature(delegation) | ||
} | ||
/** | ||
* @param {API.DID} did | ||
* @param {API.Delegation} delegation | ||
* @param {Required<API.ClaimOptions>} config | ||
* @returns {Promise<API.Result<API.Verifier, API.DIDKeyResolutionError>>} | ||
*/ | ||
const resolveVerifier = async (did, delegation, config) => { | ||
if (did === config.authority.did()) { | ||
return config.authority | ||
} else if (did.startsWith('did:key:')) { | ||
return config.principal.parse(did) | ||
} else { | ||
// First we attempt to resolve key from the embedded proofs | ||
const local = await resolveDIDFromProofs(did, delegation, config) | ||
const result = !local?.error | ||
? local | ||
: // If failed to resolve because there is an invalid proof propagate error | ||
(local?.cause?.failedProofs?.length || 0) > 0 | ||
? local | ||
: // otherwise either use resolved key or if not found attempt to resolve | ||
// did externally | ||
await config.resolveDIDKey(did) | ||
return result.error ? result : config.principal.parse(result).withDID(did) | ||
} | ||
} | ||
/** | ||
* @param {API.DID} did | ||
* @param {API.Delegation} delegation | ||
* @param {Required<API.ClaimOptions>} config | ||
* @returns {Promise<API.Result<API.DIDKey, API.DIDKeyResolutionError>>} | ||
*/ | ||
const resolveDIDFromProofs = async (did, delegation, config) => { | ||
const update = Top.derive({ | ||
to: capability({ | ||
with: Schema.literal(config.authority.did()), | ||
can: './update', | ||
nb: { key: DID.match({ method: 'key' }) }, | ||
}), | ||
derives: equalWith, | ||
}) | ||
const result = await claim(update, delegation.proofs, config) | ||
return !result.error | ||
? result.match.value.nb.key | ||
: new DIDKeyResolutionError(did, result) | ||
} | ||
const Top = capability({ | ||
can: '*', | ||
with: DID, | ||
}) | ||
/** | ||
* @param {API.Capability} to | ||
* @param {API.Capability} from | ||
*/ | ||
const equalWith = (to, from) => | ||
to.with === from.with || | ||
new Failure(`Claimed ${to.with} can not be derived from ${from.with}`) | ||
export { InvalidAudience } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
120310
35
3661
Updated@ucanto/core@^4.0.3
Updated@ucanto/interface@^4.0.3