Skip to content

Commit

Permalink
Various bugfixes, related to #2064 and #2066
Browse files Browse the repository at this point in the history
* eventsAfter: Reject early if the contractID is not set
* Fix instances causing such unset contractIDs
* Fix group members list to include members with only a contractID
* Don't set an empty contract state when receiving OP_KEY_SHARE
* Files: laxer validation when removing to process individual files separately
* Increase shelter header validity window
  • Loading branch information
corrideat committed Jun 18, 2024
1 parent 80c3ffd commit 68f402a
Show file tree
Hide file tree
Showing 8 changed files with 47 additions and 23 deletions.
26 changes: 23 additions & 3 deletions frontend/controller/actions/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -761,24 +761,44 @@ export default (sbp('sbp/selectors/register', {
}) => {
const rootGetters = sbp('state/vuex/getters')
const { identityContractID } = sbp('state/vuex/state').loggedIn
const { shouldDeleteFile, shouldDeleteToken } = option
const { shouldDeleteFile, shouldDeleteToken, maybeDeleted } = option
let deleteResult, toDelete

if (shouldDeleteFile) {
const credentials = Object.fromEntries(manifestCids.map(cid => {
// It could be that the file was already deleted, if we no longer have
// a delete token. In this case, omit those CIDs.
if (maybeDeleted && !rootGetters.currentIdentityState.fileDeleteTokens[cid]) {
return [cid, null]
};
const credential = shouldDeleteToken
? { token: rootGetters.currentIdentityState.fileDeleteTokens[cid] }
: { billableContractID: identityContractID }
return [cid, credential]
}))
await sbp('chelonia/fileDelete', manifestCids, credentials)
toDelete = maybeDeleted ? manifestCids.filter((cid) => !!credentials[cid]) : manifestCids
deleteResult = await sbp('chelonia/fileDelete', toDelete, credentials)
} else {
toDelete = manifestCids
}

if (shouldDeleteToken) {
await sbp('gi.actions/identity/removeFileDeleteToken', {
contractID: identityContractID,
data: { manifestCids }
data: {
manifestCids: deleteResult
? toDelete.filter((_, i) => {
return deleteResult[i].status === 'fulfilled'
})
: toDelete
}
})
}

if (deleteResult?.some(r => r.status === 'rejected')) {
console.error('[gi.actions/identity/removeFiles] Some CIDs could not be deleted', deleteResult.map((r, i) => r.status === 'rejected' && toDelete[i]).filter(Boolean))
throw new Error('Some CIDs could not be deleted')
}
},
'gi.actions/identity/fetchChatRoomUnreadMessages': async () => {
const { ourIdentityContractId } = sbp('state/vuex/getters')
Expand Down
3 changes: 2 additions & 1 deletion frontend/model/contracts/chatroom.js
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,8 @@ sbp('chelonia/defineContract', {
if (data.manifestCids.length) {
const option = {
shouldDeleteFile: me === innerSigningContractID,
shouldDeleteToken: me === data.messageSender
shouldDeleteToken: me === data.messageSender,
maybeDeleted: true
}
deleteEncryptedFiles(data.manifestCids, option).catch(e => {
console.error(`[gi.contracts/chatroom/deleteMessage/sideEffect] (${contractID}):`, e)
Expand Down
4 changes: 2 additions & 2 deletions frontend/model/notifications/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,12 @@ export default ({
},
PAYMENT_THANKYOU_SENT (data: { creatorID: string, fromMemberID: string, toMemberID: string }) {
return {
avatarUserID: data.creatorID,
avatarUserID: data.fromMemberID,
body: L('{name} sent you a {strong_}thank you note{_strong} for your contribution.', {
name: strong(userDisplayNameFromID(data.fromMemberID)),
...LTags('strong')
}),
creatorID: data.creatorID,
creatorID: data.fromMemberID,
icon: '',
level: 'info',
linkTo: `/payments?modal=ThankYouNoteModal&from=${data.fromMemberID}&to=${data.toMemberID}`,
Expand Down
2 changes: 1 addition & 1 deletion frontend/model/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ const getters = {
.filter(memberID => getters.groupProfiles[memberID] ||
getters.groupMembersPending[memberID].expires >= Date.now())
.map(memberID => {
const { contractID, displayName, username } = getters.globalProfile(memberID) || groupMembersPending[memberID] || {}
const { contractID, displayName, username } = getters.globalProfile(memberID) || groupMembersPending[memberID] || (getters.groupProfiles[memberID] ? { contractID: memberID } : {})
return {
id: memberID, // common unique ID: it can be either the contract ID or the invite key
contractID,
Expand Down
4 changes: 3 additions & 1 deletion frontend/views/components/AvatarUser.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export default ({
picture: {
type: [String, Object]
},
contractID: String,
contractID: {
type: String
},
alt: {
type: String,
default: ''
Expand Down
19 changes: 9 additions & 10 deletions shared/domains/chelonia/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,26 +369,25 @@ export default (sbp('sbp/selectors/register', {
throw new TypeError('A manifest CID must be provided')
}
if (!Array.isArray(manifestCid)) manifestCid = [manifestCid]
// Validation
manifestCid.forEach((cid) => {
return await Promise.allSettled(manifestCid.map(async (cid) => {
const hasCredential = has(credentials, cid)
const hasToken = has(credentials[cid], 'token')
const hasBillableContractID = has(credentials[cid], 'billableContractID')
const hasToken = has(credentials[cid], 'token') && credentials[cid].token
const hasBillableContractID = has(credentials[cid], 'billableContractID') && credentials[cid].billableContractID
if (!hasCredential || (!hasToken && hasToken === hasBillableContractID)) {
throw new TypeError(`Either a token or a billable contract ID must be provided for ${cid}`)
}
})
return await Promise.all(manifestCid.map(async (cid) => {
const { token, billableContractID } = credentials[cid]

const response = await fetch(`${this.config.connectionURL}/deleteFile/${cid}`, {
method: 'POST',
signal: this.abortController.signal,
headers: new Headers([
['authorization',
token
? `bearer ${token}`
hasToken
// $FlowFixMe[incompatible-type]
? `bearer ${credentials[cid].token}`
// $FlowFixMe[incompatible-type]
// $FlowFixMe[incompatible-call]
: buildShelterAuthorizationHeader.call(this, billableContractID)]
: buildShelterAuthorizationHeader.call(this, credentials[cid].billableContractID)]
])
})
if (!response.ok) {
Expand Down
5 changes: 1 addition & 4 deletions shared/domains/chelonia/internals.js
Original file line number Diff line number Diff line change
Expand Up @@ -723,9 +723,6 @@ export default (sbp('sbp/selectors/register', {

const cheloniaState = sbp(self.config.stateSelector)

if (!cheloniaState[v.contractID]) {
config.reactiveSet(cheloniaState, v.contractID, Object.create(null))
}
const targetState = cheloniaState[v.contractID]

let newestEncryptionKeyHeight = Number.POSITIVE_INFINITY
Expand All @@ -743,7 +740,7 @@ export default (sbp('sbp/selectors/register', {
transient
}])
if (
targetState._vm?.authorizedKeys?.[key.id]?._notBeforeHeight != null &&
targetState?._vm?.authorizedKeys?.[key.id]?._notBeforeHeight != null &&
Array.isArray(targetState._vm.authorizedKeys[key.id].purpose) &&
targetState._vm.authorizedKeys[key.id].purpose.includes('enc')
) {
Expand Down
7 changes: 6 additions & 1 deletion shared/domains/chelonia/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,11 @@ export const getContractIDfromKeyId = (contractID: string, signingKeyId: ?string
}

export function eventsAfter (contractID: string, sinceHeight: number, limit?: number, sinceHash?: string): ReadableStream {
if (!contractID) {
// Avoid making a network roundtrip to tell us what we already know
throw new Error('Missing contract ID')
}

const fetchEventsStreamReader = async () => {
requestLimit = Math.min(limit ?? MAX_EVENTS_AFTER, remainingEvents)
const eventsResponse = await fetch(`${this.config.connectionURL}/eventsAfter/${contractID}/${sinceHeight}${Number.isInteger(requestLimit) ? `/${requestLimit}` : ''}`, { signal })
Expand Down Expand Up @@ -756,7 +761,7 @@ export function verifyShelterAuthorizationHeader (authorization: string, rootSta
}
// TODO: Remember nonces and reject already used ones
const [, data, contractID, timestamp, , signature] = matches
if (Math.abs(parseInt(timestamp) - (Date.now() / 1e3 | 0)) > 2) {
if (Math.abs(parseInt(timestamp) - (Date.now() / 1e3 | 0)) > 60) {
throw new Error('Invalid signature time range')
}
if (!rootState) rootState = sbp('chelonia/rootState')
Expand Down

0 comments on commit 68f402a

Please sign in to comment.