Skip to content

Commit

Permalink
Remove all user data on logging out. (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
seanwu1105 authored Feb 19, 2021
1 parent c72a72b commit c972579
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 57 deletions.
22 changes: 16 additions & 6 deletions src/app/features/profile/profile.page.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
import { Plugins } from '@capacitor/core';
import { ToastController } from '@ionic/angular';
import { TranslocoService } from '@ngneat/transloco';
Expand All @@ -9,7 +8,10 @@ import { defer } from 'rxjs';
import { catchError, concatMapTo } from 'rxjs/operators';
import { BlockingActionService } from '../../shared/services/blocking-action/blocking-action.service';
import { WebCryptoApiSignatureProvider } from '../../shared/services/collector/signature/web-crypto-api-signature-provider/web-crypto-api-signature-provider.service';
import { Database } from '../../shared/services/database/database.service';
import { DiaBackendAuthService } from '../../shared/services/dia-backend/auth/dia-backend-auth.service';
import { ImageStore } from '../../shared/services/image-store/image-store.service';
import { PreferenceManager } from '../../shared/services/preference-manager/preference-manager.service';

const { Clipboard } = Plugins;

Expand All @@ -26,7 +28,9 @@ export class ProfilePage {
readonly privateKey$ = this.webCryptoApiSignatureProvider.getPrivateKey$();

constructor(
private readonly router: Router,
private readonly database: Database,
private readonly preferenceManager: PreferenceManager,
private readonly imageStore: ImageStore,
private readonly blockingActionService: BlockingActionService,
private readonly toastController: ToastController,
private readonly translocoService: TranslocoService,
Expand All @@ -44,18 +48,24 @@ export class ProfilePage {

logout() {
const action$ = this.diaBackendAuthService.logout$().pipe(
concatMapTo(defer(() => this.router.navigate(['/login']))),
concatMapTo(defer(() => this.imageStore.clear())),
concatMapTo(defer(() => this.database.clear())),
concatMapTo(defer(() => this.preferenceManager.clear())),
concatMapTo(defer(reloadApp)),
catchError(err =>
this.toastController
.create({ message: JSON.stringify(err.error), duration: 4000 })
.then(toast => toast.present())
)
);
this.blockingActionService
.run$(action$, {
message: this.translocoService.translate('talkingToTheServer'),
})
.run$(action$)
.pipe(untilDestroyed(this))
.subscribe();
}
}

// Reload the app to force app to re-run the initialization in AppModule.
function reloadApp() {
location.href = 'index.html';
}
10 changes: 10 additions & 0 deletions src/app/shared/services/database/database.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,14 @@ describe('Database', () => {
const id = 'id';
expect(database.getTable(id)).toBe(database.getTable(id));
});

it('should clear all tables', async () => {
const id = 'id';
const table = database.getTable(id);
await table.insert([{ a: 1 }]);

await database.clear();

expect(await table.queryAll()).toEqual([]);
});
});
4 changes: 4 additions & 0 deletions src/app/shared/services/database/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ export class Database {
this.tables.set(id, created);
return created;
}

async clear() {
return Promise.all([...this.tables.values()].map(table => table.clear()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,52 @@ describe('CapacitorFilesystemTable', () => {
});
});

it('should wipe all data after clear', async () => {
await table.insert([TUPLE1, TUPLE2]);

await table.clear();

expect(await table.queryAll()).toEqual([]);
});

it('should be able to reinitialize after clear', async () => {
await table.insert([TUPLE1, TUPLE2]);
await table.clear();

await table.insert([TUPLE1]);

expect(await table.queryAll()).toEqual([TUPLE1]);
});

it('should clear idempotently', async () => {
await table.insert([TUPLE1]);

await table.clear();
await table.clear();

expect(await table.queryAll()).toEqual([]);
});

it('should emit empty data after clear', async done => {
let counter = 0;

table.queryAll$.subscribe(value => {
if (counter === 0) {
expect(value).toEqual([]);
} else if (counter === 1) {
expect(value).toEqual([TUPLE1]);
} else if (counter === 2) {
expect(value).toEqual([]);
done();
}
counter += 1;
});

await table.insert([TUPLE1]);

await table.clear();
});

it('should update proofs', async done => {
const tupleCount = 100;
const sourceTuple: TestTuple[] = [...Array(tupleCount).keys()].map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,28 @@ export class CapacitorFilesystemTable<T extends Tuple> implements Table<T> {
});
}

async clear() {
await this.destroy();
return this.tuples$.next([]);
}

async drop() {
this.hasInitialized = false;
if (await this.hasCreatedJson()) {
await this.filesystemPlugin.deleteFile({
directory: this.directory,
path: `${this.rootDir}/${this.id}.json`,
});
}
await this.destroy();
return this.tuples$.complete();
}

private async destroy() {
return this.mutex.runExclusive(async () => {
this.hasInitialized = false;
if (await this.hasCreatedJson()) {
await this.filesystemPlugin.deleteFile({
directory: this.directory,
path: `${this.rootDir}/${this.id}.json`,
});
}
});
}

private static readonly initializationMutex = new Mutex();
}

Expand Down
1 change: 1 addition & 0 deletions src/app/shared/services/database/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface Table<T extends Tuple> {
): Promise<T[]>;
delete(tuples: T[], comparator?: (x: T, y: T) => boolean): Promise<T[]>;
update(tuples: T[], comparator: (x: T, y: T) => boolean): Promise<T[]>;
clear(): Promise<void>;
drop(): Promise<void>;
}

Expand Down
45 changes: 27 additions & 18 deletions src/app/shared/services/image-store/image-store.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ const { Filesystem } = Plugins;

describe('ImageStore', () => {
let store: ImageStore;
const sampleFile =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const sampleIndex =
'93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20';
const sampleMimeType: MimeType = 'image/png';

beforeEach(() => {
TestBed.configureTestingModule({
Expand All @@ -29,41 +24,41 @@ describe('ImageStore', () => {
it('should be created', () => expect(store).toBeTruthy());

it('should check if file exists', async () => {
expect(await store.exists(sampleIndex)).toBeFalse();
expect(await store.exists(INDEX)).toBeFalse();
});

it('should write file with Base64', async () => {
const index = await store.write(sampleFile, sampleMimeType);
const index = await store.write(FILE, MIME_TYPE);
expect(await store.exists(index)).toBeTrue();
});

it('should read file with index', async () => {
const index = await store.write(sampleFile, sampleMimeType);
expect(await store.read(index)).toEqual(sampleFile);
const index = await store.write(FILE, MIME_TYPE);
expect(await store.read(index)).toEqual(FILE);
});

it('should delete file with index', async () => {
const index = await store.write(sampleFile, sampleMimeType);
const index = await store.write(FILE, MIME_TYPE);

await store.delete(index);

expect(await store.exists(index)).toBeFalse();
});

it('should delete file with index and thumbnail', async () => {
const index = await store.write(sampleFile, sampleMimeType);
await store.readThumbnail(index, sampleMimeType);
const index = await store.write(FILE, MIME_TYPE);
await store.readThumbnail(index, MIME_TYPE);

await store.delete(index);

expect(await store.exists(index)).toBeFalse();
});

it('should remove all files after drop', async () => {
const index1 = await store.write(sampleFile, sampleMimeType);
const index1 = await store.write(FILE, MIME_TYPE);
const anotherFile =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEX/TQBcNTh/AAAAAXRSTlPM0jRW/QAAAApJREFUeJxjYgAAAAYAAzY3fKgAAAAASUVORK5CYII=';
const index2 = await store.write(anotherFile, sampleMimeType);
const index2 = await store.write(anotherFile, MIME_TYPE);

await store.drop();

Expand All @@ -78,7 +73,7 @@ describe('ImageStore', () => {
);

const indexes = await Promise.all(
files.map(file => store.write(file, sampleMimeType))
files.map(file => store.write(file, MIME_TYPE))
);

for (const index of indexes) {
Expand All @@ -94,7 +89,7 @@ describe('ImageStore', () => {
const indexes = [];

for (const file of files) {
indexes.push(await store.write(file, sampleMimeType));
indexes.push(await store.write(file, MIME_TYPE));
}

await Promise.all(indexes.map(index => store.delete(index)));
Expand All @@ -105,8 +100,22 @@ describe('ImageStore', () => {
});

it('should read thumbnail', async () => {
const index = await store.write(sampleFile, sampleMimeType);
const thumbnailFile = await store.readThumbnail(index, sampleMimeType);
const index = await store.write(FILE, MIME_TYPE);
const thumbnailFile = await store.readThumbnail(index, MIME_TYPE);
expect(thumbnailFile).toBeTruthy();
});

it('should clear all files', async () => {
const index = await store.write(FILE, MIME_TYPE);

await store.clear();

expect(await store.exists(index)).toBeFalse();
});
});

const FILE =
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const INDEX =
'93ae7d494fad0fb30cbf3ae746a39c4bc7a0f8bbf87fbb587a3f3c01f3c5ce20';
const MIME_TYPE: MimeType = 'image/png';
11 changes: 9 additions & 2 deletions src/app/shared/services/image-store/image-store.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,10 @@ export class ImageStore {
return index;
}

async drop() {
async clear() {
await this.initialize();
await this.thumbnailTable.drop();
await this.extensionTable.clear();
await this.thumbnailTable.clear();
return this.mutex.runExclusive(async () => {
this.hasInitialized = false;
await this.filesystemPlugin.rmdir({
Expand All @@ -231,6 +232,12 @@ export class ImageStore {
});
});
}

async drop() {
await this.clear();
await this.extensionTable.drop();
await this.thumbnailTable.drop();
}
}

interface ImageExtension extends Tuple {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,22 @@ describe('PreferenceManager', () => {
const id = 'id';
expect(manager.getPreferences(id)).toBe(manager.getPreferences(id));
});

it('should clear all preferences', async () => {
const key = 'key';
const defaultValue = 99;
const preference1 = manager.getPreferences('id1');
const preference2 = manager.getPreferences('id2');
await preference1.setNumber(key, 1);
await preference2.setNumber(key, 2);

await manager.clear();

expect(await preference1.getNumber(key, defaultValue)).toEqual(
defaultValue
);
expect(await preference2.getNumber(key, defaultValue)).toEqual(
defaultValue
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ export class PreferenceManager {
this.preferencesMap.set(id, created);
return created;
}

async clear() {
return Promise.all(
[...this.preferencesMap.values()].map(preferences => preferences.clear())
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,4 +213,15 @@ describe('CapacitorStoragePreferences', () => {
done();
});
});

it('should clear idempotently', async () => {
const key = 'key';
const expected = 2;
await preferences.setNumber(key, 1);

await preferences.clear();
await preferences.clear();

expect(await preferences.getNumber(key, expected)).toEqual(expected);
});
});
Loading

0 comments on commit c972579

Please sign in to comment.