Skip to content

Commit ab91c61

Browse files
committed
refactor to use userKeyring
1 parent 781c364 commit ab91c61

File tree

4 files changed

+91
-98
lines changed

4 files changed

+91
-98
lines changed

packages/auth/src/connection/Connection.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable object-shorthand */
22
import { EventEmitter } from '@herbcaudill/eventemitter42'
3-
import type { DecryptFnParams, KeysetWithSecrets } from '@localfirst/crdx'
3+
import type { DecryptFnParams } from '@localfirst/crdx'
44
import {
55
generateMessage,
66
headsAreEqual,
@@ -22,9 +22,9 @@ import {
2222
NEITHER_IS_MEMBER,
2323
SERVER_REMOVED,
2424
TIMEOUT,
25+
UNHANDLED,
2526
createErrorMessage,
2627
type ConnectionErrorType,
27-
UNHANDLED,
2828
} from 'connection/errors.js'
2929
import { getDeviceUserFromGraph } from 'connection/getDeviceUserFromGraph.js'
3030
import * as identity from 'connection/identity.js'
@@ -214,27 +214,25 @@ export class Connection extends EventEmitter<ConnectionEvents> {
214214
const { device, invitationSeed } = context
215215
assert(invitationSeed)
216216

217-
let user = context.user
218-
let allUserKeys: KeysetWithSecrets[] | undefined = undefined
219-
220-
// If we're joining as a new device for an existing member, we won't have a user object
221-
// and all generations of existing user keys yet, so we need to get those from the graph.
222-
// We use the invitation seed to generate the starter keys for the new device. We can use
223-
// these to unlock the lockboxes on the team graph that contain our user keys.
224-
if (!user) {
225-
const userWithKeys = getDeviceUserFromGraph({ serializedGraph, teamKeyring, invitationSeed })
226-
user = userWithKeys.user
227-
allUserKeys = userWithKeys.allUserKeys
228-
}
229-
217+
// If we're joining as a new device for an existing member, we won't have a user object or
218+
// user keys yet, so we need to get those from the graph. We use the invitation seed to
219+
// generate the starter keys for the new device. We can use these to unlock the lockboxes
220+
// on the team graph that contain our user keys.
221+
const { user, userKeyring } =
222+
context.user === undefined
223+
? getDeviceUserFromGraph({ serializedGraph, teamKeyring, invitationSeed })
224+
: { user: context.user, userKeyring: undefined }
225+
230226
// When admitting us, our peer added our user to the team graph. We've been given the
231227
// serialized and encrypted graph, and the team keyring. We can now decrypt the graph and
232228
// reconstruct the team in order to join it.
233229
const team = new Team({ source: serializedGraph, context: { user, device }, teamKeyring })
234230

235231
// We join the team, which adds our device to the team graph.
236-
team.join(teamKeyring, allUserKeys)
232+
team.join(teamKeyring, userKeyring)
233+
237234
this.emit('joined', { team, user, teamKeyring })
235+
238236
return { user, team }
239237
}),
240238

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Keyring, KeysetWithSecrets, UserWithSecrets } from '@localfirst/crdx'
1+
import { getLatestGeneration, type Keyring, type UserWithSecrets } from '@localfirst/crdx'
22
import { assert } from '@localfirst/shared'
33
import { generateProof } from 'invitation/generateProof.js'
44
import { generateStarterKeys } from 'invitation/generateStarterKeys.js'
@@ -11,9 +11,12 @@ const { USER } = KeyType
1111
/**
1212
* If we're joining as a new device for an existing member, we don't have a user object yet, so we
1313
* need to get those from the graph. We use the invitation seed to generate the starter keys for the
14-
* new device. We can use these to unlock the lockboxes on the team graph that contain our user keys.
15-
* Because we need all user key generations to decrypt the team graph, we return all of them along
16-
* with the user object that contains only the latest keys generation.
14+
* new device. We can use these to unlock the lockboxes on the team graph that contain our user
15+
* keys.
16+
*
17+
* Because we need all previous user keys to decrypt the team graph, we return a keyring containing
18+
* the full history of user keys, along with a user object containing just the latest generation of
19+
* keys.
1720
*/
1821
export const getDeviceUserFromGraph = ({
1922
serializedGraph,
@@ -24,8 +27,8 @@ export const getDeviceUserFromGraph = ({
2427
teamKeyring: Keyring
2528
invitationSeed: string
2629
}): {
27-
user: UserWithSecrets,
28-
allUserKeys: KeysetWithSecrets[]
30+
user: UserWithSecrets
31+
userKeyring: Keyring
2932
} => {
3033
const starterKeys = generateStarterKeys(invitationSeed)
3134
const invitationId = generateProof(invitationSeed).id
@@ -37,15 +40,9 @@ export const getDeviceUserFromGraph = ({
3740
const { userName } = select.member(state, userId)
3841
assert(userName) // this user must exist in the team graph
3942

40-
const allUserKeys = select.keyMap(state, starterKeys)[USER]?.[userId]
41-
const user = {
42-
userName,
43-
userId,
44-
keys: allUserKeys.at(-1)
45-
}
43+
const userKeyring = select.keyring(state, { type: USER, name: userId }, starterKeys)
44+
const keys = getLatestGeneration(userKeyring)
45+
const user = { userName, userId, keys }
4646

47-
return {
48-
user,
49-
allUserKeys
50-
}
47+
return { user, userKeyring }
5148
}

packages/auth/src/team/Team.ts

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
UserWithSecrets,
1313
} from '@localfirst/crdx'
1414
import {
15+
createKeyring,
1516
createKeyset,
1617
createStore,
1718
getLatestGeneration,
@@ -137,11 +138,11 @@ export class Team extends EventEmitter<TeamEvents> {
137138
}
138139

139140
this.state = this.store.getState()
140-
this.checkForNewUserKeysGeneration()
141+
this.updateUserKeys()
141142

142143
// Wire up event listeners
143144
this.on('updated', () => {
144-
this.checkForNewUserKeysGeneration()
145+
this.updateUserKeys()
145146

146147
// If we're admin, check for pending key rotations
147148
this.checkForPendingKeyRotations()
@@ -505,21 +506,17 @@ export class Team extends EventEmitter<TeamEvents> {
505506
const invitation = invitations.create({ seed, expiration, maxUses, userId: this.userId })
506507

507508
// In order for the invited device to be able to access the user's keys, we put the user keys in
508-
// lockboxes that can be opened by an ephemeral keyset generated from the secret invitation
509-
// seed.
509+
// lockboxes that can be opened by an ephemeral keyset generated from the secret invitation seed.
510510
const starterKeys = invitations.generateStarterKeys(seed)
511-
const lockboxesUserKeysForDeviceStarterKeys = this.allUserKeys()
512-
.map(keys => lockbox.create(keys, starterKeys))
511+
const allUserKeys = Object.values(this.userKeyring())
512+
const lockboxes = allUserKeys.map(keys => lockbox.create(keys, starterKeys))
513513

514514
const { id } = invitation
515515

516516
// Post invitation to graph
517517
this.dispatch({
518518
type: 'INVITE_DEVICE',
519-
payload: {
520-
invitation,
521-
lockboxes: lockboxesUserKeysForDeviceStarterKeys,
522-
},
519+
payload: { invitation, lockboxes },
523520
})
524521

525522
// Return the secret invitation seed (to pass on to invitee) and the invitation id (which could be used to revoke later)
@@ -570,8 +567,8 @@ export class Team extends EventEmitter<TeamEvents> {
570567
const { id } = proof
571568

572569
// we know the team keys, so we can put them in lockboxes for the new member now (even if we're not an admin)
573-
const lockboxesTeamKeysForMember = Object.values(this.teamKeyring())
574-
.map(keys => lockbox.create(keys, memberKeys))
570+
const allTeamKeys = Object.values(this.teamKeyring())
571+
const lockboxes = allTeamKeys.map(keys => lockbox.create(keys, memberKeys))
575572

576573
// Post admission to the graph
577574
this.dispatch({
@@ -580,7 +577,7 @@ export class Team extends EventEmitter<TeamEvents> {
580577
id,
581578
userName,
582579
memberKeys: redactKeys(memberKeys),
583-
lockboxes: lockboxesTeamKeysForMember,
580+
lockboxes,
584581
},
585582
})
586583
}
@@ -608,20 +605,21 @@ export class Team extends EventEmitter<TeamEvents> {
608605
}
609606

610607
/** Once the new member has received the graph and can instantiate the team, they call this to add their device. */
611-
public join = (teamKeyring: Keyring, allUserKeys = [this.context.user.keys]) => {
608+
public join = (teamKeyring: Keyring, userKeyring = createKeyring(this.context.user.keys)) => {
612609
assert(!this.isServer, "Can't join as member on server")
613610

614611
const { device } = this.context
615612
const teamKeys = getLatestGeneration(teamKeyring)
616613

617-
const lockboxesUserKeysForDevice = allUserKeys.map(keys => lockbox.create(keys, device.keys))
614+
// Create a lockbox for each generation of user keys
615+
const lockboxes = Object.values(userKeyring).map(keys => lockbox.create(keys, device.keys))
618616

619617
this.dispatch(
620618
{
621619
type: 'ADD_DEVICE',
622620
payload: {
623621
device: redactDevice(device),
624-
lockboxes: lockboxesUserKeysForDevice,
622+
lockboxes,
625623
},
626624
},
627625
teamKeys
@@ -767,8 +765,8 @@ export class Team extends EventEmitter<TeamEvents> {
767765
public keys = (scope: KeyMetadata | KeyScope) =>
768766
select.keys(this.state, this.context.device.keys, scope)
769767

770-
public allUserKeys = (userId = this.userId) =>
771-
select.keyMap(this.state, this.context.device.keys)[USER]?.[userId] || []
768+
public userKeyring = (userId = this.userId) =>
769+
select.keyring(this.state, { type: USER, name: userId }, this.context.device.keys)
772770

773771
/** Returns the keys for the given role. */
774772
public roleKeys = (roleName: string, generation?: number) =>
@@ -811,9 +809,9 @@ export class Team extends EventEmitter<TeamEvents> {
811809
if (isForServer) device.keys = newKeys // (a server plays the role of both a user and a device)
812810
}
813811

814-
private checkForNewUserKeysGeneration() {
812+
private updateUserKeys() {
815813
const { user } = this.context
816-
const latestUserKeys = this.allUserKeys().at(-1)
814+
const latestUserKeys = getLatestGeneration(this.userKeyring())
817815

818816
if (latestUserKeys && user.keys.generation < latestUserKeys.generation) {
819817
user.keys = latestUserKeys

0 commit comments

Comments
 (0)