Merge pull request #174 from vector-im/bwindels/fix-offline-session-backup-init

Fix session backup init breaking offline usage
This commit is contained in:
Bruno Windels 2020-10-23 11:10:50 +00:00 committed by GitHub
commit 8125173420
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 25 deletions

View file

@ -23,6 +23,9 @@ export class SessionBackupViewModel extends ViewModel {
this._showKeySetup = true;
this._error = null;
this._isBusy = false;
this.track(this._session.hasSecretStorageKey.subscribe(() => {
this.emitChange("status");
}));
}
get isBusy() {
@ -37,7 +40,11 @@ export class SessionBackupViewModel extends ViewModel {
if (this._session.sessionBackup) {
return "enabled";
} else {
return this._showKeySetup ? "setupKey" : "setupPhrase";
if (this._session.hasSecretStorageKey.get() === false) {
return this._showKeySetup ? "setupKey" : "setupPhrase";
} else {
return "pending";
}
}
}

View file

@ -62,6 +62,8 @@ export class Session {
this._getSyncToken = () => this.syncToken;
this._olmWorker = olmWorker;
this._cryptoDriver = cryptoDriver;
this._sessionBackup = null;
this._hasSecretStorageKey = new ObservableValue(null);
if (olm) {
this._olmUtil = new olm.Utility();
@ -81,6 +83,10 @@ export class Session {
return this._e2eeAccount?.identityKeys.ed25519;
}
get hasSecretStorageKey() {
return this._hasSecretStorageKey;
}
get deviceId() {
return this._sessionInfo.deviceId;
}
@ -92,6 +98,8 @@ export class Session {
// called once this._e2eeAccount is assigned
_setupEncryption() {
console.log("loaded e2ee account with keys", this._e2eeAccount.identityKeys);
// TODO: this should all go in a wrapper in e2ee/ that is bootstrapped by passing in the account
// and can create RoomEncryption objects and handle encrypted to_device messages and device list changes.
const senderKeyLock = new LockMap();
const olmDecryption = new OlmDecryption({
account: this._e2eeAccount,
@ -174,6 +182,9 @@ export class Session {
if (!this._olm) {
throw new Error("olm required");
}
if (this._sessionBackup) {
return false;
}
const key = await ssssKeyFromCredential(type, credential, this._storage, this._cryptoDriver, this._olm);
// and create session backup, which needs to read from accountData
const readTxn = this._storage.readTxn([
@ -192,6 +203,7 @@ export class Session {
throw err;
}
await writeTxn.complete();
this._hasSecretStorageKey.set(true);
}
async _createSessionBackup(ssssKey, txn) {
@ -211,12 +223,9 @@ export class Session {
return this._sessionBackup;
}
// called after load
async beforeFirstSync(isNewLogin) {
/** @internal */
async createIdentity() {
if (this._olm) {
if (isNewLogin && this._e2eeAccount) {
throw new Error("there should not be an e2ee account already on a fresh login");
}
if (!this._e2eeAccount) {
this._e2eeAccount = await E2EEAccount.create({
hsApi: this._hsApi,
@ -231,21 +240,10 @@ export class Session {
}
await this._e2eeAccount.generateOTKsIfNeeded(this._storage);
await this._e2eeAccount.uploadKeys(this._storage);
await this._deviceMessageHandler.decryptPending(this.rooms);
const txn = this._storage.readTxn([
this._storage.storeNames.session,
this._storage.storeNames.accountData,
]);
// try set up session backup if we stored the ssss key
const ssssKey = await ssssReadKey(txn);
if (ssssKey) {
// txn will end here as this does a network request
await this._createSessionBackup(ssssKey, txn);
}
}
}
/** @internal */
async load() {
const txn = this._storage.readTxn([
this._storage.storeNames.session,
@ -289,6 +287,12 @@ export class Session {
}
}
/**
* @internal called when coming back online
* @param {Object} lastVersionResponse a response from /versions, which is polled while offline,
* and useful to store so we can later tell what capabilities
* our homeserver has.
*/
async start(lastVersionResponse) {
if (lastVersionResponse) {
// store /versions response
@ -299,7 +303,21 @@ export class Session {
// TODO: what can we do if this throws?
await txn.complete();
}
// enable session backup, this requests the latest backup version
if (!this._sessionBackup) {
const txn = this._storage.readTxn([
this._storage.storeNames.session,
this._storage.storeNames.accountData,
]);
// try set up session backup if we stored the ssss key
const ssssKey = await ssssReadKey(txn);
if (ssssKey) {
// txn will end here as this does a network request
await this._createSessionBackup(ssssKey, txn);
}
this._hasSecretStorageKey.set(!!ssssKey);
}
// restore unfinished operations, like sending out room keys
const opsTxn = this._storage.readWriteTxn([
this._storage.storeNames.operations
]);
@ -333,6 +351,7 @@ export class Session {
return this._rooms;
}
/** @internal */
createRoom(roomId, pendingEvents) {
const room = new Room({
roomId,
@ -350,6 +369,7 @@ export class Session {
return room;
}
/** @internal */
async writeSync(syncResponse, syncFilterId, txn) {
const changes = {
syncInfo: null,
@ -394,6 +414,7 @@ export class Session {
return changes;
}
/** @internal */
afterSync({syncInfo, e2eeAccountChanges}) {
if (syncInfo) {
// sync transaction succeeded, modify object state now
@ -404,6 +425,7 @@ export class Session {
}
}
/** @internal */
async afterSyncCompleted(changes, isCatchupSync) {
const promises = [];
if (changes.deviceMessageDecryptionPending) {
@ -425,10 +447,12 @@ export class Session {
}
}
/** @internal */
get syncToken() {
return this._syncInfo?.token;
}
/** @internal */
get syncFilterId() {
return this._syncInfo?.filterId;
}

View file

@ -179,8 +179,10 @@ export class SessionContainer {
mediaRepository: new MediaRepository(sessionInfo.homeServer)
});
await this._session.load();
this._status.set(LoadStatus.SessionSetup);
await this._session.beforeFirstSync(isNewLogin);
if (isNewLogin) {
this._status.set(LoadStatus.SessionSetup);
await this._session.createIdentity();
}
this._sync = new Sync({hsApi: this._requestScheduler.hsApi, storage: this._storage, session: this._session});
// notify sync and session when back online

View file

@ -84,16 +84,17 @@ function isCacheableThumbnail(url) {
return false;
}
const baseURL = new URL(self.registration.scope);
async function handleRequest(request) {
const baseURL = self.registration.scope;
if (request.url === baseURL) {
request = new Request(new URL("index.html", baseURL));
const url = new URL(request.url);
if (url.origin === baseURL.origin && url.pathname === baseURL.pathname) {
request = new Request(new URL("index.html", baseURL.href));
}
let response = await readCache(request);
if (!response) {
// use cors so the resource in the cache isn't opaque and uses up to 7mb
// https://developers.google.com/web/tools/chrome-devtools/progressive-web-apps?utm_source=devtools#opaque-responses
if (isCacheableThumbnail(new URL(request.url))) {
if (isCacheableThumbnail(url)) {
response = await fetch(request, {mode: "cors", credentials: "omit"});
} else {
response = await fetch(request);

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import {TemplateView} from "../../general/TemplateView.js";
import {StaticView} from "../../general/StaticView.js";
export class SessionBackupSettingsView extends TemplateView {
render(t, vm) {
@ -23,6 +24,7 @@ export class SessionBackupSettingsView extends TemplateView {
case "enabled": return new TemplateView(vm, renderEnabled)
case "setupKey": return new TemplateView(vm, renderEnableFromKey)
case "setupPhrase": return new TemplateView(vm, renderEnableFromPhrase)
case "pending": return new StaticView(vm, t => t.p(vm.i18n`Waiting to go online…`))
}
});
}