use random pickle key on all platforms, and store access token encrypted in IDB
This commit is contained in:
parent
5f4320e80d
commit
753ec9e45a
4 changed files with 219 additions and 26 deletions
|
@ -18,6 +18,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {MatrixClient} from "matrix-js-sdk/src/client";
|
import {MatrixClient} from "matrix-js-sdk/src/client";
|
||||||
|
import {encodeUnpaddedBase64} from "matrix-js-sdk/src/crypto/olmlib";
|
||||||
import dis from './dispatcher/dispatcher';
|
import dis from './dispatcher/dispatcher';
|
||||||
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
import BaseEventIndexManager from './indexing/BaseEventIndexManager';
|
||||||
import {ActionPayload} from "./dispatcher/payloads";
|
import {ActionPayload} from "./dispatcher/payloads";
|
||||||
|
@ -25,6 +26,7 @@ import {CheckUpdatesPayload} from "./dispatcher/payloads/CheckUpdatesPayload";
|
||||||
import {Action} from "./dispatcher/actions";
|
import {Action} from "./dispatcher/actions";
|
||||||
import {hideToast as hideUpdateToast} from "./toasts/UpdateToast";
|
import {hideToast as hideUpdateToast} from "./toasts/UpdateToast";
|
||||||
import {MatrixClientPeg} from "./MatrixClientPeg";
|
import {MatrixClientPeg} from "./MatrixClientPeg";
|
||||||
|
import {idbLoad, idbSave, idbDelete} from "./IndexedDB";
|
||||||
|
|
||||||
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
|
export const SSO_HOMESERVER_URL_KEY = "mx_sso_hs_url";
|
||||||
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
|
export const SSO_ID_SERVER_URL_KEY = "mx_sso_is_url";
|
||||||
|
@ -273,8 +275,41 @@ export default abstract class BasePlatform {
|
||||||
* pickle key has been stored.
|
* pickle key has been stored.
|
||||||
*/
|
*/
|
||||||
async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
async getPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||||
|
if (!window.crypto || !window.crypto.subtle) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await idbLoad("pickleKey", [userId, deviceId]);
|
||||||
|
} catch (e) {}
|
||||||
|
if (!data) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!data.encrypted || !data.iv || !data.cryptoKey) {
|
||||||
|
console.error("Badly formatted pickle key");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const additionalData = new Uint8Array(userId.length + deviceId.length + 1);
|
||||||
|
for (let i = 0; i < userId.length; i++) {
|
||||||
|
additionalData[i] = userId.charCodeAt(i);
|
||||||
|
}
|
||||||
|
additionalData[userId.length] = 124; // "|"
|
||||||
|
for (let i = 0; i < deviceId.length; i++) {
|
||||||
|
additionalData[userId.length + 1 + i] = deviceId.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const key = await crypto.subtle.decrypt(
|
||||||
|
{name: "AES-GCM", iv: data.iv, additionalData}, data.cryptoKey,
|
||||||
|
data.encrypted,
|
||||||
|
);
|
||||||
|
return encodeUnpaddedBase64(key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error decrypting pickle key");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create and store a pickle key for encrypting libolm objects.
|
* Create and store a pickle key for encrypting libolm objects.
|
||||||
|
@ -284,8 +319,34 @@ export default abstract class BasePlatform {
|
||||||
* support storing pickle keys.
|
* support storing pickle keys.
|
||||||
*/
|
*/
|
||||||
async createPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
async createPickleKey(userId: string, deviceId: string): Promise<string | null> {
|
||||||
|
if (!window.crypto || !window.crypto.subtle) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const crypto = window.crypto;
|
||||||
|
const randomArray = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(randomArray);
|
||||||
|
const cryptoKey = await crypto.subtle.generateKey(
|
||||||
|
{name: "AES-GCM", length: 256}, false, ["encrypt", "decrypt"],
|
||||||
|
);
|
||||||
|
const iv = new Uint8Array(32);
|
||||||
|
crypto.getRandomValues(iv);
|
||||||
|
|
||||||
|
const additionalData = new Uint8Array(userId.length + deviceId.length + 1);
|
||||||
|
for (let i = 0; i < userId.length; i++) {
|
||||||
|
additionalData[i] = userId.charCodeAt(i);
|
||||||
|
}
|
||||||
|
additionalData[userId.length] = 124; // "|"
|
||||||
|
for (let i = 0; i < deviceId.length; i++) {
|
||||||
|
additionalData[userId.length + 1 + i] = deviceId.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const encrypted = await crypto.subtle.encrypt(
|
||||||
|
{name: "AES-GCM", iv, additionalData}, cryptoKey, randomArray,
|
||||||
|
);
|
||||||
|
|
||||||
|
await idbSave("pickleKey", [userId, deviceId], {encrypted, iv, cryptoKey});
|
||||||
|
return encodeUnpaddedBase64(randomArray);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a previously stored pickle key from storage.
|
* Delete a previously stored pickle key from storage.
|
||||||
|
@ -293,5 +354,8 @@ export default abstract class BasePlatform {
|
||||||
* @param {string} userId the device ID that the pickle key is for.
|
* @param {string} userId the device ID that the pickle key is for.
|
||||||
*/
|
*/
|
||||||
async destroyPickleKey(userId: string, deviceId: string): Promise<void> {
|
async destroyPickleKey(userId: string, deviceId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
await idbDelete("pickleKey", [userId, deviceId]);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
92
src/IndexedDB.ts
Normal file
92
src/IndexedDB.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Simple wrapper around IndexedDB.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let idb = null;
|
||||||
|
|
||||||
|
async function idbInit(): Promise<void> {
|
||||||
|
idb = await new Promise((resolve, reject) => {
|
||||||
|
const request = window.indexedDB.open("element", 1);
|
||||||
|
request.onerror = reject;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: TS thinks target.result doesn't exist
|
||||||
|
request.onsuccess = (event) => { resolve(event.target.result); };
|
||||||
|
request.onupgradeneeded = (event) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: TS thinks target.result doesn't exist
|
||||||
|
const db = event.target.result;
|
||||||
|
db.createObjectStore("pickleKey");
|
||||||
|
db.createObjectStore("account");
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbLoad(
|
||||||
|
table: string,
|
||||||
|
key: string | string[],
|
||||||
|
): Promise<any> {
|
||||||
|
if (!idb) {
|
||||||
|
await idbInit();
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const txn = idb.transaction([table], "readonly");
|
||||||
|
txn.onerror = reject;
|
||||||
|
|
||||||
|
const objectStore = txn.objectStore(table);
|
||||||
|
const request = objectStore.get(key);
|
||||||
|
request.onerror = reject;
|
||||||
|
request.onsuccess = (event) => { resolve(request.result); };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbSave(
|
||||||
|
table: string,
|
||||||
|
key: string | string[],
|
||||||
|
data: any,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!idb) {
|
||||||
|
await idbInit();
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const txn = idb.transaction([table], "readwrite");
|
||||||
|
txn.onerror = reject;
|
||||||
|
|
||||||
|
const objectStore = txn.objectStore(table);
|
||||||
|
const request = objectStore.put(data, key);
|
||||||
|
request.onerror = reject;
|
||||||
|
request.onsuccess = (event) => { resolve(); };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function idbDelete(
|
||||||
|
table: string,
|
||||||
|
key: string | string[],
|
||||||
|
): Promise<void> {
|
||||||
|
if (!idb) {
|
||||||
|
await idbInit();
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const txn = idb.transaction([table], "readwrite");
|
||||||
|
txn.onerror = reject;
|
||||||
|
|
||||||
|
const objectStore = txn.objectStore(table);
|
||||||
|
const request = objectStore.delete(key);
|
||||||
|
request.onerror = reject;
|
||||||
|
request.onsuccess = (event) => { resolve(); };
|
||||||
|
});
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
import { InvalidStoreError } from "matrix-js-sdk/src/errors";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
|
import { MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
|
import {decryptAES, encryptAES} from "matrix-js-sdk/src/crypto/aes";
|
||||||
|
|
||||||
import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
|
import {IMatrixClientCreds, MatrixClientPeg} from './MatrixClientPeg';
|
||||||
import SecurityCustomisations from "./customisations/Security";
|
import SecurityCustomisations from "./customisations/Security";
|
||||||
|
@ -50,6 +51,7 @@ import ThreepidInviteStore from "./stores/ThreepidInviteStore";
|
||||||
import CountlyAnalytics from "./CountlyAnalytics";
|
import CountlyAnalytics from "./CountlyAnalytics";
|
||||||
import CallHandler from './CallHandler';
|
import CallHandler from './CallHandler';
|
||||||
import LifecycleCustomisations from "./customisations/Lifecycle";
|
import LifecycleCustomisations from "./customisations/Lifecycle";
|
||||||
|
import {idbLoad, idbSave, idbDelete} from "./IndexedDB";
|
||||||
|
|
||||||
const HOMESERVER_URL_KEY = "mx_hs_url";
|
const HOMESERVER_URL_KEY = "mx_hs_url";
|
||||||
const ID_SERVER_URL_KEY = "mx_is_url";
|
const ID_SERVER_URL_KEY = "mx_is_url";
|
||||||
|
@ -147,20 +149,13 @@ export async function loadSession(opts: ILoadSessionOpts = {}): Promise<boolean>
|
||||||
* Gets the user ID of the persisted session, if one exists. This does not validate
|
* Gets the user ID of the persisted session, if one exists. This does not validate
|
||||||
* that the user's credentials still work, just that they exist and that a user ID
|
* that the user's credentials still work, just that they exist and that a user ID
|
||||||
* is associated with them. The session is not loaded.
|
* is associated with them. The session is not loaded.
|
||||||
* @returns {String} The persisted session's owner, if an owner exists. Null otherwise.
|
* @returns {[String, bool]} The persisted session's owner and whether the stored
|
||||||
|
* session is for a guest user, if an owner exists. If there is no stored session,
|
||||||
|
* return [null, null].
|
||||||
*/
|
*/
|
||||||
export function getStoredSessionOwner(): string {
|
export async function getStoredSessionOwner(): Promise<[string, boolean]> {
|
||||||
const {hsUrl, userId, accessToken} = getLocalStorageSessionVars();
|
const {hsUrl, userId, accessToken, isGuest} = await getLocalStorageSessionVars();
|
||||||
return hsUrl && userId && accessToken ? userId : null;
|
return hsUrl && userId && accessToken ? [userId, isGuest] : [null, null];
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {bool} True if the stored session is for a guest user or false if it is
|
|
||||||
* for a real user. If there is no stored session, return null.
|
|
||||||
*/
|
|
||||||
export function getStoredSessionIsGuest(): boolean {
|
|
||||||
const sessVars = getLocalStorageSessionVars();
|
|
||||||
return sessVars.hsUrl && sessVars.userId && sessVars.accessToken ? sessVars.isGuest : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -197,8 +192,8 @@ export function attemptTokenLogin(
|
||||||
},
|
},
|
||||||
).then(function(creds) {
|
).then(function(creds) {
|
||||||
console.log("Logged in with token");
|
console.log("Logged in with token");
|
||||||
return clearStorage().then(() => {
|
return clearStorage().then(async () => {
|
||||||
persistCredentialsToLocalStorage(creds);
|
await persistCredentialsToLocalStorage(creds);
|
||||||
// remember that we just logged in
|
// remember that we just logged in
|
||||||
sessionStorage.setItem("mx_fresh_login", String(true));
|
sessionStorage.setItem("mx_fresh_login", String(true));
|
||||||
return true;
|
return true;
|
||||||
|
@ -290,10 +285,17 @@ export interface ILocalStorageSession {
|
||||||
* may not be valid, as it is not tested for consistency here.
|
* may not be valid, as it is not tested for consistency here.
|
||||||
* @returns {Object} Information about the session - see implementation for variables.
|
* @returns {Object} Information about the session - see implementation for variables.
|
||||||
*/
|
*/
|
||||||
export function getLocalStorageSessionVars(): ILocalStorageSession {
|
export async function getLocalStorageSessionVars(): Promise<ILocalStorageSession> {
|
||||||
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY);
|
||||||
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY);
|
||||||
const accessToken = localStorage.getItem("mx_access_token");
|
let accessToken = await idbLoad("account", "mx_access_token");
|
||||||
|
if (!accessToken) {
|
||||||
|
accessToken = localStorage.getItem("mx_access_token");
|
||||||
|
if (accessToken) {
|
||||||
|
await idbSave("account", "mx_access_token", accessToken);
|
||||||
|
localStorage.removeItem("mx_access_token");
|
||||||
|
}
|
||||||
|
}
|
||||||
const userId = localStorage.getItem("mx_user_id");
|
const userId = localStorage.getItem("mx_user_id");
|
||||||
const deviceId = localStorage.getItem("mx_device_id");
|
const deviceId = localStorage.getItem("mx_device_id");
|
||||||
|
|
||||||
|
@ -308,6 +310,30 @@ export function getLocalStorageSessionVars(): ILocalStorageSession {
|
||||||
return {hsUrl, isUrl, accessToken, userId, deviceId, isGuest};
|
return {hsUrl, isUrl, accessToken, userId, deviceId, isGuest};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The pickle key is a string of unspecified length and format. For AES, we
|
||||||
|
// need a 256-bit Uint8Array. So we HKDF the pickle key to generate the AES
|
||||||
|
// key. The AES key should be zeroed after it is used.
|
||||||
|
async function pickleKeyToAesKey(pickleKey: string): Promise<Uint8Array> {
|
||||||
|
const pickleKeyBuffer = new Uint8Array(pickleKey.length);
|
||||||
|
for (let i = 0; i < pickleKey.length; i++) {
|
||||||
|
pickleKeyBuffer[i] = pickleKey.charCodeAt(i);
|
||||||
|
}
|
||||||
|
const hkdfKey = await window.crypto.subtle.importKey(
|
||||||
|
"raw", pickleKeyBuffer, "HKDF", false, ["deriveBits"],
|
||||||
|
);
|
||||||
|
pickleKeyBuffer.fill(0);
|
||||||
|
return new Uint8Array(await window.crypto.subtle.deriveBits(
|
||||||
|
{
|
||||||
|
name: "HKDF", hash: "SHA-256",
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/879
|
||||||
|
salt: new Uint8Array(32), info: new Uint8Array(0),
|
||||||
|
},
|
||||||
|
hkdfKey,
|
||||||
|
256,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// returns a promise which resolves to true if a session is found in
|
// returns a promise which resolves to true if a session is found in
|
||||||
// localstorage
|
// localstorage
|
||||||
//
|
//
|
||||||
|
@ -325,7 +351,7 @@ async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promis
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {hsUrl, isUrl, accessToken, userId, deviceId, isGuest} = getLocalStorageSessionVars();
|
const {hsUrl, isUrl, accessToken, userId, deviceId, isGuest} = await getLocalStorageSessionVars();
|
||||||
|
|
||||||
if (accessToken && userId && hsUrl) {
|
if (accessToken && userId && hsUrl) {
|
||||||
if (ignoreGuest && isGuest) {
|
if (ignoreGuest && isGuest) {
|
||||||
|
@ -333,9 +359,15 @@ async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promis
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let decryptedAccessToken = accessToken;
|
||||||
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
const pickleKey = await PlatformPeg.get().getPickleKey(userId, deviceId);
|
||||||
if (pickleKey) {
|
if (pickleKey) {
|
||||||
console.log("Got pickle key");
|
console.log("Got pickle key");
|
||||||
|
if (typeof accessToken !== "string") {
|
||||||
|
const encrKey = await pickleKeyToAesKey(pickleKey);
|
||||||
|
decryptedAccessToken = await decryptAES(accessToken, encrKey, "access_token");
|
||||||
|
encrKey.fill(0);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("No pickle key available");
|
console.log("No pickle key available");
|
||||||
}
|
}
|
||||||
|
@ -347,7 +379,7 @@ async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }): Promis
|
||||||
await doSetLoggedIn({
|
await doSetLoggedIn({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
accessToken: accessToken,
|
accessToken: decryptedAccessToken,
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
|
@ -516,7 +548,7 @@ async function doSetLoggedIn(
|
||||||
|
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
try {
|
try {
|
||||||
persistCredentialsToLocalStorage(credentials);
|
await persistCredentialsToLocalStorage(credentials);
|
||||||
// make sure we don't think that it's a fresh login any more
|
// make sure we don't think that it's a fresh login any more
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -545,18 +577,22 @@ function showStorageEvictedDialog(): Promise<boolean> {
|
||||||
// `instanceof`. Babel 7 supports this natively in their class handling.
|
// `instanceof`. Babel 7 supports this natively in their class handling.
|
||||||
class AbortLoginAndRebuildStorage extends Error { }
|
class AbortLoginAndRebuildStorage extends Error { }
|
||||||
|
|
||||||
function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): void {
|
async function persistCredentialsToLocalStorage(credentials: IMatrixClientCreds): Promise<void> {
|
||||||
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
|
localStorage.setItem(HOMESERVER_URL_KEY, credentials.homeserverUrl);
|
||||||
if (credentials.identityServerUrl) {
|
if (credentials.identityServerUrl) {
|
||||||
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
|
localStorage.setItem(ID_SERVER_URL_KEY, credentials.identityServerUrl);
|
||||||
}
|
}
|
||||||
localStorage.setItem("mx_user_id", credentials.userId);
|
localStorage.setItem("mx_user_id", credentials.userId);
|
||||||
localStorage.setItem("mx_access_token", credentials.accessToken);
|
|
||||||
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
localStorage.setItem("mx_is_guest", JSON.stringify(credentials.guest));
|
||||||
|
|
||||||
if (credentials.pickleKey) {
|
if (credentials.pickleKey) {
|
||||||
|
const encrKey = await pickleKeyToAesKey(credentials.pickleKey);
|
||||||
|
const encryptedAccessToken = await encryptAES(credentials.accessToken, encrKey, "access_token");
|
||||||
|
encrKey.fill(0);
|
||||||
|
await idbSave("account", "mx_access_token", encryptedAccessToken);
|
||||||
localStorage.setItem("mx_has_pickle_key", String(true));
|
localStorage.setItem("mx_has_pickle_key", String(true));
|
||||||
} else {
|
} else {
|
||||||
|
await idbSave("account", "mx_access_token", credentials.accessToken);
|
||||||
if (localStorage.getItem("mx_has_pickle_key")) {
|
if (localStorage.getItem("mx_has_pickle_key")) {
|
||||||
console.error("Expected a pickle key, but none provided. Encryption may not work.");
|
console.error("Expected a pickle key, but none provided. Encryption may not work.");
|
||||||
}
|
}
|
||||||
|
@ -733,6 +769,8 @@ async function clearStorage(opts?: { deleteEverything?: boolean }): Promise<void
|
||||||
|
|
||||||
window.localStorage.clear();
|
window.localStorage.clear();
|
||||||
|
|
||||||
|
await idbDelete("account", "mx_access_token");
|
||||||
|
|
||||||
// now restore those invites
|
// now restore those invites
|
||||||
if (!opts?.deleteEverything) {
|
if (!opts?.deleteEverything) {
|
||||||
pendingInvites.forEach(i => {
|
pendingInvites.forEach(i => {
|
||||||
|
|
|
@ -325,8 +325,7 @@ export default class Registration extends React.Component<IProps, IState> {
|
||||||
// isn't a guest user since we'll usually have set a guest user session before
|
// isn't a guest user since we'll usually have set a guest user session before
|
||||||
// starting the registration process. This isn't perfect since it's possible
|
// starting the registration process. This isn't perfect since it's possible
|
||||||
// the user had a separate guest session they didn't actually mean to replace.
|
// the user had a separate guest session they didn't actually mean to replace.
|
||||||
const sessionOwner = Lifecycle.getStoredSessionOwner();
|
const [sessionOwner, sessionIsGuest] = await Lifecycle.getStoredSessionOwner();
|
||||||
const sessionIsGuest = Lifecycle.getStoredSessionIsGuest();
|
|
||||||
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.userId) {
|
if (sessionOwner && !sessionIsGuest && sessionOwner !== response.userId) {
|
||||||
console.log(
|
console.log(
|
||||||
`Found a session for ${sessionOwner} but ${response.userId} has just registered.`,
|
`Found a session for ${sessionOwner} but ${response.userId} has just registered.`,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue