Skip to content

Commit

Permalink
fix: better lookup for profile location, fixes #377
Browse files Browse the repository at this point in the history
  • Loading branch information
hstove committed Jul 21, 2020
1 parent 3c66887 commit 9fd36e6
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 10 deletions.
1 change: 1 addition & 0 deletions packages/keychain/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
},
},
},
moduleFileExtensions: ['js', 'ts', 'd.ts'],
setupFiles: ['./tests/global-setup.ts'],
setupFilesAfterEnv: ['./tests/setup.ts'],
};
3 changes: 2 additions & 1 deletion packages/keychain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@
"c32check": "^1.0.1",
"jsontokens": "^3.0.0",
"prettier": "^2.0.5",
"triplesec": "^3.0.27"
"triplesec": "^3.0.27",
"zone-file": "^1.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
21 changes: 14 additions & 7 deletions packages/keychain/src/identity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { bip32, ECPair } from 'bitcoinjs-lib';
import { getPublicKeyFromPrivate } from 'blockstack/lib/keys';
import { makeAuthResponse } from 'blockstack/lib/auth/authMessages';
import { getProfileURLFromZoneFile } from './utils';

import { IdentityKeyPair } from './utils/index';
import {
Expand Down Expand Up @@ -116,9 +117,17 @@ export class Identity {
return appsNode.getAppPrivateKey(appDomain);
}

// eslint-disable-next-line @typescript-eslint/require-await
async profileUrl(gaiaUrl: string) {
// future proofing for code that may require network requests to find profile
async profileUrl(gaiaUrl: string): Promise<string> {
if (this.defaultUsername) {
try {
const url = await getProfileURLFromZoneFile(this.defaultUsername);
if (url) return url;
} catch (error) {
if (process.env.NODE_ENV !== 'test') {
console.warn('Error fetching profile URL from zone file:', error);
}
}
}
return `${gaiaUrl}${this.address}/profile.json`;
}

Expand All @@ -135,10 +144,7 @@ export class Identity {
*/
async refresh(opts: RefreshOptions = { gaiaUrl: DEFAULT_GAIA_HUB }) {
try {
const [names, profile] = await Promise.all([
this.fetchNames(),
fetchProfile({ identity: this, gaiaUrl: opts.gaiaUrl }),
]);
const names = await this.fetchNames();
if (names) {
if (names[0] && !this.defaultUsername) {
this.defaultUsername = names[0];
Expand All @@ -150,6 +156,7 @@ export class Identity {
}
});
}
const profile = await fetchProfile({ identity: this, gaiaUrl: opts.gaiaUrl });
if (profile) {
this.profile = profile;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/keychain/src/types/zone-file.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
declare module 'zone-file' {
interface URI {
target: string;
}
export interface ZoneFile {
$origin: string;
uri: URI[];
}

export const parseZoneFile: (zoneFile: string) => ZoneFile
}
18 changes: 18 additions & 0 deletions packages/keychain/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { BIP32Interface } from 'bitcoinjs-lib';
import IdentityAddressOwnerNode from '../nodes/identity-address-owner-node';
import { createSha2Hash } from 'blockstack/lib/encryption/sha2Hash';
import { publicKeyToAddress } from 'blockstack/lib/keys';
import '../types/zone-file';
import { parseZoneFile } from 'zone-file';
import Identity from '../identity';
import { AssertionError } from 'assert';
import { Subdomains, registrars } from '../profiles';
Expand Down Expand Up @@ -241,3 +243,19 @@ export const validateSubdomain = async (

return null;
};

interface NameInfoResponse {
address: string;
zonefile: string;
}

export const getProfileURLFromZoneFile = async (name: string) => {
const url = `https://core.blockstack.org/v1/names/${name}`;
const res = await fetch(url);
if (res.ok) {
const nameInfo: NameInfoResponse = await res.json();
const zone = parseZoneFile(nameInfo.zonefile);
return zone.uri[0].target;
}
return;
};
14 changes: 14 additions & 0 deletions packages/keychain/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,17 @@ export const profileResponse = [
},
},
];

export const nameInfoResponse = {
address: '1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs',
blockchain: 'bitcoin',
expire_block: 599266,
grace_period: false,
last_txid: '1edfa419f7b83f33e00830bc9409210da6c6d1db60f99eda10c835aa339cad6b',
renewal_deadline: 604266,
resolver: null,
status: 'registered',
zonefile:
'$ORIGIN muneeb.id\n$TTL 3600\n_http._tcp IN URI 10 1 "https://gaia.blockstack.org/hub/1J3PUxY5uDShUnHRrMyU6yKtoHEUPhKULs/0/profile.json"\n',
zonefile_hash: '37aecf837c6ae9bdc9dbd98a268f263dacd00361',
};
13 changes: 12 additions & 1 deletion packages/keychain/tests/identity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import './setup';
import { makeECPrivateKey, getPublicKeyFromPrivate } from 'blockstack/lib/keys';
import { decryptPrivateKey } from 'blockstack/lib/auth/authMessages';
import { decodeToken } from 'jsontokens';
import { getIdentity, profileResponse } from './helpers';
import { getIdentity, profileResponse, nameInfoResponse } from './helpers';
import { ecPairToAddress } from 'blockstack';
import { ECPair } from 'bitcoinjs-lib';
import { getAddress } from '../src';
Expand Down Expand Up @@ -41,6 +41,8 @@ test('adds to apps in profile if publish_data scope', async () => {
.once(JSON.stringify({}), { status: 404 })
.once(JSON.stringify({ read_url_prefix: 'https://gaia.blockstack.org/hub/' }))
.once(JSON.stringify({ read_url_prefix: 'https://gaia.blockstack.org/hub/' }))
.once(JSON.stringify({}))
.once(JSON.stringify({}))
.once(JSON.stringify({}));
const identity = await getIdentity();
const appDomain = 'https://banter.pub';
Expand Down Expand Up @@ -92,16 +94,25 @@ test('gets default profile URL', async () => {
);
});

test('can get a profile URL from a zone file', async () => {
const identity = await getIdentity();
fetchMock.once(JSON.stringify(nameInfoResponse));
const profileURL = await identity.profileUrl('asdf');
return;
});

describe('refresh', () => {
test('can fetch names for an identity', async () => {
const identity = await getIdentity();

fetchMock.once(JSON.stringify({ names: ['myname.id'] }));
fetchMock.once(JSON.stringify(nameInfoResponse));
fetchMock.once(JSON.stringify(profileResponse));

await identity.refresh();
expect(identity.defaultUsername).toEqual('myname.id');
expect(identity.usernames).toEqual(['myname.id']);
expect(identity.profile).toBeTruthy();
});

test('can fetch multiple usernames', async () => {
Expand Down
6 changes: 5 additions & 1 deletion packages/keychain/tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { Subdomains, registrars, Wallet, decrypt } from '../src';
import { mnemonicToSeed } from 'bip39';
import { bip32 } from 'bitcoinjs-lib';
import { profileResponse } from './helpers';
import { profileResponse, nameInfoResponse } from './helpers';
import { ChainID } from '@blockstack/stacks-transactions';

describe(validateSubdomainFormat.name, () => {
Expand Down Expand Up @@ -101,12 +101,16 @@ test('recursively makes identities', async () => {

fetchMock
.once(JSON.stringify({ names: ['myname.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: ['myname2.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: ['myname3.id'] }))
.once(JSON.stringify(nameInfoResponse))
.once(JSON.stringify(profileResponse))
.once(JSON.stringify({ names: [] }))
.once('', { status: 404 })
.once(JSON.stringify(profileResponse));
const identities = await recursiveRestoreIdentities({ rootNode });
expect(identities[0].defaultUsername).toEqual('myname.id');
Expand Down
69 changes: 69 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1673,6 +1673,27 @@
sha.js "^2.4.11"
smart-buffer "^4.1.0"

"@blockstack/stacks-transactions@^0.4.6":
version "0.4.6"
resolved "https://registry.yarnpkg.com/@blockstack/stacks-transactions/-/stacks-transactions-0.4.6.tgz#b774250fbaadbadf42313a82fbd628b1728cd6ed"
integrity sha512-3Hb+v0ZmG5bVZHasfM9KzlwK+2e5r6oKsKk0eRgavLb5bBMQy/cw0YYoUWmt+ipNqDP5ssZgoOA1KKRhoSWvXg==
dependencies:
"@types/bn.js" "^4.11.6"
"@types/elliptic" "^6.4.12"
"@types/lodash" "^4.14.149"
"@types/randombytes" "^2.0.0"
"@types/ripemd160" "^2.0.0"
"@types/sha.js" "^2.4.0"
bn.js "^4.11.8"
c32check "^1.0.1"
cross-fetch "^3.0.4"
elliptic "^6.5.2"
lodash "^4.17.15"
randombytes "^2.1.0"
ripemd160 "^2.0.2"
sha.js "^2.4.11"
smart-buffer "^4.1.0"

"@blockstack/stats@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@blockstack/stats/-/stats-0.7.0.tgz#be39d3e76c2a16c1cd1283aa702d1e20b5e04645"
Expand Down Expand Up @@ -5433,6 +5454,32 @@ blockstack@21.0.0:
uuid "^3.3.3"
zone-file "^1.0.0"

blockstack@21.0.0-alpha.2:
version "21.0.0-alpha.2"
resolved "https://registry.yarnpkg.com/blockstack/-/blockstack-21.0.0-alpha.2.tgz#1f223387df24b5770d5e7ec4031df52b8014ca11"
integrity sha512-I7FQTYU78H/Eok/is0G79dSaQ7tD0DuKGKRCihHDdAKHYGeiTIZyHQ8fyK3NL8yEoRzRoIlCPEefCQgh/no6hg==
dependencies:
"@types/cheerio" "^0.22.13"
"@types/elliptic" "^6.4.10"
"@types/node" "^12.7.12"
"@types/randombytes" "^2.0.0"
ajv "^4.11.5"
bip39 "^3.0.2"
bitcoinjs-lib "^5.1.6"
bn.js "^4.11.8"
cross-fetch "^3.0.4"
elliptic "^6.5.1"
form-data "^2.5.1"
jsontokens "3.0.0-alpha.0"
query-string "^6.8.3"
randombytes "^2.1.0"
request "^2.88.0"
ripemd160-min "0.0.5"
schema-inspector "^1.6.8"
tslib "^1.10.0"
uuid "^3.3.3"
zone-file "^1.0.0"

bluebird@3.5.5:
version "3.5.5"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
Expand Down Expand Up @@ -11923,6 +11970,18 @@ jsontokens@3.0.0, jsontokens@^3.0.0:
elliptic "^6.4.1"
sha.js "^2.4.11"

jsontokens@3.0.0-alpha.0:
version "3.0.0-alpha.0"
resolved "https://registry.yarnpkg.com/jsontokens/-/jsontokens-3.0.0-alpha.0.tgz#16d04a2019a6dbe2392e4eeb489ac47ec3d855d7"
integrity sha512-+2JdFr2d3XBfamUnETQdv76u3AwBl8jmLp2Hgb/uK5NTys0FpoR5KbIIqv3QrXtjn0uIIPJz9JmhqDAnXd2Nhg==
dependencies:
"@types/elliptic" "^6.4.9"
asn1.js "^5.0.1"
base64url "^3.0.1"
ecdsa-sig-formatter "^1.0.11"
elliptic "^6.4.1"
key-encoder "^2.0.3"

jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
Expand All @@ -11941,6 +12000,16 @@ jsx-ast-utils@^2.2.3, jsx-ast-utils@^2.4.1:
array-includes "^3.1.1"
object.assign "^4.1.0"

key-encoder@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/key-encoder/-/key-encoder-2.0.3.tgz#77073bb48ff1fe2173bb2088b83b91152c8fa4ba"
integrity sha512-fgBtpAGIr/Fy5/+ZLQZIPPhsZEcbSlYu/Wu96tNDFNSjSACw5lEIOFeaVdQ/iwrb8oxjlWi6wmWdH76hV6GZjg==
dependencies:
"@types/elliptic" "^6.4.9"
asn1.js "^5.0.1"
bn.js "^4.11.8"
elliptic "^6.4.1"

killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
Expand Down

0 comments on commit 9fd36e6

Please sign in to comment.