From 3e8825aadcbed3f74aeb49ec109ed26969e2454c Mon Sep 17 00:00:00 2001 From: Benjamin Goering <171782+gobengo@users.noreply.github.com> Date: Wed, 15 Mar 2023 12:55:18 -0700 Subject: [PATCH 1/2] rm mention of 'sessionPrincipal' from agent and agent-data --- packages/access-client/src/agent-data.js | 16 ---- packages/access-client/src/agent.js | 82 ++++++++----------- packages/access-client/src/types.ts | 2 - .../access-client/src/utils/did-mailto.js | 15 ++++ 4 files changed, 47 insertions(+), 68 deletions(-) create mode 100644 packages/access-client/src/utils/did-mailto.js diff --git a/packages/access-client/src/agent-data.js b/packages/access-client/src/agent-data.js index 4d93c21cc..f4c981212 100644 --- a/packages/access-client/src/agent-data.js +++ b/packages/access-client/src/agent-data.js @@ -2,7 +2,6 @@ import { Signer } from '@ucanto/principal' import { Signer as EdSigner } from '@ucanto/principal/ed25519' import { importDAG } from '@ucanto/core/delegation' import * as Ucanto from '@ucanto/interface' -import { DID } from '@ucanto/core' import { CID } from 'multiformats' import { Access } from '@web3-storage/capabilities' import { isExpired } from './delegations.js' @@ -21,7 +20,6 @@ export class AgentData { constructor(data, options = {}) { this.meta = data.meta this.principal = data.principal - this.sessionPrincipal = data.sessionPrincipal this.spaces = data.spaces this.delegations = data.delegations this.currentSpace = data.currentSpace @@ -40,7 +38,6 @@ export class AgentData { { meta: { name: 'agent', type: 'device', ...init.meta }, principal: init.principal ?? (await EdSigner.generate()), - sessionPrincipal: init.sessionPrincipal, spaces: init.spaces ?? new Map(), delegations: init.delegations ?? new Map(), currentSpace: init.currentSpace, @@ -80,10 +77,6 @@ export class AgentData { meta: raw.meta, // @ts-expect-error for some reason TS thinks this is a EdSigner principal: Signer.from(raw.principal), - // @ts-expect-error TODO figure out the types for this too - sessionPrincipal: raw.sessionPrincipal - ? DID.parse(raw.sessionPrincipal) - : undefined, currentSpace: raw.currentSpace, spaces: raw.spaces, delegations: dels, @@ -100,7 +93,6 @@ export class AgentData { const raw = { meta: this.meta, principal: this.principal.toArchive(), - sessionPrincipal: this.sessionPrincipal?.did(), currentSpace: this.currentSpace, spaces: this.spaces, delegations: new Map(), @@ -135,14 +127,6 @@ export class AgentData { await this.#save(this.export()) } - /** - * @param {import('@ucanto/interface').Principal>} principal - */ - async setSessionPrincipal(principal) { - this.sessionPrincipal = principal - await this.#save(this.export()) - } - /** * @param {import('@ucanto/interface').Delegation} delegation * @param {import('./types').DelegationMeta} [meta] diff --git a/packages/access-client/src/agent.js b/packages/access-client/src/agent.js index 6487d6f76..228ed009f 100644 --- a/packages/access-client/src/agent.js +++ b/packages/access-client/src/agent.js @@ -7,7 +7,7 @@ import * as CAR from '@ucanto/transport/car' import * as CBOR from '@ucanto/transport/cbor' import * as HTTP from '@ucanto/transport/http' import * as ucanto from '@ucanto/core' -import { URI } from '@ucanto/validator' +import { URI, DID as DIDValidator } from '@ucanto/validator' import { Peer } from './awake/peer.js' import * as Space from '@web3-storage/capabilities/space' import * as Access from '@web3-storage/capabilities/access' @@ -25,22 +25,13 @@ import { canDelegateCapability, } from './delegations.js' import { AgentData, getSessionProof } from './agent-data.js' +import { createDidMailtoFromEmail } from './utils/did-mailto.js' export { AgentData } const HOST = 'https://access.web3.storage' const PRINCIPAL = DID.parse('did:web:web3.storage') -/** - * - * @param {string} email - * @returns {Ucanto.Principal>} - */ -function emailToSessionPrincipal(email) { - const parts = email.split('@').map((s) => encodeURIComponent(s)) - return DID.parse(`did:mailto:${parts[1]}:${parts[0]}`) -} - /** * @param {Ucanto.Signer>} issuer * @param {Ucanto.DID} space @@ -190,10 +181,6 @@ export class Agent { return this.#data.spaces } - get account() { - return this.#data.sessionPrincipal - } - did() { return this.#data.principal.did() } @@ -500,18 +487,16 @@ export class Agent { * Request authorization of a session allowing this agent to issue UCANs * signed by the passed email address. * - * @param {string} email + * @param {`${string}@${string}`} email * @param {object} [opts] * @param {AbortSignal} [opts.signal] */ async authorize(email, opts) { - const sessionPrincipal = emailToSessionPrincipal(email) - const res = await this.invokeAndExecute(Access.authorize, { audience: this.connection.id, with: this.issuer.did(), nb: { - iss: sessionPrincipal.did(), + iss: createDidMailtoFromEmail(email), att: [{ can: 'store/*' }, { can: 'provider/add' }, { can: 'upload/*' }], }, }) @@ -533,7 +518,6 @@ export class Agent { } await this.addProof(sessionDelegation) - this.#data.setSessionPrincipal(sessionPrincipal) // claim delegations here because we will need an ucan/attest from the service to // pair with the session delegation we just claimed to make it work @@ -581,26 +565,21 @@ export class Agent { /** * @param {Ucanto.DID<'key'>} space + * @param {Ucanto.Principal>} account + * @param {Ucanto.DID<'web'>} provider - e.g. 'did:web:staging.web3.storage' */ - async addProvider(space) { - const sessionPrincipal = this.#data.sessionPrincipal - - if (!sessionPrincipal) { - throw new Error('cannot add provider, please authorize first') - } - + async addProvider(space, account, provider) { return this.invokeAndExecute(Provider.add, { audience: this.connection.id, - with: sessionPrincipal.did(), + with: account.did(), proofs: this.proofs([ { can: 'provider/add', - with: sessionPrincipal.did(), + with: account.did(), }, ]), nb: { - // TODO probably need to make it possible to pass other providers in - provider: 'did:web:staging.web3.storage', + provider, consumer: space, }, }) @@ -609,22 +588,11 @@ export class Agent { /** * * @param {Ucanto.DID<'key'>} space + * @param {Ucanto.Principal>} account */ - async delegateSpaceAccessToAccount(space) { - const sessionPrincipal = this.#data.sessionPrincipal - - if (!sessionPrincipal) { - throw new Error( - 'cannot add delegate space access to account, please authorize first' - ) - } - + async delegateSpaceAccessToAccount(space, account) { const spaceSaysAccountCanAdminSpace = - await createIssuerSaysAccountCanAdminSpace( - this.issuer, - space, - sessionPrincipal - ) + await createIssuerSaysAccountCanAdminSpace(this.issuer, space, account) return this.invokeAndExecute(Access.delegate, { audience: this.connection.id, with: space, @@ -652,12 +620,26 @@ export class Agent { * * It also adds a full space delegation to the service in the voucher/claim invocation to allow for recovery * + * @param {`${string}@${string}`} email * @param {object} [opts] * @param {AbortSignal} [opts.signal] + * @param {Ucanto.DID<'web'>} [opts.provider] - provider to register - defaults to this.connection.id */ - async registerSpace(opts) { + async registerSpace(email, opts) { const space = this.currentSpace() const spaceMeta = space ? this.#data.spaces.get(space) : undefined + const provider = + opts?.provider || + (() => { + const service = this.connection.id.did() + if (DIDValidator.match({ method: 'web' }).is(service)) { + // connection.id did is a valid provider value. Try using that. + return service + } + throw new Error( + `unable to determine provider to use to register space. Pass opts.provider` + ) + })() if (!space || !spaceMeta) { throw new Error('No space selected') @@ -666,14 +648,14 @@ export class Agent { if (spaceMeta && spaceMeta.isRegistered) { throw new Error('Space already registered with web3.storage.') } - const providerResult = await this.addProvider( - /** @type {Ucanto.DID<'key'>} */ (space) - ) + const account = { did: () => createDidMailtoFromEmail(email) } + const providerResult = await this.addProvider(space, account, provider) if (providerResult.error) { throw new Error(providerResult.message, { cause: providerResult }) } const delegateSpaceAccessResult = await this.delegateSpaceAccessToAccount( - space + space, + account ) if (delegateSpaceAccessResult.error) { // @ts-ignore it's very weird that this is throwing an error but line 692 above does not - ignore for now diff --git a/packages/access-client/src/types.ts b/packages/access-client/src/types.ts index 15be458cd..7342e7edc 100644 --- a/packages/access-client/src/types.ts +++ b/packages/access-client/src/types.ts @@ -170,7 +170,6 @@ export type CIDString = string export interface AgentDataModel { meta: AgentMeta principal: Signer> - sessionPrincipal?: Principal> currentSpace?: DID<'key'> spaces: Map delegations: Map @@ -184,7 +183,6 @@ export type AgentDataExport = Pick< 'meta' | 'currentSpace' | 'spaces' > & { principal: SignerArchive - sessionPrincipal?: string delegations: Map< CIDString, { diff --git a/packages/access-client/src/utils/did-mailto.js b/packages/access-client/src/utils/did-mailto.js new file mode 100644 index 000000000..a406bfb44 --- /dev/null +++ b/packages/access-client/src/utils/did-mailto.js @@ -0,0 +1,15 @@ +/** + * @param {`${string}@${string}`} email + * @returns {`did:mailto:${string}:${string}`} + */ +export function createDidMailtoFromEmail(email) { + const emailParts = email.split('@') + if (emailParts.length !== 2) { + throw new Error(`unexpected email ${email}`) + } + const [local, domain] = emailParts + const did = /** @type {const} */ ( + `did:mailto:${encodeURIComponent(domain)}:${encodeURIComponent(local)}` + ) + return did +} From 3af961ff5184af5cce177e95d9396d93f9e8c348 Mon Sep 17 00:00:00 2001 From: Benjamin Goering <171782+gobengo@users.noreply.github.com> Date: Wed, 15 Mar 2023 13:04:19 -0700 Subject: [PATCH 2/2] relax email type for compat --- packages/access-client/src/agent.js | 2 +- packages/access-client/src/utils/did-mailto.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/access-client/src/agent.js b/packages/access-client/src/agent.js index 228ed009f..a61ef6144 100644 --- a/packages/access-client/src/agent.js +++ b/packages/access-client/src/agent.js @@ -620,7 +620,7 @@ export class Agent { * * It also adds a full space delegation to the service in the voucher/claim invocation to allow for recovery * - * @param {`${string}@${string}`} email + * @param {string} email * @param {object} [opts] * @param {AbortSignal} [opts.signal] * @param {Ucanto.DID<'web'>} [opts.provider] - provider to register - defaults to this.connection.id diff --git a/packages/access-client/src/utils/did-mailto.js b/packages/access-client/src/utils/did-mailto.js index a406bfb44..45480755e 100644 --- a/packages/access-client/src/utils/did-mailto.js +++ b/packages/access-client/src/utils/did-mailto.js @@ -1,5 +1,5 @@ /** - * @param {`${string}@${string}`} email + * @param {string} email * @returns {`did:mailto:${string}:${string}`} */ export function createDidMailtoFromEmail(email) {