automerge / automerge-repo

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support authentication & authorization

HerbCaudill opened this issue · comments

What follows is a sketch of a proposed approach for supporting authentication & authorization within automerge-repo.

Why

Automerge Repo currently offers no way to authenticate a peer, and very little in the way of access control.

Our current security model is the "Rumplestiltskin rule": If you know a document's ID, you can read that document, and everyone else who knows that ID will accept your changes.

That model is good enough for a surprising number of situations — the ID serves as an unguessable secret "password" for the document — but it has limitations. Without a way to establish a peer's identity, we can't revoke access for an individual peer — say if someone leaves a team, or if a device is lost. And we can't distinguish between read and write permissions, or limit access to specific documents.

An application might implement authentication and authorization in any number of ways, so this should be pluggable — like the existing network and storage adapters.

So initializing a repo might look something like this:

import { SuperCoolAuthProvider } from 'supercool-auth-library'

const authOptions = {
  // ...options specific to this type of authentication
}
const auth = new SuperCoolAuthProvider(options)

const repo = new Repo({ network, storage, auth })

API

An auth provider inherits from this abstract class:

export abstract class AuthProvider extends EventEmitter<AuthProviderEvents> {
  /**
   * Can this peer prove their identity? The provider implementation will
   * use the web socket to communicate with the peer.
   */
  abstract async authenticate(peerId: PeerId, socket?: WebSocket): Promise<true | Error> {}

  // The following methods may be overriden by the provider and would replace
  // the existing `sharePolicy` that we pass to a repo.

  /** Should we tell this peer about the existence of this document? */
  async okToAdvertise(peerId: PeerId, documentId: DocumentId): Promise<boolean> {
    return false
  }

  /** Should we provide this document (and changes to it) to this peer when asked for it by ID? */
  async okToSend(peerId: PeerId, documentId: DocumentId): Promise<boolean> {
    return false
  }

  /** Should we accept changes to this document from this peer? */
  async okToReceive(peerId: PeerId, documentId: DocumentId): Promise<boolean> {
    return false
  }
}

Authentication

The auth provider's authenticate method is invoked when the network adapter emits a
peer-candidate event.

// NetworkSubsystem.ts
networkAdapter.on('peer-candidate', async ({ peerId, channelId, socket }) => {
  const { authenticationResult } = await authProvider.authenticate({ peerId, socket })
  if (!authenticationResult.isValid) {
    const { error } = authenticationResult
    this.emit('peer-authentication-failed', { peerId, channelId, error })
  } else {
    // ...

    this.emit('peer', { peerId, channelId })
  }
})

Authorization

Advertising

TODO

Sending changes

TODO

Receiving changes

TODO

More substantive comments later, but for now: great stuff, and bravo for the excellent term "Rumpelstiltskin rule"!

haha can't take credit for that, it's @pvh 's term

See also localfirst/auth provider for Automerge Repo (pseudo-documentation for a hypothetical provider built around @localfirst/auth).