Skip to content

Commit

Permalink
Handle identity contract fork on login. (#2569)
Browse files Browse the repository at this point in the history
* Handle identity contract fork on login.

Closes #2564.

* Feedback
  • Loading branch information
corrideat authored Jan 31, 2025
1 parent 35dd2a9 commit 16893d5
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 7 deletions.
9 changes: 7 additions & 2 deletions frontend/controller/app/identity.js
Original file line number Diff line number Diff line change
Expand Up @@ -425,16 +425,21 @@ export default (sbp('sbp/selectors/register', {
const errMessage = e?.message || String(e)
console.error('[gi.app/identity] Error during login contract sync', e)

const wipeOut = (e && (e.name === 'ChelErrorForkedChain' || e.cause?.name === 'ChelErrorForkedChain'))

const promptOptions = {
heading: L('Login error'),
question: L('Do you want to log out? {br_}Error details: {err}.', { err: errMessage, ...LTags() }),
question: wipeOut
? L('The server\'s history for your identity contract has diverged from ours. This can happen in extremely rare circumstances due to either malicious activity or a bug. {br_}To fix this, the contract needs to be resynced, and some recent events may be missing. {br_}Would you like to log out and resync data on your next login? {br_}Error details: {err}.', { err: errMessage, ...LTags() })
: L('Do you want to log out? {br_}Error details: {err}.', { err: errMessage, ...LTags() }),
primaryButton: L('No'),
secondaryButton: L('Yes')
}

const result = await sbp('gi.ui/prompt', promptOptions)
if (!result) {
return sbp('gi.app/identity/_private/logout', state)
sbp('gi.ui/clearBanner')
return sbp('gi.app/identity/_private/logout', state, wipeOut)
} else {
sbp('okTurtles.events/emit', LOGIN_ERROR, { username, identityContractID, error: e })
throw e
Expand Down
22 changes: 18 additions & 4 deletions frontend/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,31 @@ async function startApp () {
sbp('gi.ui/seriousErrorBanner', error)
if (error?.name === 'ChelErrorForkedChain') {
const rootState = sbp('state/vuex/state')
if (!rootState.contracts[contractID]) {
// If `rootState.contracts[contractID]` doesn't exist, it means we're no
// longer subscribed to the contract. This could happen, e.g., if the
// contract has since been released. In any case, the absence of
// `rootState.contracts[contractID]` means that there's nothing to
// left to recover.
console.error('Forked chain detected. However, there is no contract entry.', { contractID }, error)
return
}
const type = rootState.contracts[contractID].type || '(unknown)'
console.error('Forked chain detected', { contractID, type }, error)

const retry = confirm(L("The server's history for '{type}' has diverged from ours. This can happen in extremely rare circumstances due to either malicious activity or a bug.\n\nTo fix this, the contract needs to be resynced, and some recent events may be missing. Would you like to do so now?\n\n(If problems persist, please open the Troubleshooting page under the User Settings and resync all contracts.)", { type }))

if (retry) {
sbp('gi.ui/clearBanner')
sbp('chelonia/contract/sync', contractID, { resync: true }).catch((e) => {
console.error('Error during re-sync', contractID, e)
alert(L('There was a problem resyncing the contract: {errMsg}\n\nPlease see the Application Logs under User Settings for more details. The Troubleshooting page in User Settings may be another way to fix the problem.', { errMsg: e?.message || e }))
})
// If it's our identity contract, we need to log in again to be able
// to propery decrypt all data, since that requires the user password
;((rootState.loggedIn?.identityContractID === contractID)
? sbp('gi.actions/identity/logout', null, true)
: sbp('chelonia/contract/sync', contractID, { resync: true }))
.catch((e) => {
console.error('Error during re-sync', contractID, e)
alert(L('There was a problem resyncing the contract: {errMsg}\n\nPlease see the Application Logs under User Settings for more details. The Troubleshooting page in User Settings may be another way to fix the problem.', { errMsg: e?.message || e }))
})
}
}
if (process.env.CI) {
Expand Down
2 changes: 1 addition & 1 deletion shared/domains/chelonia/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ChelErrorGenerator = (
// $FlowFixMe[prop-missing]
if (params[1]?.cause !== this.cause) {
// $FlowFixMe[prop-missing]
Object.defineProperty(this, 'cause', { value: params[1].cause })
Object.defineProperty(this, 'cause', { configurable: true, writable: true, value: params[1].cause })
}
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
Expand Down
7 changes: 7 additions & 0 deletions shared/serdes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export const serializer = (data: any): any => {
if (value instanceof Error) {
const pos = verbatim.length
verbatim[verbatim.length] = value
// We need to also serialize `Error.cause` recursively
if (value.cause) {
value.cause = serializer(value.cause).data
}
return rawResult(['_', '_err', rawResult(['_', '_ref', pos]), value.name])
}
// Same for other types supported by structuredClone but not JSON
Expand Down Expand Up @@ -177,6 +181,9 @@ export const deserializer = (data: any): any => {
if (value[2].name !== value[3]) {
value[2].name = value[3]
}
if (value[2].cause) {
value[2].cause = deserializer(value[2].cause)
}
return value[2]
}
// These were functions converted to a MessagePort. Convert them on this
Expand Down

0 comments on commit 16893d5

Please sign in to comment.