Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle identity contract fork on login. #2569

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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