Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: allow attestations from did:mailto #326

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions packages/validator/src/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
130 changes: 129 additions & 1 deletion packages/validator/test/session.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// the account account
// the 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/)
})
Loading