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

Commit

Permalink
feat: Add support for spaces
Browse files Browse the repository at this point in the history
  • Loading branch information
oed committed Apr 19, 2019
1 parent df73b73 commit 2993814
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"homepage": "https://github.com/3box/3box-account#readme",
"dependencies": {
"@babel/runtime": "^7.1.2",
"ethers": "^4.0.20",
"ethers": "^4.0.27",
"postmsg-rpc": "^2.4.0",
"store": "^2.0.12",
"tweetnacl": "^1.0.1",
Expand Down
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
</div>
<div id="viewConsent" align="center" style="display: none; margin-top: 25px;">
</br>
<h5 id="consentText"></h5>
<p id="consentText" style="font-weight: bold;"></p>
</br>
</br>
<button id="acceptConsent" class="btn btn-primary">Accept</button>
Expand Down
79 changes: 55 additions & 24 deletions src/account.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { expose } from 'postmsg-rpc'
import nacl from 'tweetnacl'
import naclutil from 'tweetnacl-util'
import { HDNode } from 'ethers/utils'
nacl.util = naclutil
import crypto from 'crypto'
import store from 'store'

const AUTH_SERVICE_URL = 'http://localhost:3003'
const BASE_PATH = "m/51073068'/0'"

const sha256 = msg => crypto.createHash('sha256').update(msg).digest('hex')

class Account {
constructor (actions, opts = {}) {
this.actions = actions
this.persist = !opts.noPersist
this.allowedOrigins = {}
if (this.persist) {
this._seed = store.get('seed')
this._rootSeed = store.get('rootSeed')
this.allowedOrigins = store.get('allowedOrigins')
}
this.exposeRpc()
Expand All @@ -33,9 +36,7 @@ class Account {
}

async authenticateApp (origin, spaces) {
console.log('ori', origin)

if (!this.seed) {
if (!this.rootSeed) {
let err
do {
const authInput = await this.actions.getAuthInput()
Expand All @@ -44,49 +45,79 @@ class Account {
} else if (authInput.type === 'auth') {
err = await this.auth(authInput.email, authInput.pass)
} else if (authInput.type === 'cancel') {
throw new Error('Access denied')
throw new Error('Canceled')
}
if (err) {
this.actions.displayError(err)
}
} while (err)
}
if (!this.isOriginAllowed(origin)) {
await this.actions.getOriginConsent(origin)
if (!this.isOriginAllowed(origin, spaces)) {
await this.actions.getOriginConsent(origin, spaces)
// the above throws if consent not given
this.allowOrigin(origin)
this.allowOrigin(origin, spaces)
}
const keys = this.deriveKeys(spaces)
return keys
}

deriveKeys (spaces) {
const baseNode = HDNode.fromSeed(this.rootSeed).derivePath(BASE_PATH)
// for the main seed we just use the 0 path
const mainNode = baseNode.derivePath("0'/0'/0'/0'/0'/0'/0'/0'")
const spaceKeys = spaces.reduce((acc, space) => {
const spaceHash = sha256(`${space}.3box`)
// convert hash to path
const spacePath = spaceHash.match(/.{1,12}/g) // chunk hex string
.map(n => parseInt(n, 16).toString(2)) // convert to binary
.map(n => (n.length === 47 ? '0' : '') + n) // make sure that binary strings have the right length
.join('').match(/.{1,31}/g) // chunk binary string for path encoding
.map(n => parseInt(n, 2)).join("'/") + "'" // convert to uints and create path
acc[space] = baseNode.derivePath(spacePath).extendedKey
return acc
}, {})
return {
main: mainNode.extendedKey,
spaces: spaceKeys
}
return this.seed
}

get seed () {
return this._seed
get rootSeed () {
return this._rootSeed
}

set seed (seed) {
this._seed = seed
set rootSeed (rootSeed) {
this._rootSeed = `0x${rootSeed}`
if (this.persist) {
store.set('seed', seed)
store.set('rootSeed', rootSeed)
}
}

allowOrigin (origin) {
if (!this.allowedOrigins) {
this.allowedOrigins = store.get('allowedOrigins')
allowOrigin (origin, spaces) {
if (!this.allowedOrigins[origin]) {
this.allowedOrigins[origin] = {
main: true,
spaces: []
}
}
this.allowedOrigins[origin] = true
this.allowedOrigins[origin].spaces = [...new Set([
...this.allowedOrigins[origin].spaces,
...spaces
])]
if (this.persist) {
store.set('allowedOrigins', this.allowedOrigins)
}
}

isOriginAllowed (origin) {
return this.allowedOrigins[origin]
isOriginAllowed (origin, spaces) {
if (!this.allowedOrigins[origin]) return false
const spacesAllowed = spaces.every(space => this.allowedOrigins[origin].spaces.includes(space))
return this.allowedOrigins[origin].main && spacesAllowed
}

async create (email, password) {
const auth = email + password
const authProof = crypto.createHash('sha256').update(auth).digest('hex')
const authProof = sha256(auth)
const e1Salt = Buffer.from(nacl.randomBytes(32)).toString('hex')
const e1 = crypto.pbkdf2Sync(auth, e1Salt, 20000, 32, 'sha256')
const seed = nacl.randomBytes(32)
Expand Down Expand Up @@ -118,12 +149,12 @@ class Account {
if (!res.ok) {
return (await res.json()).message
}
this.seed = Buffer.from(seed).toString('hex')
this.rootSeed = Buffer.from(seed).toString('hex')
}

async auth (email, password) {
const auth = email + password
const authProof = crypto.createHash('sha256').update(auth).digest('hex')
const authProof = sha256(auth)

const res = await fetch(`${AUTH_SERVICE_URL}/authenticate?auth-proof=${authProof}`)
if (!res.ok) {
Expand All @@ -141,7 +172,7 @@ class Account {
nacl.util.decodeBase64(data['enc-seed'].nonce),
e0
)
this.seed = Buffer.from(seed).toString('hex')
this.rootSeed = Buffer.from(seed).toString('hex')
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import Account from './account'

const getOriginConsent = (origin) => {
const getOriginConsent = (origin, spaces) => {
viewConsent.style.display = 'block'
consentText.innerHTML = `Allow ${origin} to access your 3Box account`
if (spaces.length) {
consentText.innerHTML += '<br />And spaces:'
spaces.map(space => {
consentText.innerHTML += `<br />${space}`
})
}
return new Promise((resolve, reject) => {
acceptConsent.addEventListener('click', () => {
viewConsent.style.display = 'none'
Expand Down

0 comments on commit 2993814

Please sign in to comment.