Skip to content

Commit

Permalink
refactor(identity): move error handling and serialization into @tanke…
Browse files Browse the repository at this point in the history
…r/identity
  • Loading branch information
maximerety committed Mar 13, 2019
1 parent e5fbf6e commit 35c8d6c
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 67 deletions.
4 changes: 2 additions & 2 deletions ci/compat/packages/tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import uuid from 'uuid';
import { expect } from 'chai';

import { utils, type b64string } from '../../../../packages/crypto';
import { createIdentity } from '../../../../packages/identity';
import { createIdentity, _deserializeIdentity } from '../../../../packages/identity';
import { TrustchainHelper } from '../../../../packages/functional-tests/src/Helpers';
import { makeCurrentUser, makeUser } from './helpers';

Expand All @@ -17,7 +17,7 @@ function generateUserToken(trustchainId: b64string, trustchainPrivateKey: b64str
value: user_id,
delegation_signature,
user_secret,
} = utils.fromB64Json(identity);
} = _deserializeIdentity(identity);

return utils.toB64Json({
delegation_signature,
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@tanker/crypto": "0.0.1",
"@tanker/datastore-base": "0.0.1",
"@tanker/errors": "0.0.1",
"@tanker/identity": "0.0.1",
"@tanker/stream-base": "0.0.1",
"array-find": "^1.0.0",
"engine.io-client": "git+https://github.com/TankerHQ/engine.io-client.git#2996c2d746d4695742cbc3f8fb47c60d4c5757f7",
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/DataProtection/DataProtector.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import { utils, type b64string } from '@tanker/crypto';
import { _deserializePublicIdentity } from '@tanker/identity';
import { ResourceNotFound, DecryptFailed } from '../errors';
import { ResourceManager, getResourceId } from '../Resource/ResourceManager';
import { type Block } from '../Blocks/Block';
Expand Down Expand Up @@ -99,8 +100,8 @@ export default class DataProtector {
const groupIds = (shareWithOptions.shareWithGroups || []).map(g => utils.fromBase64(g));
const groups = await this._groupManager.findGroups(groupIds);
const b64UserIdentities = this._handleShareWithSelf(shareWithOptions.shareWithUsers || [], shareWithSelf);
const decodedIdentities = b64UserIdentities.map(utils.fromB64Json);
const users = await this._userAccessor.getUsers({ publicIdentities: decodedIdentities });
const deserializedIdentities = b64UserIdentities.map(i => _deserializePublicIdentity(i));
const users = await this._userAccessor.getUsers({ publicIdentities: deserializedIdentities });

if (shareWithSelf) {
const [{ resourceId, key }] = keys;
Expand Down
16 changes: 8 additions & 8 deletions packages/core/src/Groups/Manager.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
// @flow

import { tcrypto, utils, type b64string } from '@tanker/crypto';
import { type PublicIdentity } from '@tanker/identity';
import { _deserializePublicIdentity, InvalidIdentity, type PublicIdentity } from '@tanker/identity';

import UserAccessor from '../Users/UserAccessor';
import LocalUser from '../Session/LocalUser';
import { Client } from '../Network/Client';
import GroupStore from './GroupStore';
import { type ExternalGroup } from './types';
import Trustchain from '../Trustchain/Trustchain';
import { InvalidArgument, InvalidGroupSize, ServerError, InvalidIdentity } from '../errors';
import { InvalidArgument, InvalidGroupSize, ServerError } from '../errors';

export const MAX_GROUP_SIZE = 1000;

function decodePublicIdentity(publicIdentity: b64string): PublicIdentity {
const decodedIdentity = utils.fromB64Json(publicIdentity);
if (decodedIdentity.target !== 'user')
function deserializePublicIdentity(publicIdentity: b64string): PublicIdentity {
const deserializedIdentity = _deserializePublicIdentity(publicIdentity);
if (deserializedIdentity.target !== 'user')
throw new InvalidIdentity('Group members cannot be provisional identities');
return decodedIdentity;
return deserializedIdentity;
}

export default class GroupManager {
Expand Down Expand Up @@ -47,7 +47,7 @@ export default class GroupManager {
if (publicIdentities.length > MAX_GROUP_SIZE)
throw new InvalidGroupSize(`A group cannot have more than ${MAX_GROUP_SIZE} members`);

const decodedIdentities = publicIdentities.map(decodePublicIdentity);
const decodedIdentities = publicIdentities.map(deserializePublicIdentity);
const fullUsers = await this._userAccessor.getUsers({ publicIdentities: decodedIdentities });

const groupSignatureKeyPair = tcrypto.makeSignKeyPair();
Expand All @@ -71,7 +71,7 @@ export default class GroupManager {
if (publicIdentities.length > MAX_GROUP_SIZE)
throw new InvalidGroupSize(`Cannot add more than ${MAX_GROUP_SIZE} members to ${groupId}`);

const decodedIdentities = publicIdentities.map(decodePublicIdentity);
const decodedIdentities = publicIdentities.map(deserializePublicIdentity);
const fullUsers = await this._userAccessor.getUsers({ publicIdentities: decodedIdentities });

const internalGroupId = utils.fromBase64(groupId);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/Session/Keystore.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// @flow

import { tcrypto, utils, type Key } from '@tanker/crypto';
import { InvalidIdentity } from '@tanker/identity';
import { errors as dbErrors, type DataStore } from '@tanker/datastore-base';

import { InvalidIdentity } from '../errors';
import KeySafe from './KeySafe';
import { type UserKeys } from '../Blocks/payloads';

Expand Down
12 changes: 3 additions & 9 deletions packages/core/src/UserData.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow

import { utils, checkUserSecret, type b64string } from '@tanker/crypto';
import { InvalidIdentity } from './errors';
import { _deserializeIdentity, InvalidIdentity } from '@tanker/identity';
import { type DelegationToken } from './Session/delegation';

export type UserData = {
Expand All @@ -12,16 +12,10 @@ export type UserData = {
}

export function extractUserData(identityB64: b64string): UserData {
let identity;
try {
identity = utils.fromB64Json(identityB64);
} catch (e) {
throw new InvalidIdentity(e);
}
const userId = utils.fromBase64(identity.value);
const identity = _deserializeIdentity(identityB64);

const userId = utils.fromBase64(identity.value);
const userSecret = utils.fromBase64(identity.user_secret);

const trustchainId = utils.fromBase64(identity.trustchain_id);

const delegationToken: DelegationToken = {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/__tests__/UserAccessor.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @flow
import sinon from 'sinon';
import { utils } from '@tanker/crypto';
import { createIdentity, getPublicIdentity } from '@tanker/identity';
import { createIdentity, getPublicIdentity, _deserializePublicIdentity } from '@tanker/identity';

import { expect } from './chai';
import { makeUserStoreBuilder } from './UserStoreBuilder';
Expand Down Expand Up @@ -138,7 +138,7 @@ describe('Users', () => {
const aliceIdentity = createIdentity(utils.toBase64(generator.trustchainId), utils.toBase64(generator.appSignKeys.privateKey), 'alice');
const bobIdentity = createIdentity(utils.toBase64(generator.trustchainId), utils.toBase64(generator.appSignKeys.privateKey), 'bob');

const retUsers = await users.getUsers({ publicIdentities: [aliceIdentity, bobIdentity].map(u => utils.fromB64Json(getPublicIdentity(u))) });
const retUsers = await users.getUsers({ publicIdentities: [aliceIdentity, bobIdentity].map(u => _deserializePublicIdentity(getPublicIdentity(u))) });
const retUserIds = retUsers.map(u => u.userId);
const expectedUserIds = [alice, bob].map(u => utils.toBase64(u.entry.user_id));
expect(retUserIds).to.have.members(expectedUserIds);
Expand All @@ -152,7 +152,7 @@ describe('Users', () => {
const bobIdentity = createIdentity(utils.toBase64(generator.trustchainId), utils.toBase64(generator.appSignKeys.privateKey), 'bob');
const casperIdentity = createIdentity(utils.toBase64(generator.trustchainId), utils.toBase64(generator.appSignKeys.privateKey), 'casper');

await expect(users.getUsers({ publicIdentities: [aliceIdentity, bobIdentity, casperIdentity].map(u => utils.fromB64Json(getPublicIdentity(u))) }))
await expect(users.getUsers({ publicIdentities: [aliceIdentity, bobIdentity, casperIdentity].map(u => _deserializePublicIdentity(getPublicIdentity(u))) }))
.to.be.rejectedWith(RecipientsNotFound);
});
});
Expand Down
14 changes: 1 addition & 13 deletions packages/core/src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { TankerError } from '@tanker/errors';

// Re-expose these common error classes:
export { TankerError, InvalidArgument, NotEnoughData } from '@tanker/errors';
export { InvalidIdentity } from '@tanker/identity';

export class ResourceNotFound extends TankerError {
b64ResourceId: b64string;
Expand Down Expand Up @@ -36,19 +37,6 @@ export class DecryptFailed extends TankerError {
}
}

export class InvalidIdentity extends TankerError {
next: ?Error;

constructor(e: Error | string) {
if (typeof e === 'string') {
super('InvalidIdentity', e);
} else {
super('InvalidIdentity');
this.next = e;
}
}
}

export class InvalidUnlockKey extends TankerError {
next: Error;

Expand Down
3 changes: 2 additions & 1 deletion packages/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"test": "cd ../..; yarn test:identity"
},
"dependencies": {
"@tanker/crypto": "0.0.1"
"@tanker/crypto": "0.0.1",
"@tanker/errors": "0.0.1"
},
"devDependencies": {
"chai": "^4.2.0",
Expand Down
15 changes: 15 additions & 0 deletions packages/identity/src/InvalidIdentity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow
import { TankerError } from '@tanker/errors';

export class InvalidIdentity extends TankerError {
next: ?Error;

constructor(e: Error | string) {
if (typeof e === 'string') {
super('InvalidIdentity', e);
} else {
super('InvalidIdentity');
this.next = e;
}
}
}
27 changes: 16 additions & 11 deletions packages/identity/src/__tests__/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// @flow
import { expect } from 'chai';
import { generichash, obfuscateUserId, tcrypto, utils } from '@tanker/crypto';
import { createIdentity, createProvisionalIdentity, getPublicIdentity, upgradeUserToken } from '../index';
import {
_deserializeIdentity, _deserializeProvisionalIdentity, _deserializePublicIdentity,
createIdentity, createProvisionalIdentity, getPublicIdentity, upgradeUserToken, InvalidIdentity,
} from '../index';

function checkUserSecret(userSecret, obfuscatedUserId) {
expect(obfuscatedUserId).to.have.lengthOf(tcrypto.HASH_SIZE);
Expand Down Expand Up @@ -63,14 +66,14 @@ describe('Identity', () => {

it('returns a tanker identity', () => {
const b64Identity = createIdentity(trustchain.id, trustchain.sk, userId);
const identity = utils.fromB64Json(b64Identity);
const identity = _deserializeIdentity(b64Identity);
checkIdentityIntegrity(identity, trustchain);
});

it('returns a tanker provisional identity', () => {
const b64Identity = createProvisionalIdentity(userEmail, trustchain.id);

const { trustchain_id, value, target, encryption_key_pair, signature_key_pair } = utils.fromB64Json(b64Identity); // eslint-disable-line camelcase
const { trustchain_id, value, target, encryption_key_pair, signature_key_pair } = _deserializeProvisionalIdentity(b64Identity); // eslint-disable-line camelcase
expect(trustchain_id).to.equal(trustchain.id);
expect(target).to.be.equal('email');
expect(value).to.be.equal(userEmail);
Expand All @@ -83,7 +86,7 @@ describe('Identity', () => {
it('returns a tanker public identity from an tanker indentity', () => {
const b64Identity = getPublicIdentity(createIdentity(trustchain.id, trustchain.sk, userId));

const { trustchain_id, target, value, ...trail } = utils.fromB64Json(b64Identity); // eslint-disable-line camelcase
const { trustchain_id, target, value, ...trail } = _deserializePublicIdentity(b64Identity); // eslint-disable-line camelcase
expect(trustchain_id).to.equal(trustchain.id);
expect(trail).to.be.empty;

Expand All @@ -95,9 +98,11 @@ describe('Identity', () => {
const b64ProvisionalIdentity = createProvisionalIdentity(userEmail, trustchain.id);
const b64PublicIdentity = getPublicIdentity(b64ProvisionalIdentity);

const provisionalIdentity = utils.fromB64Json(b64ProvisionalIdentity);
const { trustchain_id, target, value, // eslint-disable-line camelcase
public_signature_key, public_encryption_key, ...trail } = utils.fromB64Json(b64PublicIdentity); // eslint-disable-line camelcase
const provisionalIdentity = _deserializeProvisionalIdentity(b64ProvisionalIdentity);
const {
// $FlowIKnow We know a provisional identity is expected
trustchain_id, target, value, public_signature_key, public_encryption_key, ...trail // eslint-disable-line camelcase
} = _deserializePublicIdentity(b64PublicIdentity);

expect(trustchain_id).to.equal(trustchain.id);
expect(trail).to.be.empty;
Expand All @@ -110,24 +115,24 @@ describe('Identity', () => {
});

it('parse a valid identity', () => {
const identity = utils.fromB64Json(goodIdentity);
const identity = _deserializeIdentity(goodIdentity);
checkGoodIdentity(identity);
});

it('parse a valid public identity', () => {
const identity = utils.fromB64Json(goodPublicIdentity);
const identity = _deserializePublicIdentity(goodPublicIdentity);
expect(identity.trustchain_id).to.equal(trustchain.id);
expect(identity.target).to.equal('user');
expect(identity.value).to.equal(obfuscatedUserId);
});

it('upgrade a user token to an identity', () => {
const b64Identity = upgradeUserToken(trustchain.id, userId, goodUserToken);
const identity = utils.fromB64Json(b64Identity);
const identity = _deserializeIdentity(b64Identity);
checkIdentityIntegrity(identity, trustchain);
});

it('throws when upgrading with the wrong userId', () => {
expect(() => upgradeUserToken(trustchain.id, 'bad user id', goodUserToken)).to.throw();
expect(() => upgradeUserToken(trustchain.id, 'bad user id', goodUserToken)).to.throw(InvalidIdentity);
});
});
48 changes: 40 additions & 8 deletions packages/identity/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// @flow
import { tcrypto, utils, obfuscateUserId, createUserSecretB64, type b64string } from '@tanker/crypto';

import { InvalidIdentity } from './InvalidIdentity';

export { InvalidIdentity };

type KeyPair = {|
public_key: b64string,
private_key: b64string,
Expand Down Expand Up @@ -61,6 +65,34 @@ function generatePreshareKeys(): PreshareKeys {
};
}

function _serializeIdentity(identity: Identity | PublicIdentity | ProvisionalIdentity): b64string { // eslint-disable-line no-underscore-dangle
return utils.toB64Json(identity);
}

export function _deserializeIdentity(identity: b64string): Identity { // eslint-disable-line no-underscore-dangle
try {
return utils.fromB64Json(identity);
} catch (e) {
throw new InvalidIdentity(e);
}
}

export function _deserializeProvisionalIdentity(identity: b64string): ProvisionalIdentity { // eslint-disable-line no-underscore-dangle
try {
return utils.fromB64Json(identity);
} catch (e) {
throw new InvalidIdentity(e);
}
}

export function _deserializePublicIdentity(identity: b64string): PublicIdentity { // eslint-disable-line no-underscore-dangle
try {
return utils.fromB64Json(identity);
} catch (e) {
throw new InvalidIdentity(e);
}
}

export function createIdentity(trustchainId: b64string, trustchainPrivateKey: b64string, userId: string): b64string {
const obfuscatedUserId = obfuscateUserId(utils.fromBase64(trustchainId), userId);

Expand All @@ -71,7 +103,7 @@ export function createIdentity(trustchainId: b64string, trustchainPrivateKey: b6

const userSecret = createUserSecretB64(trustchainId, userId);

return utils.toB64Json({
return _serializeIdentity({
trustchain_id: trustchainId,
target: 'user',
value: utils.toBase64(obfuscatedUserId),
Expand All @@ -89,20 +121,20 @@ export function createProvisionalIdentity(email: string, trustchainId: b64string
value: email,
...generatePreshareKeys(),
};
return utils.toB64Json(provisionalIdentity);
return _serializeIdentity(provisionalIdentity);
}

// Note: tankerIdentity is a Tanker identity created by either createIdentity() or createProvisionalIdentity()
export function getPublicIdentity(tankerIdentity: b64string): b64string {
const identity = utils.fromB64Json(tankerIdentity);
const identity = _deserializeIdentity(tankerIdentity);

if (identity.target === 'user') {
const { trustchain_id, target, value } = identity; // eslint-disable-line camelcase
return utils.toB64Json({ trustchain_id, target, value });
return _serializeIdentity({ trustchain_id, target, value });
}

if (identity.encryption_key_pair && identity.signature_key_pair) {
return utils.toB64Json({
return _serializeIdentity({
trustchain_id: identity.trustchain_id,
target: identity.target,
value: identity.value,
Expand All @@ -111,7 +143,7 @@ export function getPublicIdentity(tankerIdentity: b64string): b64string {
});
}

throw new Error('Incorrect Tanker identity provided');
throw new InvalidIdentity('Invalid Tanker identity provided');
}

// Note: userToken generated with the deprecated @tanker/user-token sdk
Expand All @@ -127,9 +159,9 @@ export function upgradeUserToken(trustchainId: b64string, userId: string, userTo
} = utils.fromB64Json(userToken);

if (utils.toBase64(obfuscatedUserId) !== user_id)
throw new Error('Invalid userId provided');
throw new InvalidIdentity('Invalid userId provided');

return utils.toB64Json({
return _serializeIdentity({
trustchain_id: trustchainId,
target: 'user',
value: user_id,
Expand Down
Loading

0 comments on commit 35c8d6c

Please sign in to comment.