blockstack-browser icon indicating copy to clipboard operation
blockstack-browser copied to clipboard

Proposal: Per-app Gaia hubs in the profile

Open jcnelson opened this issue 6 years ago • 8 comments

TL;DR

Make it so the user's profile object points to both read and write URLs for Gaia hubs on a per-app basis, and pass the write URL as hubUrl in the authResponse token.

Background

Blockstack's promise to users it that they own their own data. Part of that promise means that a user's data gets hosted wherever the user wants. Arguably, this also means the user can do so at the granularity of specific applications, since the nature of the data determines how it is stored. This entails making it so users have the ability to select which Gaia hub an application will use to store its data.

Design and Implementation

I think the shortest-path-to-success is somewhat straightforward: Extend the apps object in the user's profile to contain both Gaia read and write URLs. Right now, it only contains the read URL (and even then, it's not honored---see https://github.com/blockstack/blockstack-browser/issues/1488).

When the user completes the sign in (in app/js/auth/index.js, at completeAuthResponse()), the authResponse JWT returned to the application should contain a hubUrl value that corresponds to the user's chosen Gaia hub for that application.

  • If the user is signing in for the first time, then this URL is the Gaia URL from the settings page (i.e. this.props.api.gaiaHubConfig.server)
  • If the user is signing in for the second or later time (i.e. an entry in the apps object exists for this application), then the hubUrl should be the write URL that corresponds to the read URL in the application's entry in the apps object in the profile.

What is still needed is a way to store the write URL in the profile in a backwards compatible way. I recommend the following scheme. Instead of this:

"apps": {
   "https://app.origin": "https://user.gaia.hub/hub/1APPADDRESSxxxxxx/"
}

We have this instead:

"apps": {
   "https://app.origin": "https://user.gaia.hub/hub/1APPADDRESSxxxxxxx/"
}
"hubUrls": {
   "https://app.origin": "XXXXXEncryptedWriteURLXXXXXX"
}

The hubUrls object encodes the encrypted write URL for an application. It should be encrypted with the app public key because knowledge of the write endpoint is only pertinent to the writer. We can use the usual encryptECIES method to generate this ciphertext, and decryptECIES to decrypt it.

If the hubUrls object exists in the profile and has an entry for the application, the completeAuthResponse() method should use that instead of the default this.props.api.gaiaHubConfig.server URL). Otherwise, it uses the default this.props.api.gaiaHubConfig.server URL.

Backwards Compatibility

When the user signs into an app for the first time, then an entry is added to the hubUrls in addition to the apps objects.

If the user signs in and they don't have a hubUrls entry, they use the this.props.api.gaiaHubConfig.server value as before.

EDIT: For single-player apps, we do not want to expose either the read or write URLs. To achieve this, when a user signs into a single-player app for the first time, the Browser would insert the following:

"hubUrls": {
   "XXXXXEncryptedAppOriginXXXXX": "XXXXXEncryptedGaiaHubUrlXXXXX"
}

Both the origin and the write URL will be encrypted. No read URL will be inserted into the apps object (since this information can be obtained from the write URL).

Execution

I'm happy to take the lead on all of the above. However, I think someone more versed in non-CLI user experiences should take the lead on adding a GUI for managing your hubUrls.

Thoughts? @kantai @yknl @larrysalibra

jcnelson avatar Jun 28 '18 03:06 jcnelson

I like this proposal. Does this mean that single player apps will now also be published in the user profile? Maybe we should encrypt the app origin as well for single player apps.

yknl avatar Jun 28 '18 04:06 yknl

Yeah, that's a good idea for single player apps. I'll update the proposal.

jcnelson avatar Jun 28 '18 15:06 jcnelson

I like this proposal and wouldn't object to going forward with it, but there's another (very similar) option which might be a little less error prone in the event of a synchronization issue between those two structure (apps and hubUrls):

Basically, you still use apps and hubUrls, but hubUrls is a list of (encrypted) hubs, rather than a dictionary. Then, when logging in (or can be cached), the browser checks to see which of the hubs is capable of writing to the appropriate readURL and then uses that. It's functionally pretty similar, but it doesn't have a parallel structure that could get out of sync (let's say I'm manually editing my profile, and forget/don't know what the hubUrls entries are for).

kantai avatar Jun 28 '18 15:06 kantai

Basically, you still use apps and hubUrls, but hubUrls is a list of (encrypted) hubs, rather than a dictionary. Then, when logging in (or can be cached), the browser checks to see which of the hubs is capable of writing to the appropriate readURL and then uses that. It's functionally pretty similar, but it doesn't have a parallel structure that could get out of sync (let's say I'm manually editing my profile, and forget/don't know what the hubUrls entries are for).

As long as there is a well-defined approach to pairing Gaia hub to its read URL, then I'm fine with this solution. I'm assuming the steps are (1) get the /hub_info route for each Gaia hub in hubUrls, and (2) match the read_url_prefix. However, I still have questions pertaining to how we handle the following plausible scenarios:

  • Multiple Gaia hubs correspond to a read URL.
    • Plausible example: one Gaia hub attempts to spoof another
  • A Gaia hub may correspond to no read URLs.
    • Plausible example: a Gaia hub's read URL changes when it gets upgraded.
  • A Gaia hub corresponded to one read URL at time T, but then corresponds to another read URL at time T+1.
    • Plausible example: a Gaia hub gets upgraded or misconfigured, and collides with another Gaia hub's read URL (possibly maliciously, no less)

Having a hubUrls list gives us a way to unequivocally bind an application to a (hub, read URL) pair, so that if the hub stops corresponding to its read URL, the browser can both update the read URL and prompt/inform the user that it needs to do so on the next sign-in.

jcnelson avatar Jun 29 '18 04:06 jcnelson

Having a hubUrls list gives us a way to unequivocally bind an application to a (hub, read URL) pair, so that if the hub stops corresponding to its read URL, the browser can both update the read URL and prompt/inform the user that it needs to do so on the next sign-in.

Cool, I'm convinced.

kantai avatar Jun 29 '18 12:06 kantai

As a stepping stone to implementing this, I think it's fine for now if the user can simply change their Gaia hub in the settings page. As long as the user's Gaia hub URL in the apps field of their profile gets updated and uploaded to their zone file's Gaia hub on their next sign-in, then users can select where their application data lives and multi-reader storage can continue to work. I'll be able to give a proof-of-concept demo in the CLI shortly.

jcnelson avatar Jul 15 '18 19:07 jcnelson

I have this working in the CLI authenticator. You pass your desired hubUrl on the CLI, and your profile gets updated with the read URL of that hub whenever you sign into an app.

jcnelson avatar Aug 29 '18 14:08 jcnelson

@jcnelson it seems this issue should be closed in favor of https://github.com/blockstack/blockstack.js/issues/540?

markmhendrickson avatar Mar 19 '19 16:03 markmhendrickson