blockstack-browser
blockstack-browser copied to clipboard
Proposal: Per-app Gaia hubs in the profile
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 thehubUrl
should be the write URL that corresponds to the read URL in the application's entry in theapps
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
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.
Yeah, that's a good idea for single player apps. I'll update the proposal.
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).
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.
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.
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.
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 it seems this issue should be closed in favor of https://github.com/blockstack/blockstack.js/issues/540?