Skip to content
This repository has been archived by the owner on Aug 9, 2021. It is now read-only.

Commit

Permalink
feat: implement the user object
Browse files Browse the repository at this point in the history
  • Loading branch information
oed committed Jan 3, 2020
1 parent 57fcdd4 commit a914ee9
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 17 deletions.
72 changes: 68 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,70 @@ const log = store.log
console.log(entry)
// { op: 'PUT', key: 'Name', value: 'Botbot', timeStamp: '1538575416068' }
```
<a name="User"></a>
### User
Class representing a user.
**Kind**: global class
* [User](#User)
* [.DID](#User+DID)
* [.signClaim(payload, opts)](#User+signClaim) ⇒ <code>String</code>
* [.encrypt(message, opts, to)](#User+encrypt) ⇒ <code>Object</code>
* [.decrypt(encryptedObject)](#User+decrypt) ⇒ <code>String</code>
<a name="User+DID"></a>
#### user.DID
**Kind**: instance property of [<code>User</code>](#User)
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| DID | <code>String</code> | the DID of the user |
<a name="User+signClaim"></a>
#### user.signClaim(payload, opts) ⇒ <code>String</code>
Sign a JWT claim
**Kind**: instance method of [<code>User</code>](#User)
**Returns**: <code>String</code> - The signed JWT
| Param | Type | Description |
| --- | --- | --- |
| payload | <code>Object</code> | The payload to sign |
| opts | <code>Object</code> | Optional parameters |
<a name="User+encrypt"></a>
#### user.encrypt(message, opts, to) ⇒ <code>Object</code>
Encrypt a message. By default encrypts messages symmetrically
with the users private key. If the `to` parameter is used,
the message will be asymmetrically encrypted to the recipient.
**Kind**: instance method of [<code>User</code>](#User)
**Returns**: <code>Object</code> - An object containing the encrypted payload
| Param | Type | Description |
| --- | --- | --- |
| message | <code>String</code> | The message to encrypt |
| opts | <code>Object</code> | Optional parameters |
| to | <code>String</code> | The receiver of the message, a DID or an ethereum address |
<a name="User+decrypt"></a>
#### user.decrypt(encryptedObject) ⇒ <code>String</code>
Decrypts a message if the user owns the correct key to decrypt it.
**Kind**: instance method of [<code>User</code>](#User)
**Returns**: <code>String</code> - The clear text message
| Param | Type | Description |
| --- | --- | --- |
| encryptedObject | <code>Object</code> | The encrypted message to decrypt (as encoded by the `encrypt` method |
<a name="Space"></a>
### Space
Expand All @@ -886,7 +950,7 @@ const log = store.log
* [.public](#Space+public)
* [.private](#Space+private)
* [.syncDone](#Space+syncDone)
* [.DID](#Space+DID)
* [.user](#Space+user)
* [.joinThread(name, opts)](#Space+joinThread) ⇒ [<code>Thread</code>](#Thread)
* [.joinThreadByAddress(address, opts)](#Space+joinThreadByAddress) ⇒ [<code>Thread</code>](#Thread)
* [.subscribeThread(address, config)](#Space+subscribeThread)
Expand Down Expand Up @@ -928,15 +992,15 @@ Please use **box.openSpace** to get the instance of this class
| --- | --- | --- |
| syncDone | <code>Promise</code> | A promise that is resolved when the space data is synced |
<a name="Space+DID"></a>
<a name="Space+user"></a>
#### space.DID
#### space.user
**Kind**: instance property of [<code>Space</code>](#Space)
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| DID | <code>String</code> | the did of the user in this space |
| user | [<code>User</code>](#User) | access the user object to encrypt data and sign claims |
<a name="Space+joinThread"></a>
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"babel-core": "7.0.0-bridge.0",
"babel-loader": "^8.0.6",
"express": "^4.17.0",
"identity-wallet": "^1.0.0",
"identity-wallet": "^1.1.0-beta.1",
"jest": "^23.6.0",
"jsdoc-to-markdown": "^5.0.0",
"standard": "^14.3.1",
Expand Down
8 changes: 4 additions & 4 deletions src/3id/__tests__/3id.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ describe('3id', () => {

it('should encrypt and decrypt asymmetrically correctly', async () => {
const message = 'test message'
const { asymEncryptionKey } = await threeId.getPublicKeys(SPACE_1)
const enc1 = await threeId.encrypt(message, null, asymEncryptionKey)
expect(await threeId.decrypt(enc1, SPACE_1)).toEqual(message)
expect(await threeId.decrypt(enc1, SPACE_2)).toEqual(null)
const { asymEncryptionKey } = await idw3id.getPublicKeys(SPACE_1)
const enc1 = await idw3id.encrypt(message, null, asymEncryptionKey)
expect(await idw3id.decrypt(enc1, SPACE_1)).toEqual(message)
await expect(idw3id.decrypt(enc1, SPACE_2)).rejects.toMatchSnapshot()
})
})

Expand Down
2 changes: 2 additions & 0 deletions src/3id/__tests__/__snapshots__/3id.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ Object {
}
`;

exports[`3id get 3ID using IdentityWallet keyring logic should encrypt and decrypt asymmetrically correctly 1`] = `[Error: IdentityWallet: Could not decrypt message]`;

exports[`3id get 3ID using IdentityWallet keyring logic should encrypt and decrypt correctly 1`] = `[Error: IdentityWallet: Could not decrypt message]`;

exports[`3id get 3ID using IdentityWallet keyring logic should get public keys correctly 1`] = `
Expand Down
2 changes: 1 addition & 1 deletion src/3id/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class ThreeId {

async encrypt (message, space, to) {
if (this._has3idProv) {
return utils.callRpc(this._provider, '3id_encrypt', { message, space })
return utils.callRpc(this._provider, '3id_encrypt', { message, space, to })
} else {
const keyring = this._keyringBySpace(space)
let paddedMsg = utils.pad(message)
Expand Down
3 changes: 2 additions & 1 deletion src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ class BoxApi {
*/
static async getConfig (address, opts = {}) {
const serverUrl = opts.profileServer || PROFILE_SERVER_URL
const isAddr = address.startsWith('0x') // assume 3ID if not address
try {
return await utils.fetchJson(`${serverUrl}/config?address=${encodeURIComponent(address)}`)
return await utils.fetchJson(`${serverUrl}/config?${isAddr ? 'address' : 'did'}=${encodeURIComponent(address)}`)
} catch (err) {
throw new Error(err)
}
Expand Down
86 changes: 83 additions & 3 deletions src/space.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,86 @@
const KeyValueStore = require('./keyValueStore')
const Thread = require('./thread')
const GhostThread = require('./ghost')
const API = require('./api')
const { throwIfUndefined, throwIfNotEqualLenArrays } = require('./utils')
const OrbitDBAddress = require('orbit-db/src/orbit-db-address')
const resolveDID = require('did-resolver').default

const nameToSpaceName = name => `3box.space.${name}.keyvalue`
const namesTothreadName = (spaceName, threadName) => `3box.thread.${spaceName}.${threadName}`
const namesToChatName = (spaceName, chatName) => `3box.ghost.${spaceName}.${chatName}`
const findSpacePubKey = async (did, spaceName) => {
if (did.startsWith('0x')) {
// we got an ethereum address
did = await API.getSpaceDID(did, spaceName)
}
let doc = await resolveDID(did)
let pubkey = doc.publicKey.find(key => key.id.includes('#subEncryptionKey'))
if (!pubkey) {
// A root 3ID was passed, get the space 3ID
did = await API.getSpaceDID(did, spaceName)
doc = await resolveDID(did)
pubkey = doc.publicKey.find(key => key.id.includes('#subEncryptionKey'))
}
return pubkey.publicKeyBase64
}

/** Class representing a user. */
class User {
constructor (spaceName, threeId) {
this._name = spaceName
this._3id = threeId
}

/**
* @property {String} DID the DID of the user
*/
get DID () {
return this._3id.getSubDID(this._name)
}

/**
* Sign a JWT claim
*
* @param {Object} payload The payload to sign
* @param {Object} opts Optional parameters
*
* @return {String} The signed JWT
*/
async signClaim (payload, opts = {}) {
return this._3id.signJWT(payload, Object.assign(opts, { space: this._name }))
}

/**
* Encrypt a message. By default encrypts messages symmetrically
* with the users private key. If the `to` parameter is used,
* the message will be asymmetrically encrypted to the recipient.
*
* @param {String} message The message to encrypt
* @param {Object} opts Optional parameters
* @param {String} to The receiver of the message, a DID or an ethereum address
*
* @return {Object} An object containing the encrypted payload
*/
async encrypt (message, { to }) {
let toPubkey
if (to) {
toPubkey = await findSpacePubKey(to, this._name)
}
return this._3id.encrypt(message, this._name, toPubkey)
}

/**
* Decrypts a message if the user owns the correct key to decrypt it.
*
* @param {Object} encryptedObject The encrypted message to decrypt (as encoded by the `encrypt` method
*
* @return {String} The clear text message
*/
async decrypt (encryptedObject) {
return this._3id.decrypt(encryptedObject, this._name)
}
}

class Space {
/**
Expand All @@ -31,11 +105,17 @@ class Space {
this.syncDone = null
}

get DID () {
return this.user.DID
}

/**
* @property {String} DID the did of the user in this space
* @property {User} user access the user object to encrypt data and sign claims
*/
get DID () {
return this._3id.getSubDID(this._name)
get user () {
if (!this._3id) throw new Error('user is not authenticated')
this._user = this._user || new User(this._name, this._3id)
return this._user
}

get isOpen () {
Expand Down

0 comments on commit a914ee9

Please sign in to comment.