Skip to content

Commit

Permalink
Allow non-ucan:* delegations to still be claimed
Browse files Browse the repository at this point in the history
  • Loading branch information
Peeja committed Oct 2, 2024
1 parent 7f16b60 commit 9c94daa
Showing 1 changed file with 69 additions and 74 deletions.
143 changes: 69 additions & 74 deletions packages/upload-api/src/access/claim.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ export const provide = (ctx) =>
*/
const isAccount = (principal) => principal.did().startsWith('did:mailto:')

/**
* Returns true when the delegation has a `ucan:*` capability.
* @param {API.Delegation} delegation
* @returns boolean
*/
const isUcanStar = (delegation) =>
delegation.capabilities.some((capability) => capability.with === 'ucan:*')

/**
* @param {API.Input<Access.claim>} input
* @param {API.AccessClaimContext} ctx
Expand All @@ -38,108 +46,95 @@ export const claim = async ({ invocation }, { delegationsStorage, signer }) => {
}
}

// If this Agent has been confirmed by any Accounts, we'll find `*`/`ucan:*`
// delegations for them. But they won't have any proofs on them. They're proof
// of login, but don't serve to give the Agent any actual capabilities. That's
// our job.
const loginDelegations = storedDelegationsResult.ok.filter((delegation) => {
return delegation.capabilities.some((capability) => {
return capability.can === '*' && capability.with === 'ucan:*'
})
})
/** @type {API.Result<API.Delegation[], API.AccessClaimFailure>} */
const delegationsResult = await Promise.all(
storedDelegationsResult.ok.map(async (delegation) => {
// If this Agent has been confirmed by any Accounts, we'll find `*`/`ucan:*`
// delegations for them. But they won't have any proofs on them. They're proof
// of login, but don't serve to give the Agent any actual capabilities. That's
// our job.
if (isUcanStar(delegation)) {
// These delegations will actually have proofs granting access to the spaces.
// This collection also includes the attestation delegations which validate
// prove those delegations.
const sessionProofsResult = await createSessionProofsForLogin(
delegation,
delegationsStorage,
signer
)

if (sessionProofsResult.error) {
throw {
error: {
name: 'AccessClaimFailure',
message: 'error creating session proofs',
cause: sessionProofsResult.error,
},
}
}

// These delegations will actually have proofs granting access to the spaces.
// This collection also includes the attestation delegations which validate
// prove those delegations.
const sessionProofsResult = await createSessionProofsForLogins(
loginDelegations,
delegationsStorage,
signer
return sessionProofsResult.ok
} else {
return [delegation]
}
})
)
.then((delegations) => ({ ok: delegations.flat() }))
.catch((error) => ({ error }))

if (sessionProofsResult.error) {
return {
error: {
name: 'AccessClaimFailure',
message: 'error creating session proofs',
cause: sessionProofsResult.error,
},
}
if (delegationsResult.error) {
return delegationsResult
}

return {
ok: {
delegations: delegationsResponse.encode(sessionProofsResult.ok),
delegations: delegationsResponse.encode(delegationsResult.ok),
},
}
}

/**
* @param {API.Delegation[]} loginDelegations
* @param {API.Delegation} loginDelegation
* @param {API.DelegationsStorage} delegationsStorage
* @param {API.Signer} signer
* @returns {Promise<API.Result<API.Delegation[], API.AccessClaimFailure>>}
*/
async function createSessionProofsForLogins(
loginDelegations,
async function createSessionProofsForLogin(
loginDelegation,
delegationsStorage,
signer
) {
const sessionProofPairsResults = await Promise.all(
// Each star delegation should represent an account we're logged in as.
// Normally there's only one, but more than one is possible and valid.
loginDelegations.map(async (delegation) => {
// These should always be Accounts (did:mailto:), but if one's not, skip
// it.
if (!isAccount(delegation.issuer)) return { ok: [] }

const accountDelegationsResult = await delegationsStorage.find({
audience: delegation.issuer.did(),
})

if (accountDelegationsResult.error) {
return {
error: {
name: 'AccessClaimFailure',
message: 'error finding delegations',
cause: accountDelegationsResult.error,
},
}
}
// These should always be Accounts (did:mailto:), but if one's not, skip it.
if (!isAccount(loginDelegation.issuer)) return { ok: [] }

return {
ok: await createSessionProofs({
service: signer,
account: delegation.issuer,
agent: delegation.audience,
facts: delegation.facts,
capabilities: delegation.capabilities,
// We include all the delegations to the account so that the agent will
// have delegation chains to all the delegated resources.
// We should actually filter out only delegations that support delegated
// capabilities, but for now we just include all of them since we only
// implement sudo access anyway.
delegationProofs: accountDelegationsResult.ok,
expiration: Infinity,
}),
}
})
)
const accountDelegationsResult = await delegationsStorage.find({
audience: loginDelegation.issuer.did(),
})

if (sessionProofPairsResults.some((result) => result.error)) {
if (accountDelegationsResult.error) {
return {
error: {
name: 'AccessClaimFailure',
message: 'error creating session proofs',
cause: sessionProofPairsResults.find((result) => result.error),
message: 'error finding delegations',
cause: accountDelegationsResult.error,
},
}
}

return {
ok: sessionProofPairsResults.flatMap((result) =>
// result.ok is always present here, but TS-in-JS can't tell that.
result.ok ? result.ok : []
),
ok: await createSessionProofs({
service: signer,
account: loginDelegation.issuer,
agent: loginDelegation.audience,
facts: loginDelegation.facts,
capabilities: loginDelegation.capabilities,
// We include all the delegations to the account so that the agent will
// have delegation chains to all the delegated resources.
// We should actually filter out only delegations that support delegated
// capabilities, but for now we just include all of them since we only
// implement sudo access anyway.
delegationProofs: accountDelegationsResult.ok,
expiration: Infinity,
}),
}
}

0 comments on commit 9c94daa

Please sign in to comment.