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