chore: remove duplicate profile definition (#1478)

* chore: remove duplicate profile definition

* test: add fetchProfileFromUrl tests

---------

Co-authored-by: janniks <janniks@users.noreply.github.com>
This commit is contained in:
janniks
2023-04-28 13:29:03 +02:00
committed by GitHub
parent c116a851c4
commit 4da745ae96
9 changed files with 88 additions and 65 deletions

1
package-lock.json generated
View File

@@ -25686,6 +25686,7 @@
"@types/node": "^18.0.4",
"assert": "^2.0.0",
"crypto-browserify": "^3.12.0",
"jest-fetch-mock": "^3.0.3",
"process": "^0.11.10",
"yalc": "1.0.0-pre.49"
}

View File

@@ -15,4 +15,4 @@ export {
extractProfile,
} from './profileTokens';
export { PublicProfile, PublicPersonProfile } from './types';
export { PublicProfileBase, PublicProfile, PublicPersonProfile } from './types';

View File

@@ -36,7 +36,6 @@ const schemaDefinition: { [key: string]: any } = {
/**
* Represents a user profile
*
*/
export class Profile {
_profile: { [key: string]: any };

View File

@@ -23,6 +23,7 @@ export interface PublicProfileBase {
storage: string;
};
};
[k: string]: unknown;
}
export interface PublicPersonProfile extends PublicProfileBase {

View File

@@ -47,6 +47,7 @@
"@types/node": "^18.0.4",
"assert": "^2.0.0",
"crypto-browserify": "^3.12.0",
"jest-fetch-mock": "^3.0.3",
"process": "^0.11.10",
"yalc": "1.0.0-pre.49"
},

View File

@@ -2,6 +2,7 @@ import { getPublicKeyFromPrivate, publicKeyToBtcAddress } from '@stacks/encrypti
// https://github.com/paulmillr/scure-bip32
// Secure, audited & minimal implementation of BIP32 hierarchical deterministic (HD) wallets.
import { HDKey } from '@scure/bip32';
import { Profile } from '@stacks/profile';
export interface Account {
/** The private key used for STX payments */
@@ -24,33 +25,6 @@ export interface Account {
// Used to replicate deriveHardened bip32 method using deriveChild of scure-bip32 to offload old bip32 library
export const HARDENED_OFFSET = 0x80_00_00_00;
const PERSON_TYPE = 'Person';
const CONTEXT = 'http://schema.org';
const IMAGE_TYPE = 'ImageObject';
export interface ProfileImage {
'@type': typeof IMAGE_TYPE;
name: string;
contentUrl: string;
}
export interface Profile {
'@type': typeof PERSON_TYPE;
'@context': typeof CONTEXT;
apps?: {
[origin: string]: string;
};
appsMeta?: {
[origin: string]: {
publicKey: string;
storage: string;
};
};
name?: string;
image?: ProfileImage[];
[key: string]: any;
}
/**
* This object represents the keys that were derived from the root-level
* keychain of a wallet.

View File

@@ -1,11 +1,16 @@
import { getProfileURLFromZoneFile } from '../utils';
import { signProfileToken, wrapProfileToken } from '@stacks/profile';
import { connectToGaiaHub, GaiaHubConfig, uploadToGaiaHub } from '@stacks/storage';
import { getPublicKeyFromPrivate } from '@stacks/encryption';
import { Account, Profile, getGaiaAddress } from './common';
import { createFetchFn, FetchFn } from '@stacks/network';
import { FetchFn, createFetchFn } from '@stacks/network';
import {
PublicPersonProfile,
PublicProfileBase,
signProfileToken,
wrapProfileToken,
} from '@stacks/profile';
import { GaiaHubConfig, connectToGaiaHub, uploadToGaiaHub } from '@stacks/storage';
import { getProfileURLFromZoneFile } from '../utils';
import { Account, getGaiaAddress } from './common';
export const DEFAULT_PROFILE: Profile = {
export const DEFAULT_PROFILE: PublicPersonProfile = {
'@type': 'Person',
'@context': 'http://schema.org',
};
@@ -21,11 +26,9 @@ export const fetchProfileFromUrl = async (
if (res.ok) {
const json = await res.json();
const { decodedToken } = json[0];
return decodedToken.payload?.claim as Profile;
}
if (res.status === 404) {
return null;
return decodedToken.payload?.claim as PublicProfileBase;
}
if (res.status === 404) return null;
throw new Error('Network error when fetching profile');
} catch (error) {
return null;
@@ -52,7 +55,13 @@ export const fetchAccountProfileUrl = async ({
return `${gaiaHubUrl}${getGaiaAddress(account)}/profile.json`;
};
export function signProfileForUpload({ profile, account }: { profile: Profile; account: Account }) {
export function signProfileForUpload({
profile,
account,
}: {
profile: PublicProfileBase;
account: Account;
}) {
// the profile is always signed with the stx private key
// because a username (if any) is owned by the stx private key
const privateKey = account.stxPrivateKey;
@@ -95,7 +104,7 @@ export const signAndUploadProfile = async ({
account,
gaiaHubConfig,
}: {
profile: Profile;
profile: PublicProfileBase;
gaiaHubUrl: string;
account: Account;
gaiaHubConfig?: GaiaHubConfig;

View File

@@ -1,5 +1,7 @@
import { Wallet, WalletConfig } from '../src';
// todo: clean up unused mock values
export const mockWallet: Wallet = {
salt: 'c15619adafe7e75a195a1a2b5788ca42e585a3fd181ae2ff009c6089de54ed9e',
rootKey:
@@ -44,7 +46,7 @@ export const mockGaiaHubInfo = JSON.stringify({
latest_auth_version: 'v1',
});
export const mockProfileResponse = JSON.stringify([
export const MOCK_PROFILE_RESPONSE = [
{
token:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJqdGkiOiJmYmY1YjU4OC03NjA1LTQ4YWEtOWZkZi1iMTI2ODhhMGQwNDciLCJpYXQiOiIyMDIwLTA1LTE5VDEyOjQwOjA5LjQ4OVoiLCJleHAiOiIyMDIxLTA1LTE5VDEyOjQwOjA5LjQ4OVoiLCJzdWJqZWN0Ijp7InB1YmxpY0tleSI6IjAzZTkzYWU2NWQ2Njc1MDYxYTE2N2MzNGI4MzIxYmVmODc1OTQ0NjhlOWIyZGQxOWMwNWE2N2E3YjRjYWVmYTAxNyJ9LCJpc3N1ZXIiOnsicHVibGljS2V5IjoiMDNlOTNhZTY1ZDY2NzUwNjFhMTY3YzM0YjgzMjFiZWY4NzU5NDQ2OGU5YjJkZDE5YzA1YTY3YTdiNGNhZWZhMDE3In0sImNsYWltIjp7IkB0eXBlIjoiUGVyc29uIiwiQGNvbnRleHQiOiJodHRwOi8vc2NoZW1hLm9yZyIsImFwcHMiOnsiaHR0cHM6Ly9iYW50ZXIucHViIjoiaHR0cHM6Ly9nYWlhLmJsb2Nrc3RhY2sub3JnL2h1Yi8xRGt1QUNodWZZalRrVENlakpnU3p0dXFwNUtkeWtwV2FwLyIsImh0dHA6Ly8xMjcuMC4wLjE6MzAwMCI6Imh0dHBzOi8vZ2FpYS5ibG9ja3N0YWNrLm9yZy9odWIvMTVoQUxuRUo4ZnZYTmdSeXptVnNwRHlaY0dFeExHSE5TZi8iLCJodHRwczovL2Jsb2Nrc3RhY2suZ2l0aHViLmlvIjoiaHR0cHM6Ly9nYWlhLmJsb2Nrc3RhY2sub3JnL2h1Yi8xRUR1dktmenVOUlVlbnR6MXQ1amZ4VDlmVzFIUDJOSkdXLyJ9LCJhcGkiOnsiZ2FpYUh1YkNvbmZpZyI6eyJ1cmxfcHJlZml4IjoiaHR0cHM6Ly9nYWlhLmJsb2Nrc3RhY2sub3JnL2h1Yi8ifSwiZ2FpYUh1YlVybCI6Imh0dHBzOi8vaHViLmJsb2Nrc3RhY2sub3JnIn0sImFwcHNNZXRhIjp7Imh0dHBzOi8vYmFudGVyLnB1YiI6eyJzdG9yYWdlIjoiaHR0cHM6Ly9nYWlhLmJsb2Nrc3RhY2sub3JnL2h1Yi8xRGt1QUNodWZZalRrVENlakpnU3p0dXFwNUtkeWtwV2FwLyIsInB1YmxpY0tleSI6IjAyMDIxZWZkMGVjM2E0Y2ZkZGQ5ZjA1OTg4NGE4MzRiYjMzMmM4N2ZkYWRhMDUzODA3NzBiZmY1M2Q1ODQ2ZThhYSJ9fX19.GyFt9EZXwr8_jbunvrA38Rv80oBsgjokEPz4SXmZ724I8FCn22g7PK5tWBmpLCZAqVaaYgls9oJcX0vYN-BVsA',
@@ -92,4 +94,4 @@ export const mockProfileResponse = JSON.stringify([
'GyFt9EZXwr8_jbunvrA38Rv80oBsgjokEPz4SXmZ724I8FCn22g7PK5tWBmpLCZAqVaaYgls9oJcX0vYN-BVsA',
},
},
]);
];

View File

@@ -1,48 +1,84 @@
import { getPublicKeyFromPrivate } from "@stacks/encryption";
import { makeRandomPrivKey, privateKeyToString } from "@stacks/transactions";
import { TokenVerifier } from "jsontokens";
import { DEFAULT_PROFILE, signProfileForUpload } from "../../src/models/profile";
import { mockAccount } from "../mocks";
import { getPublicKeyFromPrivate } from '@stacks/encryption';
import { makeRandomPrivKey, privateKeyToString } from '@stacks/transactions';
import fetchMock from 'jest-fetch-mock';
import { TokenVerifier } from 'jsontokens';
import {
DEFAULT_PROFILE,
fetchProfileFromUrl,
signProfileForUpload,
} from '../../src/models/profile';
import { MOCK_PROFILE_RESPONSE, mockAccount } from '../mocks';
describe(signProfileForUpload, () => {
test('sign with the stx private key', () => {
const account = mockAccount;
const profile = DEFAULT_PROFILE
const profile = DEFAULT_PROFILE;
const signedProfileString = signProfileForUpload({profile, account});
const signedProfileToken = JSON.parse(signedProfileString)[0]
const signedProfileString = signProfileForUpload({ profile, account });
const signedProfileToken = JSON.parse(signedProfileString)[0];
const tokenVerifierData = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.dataPrivateKey.slice(0,64)));
const tokenVerifierData = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.dataPrivateKey.slice(0, 64))
);
expect(tokenVerifierData.verify(signedProfileToken.token)).toEqual(false);
const tokenVerifierStx = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.stxPrivateKey.slice(0,64)));
const tokenVerifierStx = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.stxPrivateKey.slice(0, 64))
);
expect(tokenVerifierStx.verify(signedProfileToken.token)).toEqual(true);
});
test('sign with the data private key', () => {
const account = mockAccount;
account.stxPrivateKey = account.dataPrivateKey;
const profile = DEFAULT_PROFILE
const profile = DEFAULT_PROFILE;
const signedProfileString = signProfileForUpload({profile, account});
const signedProfileToken = JSON.parse(signedProfileString)[0]
const tokenVerifierData = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.dataPrivateKey.slice(0,64)));
const signedProfileString = signProfileForUpload({ profile, account });
const signedProfileToken = JSON.parse(signedProfileString)[0];
const tokenVerifierData = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.dataPrivateKey.slice(0, 64))
);
expect(tokenVerifierData.verify(signedProfileToken.token)).toEqual(true);
const tokenVerifierStx = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.stxPrivateKey.slice(0,64)));
const tokenVerifierStx = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.stxPrivateKey.slice(0, 64))
);
expect(tokenVerifierStx.verify(signedProfileToken.token)).toEqual(true);
});
test('sign with unknown private key', () => {
const account = mockAccount;
account.stxPrivateKey = privateKeyToString(makeRandomPrivKey());
const profile = DEFAULT_PROFILE
const profile = DEFAULT_PROFILE;
const signedProfileString = signProfileForUpload({profile, account});
const signedProfileToken = JSON.parse(signedProfileString)[0]
const tokenVerifierData = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.dataPrivateKey.slice(0,64)));
const signedProfileString = signProfileForUpload({ profile, account });
const signedProfileToken = JSON.parse(signedProfileString)[0];
const tokenVerifierData = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.dataPrivateKey.slice(0, 64))
);
expect(tokenVerifierData.verify(signedProfileToken.token)).toEqual(false);
const tokenVerifierStx = new TokenVerifier('ES256k', getPublicKeyFromPrivate(account.stxPrivateKey.slice(0,64)));
const tokenVerifierStx = new TokenVerifier(
'ES256k',
getPublicKeyFromPrivate(account.stxPrivateKey.slice(0, 64))
);
expect(tokenVerifierStx.verify(signedProfileToken.token)).toEqual(true);
});
});
describe('fetchProfileFromUrl', () => {
test('profile claim', async () => {
fetchMock.mockResponseOnce(JSON.stringify(MOCK_PROFILE_RESPONSE));
const profile = await fetchProfileFromUrl(
'https://gaia.blockstack.org/hub/1Dvq2tBg8FLeWs5H93Dt5jVfEEiJFa3R8C/profile.json'
);
expect(profile).toEqual(MOCK_PROFILE_RESPONSE[0].decodedToken.payload.claim);
});
test('404', async () => {
const profile = await fetchProfileFromUrl('https://gaia.blockstack.org/hub/404');
expect(profile).toBe(null);
});
});