What does it mean for an application using a distributed database to be federated? Gun already knows how to distribute data efficiently, so federation here is not about data, but about identity.
If you've read this far you know that account codes are the main reference to an account in RSStream, and they need to be stored on the host account. In a federated system there is more than one host account, because there is more than one application or at least different providers of the same application. Each host will treat the others as authoritative for their relevant application data under their host accounts. This means hosts need to share the same data structure for their user data, and account codes need to be unique across the federation.
How do you create unique invites codes amongst multiple hosts? This part turns out to be relatively easy. When creating new codes you just need to query the other hosts for any duplicates. You provide the new codes you've generated and if all hosts return that no duplicates were found, it's fine to add the new codes as available for use in your application. They just need to do the same when they're generating new codes.
async function checkCodes(newCodes) {
const codes = Object.keys(await user.get("accounts").then())
for (let i = 0; i < newCodes.length; i++) {
if (codes.includes(newCodes[i])) return false
if (inviteCodes.has(newCodes[i])) return false
}
return true
}
In the future this should make it possible to log into other applications that share federated hosts. To make this happen each application just needs to listen for account changes in Gun under each of the host accounts and store the account details alongside their own user data in their host account.
It will also make it possible to switch hosts, and there's an added benefit that only the new host needs to know about moving accounts. That means there's no problems for users if their existing host disappears from the federation.
If an account switches hosts and a password reset is later required, the new host can't decrypt the user's email address to help with account recovery. This means there would need to be a process of re-verifying the account. The current host domain is stored on the user's account so that other hosts can check if this is required, or they could point them back to the original host if they haven't been verified yet.
Encrypted user data
Public data in Gun can be read by anyone. They may not have a full copy of the database, but if someone knows your public key they can request everything under your account. This is by design and makes account migration much easier if someone needs to reset their password.
Private data in Gun can also be read by anyone, but the difference is that it's encrypted. It should only be possible for users who you've shared data with to decrypt your messages. RSStream doesn't create much encrypted user data yet, but it does support a mechanism for encrypted data recovery.
The recovery method built into the application is to listen for public key changes on accounts you already follow. This is possible because account codes don't change. If someone you know needs a password reset and creates a new public key, you know that they've lost effectively all the encrypted data on their account. What you're able to do as a contact is re-share your copy of any data you previously encrypted for them. This is done automatically because RSStream knows what data was previously shared and now has their new public key with which to encrypt the same data. If all users do this they will effectively restore all previous conversations.
Here's what the code looks like for that:
const updateAccount = (account, accountCode) => {
if (!account) return
let check = {
pub: account.pub,
alias: account.alias,
name: account.name,
ref: account.ref,
host: account.host,
}
// Check this account against the users list of contacts.
const contacts = user.get("public").get("contacts")
contacts.once(async c => {
if (c) delete c._
let found = false
for (const contactCode of Object.keys(c ?? {})) {
if (contactCode !== accountCode) continue
found = true
const contact = await contacts.get(contactCode).then()
if (contact.pub === check.pub) break
// If the public key has changed for this contact then store their
// new account details and re-share encrypted data with them to
// help restore their account.
contacts.get(contactCode).put(check, ack => {
if (ack.err) console.error(ack.err)
})
gun
.user(contact.pub)
.get("epub")
.once(oldEPub => {
if (!oldEPub) {
console.error("User not found for old public key")
return
}
gun
.user(check.pub)
.get("epub")
.once(epub => {
if (!epub) {
console.error("User not found for new public key")
return
}
const shared = user.get("shared").get(contactCode)
shared.once(async s => {
if (!s) return
delete s._
const oldSecret = await Gun.SEA.secret(oldEPub, user._.sea)
const secret = await Gun.SEA.secret(epub, user._.sea)
for (const key of Object.keys(s)) {
if (!key) continue
const oldEnc = await shared.get(key).then()
let data = await Gun.SEA.decrypt(oldEnc, oldSecret)
let enc = await Gun.SEA.encrypt(data, secret)
shared.get(key).put(enc, ack => {
if (ack.err) console.error(ack.err)
})
}
})
})
})
break
}
// Add the new contact if we referred them.
if (!found && account.ref === code) {
contacts.get(accountCode).put(check, ack => {
if (ack.err) console.error(ack.err)
})
}
})
}
// Listen for account changes to add new contacts, update existing contacts.
gun.user(pub).get("accounts").map().on(updateAccount)
Federation
If you've read this far you know that account codes are the main reference to an account in RSStream, and they need to be stored on the host account. In a federated system there is more than one host account, because there is more than one application or at least different providers of the same application. Each host will treat the others as authoritative for their relevant application data under their host accounts. This means hosts need to share the same data structure for their user data, and account codes need to be unique across the federation.
How do you create unique invites codes amongst multiple hosts? This part turns out to be relatively easy. When creating new codes you just need to query the other hosts for any duplicates. You provide the new codes you've generated and if all hosts return that no duplicates were found, it's fine to add the new codes as available for use in your application. They just need to do the same when they're generating new codes.
In the future this should make it possible to log into other applications that share federated hosts. To make this happen each application just needs to listen for account changes in Gun under each of the host accounts and store the account details alongside their own user data in their host account.
It will also make it possible to switch hosts, and there's an added benefit that only the new host needs to know about moving accounts. That means there's no problems for users if their existing host disappears from the federation.
If an account switches hosts and a password reset is later required, the new host can't decrypt the user's email address to help with account recovery. This means there would need to be a process of re-verifying the account. The current host domain is stored on the user's account so that other hosts can check if this is required, or they could point them back to the original host if they haven't been verified yet.
Encrypted user data
Private data in Gun can also be read by anyone, but the difference is that it's encrypted. It should only be possible for users who you've shared data with to decrypt your messages. RSStream doesn't create much encrypted user data yet, but it does support a mechanism for encrypted data recovery.
The recovery method built into the application is to listen for public key changes on accounts you already follow. This is possible because account codes don't change. If someone you know needs a password reset and creates a new public key, you know that they've lost effectively all the encrypted data on their account. What you're able to do as a contact is re-share your copy of any data you previously encrypted for them. This is done automatically because RSStream knows what data was previously shared and now has their new public key with which to encrypt the same data. If all users do this they will effectively restore all previous conversations.
Here's what the code looks like for that: