Prompt for recovery key on login rather than passphrase
Only show passphrase options at all if the user has a passphrase on their SSSS key.
This commit is contained in:
parent
631184c661
commit
e06ba2003b
9 changed files with 224 additions and 20 deletions
|
@ -70,6 +70,10 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_CompleteSecurity_recoveryKeyInput {
|
||||||
|
width: 368px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_CompleteSecurity_heroIcon {
|
.mx_CompleteSecurity_heroIcon {
|
||||||
width: 128px;
|
width: 128px;
|
||||||
height: 128px;
|
height: 128px;
|
||||||
|
|
|
@ -30,6 +30,8 @@ import {encodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
// operation ends.
|
// operation ends.
|
||||||
let secretStorageKeys = {};
|
let secretStorageKeys = {};
|
||||||
let secretStorageBeingAccessed = false;
|
let secretStorageBeingAccessed = false;
|
||||||
|
// Stores the 'passphraseOnly' option for the active storage access operation
|
||||||
|
let passphraseOnlyOption = null;
|
||||||
|
|
||||||
function isCachingAllowed() {
|
function isCachingAllowed() {
|
||||||
return (
|
return (
|
||||||
|
@ -99,6 +101,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
return await MatrixClientPeg.get().checkSecretStorageKey(key, info);
|
return await MatrixClientPeg.get().checkSecretStorageKey(key, info);
|
||||||
},
|
},
|
||||||
|
passphraseOnly: passphraseOnlyOption,
|
||||||
},
|
},
|
||||||
/* className= */ null,
|
/* className= */ null,
|
||||||
/* isPriorityModal= */ false,
|
/* isPriorityModal= */ false,
|
||||||
|
@ -213,19 +216,27 @@ export async function promptForBackupPassphrase() {
|
||||||
*
|
*
|
||||||
* @param {Function} [func] An operation to perform once secret storage has been
|
* @param {Function} [func] An operation to perform once secret storage has been
|
||||||
* bootstrapped. Optional.
|
* bootstrapped. Optional.
|
||||||
* @param {bool} [forceReset] Reset secret storage even if it's already set up
|
* @param {object} [opts] Named options
|
||||||
|
* @param {bool} [opts.forceReset] Reset secret storage even if it's already set up
|
||||||
|
* @param {object} [opts.withKeys] Map of key ID to key for SSSS keys that the client
|
||||||
|
* already has available. If a key is not supplied here, the user will be prompted.
|
||||||
|
* @param {bool} [opts.passphraseOnly] If true, do not prompt for recovery key or to reset keys
|
||||||
*/
|
*/
|
||||||
export async function accessSecretStorage(func = async () => { }, forceReset = false) {
|
export async function accessSecretStorage(
|
||||||
|
func = async () => { }, opts = {},
|
||||||
|
) {
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
secretStorageBeingAccessed = true;
|
secretStorageBeingAccessed = true;
|
||||||
|
passphraseOnlyOption = opts.passphraseOnly;
|
||||||
|
secretStorageKeys = Object.assign({}, opts.withKeys || {});
|
||||||
try {
|
try {
|
||||||
if (!await cli.hasSecretStorageKey() || forceReset) {
|
if (!await cli.hasSecretStorageKey() || opts.forceReset) {
|
||||||
// This dialog calls bootstrap itself after guiding the user through
|
// This dialog calls bootstrap itself after guiding the user through
|
||||||
// passphrase creation.
|
// passphrase creation.
|
||||||
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
const { finished } = Modal.createTrackedDialogAsync('Create Secret Storage dialog', '',
|
||||||
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
import("./async-components/views/dialogs/secretstorage/CreateSecretStorageDialog"),
|
||||||
{
|
{
|
||||||
force: forceReset,
|
force: opts.forceReset,
|
||||||
},
|
},
|
||||||
null, /* priority = */ false, /* static = */ true,
|
null, /* priority = */ false, /* static = */ true,
|
||||||
);
|
);
|
||||||
|
@ -263,5 +274,6 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
if (!isCachingAllowed()) {
|
if (!isCachingAllowed()) {
|
||||||
secretStorageKeys = {};
|
secretStorageKeys = {};
|
||||||
}
|
}
|
||||||
|
passphraseOnlyOption = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import * as sdk from '../../../index';
|
||||||
import {
|
import {
|
||||||
SetupEncryptionStore,
|
SetupEncryptionStore,
|
||||||
PHASE_INTRO,
|
PHASE_INTRO,
|
||||||
|
PHASE_RECOVERY_KEY,
|
||||||
PHASE_BUSY,
|
PHASE_BUSY,
|
||||||
PHASE_DONE,
|
PHASE_DONE,
|
||||||
PHASE_CONFIRM_SKIP,
|
PHASE_CONFIRM_SKIP,
|
||||||
|
@ -61,6 +62,9 @@ export default class CompleteSecurity extends React.Component {
|
||||||
if (phase === PHASE_INTRO) {
|
if (phase === PHASE_INTRO) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_warning" />;
|
||||||
title = _t("Verify this login");
|
title = _t("Verify this login");
|
||||||
|
} else if (phase === PHASE_RECOVERY_KEY) {
|
||||||
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified" />;
|
||||||
|
title = _t("Recovery Key");
|
||||||
} else if (phase === PHASE_DONE) {
|
} else if (phase === PHASE_DONE) {
|
||||||
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified" />;
|
icon = <span className="mx_CompleteSecurity_headerIcon mx_E2EIcon_verified" />;
|
||||||
title = _t("Session verified");
|
title = _t("Session verified");
|
||||||
|
|
|
@ -19,15 +19,26 @@ import PropTypes from 'prop-types';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import * as sdk from '../../../index';
|
import * as sdk from '../../../index';
|
||||||
|
import withValidation from '../../views/elements/Validation';
|
||||||
|
import { decodeRecoveryKey } from 'matrix-js-sdk/src/crypto/recoverykey';
|
||||||
import {
|
import {
|
||||||
SetupEncryptionStore,
|
SetupEncryptionStore,
|
||||||
PHASE_INTRO,
|
PHASE_INTRO,
|
||||||
|
PHASE_RECOVERY_KEY,
|
||||||
PHASE_BUSY,
|
PHASE_BUSY,
|
||||||
PHASE_DONE,
|
PHASE_DONE,
|
||||||
PHASE_CONFIRM_SKIP,
|
PHASE_CONFIRM_SKIP,
|
||||||
PHASE_FINISHED,
|
PHASE_FINISHED,
|
||||||
} from '../../../stores/SetupEncryptionStore';
|
} from '../../../stores/SetupEncryptionStore';
|
||||||
|
|
||||||
|
function keyHasPassphrase(keyInfo) {
|
||||||
|
return (
|
||||||
|
keyInfo.passphrase &&
|
||||||
|
keyInfo.passphrase.salt &&
|
||||||
|
keyInfo.passphrase.iterations
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default class SetupEncryptionBody extends React.Component {
|
export default class SetupEncryptionBody extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onFinished: PropTypes.func.isRequired,
|
onFinished: PropTypes.func.isRequired,
|
||||||
|
@ -45,6 +56,11 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
// Because of the latter, it lives in the state.
|
// Because of the latter, it lives in the state.
|
||||||
verificationRequest: store.verificationRequest,
|
verificationRequest: store.verificationRequest,
|
||||||
backupInfo: store.backupInfo,
|
backupInfo: store.backupInfo,
|
||||||
|
recoveryKey: '',
|
||||||
|
// whether the recovery key is a valid recovery key
|
||||||
|
recoveryKeyValid: null,
|
||||||
|
// whether the recovery key is the correct key or not
|
||||||
|
recoveryKeyCorrect: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,9 +83,14 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
store.stop();
|
store.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUsePassphraseClick = async () => {
|
_onUseRecoveryKeyClick = async () => {
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
store.usePassPhrase();
|
store.useRecoveryKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRecoveryKeyCancelClick() {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.cancelUseRecoveryKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
onSkipClick = () => {
|
onSkipClick = () => {
|
||||||
|
@ -92,6 +113,66 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
store.done();
|
store.done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onUsePassphraseClick = () => {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.usePassPhrase();
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRecoveryKeyChange = (e) => {
|
||||||
|
this.setState({recoveryKey: e.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
_onRecoveryKeyValidate = async (fieldState) => {
|
||||||
|
const result = await this._validateRecoveryKey(fieldState);
|
||||||
|
this.setState({recoveryKeyValid: result.valid});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
_validateRecoveryKey = withValidation({
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
key: "required",
|
||||||
|
test: async (state) => {
|
||||||
|
try {
|
||||||
|
const decodedKey = decodeRecoveryKey(state.value);
|
||||||
|
const correct = await MatrixClientPeg.get().checkSecretStorageKey(
|
||||||
|
decodedKey, SetupEncryptionStore.sharedInstance().keyInfo,
|
||||||
|
);
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyValid: true,
|
||||||
|
recoveryKeyCorrect: correct,
|
||||||
|
});
|
||||||
|
return correct;
|
||||||
|
} catch (e) {
|
||||||
|
this.setState({
|
||||||
|
recoveryKeyValid: false,
|
||||||
|
recoveryKeyCorrect: false,
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invalid: function() {
|
||||||
|
if (this.state.recoveryKeyValid) {
|
||||||
|
return _t("This isn't the recovery key for your account");
|
||||||
|
} else {
|
||||||
|
return _t("This isn't a valid recovery key");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
valid: function() {
|
||||||
|
return _t("Looks good!");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
_onRecoveryKeyFormSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!this.state.recoveryKeyCorrect) return;
|
||||||
|
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
store.setupWithRecoveryKey(decodeRecoveryKey(this.state.recoveryKey));
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
||||||
|
|
||||||
|
@ -108,6 +189,13 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
member={MatrixClientPeg.get().getUser(this.state.verificationRequest.otherUserId)}
|
||||||
/>;
|
/>;
|
||||||
} else if (phase === PHASE_INTRO) {
|
} else if (phase === PHASE_INTRO) {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
let recoveryKeyPrompt;
|
||||||
|
if (keyHasPassphrase(store.keyInfo)) {
|
||||||
|
recoveryKeyPrompt = _t("Use Recovery Key or Passphrase");
|
||||||
|
} else {
|
||||||
|
recoveryKeyPrompt = _t("Use Recovery Key");
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t(
|
<p>{_t(
|
||||||
|
@ -131,8 +219,8 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
<AccessibleButton kind="link" onClick={this._onUsePassphraseClick}>
|
<AccessibleButton kind="link" onClick={this._onUseRecoveryKeyClick}>
|
||||||
{_t("Use Recovery Passphrase or Key")}
|
{recoveryKeyPrompt}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
<AccessibleButton kind="danger" onClick={this.onSkipClick}>
|
||||||
{_t("Skip")}
|
{_t("Skip")}
|
||||||
|
@ -140,6 +228,47 @@ export default class SetupEncryptionBody extends React.Component {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (phase === PHASE_RECOVERY_KEY) {
|
||||||
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
|
let keyPrompt;
|
||||||
|
if (keyHasPassphrase(store.keyInfo)) {
|
||||||
|
keyPrompt = _t(
|
||||||
|
"Enter your Recovery Key or enter a <a>Recovery Passphrase</a> to continue.", {},
|
||||||
|
{
|
||||||
|
a: sub => <AccessibleButton
|
||||||
|
element="span"
|
||||||
|
className="mx_linkButton"
|
||||||
|
onClick={this._onUsePassphraseClick}
|
||||||
|
>{sub}</AccessibleButton>,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
keyPrompt = _t("Enter your Recovery Key to continue.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Field = sdk.getComponent('elements.Field');
|
||||||
|
return <form onSubmit={this._onRecoveryKeyFormSubmit}>
|
||||||
|
<p>{keyPrompt}</p>
|
||||||
|
<div className="mx_CompleteSecurity_recoveryKeyEntry">
|
||||||
|
<Field
|
||||||
|
type="text"
|
||||||
|
label={_t('Recovery Key')}
|
||||||
|
value={this.state.recoveryKey}
|
||||||
|
onChange={this._onRecoveryKeyChange}
|
||||||
|
onValidate={this._onRecoveryKeyValidate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
|
<AccessibleButton kind="secondary" onClick={this._onRecoveryKeyCancelClick}>
|
||||||
|
{_t("Cancel")}
|
||||||
|
</AccessibleButton>
|
||||||
|
<AccessibleButton kind="primary"
|
||||||
|
disabled={!this.state.recoveryKeyCorrect}
|
||||||
|
>
|
||||||
|
{_t("Continue")}
|
||||||
|
</AccessibleButton>
|
||||||
|
</div>
|
||||||
|
</form>;
|
||||||
} else if (phase === PHASE_DONE) {
|
} else if (phase === PHASE_DONE) {
|
||||||
let message;
|
let message;
|
||||||
if (this.state.backupInfo) {
|
if (this.state.backupInfo) {
|
||||||
|
|
|
@ -94,7 +94,7 @@ export default class RestoreKeyBackupDialog extends React.PureComponent {
|
||||||
if (SettingsStore.getValue("feature_cross_signing")) {
|
if (SettingsStore.getValue("feature_cross_signing")) {
|
||||||
// If cross-signing is enabled, we reset the SSSS recovery passphrase (and cross-signing keys)
|
// If cross-signing is enabled, we reset the SSSS recovery passphrase (and cross-signing keys)
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
accessSecretStorage(() => {}, /* forceReset = */ true);
|
accessSecretStorage(() => {}, {forceReset: true});
|
||||||
} else {
|
} else {
|
||||||
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
Modal.createTrackedDialogAsync('Key Backup', 'Key Backup',
|
||||||
import('../../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
|
import('../../../../async-components/views/dialogs/keybackup/CreateKeyBackupDialog'),
|
||||||
|
|
|
@ -32,6 +32,9 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
keyInfo: PropTypes.object.isRequired,
|
keyInfo: PropTypes.object.isRequired,
|
||||||
// Function from one of { passphrase, recoveryKey } -> boolean
|
// Function from one of { passphrase, recoveryKey } -> boolean
|
||||||
checkPrivateKey: PropTypes.func.isRequired,
|
checkPrivateKey: PropTypes.func.isRequired,
|
||||||
|
// If true, only prompt for a passphrase and do not offer to restore with
|
||||||
|
// a recovery key or reset keys.
|
||||||
|
passphraseOnly: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -58,7 +61,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
_onResetRecoveryClick = () => {
|
_onResetRecoveryClick = () => {
|
||||||
// Re-enter the access flow, but resetting storage this time around.
|
// Re-enter the access flow, but resetting storage this time around.
|
||||||
this.props.onFinished(false);
|
this.props.onFinished(false);
|
||||||
accessSecretStorage(() => {}, /* forceReset = */ true);
|
accessSecretStorage(() => {}, {forceReset: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRecoveryKeyChange = (e) => {
|
_onRecoveryKeyChange = (e) => {
|
||||||
|
@ -164,7 +167,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
primaryDisabled={this.state.passPhrase.length === 0}
|
primaryDisabled={this.state.passPhrase.length === 0}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
{_t(
|
{this.props.passphraseOnly ? null : _t(
|
||||||
"If you've forgotten your recovery passphrase you can "+
|
"If you've forgotten your recovery passphrase you can "+
|
||||||
"<button1>use your recovery key</button1> or " +
|
"<button1>use your recovery key</button1> or " +
|
||||||
"<button2>set up new recovery options</button2>."
|
"<button2>set up new recovery options</button2>."
|
||||||
|
@ -234,7 +237,7 @@ export default class AccessSecretStorageDialog extends React.PureComponent {
|
||||||
primaryDisabled={!this.state.recoveryKeyValid}
|
primaryDisabled={!this.state.recoveryKeyValid}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
{_t(
|
{this.props.passphraseOnly ? null : _t(
|
||||||
"If you've forgotten your recovery key you can "+
|
"If you've forgotten your recovery key you can "+
|
||||||
"<button>set up new recovery options</button>."
|
"<button>set up new recovery options</button>."
|
||||||
, {}, {
|
, {}, {
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default class CrossSigningPanel extends React.PureComponent {
|
||||||
_bootstrapSecureSecretStorage = async (forceReset=false) => {
|
_bootstrapSecureSecretStorage = async (forceReset=false) => {
|
||||||
this.setState({ error: null });
|
this.setState({ error: null });
|
||||||
try {
|
try {
|
||||||
await accessSecretStorage(() => undefined, forceReset);
|
await accessSecretStorage(() => undefined, {forceReset});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.setState({ error: e });
|
this.setState({ error: e });
|
||||||
console.error("Error bootstrapping secret storage", e);
|
console.error("Error bootstrapping secret storage", e);
|
||||||
|
|
|
@ -2083,6 +2083,7 @@
|
||||||
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
"Uploading %(filename)s and %(count)s others|one": "Uploading %(filename)s and %(count)s other",
|
||||||
"Could not load user profile": "Could not load user profile",
|
"Could not load user profile": "Could not load user profile",
|
||||||
"Verify this login": "Verify this login",
|
"Verify this login": "Verify this login",
|
||||||
|
"Recovery Key": "Recovery Key",
|
||||||
"Session verified": "Session verified",
|
"Session verified": "Session verified",
|
||||||
"Failed to send email": "Failed to send email",
|
"Failed to send email": "Failed to send email",
|
||||||
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
|
"The email address linked to your account must be entered.": "The email address linked to your account must be entered.",
|
||||||
|
@ -2136,10 +2137,16 @@
|
||||||
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
|
"You can now close this window or <a>log in</a> to your new account.": "You can now close this window or <a>log in</a> to your new account.",
|
||||||
"Registration Successful": "Registration Successful",
|
"Registration Successful": "Registration Successful",
|
||||||
"Create your account": "Create your account",
|
"Create your account": "Create your account",
|
||||||
|
"This isn't the recovery key for your account": "This isn't the recovery key for your account",
|
||||||
|
"This isn't a valid recovery key": "This isn't a valid recovery key",
|
||||||
|
"Looks good!": "Looks good!",
|
||||||
|
"Use Recovery Key or Passphrase": "Use Recovery Key or Passphrase",
|
||||||
|
"Use Recovery Key": "Use Recovery Key",
|
||||||
"Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.",
|
"Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.": "Confirm your identity by verifying this login from one of your other sessions, granting it access to encrypted messages.",
|
||||||
"This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:",
|
"This requires the latest Riot on your other devices:": "This requires the latest Riot on your other devices:",
|
||||||
"or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client",
|
"or another cross-signing capable Matrix client": "or another cross-signing capable Matrix client",
|
||||||
"Use Recovery Passphrase or Key": "Use Recovery Passphrase or Key",
|
"Enter your Recovery Key or enter a <a>Recovery Passphrase</a> to continue.": "Enter your Recovery Key or enter a <a>Recovery Passphrase</a> to continue.",
|
||||||
|
"Enter your Recovery Key to continue.": "Enter your Recovery Key to continue.",
|
||||||
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
"Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.": "Your new session is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
||||||
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
"Your new session is now verified. Other users will see it as trusted.": "Your new session is now verified. Other users will see it as trusted.",
|
||||||
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
"Without completing security on this session, it won’t have access to encrypted messages.": "Without completing security on this session, it won’t have access to encrypted messages.",
|
||||||
|
@ -2182,9 +2189,9 @@
|
||||||
"Import": "Import",
|
"Import": "Import",
|
||||||
"Confirm encryption setup": "Confirm encryption setup",
|
"Confirm encryption setup": "Confirm encryption setup",
|
||||||
"Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.",
|
"Click the button below to confirm setting up encryption.": "Click the button below to confirm setting up encryption.",
|
||||||
"Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:",
|
|
||||||
"Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption",
|
"Restore your key backup to upgrade your encryption": "Restore your key backup to upgrade your encryption",
|
||||||
"Restore": "Restore",
|
"Restore": "Restore",
|
||||||
|
"Enter your account password to confirm the upgrade:": "Enter your account password to confirm the upgrade:",
|
||||||
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
|
"You'll need to authenticate with the server to confirm the upgrade.": "You'll need to authenticate with the server to confirm the upgrade.",
|
||||||
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
|
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
|
||||||
"Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.": "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.",
|
"Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.": "Store your Recovery Key somewhere safe, it can be used to unlock your encrypted messages & data.",
|
||||||
|
|
|
@ -20,10 +20,11 @@ import { accessSecretStorage, AccessCancelledError } from '../CrossSigningManage
|
||||||
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
import { PHASE_DONE as VERIF_PHASE_DONE } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
|
||||||
|
|
||||||
export const PHASE_INTRO = 0;
|
export const PHASE_INTRO = 0;
|
||||||
export const PHASE_BUSY = 1;
|
export const PHASE_RECOVERY_KEY = 1;
|
||||||
export const PHASE_DONE = 2; //final done stage, but still showing UX
|
export const PHASE_BUSY = 2;
|
||||||
export const PHASE_CONFIRM_SKIP = 3;
|
export const PHASE_DONE = 3; //final done stage, but still showing UX
|
||||||
export const PHASE_FINISHED = 4; //UX can be closed
|
export const PHASE_CONFIRM_SKIP = 4;
|
||||||
|
export const PHASE_FINISHED = 5; //UX can be closed
|
||||||
|
|
||||||
export class SetupEncryptionStore extends EventEmitter {
|
export class SetupEncryptionStore extends EventEmitter {
|
||||||
static sharedInstance() {
|
static sharedInstance() {
|
||||||
|
@ -36,11 +37,19 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._started = true;
|
this._started = true;
|
||||||
this.phase = PHASE_INTRO;
|
this.phase = PHASE_BUSY;
|
||||||
this.verificationRequest = null;
|
this.verificationRequest = null;
|
||||||
this.backupInfo = null;
|
this.backupInfo = null;
|
||||||
|
|
||||||
|
// ID of the key that the secrets we want are encrypted with
|
||||||
|
this.keyId = null;
|
||||||
|
// Descriptor of the key that the secrets we want are encrypted with
|
||||||
|
this.keyInfo = null;
|
||||||
|
|
||||||
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
MatrixClientPeg.get().on("crypto.verification.request", this.onVerificationRequest);
|
||||||
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
MatrixClientPeg.get().on('userTrustStatusChanged', this._onUserTrustStatusChanged);
|
||||||
|
|
||||||
|
this.fetchKeyInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
@ -57,7 +66,40 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchKeyInfo() {
|
||||||
|
const keys = await MatrixClientPeg.get().isSecretStored('m.cross_signing.master', false);
|
||||||
|
if (Object.keys(keys).length === 0) {
|
||||||
|
this.keyId = null;
|
||||||
|
this.keyInfo = null;
|
||||||
|
} else {
|
||||||
|
// If the secret is stored under more than one key, we just pick an arbitrary one
|
||||||
|
this.keyId = Object.keys(keys)[0];
|
||||||
|
this.keyInfo = keys[this.keyId];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
async useRecoveryKey() {
|
||||||
|
this.phase = PHASE_RECOVERY_KEY;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelUseRecoveryKey() {
|
||||||
|
this.phase = PHASE_INTRO;
|
||||||
|
this.emit("update");
|
||||||
|
}
|
||||||
|
|
||||||
|
async setupWithRecoveryKey(recoveryKey) {
|
||||||
|
this.startTrustCheck({[this.keyId]: recoveryKey});
|
||||||
|
}
|
||||||
|
|
||||||
async usePassPhrase() {
|
async usePassPhrase() {
|
||||||
|
this.startTrustCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
async startTrustCheck(withKeys) {
|
||||||
this.phase = PHASE_BUSY;
|
this.phase = PHASE_BUSY;
|
||||||
this.emit("update");
|
this.emit("update");
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
@ -84,6 +126,9 @@ export class SetupEncryptionStore extends EventEmitter {
|
||||||
// to advance before this.
|
// to advance before this.
|
||||||
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
await cli.restoreKeyBackupWithSecretStorage(backupInfo);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
withKeys,
|
||||||
|
passphraseOnly: true,
|
||||||
}).catch(reject);
|
}).catch(reject);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
Loading…
Reference in a new issue