import { Ability, Capability, DID, Link, Resource } from '@ipld/dag-ucan'; import * as UCAN from '@ipld/dag-ucan'; import { AuthorityProver, Delegation, Result, Failure, PrincipalParser, PrincipalResolver, URI, UCANLink, Await, IssuedInvocationView, UCANOptions, Verifier, Unit } from './lib.js'; export interface Source { capability: { can: Ability; with: URI; nb?: Caveats; }; delegation: Delegation; } export interface Match extends Selector { source: Source[]; value: T; proofs: Delegation[]; prune: (config: CanIssue) => null | Match; } export interface UnknownMatch extends Match { } export interface Matcher { match(capability: Source): MatchResult; } export interface Selector { select(sources: Source[]): Select; } export interface Select { matches: M[]; errors: DelegationError[]; unknown: Capability[]; } export interface GroupSelector extends Selector> { } export interface MatchSelector extends Matcher, Selector { } export interface DirectMatch extends Match> { } /** * Generic reader interface that can be used to read `O` value form the * input `I` value. Reader may fail and error is denoted by `X` type. * * @template O - The output type of this reader * @template I - The input type of this reader. * @template X - The error type denotes failure reader may produce. */ export interface Reader { read: (input: I) => Result; } export interface Caveats { [key: string]: unknown; } export type MatchResult = Result; /** * Error produced when parsing capabilities */ export type InvalidCapability = UnknownCapability | MalformedCapability; export interface DerivedMatch extends Match> { } /** * Utility type is used to infer the type of the capability passed into * `derives` handler. It simply makes all `nb` fields optional because * in delegation all `nb` fields could be left out implying no restrictions. */ type ToDeriveClaim = T | ParsedCapability>; /** * Utility type is used to infer type of the second argument of `derives` * handler (in the `cap.derive({ to, derives: (claim, proof) => true })`) * which could be either capability or set of capabilities. It simply makes * all `nb` fields optional, because in delegation all `nb` fields could be * left out implying no restrictions. */ export type InferDeriveProof = T extends ParsedCapability ? InferDelegatedCapability : InferDeriveProofs; /** * Another helper type which is equivalent of `ToDeriveClaim` except it works * on tuple of capabilities. */ type InferDeriveProofs = T extends [infer U, ...infer E] ? [ToDeriveClaim, ...InferDeriveProofs] : T extends never[] ? [] : never; export interface Derives { (claim: T, proof: U): Result<{}, Failure>; } export interface View extends Matcher, Selector { /** * Defined a derived capability which can be delegated from `this` capability. * For example if you define `"account/validate"` capability and derive * `"account/register"` capability from it when validating claimed * `"account/register"` capability it could be proved with either * "account/register" delegation or "account/validate" delegation. * * ```js * // capability issued by account verification service on email validation * const verify = capability({ * can: "account/verify", * with: URI({ protocol: "mailto:" }) * derives: ({ with: url }, from) => * url.href.startsWith(from.with.href) || * new Failure(`${url.href} is not contained in ${from.with.href}`) * }) * * // derive registration capability from email verification * const register = validate.derive({ * to: capability({ * can: "account/register", * with: URI({ protocol: "mailto:" }), * derives: ({ with: url }, from) => * url.href.startsWith(from.with.href) || * new Failure(`${url.href} is not contained in ${from.with.href}`) * }), * derives: (registered, verified) => * registered.with.href === verified.with.href || * new Failure(`Registration email ${registered.pathname} does not match verified email ${verified.with.pathname}`) * }) * ``` */ derive(options: { to: TheCapabilityParser>; derives: Derives>; }): TheCapabilityParser>; } export interface TheCapabilityParser> extends CapabilityParser { readonly can: M['value']['can']; create(input: InferCreateOptions): InferCapability; /** * Creates an invocation of this capability. Function throws exception if * non-optional fields are omitted. */ invoke(options: InferInvokeOptions): IssuedInvocationView>; /** * Creates a delegation of this capability. Please note that all the * `nb` fields are optional in delegation and only provided ones will * be validated. */ delegate(options: InferDelegationOptions): Promise]>>; } /** * When normalize capabilities by removing `nb` if it is a `{}`. This type * does that normalization at the type level. */ export type InferCapability = keyof T['nb'] extends never ? { can: T['can']; with: T['with']; } : { can: T['can']; with: T['with']; nb: T['nb']; }; /** * In delegation capability all the `nb` fields are optional. This type maps * capability type (as it would be in the invocation) to the form it will be * in the delegation. */ export type InferDelegatedCapability = keyof T['nb'] extends never ? { can: T['can']; with: T['with']; } : { can: T['can']; with: T['with']; nb: Partial; }; export type InferCreateOptions = keyof C extends never ? { with: R; nb?: never; } : { with: R; nb: C; }; export type InferInvokeOptions = UCANOptions & { issuer: UCAN.Signer; } & InferCreateOptions; export type InferDelegationOptions = UCANOptions & { issuer: UCAN.Signer; with: R; nb?: Partial['nb']>; }; export type EmptyObject = { [key: string | number | symbol]: never; }; export interface CapabilityParser extends View { /** * Defines capability that is either `this` or the the given `other`. This * allows you to compose multiple capabilities into one so that you could * validate any of one of them without having to maintain list of supported * capabilities. It is especially useful when dealing with derived * capability chains when you might derive capability from either one or the * other. */ or(other: MatchSelector): CapabilityParser; /** * Combines this capability and the other into a capability group. This allows * you to define right amplifications e.g `file/read+write` could be derived * from `file/read` and `file/write`. * @example * ```js * const read = capability({ * can: "file/read", * with: URI({ protocol: "file:" }), * derives: (claimed, delegated) => * claimed.with.pathname.startsWith(delegated.with.pathname) * ? { ok: {} } * : { error: new Failure(`'${claimed.with.href}' is not contained in '${delegated.with.href}'`) } * }) * * const write = capability({ * can: "file/write", * with: URI({ protocol: "file:" }), * derives: (claimed, delegated) => * claimed.with.pathname.startsWith(delegated.with.pathname) * ? { ok: {} } * : { error: new Failure(`'${claimed.with.href}' is not contained in '${delegated.with.href}'`) } * }) * * const readwrite = read.and(write).derive({ * to: capability({ * can: "file/read+write", * with: URI({ protocol: "file:" }), * derives: (claimed, delegated) => * claimed.with.pathname.startsWith(delegated.with.pathname) * ? { ok: {} } * : { error: new Failure(`'${claimed.with.href}' is not contained in '${delegated.with.href}'`) } * }), * derives: (claimed, [read, write]) => { * if (!claimed.with.pathname.startsWith(read.with.pathname)) { * return { error: new Failure(`'${claimed.with.href}' is not contained in '${read.with.href}'`) } * } else if (!claimed.with.pathname.startsWith(write.with.pathname)) { * return { error: new Failure(`'${claimed.with.href}' is not contained in '${write.with.href}'`) } * } else { * return { ok: {} } * } * } * }) *``` */ and(other: MatchSelector): CapabilitiesParser<[M, W]>; } export interface CapabilitiesParser extends View> { /** * Creates new capability group containing capabilities from this group and * provided `other` capability. This method complements `and` method on * `Capability` to allow chaining e.g. `read.and(write).and(modify)`. */ and(other: MatchSelector): CapabilitiesParser<[...M, W]>; } export interface Amplify extends Match, Amplify>> { } export type InferMembers = Selectors extends [ MatchSelector, ...infer Rest ] ? [Match, ...InferMembers] : Selectors extends [] ? [] : never; export type InferValue = Members extends [] ? [] : Members extends [Match, ...infer Rest] ? [T, ...InferValue] : never; export type InferMatch = Members extends [] ? [] : Members extends [Match, ...infer Rest] ? [M, ...InferMatch] : never; export interface ParsedCapability { can: Can; with: Resource; nb: C; } export interface CapabilityMatch extends DirectMatch> { } export interface CanIssue { /** * Informs validator whether given capability can be issued by a given * DID or whether it needs to be delegated to the issuer. */ canIssue(capability: ParsedCapability, issuer: DID): boolean; } export interface PrincipalOptions { principal: PrincipalParser; } export interface ProofResolver extends PrincipalOptions { /** * You can provide a proof resolver that validator will call when UCAN * links to external proof. If resolver is not provided validator may not * be able to explore corresponding path within a proof chain. */ resolve?: (proof: Link) => Await>; } export interface RevocationChecker { validateAuthorization: (authorization: Authorization) => Await>; } export interface Validator { /** * Validator must be provided a `Verifier` corresponding to local authority. * Capability provider service will use one corresponding to own DID or it's * supervisor's DID if it acts under it's authority. * * This allows service identified by non did:key e.g. did:web or did:dns to * pass resolved key so it does not need to be resolved at runtime. */ authority: Verifier; } export interface ValidationOptions extends Partial, Validator, PrincipalOptions, PrincipalResolver, ProofResolver, RevocationChecker, Partial { capability: CapabilityParser>; } export interface ClaimOptions extends Partial, Validator, PrincipalOptions, PrincipalResolver, ProofResolver, RevocationChecker, Partial { } export interface DelegationError extends Failure { name: 'InvalidClaim'; causes: (InvalidCapability | EscalatedDelegation | DelegationError)[]; cause: InvalidCapability | EscalatedDelegation | DelegationError; } export interface EscalatedDelegation extends Failure { name: 'EscalatedCapability'; claimed: ParsedCapability; delegated: object; cause: Failure; } export interface UnknownCapability extends Failure { name: 'UnknownCapability'; capability: Capability; } export interface MalformedCapability extends Failure { name: 'MalformedCapability'; capability: Capability; } export interface InvalidAudience extends Failure { readonly name: 'InvalidAudience'; } export interface UnavailableProof extends Failure { readonly name: 'UnavailableProof'; readonly link: UCANLink; } export interface DIDKeyResolutionError extends Failure { readonly name: 'DIDKeyResolutionError'; readonly did: UCAN.DID; readonly cause?: Failure; } export interface Expired extends Failure { readonly name: 'Expired'; readonly delegation: Delegation; readonly expiredAt: number; } export interface Revoked extends Failure { readonly name: 'Revoked'; readonly delegation: Delegation; } export interface NotValidBefore extends Failure { readonly name: 'NotValidBefore'; readonly delegation: Delegation; readonly validAt: number; } export interface InvalidSignature extends Failure { readonly name: 'InvalidSignature'; readonly issuer: UCAN.Principal; readonly audience: UCAN.Principal; readonly delegation: Delegation; } export interface SessionEscalation extends Failure { readonly name: 'SessionEscalation'; readonly delegation: Delegation; readonly cause: Failure; } /** * Error produces by invalid proof */ export type InvalidProof = Expired | Revoked | NotValidBefore | InvalidSignature | InvalidAudience | SessionEscalation | DIDKeyResolutionError | UnavailableProof; export interface Unauthorized extends Failure { name: 'Unauthorized'; delegationErrors: DelegationError[]; unknownCapabilities: Capability[]; invalidProofs: InvalidProof[]; failedProofs: InvalidClaim[]; } export interface Authorization { delegation: Delegation; capability: Capability; proofs: Authorization[]; issuer: UCAN.Principal; audience: UCAN.Principal; } export interface InvalidClaim extends Failure { issuer: UCAN.Principal; name: 'InvalidClaim'; delegation: Delegation; message: string; } export {}; //# sourceMappingURL=capability.d.ts.map