Skip to content

Commit

Permalink
2626 sigchain persistence (#2659)
Browse files Browse the repository at this point in the history
* update commit pointer to new head of main

* port auth module from demo

* rm `.js` from imports

* update loggers with nested paths

* rename services closer to our pattern

* add tests

* update changelog

* update package.lock

* update changelog

* update submodule pointer

* Update packages CHANGELOG.md

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* fix android build

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* update macos runner

* update filename

* update release asset name as well

* fix mounting

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* specify macos-15

* try macos-13

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* update provisioning profile

* fix identity saving

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* fix backend tor test

* Publish

 - @quiet/[email protected]
 - @quiet/[email protected]

* Update packages CHANGELOG.md

* Convert SigChainManager to NestJS service and integrate with ConnectionsManager

* add sigchain saving and loading test

* Add logging for setting and getting sigchains in LocalDbService

* update changelog

* Merge branch 'develop' into 2626-sigchain-persistence

* restore unaltered provisioning profile

* fix merge conflict inclusion

* undo meaningless package json changes

* inject localdbservice into sigchainservice

* * Use prefix key instead of nesting when saving sigchains
* Import LocalDBService into SigChainService
* Improve async handling

* add deletion test

* fix error handling
  • Loading branch information
adrastaea authored Dec 2, 2024
1 parent 46c0716 commit 3c94032
Show file tree
Hide file tree
Showing 19 changed files with 506 additions and 275 deletions.
8 changes: 3 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Changelog

## [Unreleased]
## [unreleased]

### New features

* Adds basic sigchain functions ([#2625](https://github.com/TryQuiet/quiet/issues/2625))
* Instantiates signature chain when creating communities and reloading application ([#2626](https://github.com/TryQuiet/quiet/issues/2626))
* Added in LFA-ready invite links ([#2627](https://github.com/TryQuiet/quiet/issues/2627))

## [2.3.2]
Expand All @@ -13,10 +15,6 @@
* Moved some responsibilities of identity management to the backend ([#2602](https://github.com/TryQuiet/quiet/issues/2602))
* Added auth submodule in preparation for future encyrption work ([#2623](https://github.com/TryQuiet/quiet/issues/2623))

### New features

* Adds basic sigchain functions ([#2649](https://github.com/TryQuiet/quiet/pull/2649))

### Fixes

* Fixed memory leak associated with autoUpdater ([#2606](https://github.com/TryQuiet/quiet/issues/2606))
Expand Down
11 changes: 1 addition & 10 deletions packages/backend/src/nest/auth/services/crypto/crypto.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,8 @@ import * as bs58 from 'bs58'
import { EncryptedAndSignedPayload, EncryptedPayload, EncryptionScope, EncryptionScopeType } from './types'
import { ChainServiceBase } from '../chainServiceBase'
import { SigChain } from '../../sigchain'
import {
asymmetric,
Base58,
Keyset,
KeysetWithSecrets,
LocalUserContext,
Member,
SignedEnvelope,
} from '@localfirst/auth'
import { asymmetric, Base58, Keyset, LocalUserContext, Member, SignedEnvelope } from '@localfirst/auth'
import { DEFAULT_SEARCH_OPTIONS, MemberSearchOptions } from '../members/types'
import { ChannelService } from '../roles/channel.service'
import { createLogger } from '../../../common/logger'

const logger = createLogger('auth:cryptoService')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { jest } from '@jest/globals'
import { SigChain } from '../../sigchain'
import { SigChainManager } from '../../sigchainManager'
import { SigChainService } from '../../sigchain.service'
import { createLogger } from '../../../common/logger'
import { device, InviteResult, LocalUserContext } from '@localfirst/auth'
import { RoleName } from '..//roles/roles'
Expand All @@ -12,17 +12,15 @@ const logger = createLogger('auth:services:invite.spec')

describe('invites', () => {
let adminSigChain: SigChain
let adminContext: LocalUserContext
let newMemberSigChain: SigChain
let newMemberContext: LocalUserContext
it('should initialize a new sigchain and be admin', () => {
;({ sigChain: adminSigChain, context: adminContext } = SigChain.create('test', 'user'))
adminSigChain = SigChain.create('test', 'user')
expect(adminSigChain).toBeDefined()
expect(adminContext).toBeDefined()
expect(adminSigChain.context).toBeDefined()
expect(adminSigChain.team.teamName).toBe('test')
expect(adminContext.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.context.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('admin should generate an invite and it be added to team graph', () => {
const newInvite = adminSigChain.invites.createUserInvite()
Expand All @@ -38,26 +36,26 @@ describe('invites', () => {
expect(inviteProof).toBeDefined()
expect(adminSigChain.invites.validateProof(inviteProof)).toBe(true)
expect(prospectiveMember).toBeDefined()
;({ sigChain: newMemberSigChain, context: newMemberContext } = SigChain.join(
newMemberSigChain = SigChain.join(
prospectiveMember.context,
adminSigChain.team.save(),
adminSigChain.team.teamKeyring()
))
)
expect(newMemberSigChain).toBeDefined()
expect(newMemberContext).toBeDefined()
expect(newMemberContext.user.userName).toBe('user2')
expect(newMemberContext.user.userId).not.toBe(adminContext.user.userId)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.MEMBER)).toBe(false)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.ADMIN)).toBe(false)
expect(newMemberSigChain.context).toBeDefined()
expect(newMemberSigChain.context.user.userName).toBe('user2')
expect(newMemberSigChain.context.user.userId).not.toBe(adminSigChain.context.user.userId)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.MEMBER)).toBe(false)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.ADMIN)).toBe(false)
expect(
adminSigChain.invites.admitMemberFromInvite(
inviteProof,
newMemberContext.user.userName,
newMemberContext.user.userId,
newMemberContext.user.keys
newMemberSigChain.context.user.userName,
newMemberSigChain.context.user.userId,
newMemberSigChain.context.user.keys
)
).toBeDefined()
expect(adminSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('admin should be able to revoke an invite', () => {
const inviteToRevoke = adminSigChain.invites.createUserInvite()
Expand Down Expand Up @@ -88,7 +86,7 @@ describe('invites', () => {
}).toThrowError()
})
it('should invite device', () => {
const newDevice = DeviceService.generateDeviceForUser(adminContext.user.userId)
const newDevice = DeviceService.generateDeviceForUser(adminSigChain.context.user.userId)
const deviceInvite = adminSigChain.invites.createDeviceInvite()
const inviteProof = InviteService.generateProof(deviceInvite.seed)
expect(inviteProof).toBeDefined()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@ const logger = createLogger('auth:services:device.spec')

describe('invites', () => {
let adminSigChain: SigChain
let adminContext: LocalUserContext
let newDevice: DeviceWithSecrets

it('should initialize a new sigchain and be admin', () => {
;({ sigChain: adminSigChain, context: adminContext } = SigChain.create('test', 'user'))
adminSigChain = SigChain.create('test', 'user')
expect(adminSigChain).toBeDefined()
expect(adminContext).toBeDefined()
expect(adminSigChain.context).toBeDefined()
expect(adminSigChain.team.teamName).toBe('test')
expect(adminContext.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.context.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('sigchain should contain admin device', () => {
const adminDeviceName = DeviceService.determineDeviceName()
adminSigChain.team.hasDevice(adminContext.device.deviceId)
adminSigChain.team.hasDevice(adminSigChain.context.device.deviceId)
})
it('should generate a new device', () => {
newDevice = DeviceService.generateDeviceForUser(adminContext.user.userId)
newDevice = DeviceService.generateDeviceForUser(adminSigChain.context.user.userId)
expect(newDevice).toBeDefined()
})
it('should redactDevice', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { jest } from '@jest/globals'
import { SigChain } from '../../sigchain'
import { SigChainManager } from '../../sigchainManager'
import { SigChainService } from '../../sigchain.service'
import { createLogger } from '../../../common/logger'
import { device, InviteResult, LocalUserContext } from '@localfirst/auth'
import { RoleName } from '..//roles/roles'
Expand All @@ -11,15 +11,15 @@ const logger = createLogger('auth:services:invite.spec')

describe('invites', () => {
let adminSigChain: SigChain
let adminContext: LocalUserContext

it('should initialize a new sigchain and be admin', () => {
;({ sigChain: adminSigChain, context: adminContext } = SigChain.create('test', 'user'))
adminSigChain = SigChain.create('test', 'user')
expect(adminSigChain).toBeDefined()
expect(adminContext).toBeDefined()
expect(adminSigChain.context).toBeDefined()
expect(adminSigChain.team.teamName).toBe('test')
expect(adminContext.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.context.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('should get keys', () => {
const keys = adminSigChain.users.getKeys()
Expand All @@ -30,17 +30,17 @@ describe('invites', () => {
expect(users).toBeDefined()
})
it('get admin member by id', () => {
const users = adminSigChain.users.getUsersById([adminContext.user.userId])
expect(users.map(u => u.userId)).toContain(adminContext.user.userId)
const users = adminSigChain.users.getUsersById([adminSigChain.context.user.userId])
expect(users.map(u => u.userId)).toContain(adminSigChain.context.user.userId)
})
it('get admin member by name', () => {
const user = adminSigChain.users.getUserByName(adminContext.user.userName)
expect(user!.userName).toEqual(adminContext.user.userName)
const user = adminSigChain.users.getUserByName(adminSigChain.context.user.userName)
expect(user!.userName).toEqual(adminSigChain.context.user.userName)
})
it('should redact user', () => {
const redactedUser = UserService.redactUser(adminContext.user)
const redactedUser = UserService.redactUser(adminSigChain.context.user)
expect(redactedUser).toBeDefined()
expect(redactedUser.userId).toBe(adminContext.user.userId)
expect(redactedUser.userName).toBe(adminContext.user.userName)
expect(redactedUser.userId).toBe(adminSigChain.context.user.userId)
expect(redactedUser.userName).toBe(adminSigChain.context.user.userName)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,19 @@ const privateChannelName = 'testChannel'

describe('invites', () => {
let adminSigChain: SigChain
let adminContext: LocalUserContext
let newMemberSigChain: SigChain
let newMemberContext: LocalUserContext

it('should initialize a new sigchain and be admin', () => {
;({ sigChain: adminSigChain, context: adminContext } = SigChain.create('test', 'user'))
adminSigChain = SigChain.create('test', 'user')
expect(adminSigChain).toBeDefined()
expect(adminContext).toBeDefined()
expect(adminSigChain.context).toBeDefined()
expect(adminSigChain.team.teamName).toBe('test')
expect(adminContext.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.context.user.userName).toBe('user')
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.ADMIN)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(adminSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('should create a private channel', () => {
const privateChannel = adminSigChain.channels.createPrivateChannel(privateChannelName, adminContext)
const privateChannel = adminSigChain.channels.createPrivateChannel(privateChannelName, adminSigChain.context)
expect(privateChannel).toBeDefined()
})
it('admin should generate an invite seed and admit a new user from it', () => {
Expand All @@ -36,49 +34,56 @@ describe('invites', () => {
expect(inviteProof).toBeDefined()
expect(adminSigChain.invites.validateProof(inviteProof)).toBe(true)
expect(prospectiveMember).toBeDefined()
;({ sigChain: newMemberSigChain, context: newMemberContext } = SigChain.join(
newMemberSigChain = SigChain.join(
prospectiveMember.context,
adminSigChain.team.save(),
adminSigChain.team.teamKeyring()
))
)
expect(newMemberSigChain).toBeDefined()
expect(newMemberContext).toBeDefined()
expect(newMemberContext.user.userName).toBe('user2')
expect(newMemberContext.user.userId).not.toBe(adminContext.user.userId)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.MEMBER)).toBe(false)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.ADMIN)).toBe(false)
expect(newMemberSigChain.context).toBeDefined()
expect(newMemberSigChain.context.user.userName).toBe('user2')
expect(newMemberSigChain.context.user.userId).not.toBe(adminSigChain.context.user.userId)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.MEMBER)).toBe(false)
expect(newMemberSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.ADMIN)).toBe(false)
expect(
adminSigChain.invites.admitMemberFromInvite(
inviteProof,
newMemberContext.user.userName,
newMemberContext.user.userId,
newMemberContext.user.keys
newMemberSigChain.context.user.userName,
newMemberSigChain.context.user.userId,
newMemberSigChain.context.user.keys
)
).toBeDefined()
expect(adminSigChain.roles.amIMemberOfRole(newMemberContext, RoleName.MEMBER)).toBe(true)
expect(adminSigChain.roles.amIMemberOfRole(newMemberSigChain.context, RoleName.MEMBER)).toBe(true)
})
it('should add the new member to the private channel', () => {
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminContext)
adminSigChain.channels.addMemberToPrivateChannel(newMemberContext.user.userId, privateChannel.channelName)
expect(adminSigChain.channels.memberInChannel(newMemberContext.user.userId, privateChannel.channelName)).toBe(true)
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminSigChain.context)
adminSigChain.channels.addMemberToPrivateChannel(newMemberSigChain.context.user.userId, privateChannel.channelName)
expect(
adminSigChain.channels.memberInChannel(newMemberSigChain.context.user.userId, privateChannel.channelName)
).toBe(true)
})
it('should remove the new member from the private channel', () => {
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminContext)
adminSigChain.channels.revokePrivateChannelMembership(newMemberContext.user.userId, privateChannel.channelName)
expect(adminSigChain.channels.getChannels(newMemberContext, true).length).toBe(0)
expect(adminSigChain.channels.memberInChannel(newMemberContext.user.userId, privateChannel.channelName)).toBe(false)
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminSigChain.context)
adminSigChain.channels.revokePrivateChannelMembership(
newMemberSigChain.context.user.userId,
privateChannel.channelName
)
expect(adminSigChain.channels.getChannels(newMemberSigChain.context, true).length).toBe(0)
expect(
adminSigChain.channels.memberInChannel(newMemberSigChain.context.user.userId, privateChannel.channelName)
).toBe(false)
})
it('should delete channel', () => {
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminContext)
const privateChannel = adminSigChain.channels.getChannel(privateChannelName, adminSigChain.context)
adminSigChain.channels.deletePrivateChannel(privateChannel.channelName)
expect(adminSigChain.channels.getChannels(adminContext).length).toBe(0)
expect(adminSigChain.channels.getChannels(adminSigChain.context).length).toBe(0)
})
it('should create new channel and then leave it', () => {
const channel = adminSigChain.channels.createPrivateChannel(privateChannelName, adminContext)
const channel = adminSigChain.channels.createPrivateChannel(privateChannelName, adminSigChain.context)
expect(channel).toBeDefined()
adminSigChain.channels.leaveChannel(channel.channelName, adminContext)
expect(adminSigChain.channels.memberInChannel(adminContext.user.userId, channel.channelName)).toBe(false)
expect(adminSigChain.channels.getChannels(adminContext).length).toBe(1)
expect(adminSigChain.channels.getChannels(adminContext, true).length).toBe(0)
adminSigChain.channels.leaveChannel(channel.channelName, adminSigChain.context)
expect(adminSigChain.channels.memberInChannel(adminSigChain.context.user.userId, channel.channelName)).toBe(false)
expect(adminSigChain.channels.getChannels(adminSigChain.context).length).toBe(1)
expect(adminSigChain.channels.getChannels(adminSigChain.context, true).length).toBe(0)
})
})
10 changes: 10 additions & 0 deletions packages/backend/src/nest/auth/sigchain.service.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common'
import { SigChainService } from './sigchain.service'
import { LocalDbModule } from '../local-db/local-db.module'

@Module({
providers: [SigChainService],
exports: [SigChainService],
imports: [LocalDbModule],
})
export class SigChainModule {}
Loading

0 comments on commit 3c94032

Please sign in to comment.