From fb738622fe621af97c4dad7b32583f100a5eddf8 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Sat, 30 Dec 2017 11:45:58 +0000 Subject: [PATCH] API: Add API to handle UserInfo. --- src/api/EteSync.test.ts | 28 +++++++++++ src/api/EteSync.ts | 100 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 7 deletions(-) diff --git a/src/api/EteSync.test.ts b/src/api/EteSync.test.ts index c947507..b31bf87 100644 --- a/src/api/EteSync.test.ts +++ b/src/api/EteSync.test.ts @@ -127,3 +127,31 @@ it('Journal Entry sync', async () => { } }).toThrowError(); }); + +it('User info sync', async () => { + const userInfoManager = new EteSync.UserInfoManager(credentials, testApiBase); + + // Get when there's nothing + await expect(userInfoManager.fetch(USER)).rejects.toBeInstanceOf(EteSync.HTTPError); + + // Create + let userInfo = new EteSync.UserInfo(); + userInfo.deserialize({pubkey: 'dGVzdAo=', content: 'dGVzdAo=', owner: USER}); + await expect(userInfoManager.create(userInfo)).resolves.not.toBeNull(); + + // Get + let userInfo2 = await userInfoManager.fetch(USER); + expect(userInfo2).not.toBeNull(); + expect(userInfo.serialize().content).toEqual(userInfo2!.serialize().content); + + // Update + userInfo.deserialize({pubkey: 'dGVzdDIK', content: 'dGVzdDIK', owner: USER}); + await userInfoManager.update(userInfo); + userInfo2 = await userInfoManager.fetch(USER); + expect(userInfo2).not.toBeNull(); + expect(userInfo.serialize().content).toEqual(userInfo2!.serialize().content); + + // Delete + await userInfoManager.delete(userInfo); + await expect(userInfoManager.fetch(USER)).rejects.toBeInstanceOf(EteSync.HTTPError); +}); diff --git a/src/api/EteSync.ts b/src/api/EteSync.ts index 619105e..3bc08c3 100644 --- a/src/api/EteSync.ts +++ b/src/api/EteSync.ts @@ -65,12 +65,11 @@ export class CollectionInfo { } } -interface BaseJson { - uid: string; +interface BaseItemJson { content: base64; } -class BaseJournal { +class BaseItem { protected _json: T; protected _encrypted: byte[]; protected _content?: object; @@ -85,10 +84,6 @@ class BaseJournal { this._content = undefined; } - get uid(): string { - return this._json.uid; - } - serialize(): T { return Object.assign( {}, @@ -109,6 +104,16 @@ class BaseJournal { } } +interface BaseJson extends BaseItemJson { + uid: string; +} + +class BaseJournal extends BaseItem { + get uid(): string { + return this._json.uid; + } +} + export interface JournalJson extends BaseJson { version: number; owner: string; @@ -219,6 +224,43 @@ export class Entry extends BaseJournal { } } +export interface UserInfoJson extends BaseItemJson { + version?: number; + owner: string; + pubkey: base64; +} + +export class UserInfo extends BaseItem { + constructor(version: number = Constants.CURRENT_VERSION) { + super(); + this._json.version = version; + } + + get version(): number { + return this._json.version!; + } + + get owner() { + return this._json.owner; + } + + calculateHmac(cryptoManager: CryptoManager, encrypted: byte[]): byte[] { + let postfix = stringToByteArray(this._json.pubkey); + return cryptoManager.hmac(encrypted.concat(postfix)); + } + + verify(cryptoManager: CryptoManager) { + let calculated = this.calculateHmac(cryptoManager, this.encryptedContent()); + let hmac = this._encrypted.slice(0, HMAC_SIZE_BYTES); + + super.verifyBase(hmac, calculated); + } + + private encryptedContent(): byte[] { + return this._encrypted.slice(HMAC_SIZE_BYTES); + } +} + // FIXME: baseUrl and apiBase should be the right type all around. class BaseNetwork { @@ -421,3 +463,47 @@ export class EntryManager extends BaseManager { return this.newCall(undefined, extra, apiBase); } } + +export class UserInfoManager extends BaseManager { + constructor(credentials: Credentials, apiBase: string) { + super(credentials, apiBase, ['user', '']); + } + + fetch(owner: string): Promise { + return new Promise((resolve, reject) => { + this.newCall([owner, '']).then((json: UserInfoJson) => { + let userInfo = new UserInfo(json.version); + userInfo.deserialize(json); + resolve(userInfo); + }).catch((error: Error) => { + reject(error); + }); + }); + } + + create(userInfo: UserInfo): Promise<{}> { + const extra = { + method: 'post', + body: JSON.stringify(userInfo.serialize()), + }; + + return this.newCall([], extra); + } + + update(userInfo: UserInfo): Promise<{}> { + const extra = { + method: 'put', + body: JSON.stringify(userInfo.serialize()), + }; + + return this.newCall([userInfo.owner, ''], extra); + } + + delete(userInfo: UserInfo): Promise<{}> { + const extra = { + method: 'delete', + }; + + return this.newCall([userInfo.owner, ''], extra); + } +}