Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove all user data on logging out. #545

Merged
merged 1 commit into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the drop() function isn't used anywhere except for tests, and same for the capacitor filesystem table drop implementation. If it's not used then it should be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop method helps the tests to clean up the persistence storage without exposing the internal implementation. Thus, IMHO, the method still need to exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it's a good reason and is the cleanest way to do it so I'm fine with that.

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