From 90a3bd0d8e79f4f35d8076f375c29dd6d1c71393 Mon Sep 17 00:00:00 2001 From: Irakli Gozalishvili Date: Mon, 30 Oct 2023 13:14:09 -0700 Subject: [PATCH] feat!: allow attestations from did:mailto --- packages/validator/src/lib.js | 30 ++++-- packages/validator/test/session.spec.js | 130 +++++++++++++++++++++++- 2 files changed, 151 insertions(+), 9 deletions(-) diff --git a/packages/validator/src/lib.js b/packages/validator/src/lib.js index d973ea48..59e231bd 100644 --- a/packages/validator/src/lib.js +++ b/packages/validator/src/lib.js @@ -257,7 +257,7 @@ export const claim = async ( invalidProofs.push(...errors) for (const proof of delegations) { - // Validate each proof if valid add ech capability to the list of sources. + // Validate each proof if valid add each capability to the list of sources. // otherwise collect the error. const validation = await validate(proof, delegations, config) if (validation.ok) { @@ -543,18 +543,32 @@ const verifySignature = async (delegation, verifier) => { */ const verifySession = async (delegation, proofs, config) => { // Create a schema that will match an authorization for this exact delegation - const attestation = capability({ + const attestationFromAuthority = capability({ + can: 'ucan/attest', with: Schema.literal(config.authority.did()), + nb: Schema.struct({ + proof: Schema.link(delegation.cid), + }), + }) + + // We omit the delegation otherwise we may end up in an infinite loop + const delegations = proofs.filter(proof => proof != delegation) + + const result = await claim(attestationFromAuthority, delegations, config) + + // If we did not found an attestation from the authority attempt to find + // an attestation from the resource owner. + if (result.ok) { + return result + } + + const attestationFromOwner = capability({ can: 'ucan/attest', + with: Schema.literal(delegation.capabilities[0].with), nb: Schema.struct({ proof: Schema.link(delegation.cid), }), }) - return await claim( - attestation, - // We omit the delegation otherwise we may end up in an infinite loop - proofs.filter(proof => proof != delegation), - config - ) + return claim(attestationFromOwner, delegations, config) } diff --git a/packages/validator/test/session.spec.js b/packages/validator/test/session.spec.js index 391fa2ab..310c6dba 100644 --- a/packages/validator/test/session.spec.js +++ b/packages/validator/test/session.spec.js @@ -290,7 +290,7 @@ test('fail invalid ucan/attest proof', async () => { }, }) - assert.match(`${result.error}`, /has an invalid session/) + assert.match(`${result.error}`, /not authorized/) }) test('resolve key', async () => { @@ -447,3 +447,131 @@ test('service can not delegate account resource', async () => { assert.equal(!result.ok, true) }) + +test('can attest with did:mailto', async () => { + const account = Absentee.from({ id: 'did:mailto:web.mail:alice' }) + const agent = alice + const service = w3 + + // account authorized agent to issue attestations on its behalf + const authorization = await attest.delegate({ + issuer: account, + audience: agent, + with: account.did(), + expiration: Infinity, + }) + + // service attests that authorization is valid + const attestation = await attest.delegate({ + issuer: service, + audience: agent, + with: service.did(), + nb: { proof: authorization.cid }, + expiration: Infinity, + }) + + // now agent creates delegation on behalf of the account + const delegation = await echo.delegate({ + issuer: agent.withDID(account.did()), + audience: agent, + with: account.did(), + expiration: Infinity, + }) + + // and creates an attestation using an authorization from + // the account account + const proof = await attest.delegate({ + issuer: agent, + audience: agent, + with: account.did(), + nb: { + proof: delegation.cid, + }, + proofs: [authorization, attestation], + expiration: Infinity, + }) + + // This allows agent to invoke capability that it delegated on behalf of + // the account and attest it too. + const task = echo.invoke({ + issuer: agent, + audience: service, + with: account.did(), + nb: { message: 'hello world' }, + proofs: [delegation, proof], + expiration: Infinity, + }) + + const result = await access(await task.delegate(), { + authority: service, + capability: echo, + principal: Verifier, + validateAuthorization: () => ({ ok: {} }), + }) + + assert.equal(result.error, undefined) +}) + +test('attestation with did:mailto fails unless attested by authority', async () => { + const account = Absentee.from({ id: 'did:mailto:web.mail:alice' }) + const agent = alice + const service = w3 + + // account authorized agent to issue attestations on its behalf + const authorization = await attest.delegate({ + issuer: account, + audience: agent, + with: account.did(), + expiration: Infinity, + }) + + // service attests that authorization is valid + const attestation = await attest.delegate({ + issuer: account, + audience: agent, + with: service.did(), + nb: { proof: authorization.cid }, + expiration: Infinity, + }) + + // now agent creates delegation on behalf of the account + const delegation = await echo.delegate({ + issuer: agent.withDID(account.did()), + audience: agent, + with: account.did(), + expiration: Infinity, + }) + + // and creates an attestation using an authorization from + // the account account + const proof = await attest.delegate({ + issuer: agent, + audience: agent, + with: account.did(), + nb: { + proof: delegation.cid, + }, + proofs: [authorization, attestation], + expiration: Infinity, + }) + + // This allows agent to invoke capability that it delegated on behalf of + // the account and attest it too. + const task = echo.invoke({ + issuer: agent, + audience: service, + with: account.did(), + nb: { message: 'hello world' }, + proofs: [delegation, proof], + expiration: Infinity, + }) + + const result = await access(await task.delegate(), { + authority: service, + capability: echo, + principal: Verifier, + validateAuthorization: () => ({ ok: {} }), + }) + + assert.match(String(result.error), /Unauthorized/) +})