Merge pull request #5307 from matrix-org/jryans/sso-4s-integration
Convert `src/SecurityManager.js` to TypeScript
This commit is contained in:
commit
26e63c23b6
4 changed files with 55 additions and 35 deletions
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "matrix-js-sdk/src/@types/global"; // load matrix-js-sdk's type extensions first
|
||||||
import * as ModernizrStatic from "modernizr";
|
import * as ModernizrStatic from "modernizr";
|
||||||
import ContentMessages from "../ContentMessages";
|
import ContentMessages from "../ContentMessages";
|
||||||
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
import { IMatrixClientPeg } from "../MatrixClientPeg";
|
||||||
|
|
|
@ -17,6 +17,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ICreateClientOpts } from 'matrix-js-sdk/src/matrix';
|
||||||
import {MatrixClient} from 'matrix-js-sdk/src/client';
|
import {MatrixClient} from 'matrix-js-sdk/src/client';
|
||||||
import {MemoryStore} from 'matrix-js-sdk/src/store/memory';
|
import {MemoryStore} from 'matrix-js-sdk/src/store/memory';
|
||||||
import * as utils from 'matrix-js-sdk/src/utils';
|
import * as utils from 'matrix-js-sdk/src/utils';
|
||||||
|
@ -249,8 +250,7 @@ class _MatrixClientPeg implements IMatrixClientPeg {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createClient(creds: IMatrixClientCreds): void {
|
private createClient(creds: IMatrixClientCreds): void {
|
||||||
// TODO: Make these opts typesafe with the js-sdk
|
const opts: ICreateClientOpts = {
|
||||||
const opts = {
|
|
||||||
baseUrl: creds.homeserverUrl,
|
baseUrl: creds.homeserverUrl,
|
||||||
idBaseUrl: creds.identityServerUrl,
|
idBaseUrl: creds.identityServerUrl,
|
||||||
accessToken: creds.accessToken,
|
accessToken: creds.accessToken,
|
||||||
|
|
|
@ -132,7 +132,7 @@ export class ModalManager {
|
||||||
public createTrackedDialogAsync<T extends any[]>(
|
public createTrackedDialogAsync<T extends any[]>(
|
||||||
analyticsAction: string,
|
analyticsAction: string,
|
||||||
analyticsInfo: string,
|
analyticsInfo: string,
|
||||||
...rest: Parameters<ModalManager["appendDialogAsync"]>
|
...rest: Parameters<ModalManager["createDialogAsync"]>
|
||||||
) {
|
) {
|
||||||
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
Analytics.trackEvent('Modal', analyticsAction, analyticsInfo);
|
||||||
return this.createDialogAsync<T>(...rest);
|
return this.createDialogAsync<T>(...rest);
|
||||||
|
|
|
@ -14,6 +14,8 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo } from 'matrix-js-sdk/src/matrix';
|
||||||
|
import { MatrixClient } from 'matrix-js-sdk/src/client';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import * as sdk from './index';
|
import * as sdk from './index';
|
||||||
import {MatrixClientPeg} from './MatrixClientPeg';
|
import {MatrixClientPeg} from './MatrixClientPeg';
|
||||||
|
@ -31,15 +33,18 @@ import SettingsStore from "./settings/SettingsStore";
|
||||||
// during the same single operation. Use `accessSecretStorage` below to scope a
|
// during the same single operation. Use `accessSecretStorage` below to scope a
|
||||||
// single secret storage operation, as it will clear the cached keys once the
|
// single secret storage operation, as it will clear the cached keys once the
|
||||||
// operation ends.
|
// operation ends.
|
||||||
let secretStorageKeys = {};
|
let secretStorageKeys: Record<string, Uint8Array> = {};
|
||||||
let secretStorageKeyInfo = {};
|
let secretStorageKeyInfo: Record<string, ISecretStorageKeyInfo> = {};
|
||||||
let secretStorageBeingAccessed = false;
|
let secretStorageBeingAccessed = false;
|
||||||
|
|
||||||
let nonInteractive = false;
|
let nonInteractive = false;
|
||||||
|
|
||||||
let dehydrationCache = {};
|
let dehydrationCache: {
|
||||||
|
key?: Uint8Array,
|
||||||
|
keyInfo?: ISecretStorageKeyInfo,
|
||||||
|
} = {};
|
||||||
|
|
||||||
function isCachingAllowed() {
|
function isCachingAllowed(): boolean {
|
||||||
return secretStorageBeingAccessed;
|
return secretStorageBeingAccessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +55,7 @@ function isCachingAllowed() {
|
||||||
*
|
*
|
||||||
* @returns {bool}
|
* @returns {bool}
|
||||||
*/
|
*/
|
||||||
export function isSecretStorageBeingAccessed() {
|
export function isSecretStorageBeingAccessed(): boolean {
|
||||||
return secretStorageBeingAccessed;
|
return secretStorageBeingAccessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +65,7 @@ export class AccessCancelledError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmToDismiss() {
|
async function confirmToDismiss(): Promise<boolean> {
|
||||||
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
|
||||||
const [sure] = await Modal.createDialog(QuestionDialog, {
|
const [sure] = await Modal.createDialog(QuestionDialog, {
|
||||||
title: _t("Cancel entering passphrase?"),
|
title: _t("Cancel entering passphrase?"),
|
||||||
|
@ -72,7 +77,9 @@ async function confirmToDismiss() {
|
||||||
return !sure;
|
return !sure;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeInputToKey(keyInfo) {
|
function makeInputToKey(
|
||||||
|
keyInfo: ISecretStorageKeyInfo,
|
||||||
|
): (keyParams: { passphrase: string, recoveryKey: string }) => Promise<Uint8Array> {
|
||||||
return async ({ passphrase, recoveryKey }) => {
|
return async ({ passphrase, recoveryKey }) => {
|
||||||
if (passphrase) {
|
if (passphrase) {
|
||||||
return deriveKey(
|
return deriveKey(
|
||||||
|
@ -86,7 +93,10 @@ function makeInputToKey(keyInfo) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
async function getSecretStorageKey(
|
||||||
|
{ keys: keyInfos }: { keys: Record<string, ISecretStorageKeyInfo> },
|
||||||
|
ssssItemName,
|
||||||
|
): Promise<[string, Uint8Array]> {
|
||||||
const keyInfoEntries = Object.entries(keyInfos);
|
const keyInfoEntries = Object.entries(keyInfos);
|
||||||
if (keyInfoEntries.length > 1) {
|
if (keyInfoEntries.length > 1) {
|
||||||
throw new Error("Multiple storage key requests not implemented");
|
throw new Error("Multiple storage key requests not implemented");
|
||||||
|
@ -100,7 +110,7 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
|
|
||||||
if (dehydrationCache.key) {
|
if (dehydrationCache.key) {
|
||||||
if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
|
if (await MatrixClientPeg.get().checkSecretStorageKey(dehydrationCache.key, keyInfo)) {
|
||||||
cacheSecretStorageKey(keyId, dehydrationCache.key, keyInfo);
|
cacheSecretStorageKey(keyId, keyInfo, dehydrationCache.key);
|
||||||
return [keyId, dehydrationCache.key];
|
return [keyId, dehydrationCache.key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,12 +149,15 @@ async function getSecretStorageKey({ keys: keyInfos }, ssssItemName) {
|
||||||
const key = await inputToKey(input);
|
const key = await inputToKey(input);
|
||||||
|
|
||||||
// Save to cache to avoid future prompts in the current session
|
// Save to cache to avoid future prompts in the current session
|
||||||
cacheSecretStorageKey(keyId, key, keyInfo);
|
cacheSecretStorageKey(keyId, keyInfo, key);
|
||||||
|
|
||||||
return [keyId, key];
|
return [keyId, key];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDehydrationKey(keyInfo, checkFunc) {
|
export async function getDehydrationKey(
|
||||||
|
keyInfo: ISecretStorageKeyInfo,
|
||||||
|
checkFunc: (Uint8Array) => void,
|
||||||
|
): Promise<Uint8Array> {
|
||||||
const inputToKey = makeInputToKey(keyInfo);
|
const inputToKey = makeInputToKey(keyInfo);
|
||||||
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
const { finished } = Modal.createTrackedDialog("Access Secret Storage dialog", "",
|
||||||
AccessSecretStorageDialog,
|
AccessSecretStorageDialog,
|
||||||
|
@ -185,20 +198,24 @@ export async function getDehydrationKey(keyInfo, checkFunc) {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cacheSecretStorageKey(keyId, key, keyInfo) {
|
function cacheSecretStorageKey(
|
||||||
|
keyId: string,
|
||||||
|
keyInfo: ISecretStorageKeyInfo,
|
||||||
|
key: Uint8Array,
|
||||||
|
): void {
|
||||||
if (isCachingAllowed()) {
|
if (isCachingAllowed()) {
|
||||||
secretStorageKeys[keyId] = key;
|
secretStorageKeys[keyId] = key;
|
||||||
secretStorageKeyInfo[keyId] = keyInfo;
|
secretStorageKeyInfo[keyId] = keyInfo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSecretRequested = async function({
|
async function onSecretRequested(
|
||||||
user_id: userId,
|
userId: string,
|
||||||
device_id: deviceId,
|
deviceId: string,
|
||||||
request_id: requestId,
|
requestId: string,
|
||||||
name,
|
name: string,
|
||||||
device_trust: deviceTrust,
|
deviceTrust: IDeviceTrustLevel,
|
||||||
}) {
|
): Promise<string> {
|
||||||
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
|
console.log("onSecretRequested", userId, deviceId, requestId, name, deviceTrust);
|
||||||
const client = MatrixClientPeg.get();
|
const client = MatrixClientPeg.get();
|
||||||
if (userId !== client.getUserId()) {
|
if (userId !== client.getUserId()) {
|
||||||
|
@ -233,16 +250,16 @@ const onSecretRequested = async function({
|
||||||
return key && encodeBase64(key);
|
return key && encodeBase64(key);
|
||||||
}
|
}
|
||||||
console.warn("onSecretRequested didn't recognise the secret named ", name);
|
console.warn("onSecretRequested didn't recognise the secret named ", name);
|
||||||
};
|
}
|
||||||
|
|
||||||
export const crossSigningCallbacks = {
|
export const crossSigningCallbacks: ICryptoCallbacks = {
|
||||||
getSecretStorageKey,
|
getSecretStorageKey,
|
||||||
cacheSecretStorageKey,
|
cacheSecretStorageKey,
|
||||||
onSecretRequested,
|
onSecretRequested,
|
||||||
getDehydrationKey,
|
getDehydrationKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function promptForBackupPassphrase() {
|
export async function promptForBackupPassphrase(): Promise<Uint8Array> {
|
||||||
let key;
|
let key;
|
||||||
|
|
||||||
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
const { finished } = Modal.createTrackedDialog('Restore Backup', '', RestoreKeyBackupDialog, {
|
||||||
|
@ -292,7 +309,7 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
/* priority = */ false,
|
/* priority = */ false,
|
||||||
/* static = */ true,
|
/* static = */ true,
|
||||||
/* options = */ {
|
/* options = */ {
|
||||||
onBeforeClose(reason) {
|
onBeforeClose: async (reason) => {
|
||||||
// If Secure Backup is required, you cannot leave the modal.
|
// If Secure Backup is required, you cannot leave the modal.
|
||||||
if (reason === "backgroundClick") {
|
if (reason === "backgroundClick") {
|
||||||
return !isSecureBackupRequired();
|
return !isSecureBackupRequired();
|
||||||
|
@ -329,10 +346,10 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
|
|
||||||
const keyId = Object.keys(secretStorageKeys)[0];
|
const keyId = Object.keys(secretStorageKeys)[0];
|
||||||
if (keyId && SettingsStore.getValue("feature_dehydration")) {
|
if (keyId && SettingsStore.getValue("feature_dehydration")) {
|
||||||
const dehydrationKeyInfo =
|
let dehydrationKeyInfo = {};
|
||||||
secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase
|
if (secretStorageKeyInfo[keyId] && secretStorageKeyInfo[keyId].passphrase) {
|
||||||
? {passphrase: secretStorageKeyInfo[keyId].passphrase}
|
dehydrationKeyInfo = { passphrase: secretStorageKeyInfo[keyId].passphrase };
|
||||||
: {};
|
}
|
||||||
console.log("Setting dehydration key");
|
console.log("Setting dehydration key");
|
||||||
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
|
await cli.setDehydrationKey(secretStorageKeys[keyId], dehydrationKeyInfo, "Backup device");
|
||||||
} else {
|
} else {
|
||||||
|
@ -354,7 +371,9 @@ export async function accessSecretStorage(func = async () => { }, forceReset = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this function name is a bit of a mouthful
|
// FIXME: this function name is a bit of a mouthful
|
||||||
export async function tryToUnlockSecretStorageWithDehydrationKey(client) {
|
export async function tryToUnlockSecretStorageWithDehydrationKey(
|
||||||
|
client: MatrixClient,
|
||||||
|
): Promise<void> {
|
||||||
const key = dehydrationCache.key;
|
const key = dehydrationCache.key;
|
||||||
let restoringBackup = false;
|
let restoringBackup = false;
|
||||||
if (key && await client.isSecretStorageReady()) {
|
if (key && await client.isSecretStorageReady()) {
|
||||||
|
@ -366,10 +385,10 @@ export async function tryToUnlockSecretStorageWithDehydrationKey(client) {
|
||||||
|
|
||||||
// we also need to set a new dehydrated device to replace the
|
// we also need to set a new dehydrated device to replace the
|
||||||
// device we rehydrated
|
// device we rehydrated
|
||||||
const dehydrationKeyInfo =
|
let dehydrationKeyInfo = {};
|
||||||
dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase
|
if (dehydrationCache.keyInfo && dehydrationCache.keyInfo.passphrase) {
|
||||||
? {passphrase: dehydrationCache.keyInfo.passphrase}
|
dehydrationKeyInfo = { passphrase: dehydrationCache.keyInfo.passphrase };
|
||||||
: {};
|
}
|
||||||
await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device");
|
await client.setDehydrationKey(key, dehydrationKeyInfo, "Backup device");
|
||||||
|
|
||||||
// and restore from backup
|
// and restore from backup
|
Loading…
Reference in a new issue