-
Notifications
You must be signed in to change notification settings - Fork 23
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!: implement new account-based multi-device flow #433
Changes from 51 commits
ced0e0d
ddd3d0c
6b1d820
daace23
ba1ce34
941bb4d
92f7caa
75e243f
4ceb10b
7df2133
86d77a1
0471e89
098d488
78cb8bf
f4ad371
95a1c4a
5c2bc71
2156f23
6e89c42
dfe264d
459aa6f
1c79d0b
e5e9a43
0cc4e47
6d942f1
1ea805e
50402a4
53c63b8
c0672ca
9aa3a26
36628f3
a637d04
08db79b
7aa68d4
48903aa
3d70dd9
14390af
ea385ba
e28f993
50960f0
a66a294
6885743
1e9e70f
0432309
97669c5
0b2e268
00d125c
5d5f577
8ebf0b9
c0e1bb5
0d8dd30
4e74674
3af8816
2658b44
9ca0df7
571713d
d1ea1b0
70b663d
943458d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,11 @@ | ||
import * as Ucanto from '@ucanto/interface' | ||
import * as ucanto from '@ucanto/core' | ||
import { Verifier, Absentee } from '@ucanto/principal' | ||
import { Absentee } from '@ucanto/principal' | ||
import { collect } from 'streaming-iterables' | ||
import * as Access from '@web3-storage/capabilities/access' | ||
import { delegationsToString } from '@web3-storage/access/encoding' | ||
import * as delegationsResponse from '../utils/delegations-response.js' | ||
import * as validator from '@ucanto/validator' | ||
|
||
/** | ||
* @typedef {import('@web3-storage/capabilities/types').AccessConfirmSuccess} AccessConfirmSuccess | ||
|
@@ -18,7 +19,9 @@ export function parse(invocation) { | |
const capability = invocation.capabilities[0] | ||
// Create a absentee signer for the account that authorized the delegation | ||
const account = Absentee.from({ id: capability.nb.iss }) | ||
const agent = Verifier.parse(capability.nb.aud) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I liked the former version better. It's also more immune to the changes in ucanto if say one day we change principal interface for whatever reason. If you do want to do method validation you could still do it using DID.match... before passing to verifier. Also I'm not sure we do need to care if agent uses did:key or not. Why do we care what did they use if they are able to proof ownership. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was to appease the fact that I wanted to make |
||
const agent = { | ||
did: () => validator.DID.match({ method: 'key' }).from(capability.nb.aud), | ||
} | ||
return { | ||
account, | ||
agent, | ||
|
@@ -50,31 +53,21 @@ export async function handleAccessConfirm(invocation, ctx) { | |
})) | ||
) | ||
|
||
// create an delegation on behalf of the account with an absent signature. | ||
const delegation = await ucanto.delegate({ | ||
issuer: account, | ||
audience: agent, | ||
const [delegation, attestation] = await createSessionProofs( | ||
ctx.signer, | ||
account, | ||
agent, | ||
capabilities, | ||
expiration: Infinity, | ||
// 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. | ||
proofs: await collect( | ||
ctx.models.delegations.find({ | ||
audience: account.did(), | ||
}) | ||
), | ||
}) | ||
|
||
const attestation = await Access.session.delegate({ | ||
issuer: ctx.signer, | ||
audience: agent, | ||
with: ctx.signer.did(), | ||
nb: { proof: delegation.cid }, | ||
expiration: Infinity, | ||
}) | ||
ctx.models.delegations.find({ | ||
audience: account.did(), | ||
}), | ||
Infinity | ||
) | ||
|
||
// Store the delegations so that they can be pulled with access/claim | ||
// The fact that we're storing proofs chains that we pulled from the | ||
|
@@ -89,3 +82,40 @@ export async function handleAccessConfirm(invocation, ctx) { | |
delegations: delegationsResponse.encode([delegation, attestation]), | ||
} | ||
} | ||
|
||
/** | ||
* @param {Ucanto.Signer} service | ||
* @param {Ucanto.Principal<Ucanto.DID<'mailto'>>} account | ||
* @param {Ucanto.Principal<Ucanto.DID<'key'>>} agent | ||
* @param {Ucanto.Capabilities} capabilities | ||
* @param {AsyncIterable<Ucanto.Delegation>} delegationProofs | ||
* @param {number} expiration | ||
* @returns {Promise<[delegation: Ucanto.Delegation, attestation: Ucanto.Delegation]>} | ||
*/ | ||
export async function createSessionProofs( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I think named parameters are better especially when your function takes more than 3 params. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
service, | ||
account, | ||
agent, | ||
capabilities, | ||
delegationProofs, | ||
expiration | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Generally I think we want attestations to be non expiring because otherwise all the delegations issued by account will be time bound as well. I would suggest removing this parameter and perhaps adding a code comment in the delegations below explaining rational for non expiring delegation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
) { | ||
// create an delegation on behalf of the account with an absent signature. | ||
const delegation = await ucanto.delegate({ | ||
issuer: Absentee.from({ id: account.did() }), | ||
audience: agent, | ||
capabilities, | ||
expiration, | ||
proofs: [...(await collect(delegationProofs))], | ||
}) | ||
|
||
const attestation = await Access.session.delegate({ | ||
issuer: service, | ||
audience: agent, | ||
with: service.did(), | ||
nb: { proof: delegation.cid }, | ||
expiration, | ||
}) | ||
|
||
return [delegation, attestation] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import * as Ucanto from '@ucanto/interface' | ||
|
||
export type ServiceInvoke< | ||
Service extends Record<string, any>, | ||
InvocationCapabilities extends Ucanto.Capability = Ucanto.Capability | ||
> = <Capability extends InvocationCapabilities>( | ||
invocation: Ucanto.ServiceInvocation<Capability> | ||
) => Promise<Ucanto.InferServiceInvocationReturn<Capability, Service>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something more descriptive here like:
"can not authorize this session, probably link in the email has expired ${confirmation}"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gobengo