blockstack
Version:
The Blockstack Javascript library for identity and authentication.
341 lines (274 loc) • 12.8 kB
JavaScript
import test from 'tape'
import FetchMock from 'fetch-mock'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
import bitcoin from 'bitcoinjs-lib'
import localStorage from 'mock-local-storage'
import { uploadToGaiaHub, getFullReadUrl,
connectToGaiaHub, BLOCKSTACK_GAIA_HUB_LABEL,
getBucketUrl } from '../../../lib/storage/hub'
import { getFile, putFile, getUserAppFileUrl } from '../../../lib/storage'
global.window = {}
window.localStorage = global.localStorage
export function runStorageTests() {
test('getFile unencrypted', (t) => {
t.plan(2)
const path = 'file.json'
const gaiaHubConfig = {
address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U',
server: 'https://hub.blockstack.org',
token: '',
url_prefix: 'gaia.testblockstack.org/hub/'
}
const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/file.json'
const fileContent = { test: 'test' }
const getOrSetLocalGaiaHubConnection = sinon.stub().resolves(gaiaHubConfig)
const getFullReadUrl = sinon.stub().resolves(fullReadUrl)
const { getFile } = proxyquire('../../../lib/storage', {
'./hub': { getOrSetLocalGaiaHubConnection, getFullReadUrl },
})
FetchMock.get(fullReadUrl, fileContent)
getFile(path)
.then((file) => {
t.ok(file, 'Returns file content')
t.same(JSON.parse(file), fileContent)
})
})
test('getFile unencrypted - multi-reader', (t) => {
t.plan(4)
const path = 'file.json'
const gaiaHubConfig = {
address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U',
server: 'https://hub.blockstack.org',
token: '',
url_prefix: 'gaia.testblockstack.org/hub/'
}
const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/file.json'
const fileContent = { test: 'test' }
const getOrSetLocalGaiaHubConnection = sinon.stub().resolves(gaiaHubConfig)
const getFullReadUrl = sinon.stub().resolves(fullReadUrl)
const { getFile } = proxyquire('../../../lib/storage', {
'./hub': { getOrSetLocalGaiaHubConnection, getFullReadUrl },
})
FetchMock.get(fullReadUrl, fileContent)
const nameLookupUrl = 'http://localhost:6270/v1/names/yukan.id'
const nameRecord = {"status": "registered", "zonefile": "$ORIGIN yukan.id\n$TTL 3600\n_http._tcp URI 10 1 \"https://gaia.blockstack.org/hub/16zVUoP7f15nfTiHw2UNiX8NT5SWYqwNv3/0/profile.json\"\n", "expire_block": 581432, "blockchain": "bitcoin", "last_txid": "f7fa811518566b1914a098c3bd61a810aee56390815bd608490b0860ac1b5b4d", "address": "16zVUoP7f15nfTiHw2UNiX8NT5SWYqwNv3", "zonefile_hash": "98f42e11026d42d394b3424d4d7f0cccd6f376e2"}
const nameRecordContent = JSON.stringify(nameRecord)
FetchMock.get(nameLookupUrl, nameRecordContent)
const profileUrl = 'https://gaia.blockstack.org/hub/16zVUoP7f15nfTiHw2UNiX8NT5SWYqwNv3/0/profile.json'
const profileContent = [
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJqdGkiOiJjNDhmOTQ0OC1hMGZlLTRiOWUtOWQ2YS1mYzA5MzhjOGUyNzAiLCJpYXQiOiIyMDE4LTAxLTA4VDE4OjIyOjI0Ljc5NloiLCJleHAiOiIyMDE5LTAxLTA4VDE4OjIyOjI0Ljc5NloiLCJzdWJqZWN0Ijp7InB1YmxpY0tleSI6IjAyNDg3YTkxY2Q5NjZmYWVjZWUyYWVmM2ZkZTM3MjgwOWI0NmEzNmVlMTkyNDhjMDFmNzJiNjQ1ZjQ0Y2VmMmUyYyJ9LCJpc3N1ZXIiOnsicHVibGljS2V5IjoiMDI0ODdhOTFjZDk2NmZhZWNlZTJhZWYzZmRlMzcyODA5YjQ2YTM2ZWUxOTI0OGMwMWY3MmI2NDVmNDRjZWYyZTJjIn0sImNsYWltIjp7IkB0eXBlIjoiUGVyc29uIiwiQGNvbnRleHQiOiJodHRwOi8vc2NoZW1hLm9yZyIsImltYWdlIjpbeyJAdHlwZSI6IkltYWdlT2JqZWN0IiwibmFtZSI6ImF2YXRhciIsImNvbnRlbnRVcmwiOiJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL2oxaDBrdHMwbTdhYWRpcC9hdmF0YXItMD9kbD0xIn1dLCJnaXZlbk5hbWUiOiIiLCJmYW1pbHlOYW1lIjoiIiwiZGVzY3JpcHRpb24iOiIiLCJhY2NvdW50IjpbeyJAdHlwZSI6IkFjY291bnQiLCJwbGFjZWhvbGRlciI6ZmFsc2UsInNlcnZpY2UiOiJoYWNrZXJOZXdzIiwiaWRlbnRpZmllciI6Inl1a2FubCIsInByb29mVHlwZSI6Imh0dHAiLCJwcm9vZlVybCI6Imh0dHBzOi8vbmV3cy55Y29tYmluYXRvci5jb20vdXNlcj9pZD15dWthbmwifSx7IkB0eXBlIjoiQWNjb3VudCIsInBsYWNlaG9sZGVyIjpmYWxzZSwic2VydmljZSI6ImdpdGh1YiIsImlkZW50aWZpZXIiOiJ5a25sIiwicHJvb2ZUeXBlIjoiaHR0cCIsInByb29mVXJsIjoiaHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20veWtubC8xZjcwMThiOThmNzE2ZjAxNWE2Y2Y0NGZkYTA4MDZkNyJ9LHsiQHR5cGUiOiJBY2NvdW50IiwicGxhY2Vob2xkZXIiOmZhbHNlLCJzZXJ2aWNlIjoidHdpdHRlciIsImlkZW50aWZpZXIiOiJ5dWthbmwiLCJwcm9vZlR5cGUiOiJodHRwIiwicHJvb2ZVcmwiOiJodHRwczovL3R3aXR0ZXIuY29tL3l1a2FuTC9zdGF0dXMvOTE2NzQwNzQ5MjM2MTAxMTIwIn1dLCJuYW1lIjoiS2VuIExpYW8iLCJhcHBzIjp7Imh0dHA6Ly9sb2NhbGhvc3Q6ODA4MCI6Imh0dHBzOi8vZ2FpYS5ibG9ja3N0YWNrLm9yZy9odWIvMUREVXFmS3RRZ1lOdDcyMnd1QjRaMmZQQzdhaU5HUWE1Ui8ifX19.UyQNZ02kBFHEovbwiGaS-VQd57w9kcwn1Nt3QbW3afEMArg1OndmeplB7lzjMuRCLAi-88lkpQLkFw7LwKZ31Q",
"decodedToken": {
"header": {
"typ": "JWT",
"alg": "ES256K"
},
"payload": {
"jti": "c48f9448-a0fe-4b9e-9d6a-fc0938c8e270",
"iat": "2018-01-08T18:22:24.796Z",
"exp": "2019-01-08T18:22:24.796Z",
"subject": {
"publicKey": "02487a91cd966faecee2aef3fde372809b46a36ee19248c01f72b645f44cef2e2c"
},
"issuer": {
"publicKey": "02487a91cd966faecee2aef3fde372809b46a36ee19248c01f72b645f44cef2e2c"
},
"claim": {
"@type": "Person",
"@context": "http://schema.org",
"image": [
{
"@type": "ImageObject",
"name": "avatar",
"contentUrl": "https://www.dropbox.com/s/j1h0kts0m7aadip/avatar-0?dl=1"
}
],
"givenName": "",
"familyName": "",
"description": "",
"account": [
{
"@type": "Account",
"placeholder": false,
"service": "hackerNews",
"identifier": "yukanl",
"proofType": "http",
"proofUrl": "https://news.ycombinator.com/user?id=yukanl"
},
{
"@type": "Account",
"placeholder": false,
"service": "github",
"identifier": "yknl",
"proofType": "http",
"proofUrl": "https://gist.github.com/yknl/1f7018b98f716f015a6cf44fda0806d7"
},
{
"@type": "Account",
"placeholder": false,
"service": "twitter",
"identifier": "yukanl",
"proofType": "http",
"proofUrl": "https://twitter.com/yukanL/status/916740749236101120"
}
],
"name": "Ken Liao",
"apps": {
"http://localhost:8080": "https://gaia.blockstack.org/hub/1DDUqfKtQgYNt722wuB4Z2fPC7aiNGQa5R/"
}
}
},
"signature": "UyQNZ02kBFHEovbwiGaS-VQd57w9kcwn1Nt3QbW3afEMArg1OndmeplB7lzjMuRCLAi-88lkpQLkFw7LwKZ31Q"
}
}
]
FetchMock.get(profileUrl, profileContent)
const fileUrl = 'https://gaia.blockstack.org/hub/1DDUqfKtQgYNt722wuB4Z2fPC7aiNGQa5R/file.json'
const fileContents = JSON.stringify({ "key": "value" })
FetchMock.get(fileUrl, fileContents)
const options = {
username: 'yukan.id',
app: 'http://localhost:8080'
}
getFile(path, options)
.then((file) => {
t.ok(file, 'Returns file content')
t.same(JSON.parse(file), JSON.parse(fileContents))
})
const optionsNoApp = {
username: 'yukan.id'
}
global.window = {
location: {
origin: 'http://localhost:8080'
}
}
getFile(path, optionsNoApp)
.then((file) => {
t.ok(file, 'Returns file content')
t.same(JSON.parse(file), JSON.parse(fileContents))
})
})
test('putFile unencrypted', (t) => {
t.plan(1)
const path = 'file.json'
const gaiaHubConfig = {
address: '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U',
server: 'https://hub.blockstack.org',
token: '',
url_prefix: 'gaia.testblockstack.org/hub/'
}
const fullReadUrl = 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/file.json'
const fileContent = { test: 'test' }
const getOrSetLocalGaiaHubConnection = sinon.stub().resolves(gaiaHubConfig)
const uploadToGaiaHub = sinon.stub().resolves(fullReadUrl)
const { putFile } = proxyquire('../../../lib/storage', {
'./hub': { getOrSetLocalGaiaHubConnection, uploadToGaiaHub },
})
putFile(path, fileContent)
.then((publicURL) => {
t.ok(publicURL, fullReadUrl)
})
})
test('fetch404null', (t) => {
t.plan(2)
const config = { address: '19MoWG8u88L6t766j7Vne21Mg4wHsCQ7vk',
url_prefix: 'gaia.testblockstack.org/hub/',
token: '',
server: 'hub.testblockstack.org' }
global.localStorage.setItem(BLOCKSTACK_GAIA_HUB_LABEL, JSON.stringify(config))
FetchMock.get(`${config.url_prefix}${config.address}/foo.json`,
{ status: 404 })
getFile('foo.json', false)
.then(x => t.equal(x, null, '404 should return null'))
getFile('foo.json', true)
.then(x => t.equal(x, null, '404 should return null, even if we try to decrypt'))
})
test('uploadToGaiaHub', (t) => {
t.plan(2)
const config = { address: '19MoWG8u88L6t766j7Vne21Mg4wHsCQ7vk',
url_prefix: 'gaia.testblockstack.org',
token: '',
server: 'hub.testblockstack.org' }
FetchMock.post(`${config.server}/store/${config.address}/foo.json`,
JSON.stringify({publicURL: '${config.url_prefix}/${config.address}/foo.json'}))
uploadToGaiaHub('foo.json', 'foo the bar', config)
.then((url) => {
t.ok(url, 'URL returned')
t.equal(url, '${config.url_prefix}/${config.address}/foo.json')
})
})
test('getFullReadUrl', (t) => {
t.plan(1)
const config = { address: '19MoWG8u88L6t766j7Vne21Mg4wHsCQ7vk',
url_prefix: 'gaia.testblockstack.org',
token: '',
server: 'hub.testblockstack.org' }
const outUrl = getFullReadUrl('foo.json', config)
t.equal(`${config.url_prefix}${config.address}/foo.json`, outUrl)
})
test('connectToGaiaHub', (t) => {
t.plan(5)
const hubServer = 'hub.testblockstack.org'
const hubInfo = {
read_url_prefix: 'gaia.testblockstack.org',
challenge_text: 'please-sign',
}
const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229'
const address = '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U'
const publicKey = '027d28f9951ce46538951e3697c62588a87f1f1f295de4a14fdd4c780fc52cfe69'
FetchMock.get(`${hubServer}/hub_info`,
JSON.stringify(hubInfo))
connectToGaiaHub(hubServer, privateKey)
.then((config) => {
t.ok(config, 'Config returned by connectToGaiaHub()')
t.equal(hubInfo.read_url_prefix, config.url_prefix)
t.equal(address, config.address)
t.equal(hubServer, config.server)
const verificationKey = bitcoin.ECPair.fromPublicKeyBuffer(Buffer.from(publicKey, 'hex'))
const decoded = JSON.parse(Buffer.from(config.token, 'base64').toString())
const signature = bitcoin.ECSignature.fromDER(Buffer.from(
decoded.signature, 'hex'))
t.ok(verificationKey.verify(
bitcoin.crypto.sha256(hubInfo.challenge_text), signature), 'Verified token')
})
})
test('getBucketUrl', (t) => {
t.plan(2)
const hubServer = 'hub2.testblockstack.org'
const hubInfo = {
read_url_prefix: 'https://gaia.testblockstack.org/hub/',
challenge_text: 'please-sign',
}
const privateKey = 'a5c61c6ca7b3e7e55edee68566aeab22e4da26baa285c7bd10e8d2218aa3b229'
const address = '1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U'
FetchMock.get(`${hubServer}/hub_info`, JSON.stringify(hubInfo))
getBucketUrl(hubServer, privateKey)
.then((bucketUrl) => {
t.ok(bucketUrl, 'App index file URL returned by getBucketUrl')
t.equal(bucketUrl, `${hubInfo.read_url_prefix}${address}/`)
})
})
test('getUserAppFileUrl', (t) => {
t.plan(2)
const path = 'file.json'
const name = 'test.id'
const appOrigin = 'testblockstack.org'
const profile = {
'apps': {
'testblockstack.org': 'https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/'
}
}
const fileUrl = "https://gaia.testblockstack.org/hub/1NZNxhoxobqwsNvTb16pdeiqvFvce3Yg8U/file.json"
const lookupProfile = sinon.stub().resolves(profile)
const { getUserAppFileUrl } = proxyquire('../../../lib/storage', {
'../profiles': { lookupProfile }
})
getUserAppFileUrl(path, name, appOrigin)
.then((url) => {
t.ok(url, 'Returns user app file url')
t.equals(url, fileUrl)
})
})
}