🌮 taco-js
Decentralized authentication and authorization for team collaboration, using a secure chain of cryptographic signatures.
Why
👩🏾
👨🏾💻
How
Taco solves the following problems without requiring a server or any other central source of truth:
- Authorization, using a signature chain
- Authentication, using signature challenges
- Invitations, using a Seitan token exchange
- Multi-reader encryption, using lockboxes
- Key revocation and rotation, using an acyclic directed graph of keys and lockboxes
Each user is assigned a set of cryptographic keys for signatures, asymmetric encryption, and symmetric encryption. These are stored in their device's secure storage.
When Alice first creates a team, she writes the first link of a signature chain, containing her public keys for signatures and encryption. All subsequent links must be signed by Alice or by another team member with admin permissions.
Subsequent links in the chain can serve to add new team members, authorize new devices, define roles, and assign people to roles.
When roles are changed, members leave, or devices are lost or replaced, keys are rotated and associated data re-encrypted.
What
Taco exposes a Team
class, which wraps the signature chain and encapsulates the team's members,
devices, and roles. With this object, you can invite new members and manage their
permissions.
This object can also use the public keys embedded in the signature chain, along with the user's own secret keys, to provide encryption and signature verification within the team.
Not included
- Storage Taco uses the secure storage provided by the device to store the user's keys. Taco does not provide storage for the signature chain.
- Networking Taco can communicate with other instances to synchronize everyone's signature chains, but you need to provide a working socket connecting us to a peer.
Examples
yarn add taco-js
Alice creates a new team
import { user, team } from 'taco'
// 👩🏾 Alice
const alice = user.create('alice')
const alicesTeam = team.create({ name: 'Spies Я Us', context: { user: alice } })
Usernames (alice
in the example) identify a person uniquely within the team. You could use existing user IDs or names, or email addresses.
Alice invites Bob
// 👩🏾 Alice
const { secretKey } = alicesTeam.invite('bob')
The invitation key is a single-use secret that only Alice and Bob will ever know. By default, it is
a 16-character string like aj7x d2jr 9c8f zrbs
, and to make it easier to retype if needed, it is
in base-30 format, which omits easily confused characters. It might be typed directly into your
application, or appended to a URL that Bob can click to accept:
Alice has invited you to team XYZ. To accept, click: http://xyz.org/accept/aj7x+d2jr+9c8f+zrbs
Alice will send the invitation to Bob via a side channel she already trusts (phone call, email, SMS, WhatsApp, Telegram, etc).
Bob accepts the invitation
Bob uses the secret invitation key to generate proof that he was invited, without divulging the key.
// 👨🏻🦲 Bob
import { accept } from 'taco'
const proofOfInvitation = accept('aj7x d2jr 9c8f zrbs')
When Bob shows up to join the team, anyone can validate his proof of invitation to admit him to the team - it doesn't have to be an admin.
// 👳🏽♂️ Charlie
team.admit(proofOfInvitation)
const success = team.has('bob') // TRUE
Alice defines a role and adds Bob
// 👩🏾 Alice
team.addRole('managers')
team.addMemberRole('bob', 'managers')
Alice checks Bob's role membership
// 👩🏾 Alice
const isAdmin = team.isAdmin('bob') // TRUE
Alice encrypts a message for managers
// 👩🏾 Alice
const message = 'the condor flies at midnight'
const encrypted = team.encrypt(message, 'managers')
Bob decrypts the message
// 👨🏻🦲 Bob
const decrypted = team.decrypt(encrypted) // 'the condor flies at midnight'
Prior art
TACO stands for Trust After Confirmation Of invitation.