Skip to content

Commit

Permalink
Merge pull request #1758 from okTurtles/e2e-protocol-ricardo
Browse files Browse the repository at this point in the history
E2e protocol ricardo
  • Loading branch information
taoeffect authored Oct 18, 2023
2 parents 7cc4400 + 70e3c59 commit 58e6c31
Show file tree
Hide file tree
Showing 19 changed files with 435 additions and 277 deletions.
10 changes: 4 additions & 6 deletions frontend/controller/actions/chatroom.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export default (sbp('sbp/selectors/register', {

// Before creating the contract, put all keys into transient store
sbp('chelonia/storeSecretKeys',
[cekOpts._rawKey, cskOpts._rawKey].map(key => ({ key, transient: true }))
// $FlowFixMe[incompatible-use]
() => [cekOpts._rawKey, cskOpts._rawKey].map(key => ({ key, transient: true }))
)

const userCSKid = findKeyIdByName(rootState[userID], 'csk')
Expand Down Expand Up @@ -173,15 +174,12 @@ export default (sbp('sbp/selectors/register', {
contractName: 'gi.contracts/chatroom'
})

const contractID = chatroom.contractID()

// After the contract has been created, store pesistent keys
sbp('chelonia/storeSecretKeys',
[cekOpts._rawKey, cskOpts._rawKey].map(key => ({ key }))
// $FlowFixMe[incompatible-use]
() => [cekOpts._rawKey, cskOpts._rawKey].map(key => ({ key }))
)

await sbp('chelonia/contract/sync', contractID)

return chatroom
} catch (e) {
console.error('gi.actions/chatroom/register failed!', e)
Expand Down
5 changes: 2 additions & 3 deletions frontend/controller/actions/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export default (sbp('sbp/selectors/register', {

// Before creating the contract, put all keys into transient store
sbp('chelonia/storeSecretKeys',
[CEK, CSK].map(key => ({ key, transient: true }))
() => [CEK, CSK].map(key => ({ key, transient: true }))
)

const userCSKid = findKeyIdByName(rootState[userID], 'csk')
Expand Down Expand Up @@ -234,10 +234,9 @@ export default (sbp('sbp/selectors/register', {

// After the contract has been created, store pesistent keys
sbp('chelonia/storeSecretKeys',
[CEK, CSK, inviteKey].map(key => ({ key }))
() => [CEK, CSK, inviteKey].map(key => ({ key }))
)

await sbp('chelonia/contract/sync', contractID)
saveLoginState('creating', contractID)

// Save the initial invite
Expand Down
11 changes: 5 additions & 6 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export default (sbp('sbp/selectors/register', {

// Before creating the contract, put all keys into transient store
sbp('chelonia/storeSecretKeys',
[IPK, IEK, CEK, CSK, PEK].map(key => ({ key, transient: true }))
() => [IPK, IEK, CEK, CSK, PEK].map(key => ({ key, transient: true }))
)

let userID
Expand Down Expand Up @@ -231,12 +231,10 @@ export default (sbp('sbp/selectors/register', {

// After the contract has been created, store pesistent keys
sbp('chelonia/storeSecretKeys',
[CEK, CSK, PEK].map(key => ({ key }))
() => [CEK, CSK, PEK].map(key => ({ key }))
)
// And remove transient keys, which require a user password
sbp('chelonia/clearTransientSecretKeys', [IEKid, IPKid])

await sbp('chelonia/contract/sync', userID)
} catch (e) {
console.error('gi.actions/identity/create failed!', e)
throw new GIErrorUIRuntimeError(L('Failed to create user identity: {reportError}', LError(e)))
Expand Down Expand Up @@ -408,7 +406,7 @@ export default (sbp('sbp/selectors/register', {
}

sbp('state/vuex/commit', 'login', loginAttributes)
await sbp('chelonia/storeSecretKeys', transientSecretKeys)
await sbp('chelonia/storeSecretKeys', () => transientSecretKeys)
// IMPORTANT: we avoid using 'await' on the syncs so that Vue.js can proceed
// loading the website instead of stalling out.
// See the TODO note in startApp (main.js) for why this is not awaited
Expand All @@ -418,7 +416,7 @@ export default (sbp('sbp/selectors/register', {
// similarly, since removeMember may have triggered saveOurLoginState asynchronously,
// we must re-sync our identity contract again to ensure we don't rejoin a group we
// were just kicked out of
await sbp('chelonia/contract/sync', identityContractID)
await sbp('chelonia/contract/sync', identityContractID, { force: true })
await sbp('gi.actions/identity/updateLoginStateUponLogin')
await sbp('gi.actions/identity/saveOurLoginState') // will only update it if it's different

Expand Down Expand Up @@ -516,6 +514,7 @@ export default (sbp('sbp/selectors/register', {
const rootState = sbp('state/vuex/state')
const state = rootState[contractID]

// TODO: Also share PEK with DMs
return Promise.all((state.loginState?.groupIds || []).filter(groupID => !!rootState.contracts[groupID]).map(groupID => {
const groupState = rootState[groupID]
const CEKid = findKeyIdByName(rootState[groupID], 'cek')
Expand Down
1 change: 1 addition & 0 deletions frontend/controller/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ sbp('sbp/selectors/register', {

const contractState = await sbp('chelonia/latestContractState', subjectContractID)

await sbp('chelonia/contract/sync', contractID)
const state = await sbp('chelonia/latestContractState', contractID)

const CEKid = findKeyIdByName(state, 'cek')
Expand Down
2 changes: 1 addition & 1 deletion frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ async function startApp () {
'chelonia/db/get',
'chelonia/db/set',
'chelonia/haveSecretKey',
'chelonia/storeSecretKeys',
'chelonia/private/enqueuePostSyncOps',
'state/vuex/state',
'state/vuex/getters',
'state/vuex/settings',
Expand Down
14 changes: 7 additions & 7 deletions frontend/model/captureLogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ const originalConsole = console
// These are initialized in `captureLogsStart()`.
let appLogsFilter: string[] = []
let logger: Object = null
let username: string = ''
let identityContractID: string = ''

// A default storage backend using `localStorage`.
const getItem = (key: string): ?string => localStorage.getItem(`giConsole/${username}/${key}`)
const removeItem = (key: string): void => localStorage.removeItem(`giConsole/${username}/${key}`)
const getItem = (key: string): ?string => localStorage.getItem(`giConsole/${identityContractID}/${key}`)
const removeItem = (key: string): void => localStorage.removeItem(`giConsole/${identityContractID}/${key}`)
const setItem = (key: string, value: any): void => {
localStorage.setItem(`giConsole/${username}/${key}`, typeof value === 'string' ? value : JSON.stringify(value))
localStorage.setItem(`giConsole/${identityContractID}/${key}`, typeof value === 'string' ? value : JSON.stringify(value))
}

function createLogger (config: Object): Object {
Expand Down Expand Up @@ -77,7 +77,7 @@ function captureLogEntry (type, ...args) {
}

function captureLogsStart (userLogged: string) {
username = userLogged
identityContractID = userLogged

logger = getLogger()

Expand Down Expand Up @@ -122,7 +122,7 @@ function downloadLogs (elLink: Object): void {
_instructions: 'GROUP INCOME - Application Logs - Attach this file when reporting an issue: https://github.com/okTurtles/group-income/issues',
ua: navigator.userAgent,
logs: getLogger().entries.toArray()
})], { type: 'application/json' })
}, undefined, 2)], { type: 'application/json' })

const url = URL.createObjectURL(file)
elLink.href = url
Expand Down Expand Up @@ -169,5 +169,5 @@ sbp('sbp/selectors/register', {
'appLogs/get' () { return getLogger()?.entries?.toArray() ?? [] },
'appLogs/save' () { getLogger()?.save() },
'appLogs/pauseCapture' ({ wipeOut }) { captureLogsPause({ wipeOut }) },
'appLogs/startCapture' (username) { captureLogsStart(username) }
'appLogs/startCapture' (identityContractID) { captureLogsStart(identityContractID) }
})
78 changes: 42 additions & 36 deletions frontend/model/contracts/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,7 @@ sbp('chelonia/defineContract', {
.catch(e => {
console.error(`sideEffect(removeMember): ${e.name} thrown during queueEvent to ${contractID} by saveOurLoginState:`, e)
})
.then(() => sbp('gi.contracts/group/revokeGroupKeyAndRotateOurPEK', contractID))
.then(() => sbp('gi.contracts/group/revokeGroupKeyAndRotateOurPEK', contractID, true))
.catch(e => {
console.error(`sideEffect(removeMember): ${e.name} thrown during revokeGroupKeyAndRotateOurPEK to ${contractID}:`, e)
})
Expand All @@ -970,7 +970,11 @@ sbp('chelonia/defineContract', {

// gi.contracts/group/removeOurselves will eventually trigger this
// as well
sbp('gi.contracts/group/rotateKeys', contractID, state)
sbp('gi.contracts/group/rotateKeys', contractID, state).then(() => {
return sbp('gi.contracts/group/revokeGroupKeyAndRotateOurPEK', contractID, false)
}).catch((e) => {
console.error('Error rotating group keys or our PEK', e)
})

const rootGetters = sbp('state/vuex/getters')
const userID = rootGetters.ourContactProfiles[data.member]?.contractID
Expand Down Expand Up @@ -1376,8 +1380,8 @@ sbp('chelonia/defineContract', {
// IMPORTANT: they MUST begin with the name of the contract.
methods: {
'gi.contracts/group/archiveProposal': async function (contractID, proposalHash, proposal) {
const { username } = sbp('state/vuex/state').loggedIn
const key = `proposals/${username}/${contractID}`
const { identityContractID } = sbp('state/vuex/state').loggedIn
const key = `proposals/${identityContractID}/${contractID}`
const proposals = await sbp('gi.db/archive/load', key) || []
// newest at the front of the array, oldest at the back
proposals.unshift([proposalHash, proposal])
Expand All @@ -1389,13 +1393,13 @@ sbp('chelonia/defineContract', {
},
'gi.contracts/group/archivePayments': async function (contractID, archivingPayments) {
const { paymentsByPeriod, payments } = archivingPayments
const { username } = sbp('state/vuex/state').loggedIn
const { identityContractID, username } = sbp('state/vuex/state').loggedIn

// NOTE: we save payments by period and also in types of 'Sent' and 'Received' as well
// because it's not efficient to find all sent/received payments from the payments list
const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`
const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`
const archPaymentsByPeriod = await sbp('gi.db/archive/load', archPaymentsByPeriodKey) || {}
const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`
const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`
const archSentOrReceivedPayments = await sbp('gi.db/archive/load', archSentOrReceivedPaymentsKey) || { sent: [], received: [] }

// sort payments in order to keep the same sorting format as the recent data in vuex
Expand Down Expand Up @@ -1424,7 +1428,7 @@ sbp('chelonia/defineContract', {
archSentOrReceivedPayments.sent = [...sortPayments(newSentOrReceivedPayments.sent), ...archSentOrReceivedPayments.sent]
archSentOrReceivedPayments.received = [...sortPayments(newSentOrReceivedPayments.received), ...archSentOrReceivedPayments.received]

const archPaymentsKey = `payments/${username}/${period}/${contractID}`
const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`
const hashes = paymentHashesFromPaymentPeriod(paymentsByPeriod[period])
const archPayments = Object.fromEntries(hashes.map(hash => [hash, payments[hash]]))

Expand All @@ -1433,7 +1437,7 @@ sbp('chelonia/defineContract', {
const shouldBeDeletedPeriod = Object.keys(archPaymentsByPeriod).sort().shift()
const paymentHashes = paymentHashesFromPaymentPeriod(archPaymentsByPeriod[shouldBeDeletedPeriod])

await sbp('gi.db/archive/delete', `payments/${shouldBeDeletedPeriod}/${username}/${contractID}`)
await sbp('gi.db/archive/delete', `payments/${shouldBeDeletedPeriod}/${identityContractID}/${contractID}`)
delete archPaymentsByPeriod[shouldBeDeletedPeriod]

archSentOrReceivedPayments.sent = archSentOrReceivedPayments.sent.filter(payment => !paymentHashes.includes(payment.hash))
Expand All @@ -1449,17 +1453,17 @@ sbp('chelonia/defineContract', {
sbp('okTurtles.events/emit', PAYMENTS_ARCHIVED, { paymentsByPeriod, payments })
},
'gi.contracts/group/removeArchivedProposals': async function (contractID) {
const { username } = sbp('state/vuex/state').loggedIn
const key = `proposals/${username}/${contractID}`
const { identityContractID } = sbp('state/vuex/state').loggedIn
const key = `proposals/${identityContractID}/${contractID}`
await sbp('gi.db/archive/delete', key)
},
'gi.contracts/group/removeArchivedPayments': async function (contractID) {
const { username } = sbp('state/vuex/state').loggedIn
const archPaymentsByPeriodKey = `paymentsByPeriod/${username}/${contractID}`
const { identityContractID } = sbp('state/vuex/state').loggedIn
const archPaymentsByPeriodKey = `paymentsByPeriod/${identityContractID}/${contractID}`
const periods = Object.keys(await sbp('gi.db/archive/load', archPaymentsByPeriodKey) || {})
const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${username}/${contractID}`
const archSentOrReceivedPaymentsKey = `sentOrReceivedPayments/${identityContractID}/${contractID}`
for (const period of periods) {
const archPaymentsKey = `payments/${username}/${period}/${contractID}`
const archPaymentsKey = `payments/${identityContractID}/${period}/${contractID}`
await sbp('gi.db/archive/delete', archPaymentsKey)
}
await sbp('gi.db/archive/delete', archPaymentsByPeriodKey)
Expand Down Expand Up @@ -1523,11 +1527,11 @@ sbp('chelonia/defineContract', {
Vue.set(state._volatile.pendingKeyRevocations, CSKid, true)
Vue.set(state._volatile.pendingKeyRevocations, CEKid, true)

sbp('chelonia/queueInvocation', contractID, ['gi.actions/out/rotateKeys', contractID, 'gi.contracts/group', 'pending', 'gi.actions/group/shareNewKeys']).catch(e => {
return sbp('chelonia/queueInvocation', contractID, ['gi.actions/out/rotateKeys', contractID, 'gi.contracts/group', 'pending', 'gi.actions/group/shareNewKeys']).catch(e => {
console.warn(`rotateKeys: ${e.name} thrown during queueEvent to ${contractID}:`, e)
})
},
'gi.contracts/group/revokeGroupKeyAndRotateOurPEK': (groupContractID) => {
'gi.contracts/group/revokeGroupKeyAndRotateOurPEK': (groupContractID, disconnectGroup: ?boolean) => {
const rootState = sbp('state/vuex/state')
const { identityContractID } = rootState.loggedIn
const state = rootState[identityContractID]
Expand All @@ -1538,31 +1542,33 @@ sbp('chelonia/defineContract', {
const CEKid = findKeyIdByName(state, 'cek')
const PEKid = findKeyIdByName(state, 'pek')

const groupCSKids = findForeignKeysByContractID(state, groupContractID)

Vue.set(state._volatile.pendingKeyRevocations, PEKid, true)

if (groupCSKids?.length) {
if (!CEKid) {
throw new Error('Identity CEK not found')
if (disconnectGroup) {
const groupCSKids = findForeignKeysByContractID(state, groupContractID)

if (groupCSKids?.length) {
if (!CEKid) {
throw new Error('Identity CEK not found')
}

sbp('chelonia/queueInvocation', identityContractID, ['chelonia/out/keyDel', {
contractID: identityContractID,
contractName: 'gi.contracts/identity',
data: groupCSKids,
signingKeyId: CSKid
}])
.catch(e => {
console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e)
})
}

sbp('chelonia/queueInvocation', identityContractID, ['chelonia/out/keyDel', {
contractID: identityContractID,
contractName: 'gi.contracts/identity',
data: groupCSKids,
signingKeyId: CSKid
}])
.catch(e => {
console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during keyDel to ${identityContractID}:`, e)
})
sbp('chelonia/queueInvocation', identityContractID, ['chelonia/contract/disconnect', identityContractID, groupContractID]).catch(e => {
console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e)
})
}

sbp('chelonia/queueInvocation', identityContractID, ['chelonia/contract/disconnect', identityContractID, groupContractID]).catch(e => {
console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e)
})

sbp('chelonia/queueInvocation', identityContractID, ['gi.actions/out/rotateKeys', identityContractID, 'gi.contracts/identity', 'pending', 'gi.actions/identity/shareNewPEK']).catch(e => {
return sbp('chelonia/queueInvocation', identityContractID, ['gi.actions/out/rotateKeys', identityContractID, 'gi.contracts/identity', 'pending', 'gi.actions/identity/shareNewPEK']).catch(e => {
console.error(`revokeGroupKeyAndRotateOurPEK: ${e.name} thrown during queueEvent to ${identityContractID}:`, e)
})
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/model/contracts/manifests.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifests": {
"gi.contracts/chatroom": "21XWnNGb27BabxLKJcHvKPjxLbzETfrcthDshd5GRsrpTXvY54",
"gi.contracts/group": "21XWnNNBbhSoianhSffVBT45mEsobrrcH7Pp2JuBVjwcuc3FDt",
"gi.contracts/chatroom": "21XWnNLtWByWqqxVAzC4hKTFBPeuc2WkiT72MivJcfYLizqHi1",
"gi.contracts/group": "21XWnNTihqK5Pwu1Fy9Bf3Eh4MN446oKu7QTSgnQD54q4R93Cy",
"gi.contracts/identity": "21XWnNSxNrVFTMBCUVcZV3CG833gTbJ9V8ZyX7Fxgmpj9rNy4r"
}
}
2 changes: 1 addition & 1 deletion frontend/views/containers/chatroom/ChatMain.vue
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,7 @@ export default ({
}
},
archiveKeyFromChatRoomId (chatRoomId) {
return `messages/${this.ourUsername}/${chatRoomId}`
return `messages/${this.ourIdentityContractId}/${chatRoomId}`
},
refreshContent: debounce(function () {
// NOTE: using debounce we can skip unnecessary rendering contents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ export default ({
// complexity.
await sbp('okTurtles.events/emit', OPEN_MODAL, 'PropositionsAllModal')
/*
const key = `proposals/${this.ourUsername}/${this.currentGroupId}`
const key = `proposals/${this.ourIdentityContractId}/${this.currentGroupId}`
const archivedProposals = await sbp('gi.db/archive/load', key) || []
const proposalItemExists = archivedProposals.length > 0 || archivedProposals.some(entry => {
const { data, payload } = entry[1]
Expand Down
5 changes: 3 additions & 2 deletions frontend/views/containers/payments/PaymentsMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const PaymentsMixin: Object = {
'groupSettings',
'groupSortedPeriodKeys',
'ourUsername',
'ourIdentityContractId',
'ourPayments',
'periodAfterPeriod',
'periodBeforePeriod',
Expand Down Expand Up @@ -69,7 +70,7 @@ const PaymentsMixin: Object = {

// ====================
async getHistoricalPeriodPayments () {
const ourArchiveKey = `paymentsByPeriod/${this.ourUsername}/${this.currentGroupId}`
const ourArchiveKey = `paymentsByPeriod/${this.ourIdentityContractId}/${this.currentGroupId}`
return await sbp('gi.db/archive/load', ourArchiveKey) ?? {}
},

Expand Down Expand Up @@ -133,7 +134,7 @@ const PaymentsMixin: Object = {
return payments
},
async getHistoricalPaymentDetailsByPeriod (period: string) {
const paymentsKey = `payments/${this.ourUsername}/${period}/${this.currentGroupId}`
const paymentsKey = `payments/${this.ourIdentityContractId}/${period}/${this.currentGroupId}`
const paymentDetails = await sbp('gi.db/archive/load', paymentsKey) || {}

return paymentDetails
Expand Down
3 changes: 2 additions & 1 deletion frontend/views/containers/proposals/ProposalsWidget.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export default ({
'groupShouldPropose',
'groupSettings',
'ourUsername',
'ourIdentityContractId',
'groupMembersCount'
]),
hasProposals () {
Expand Down Expand Up @@ -169,7 +170,7 @@ export default ({
async refreshArchivedProposals () {
// NOTE: all the archived proposals are displayed in the dashboard widget for 24 hours
// https://github.com/okTurtles/group-income/pull/1723#discussion_r1323369824
const key = `proposals/${this.ourUsername}/${this.currentGroupId}`
const key = `proposals/${this.ourIdentityContractId}/${this.currentGroupId}`
const archivedProposals = await sbp('gi.db/archive/load', key) || []
// proposals which are archived in the last 24 hours
this.ephemeral.archivedProposals = archivedProposals
Expand Down
Loading

0 comments on commit 58e6c31

Please sign in to comment.