From 4da745ae964f9a58bdc78f9cd5f84c5ed0b877ee Mon Sep 17 00:00:00 2001 From: janniks <6362150+janniks@users.noreply.github.com> Date: Fri, 28 Apr 2023 13:29:03 +0200 Subject: [PATCH] chore: remove duplicate profile definition (#1478) * chore: remove duplicate profile definition * test: add fetchProfileFromUrl tests --------- Co-authored-by: janniks --- package-lock.json | 1 + packages/profile/src/index.ts | 2 +- packages/profile/src/profile.ts | 1 - packages/profile/src/types.ts | 1 + packages/wallet-sdk/package.json | 1 + packages/wallet-sdk/src/models/common.ts | 28 +------ packages/wallet-sdk/src/models/profile.ts | 33 +++++--- packages/wallet-sdk/tests/mocks.ts | 6 +- .../wallet-sdk/tests/models/profile.test.ts | 80 ++++++++++++++----- 9 files changed, 88 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 302a0b10..dac2412a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/packages/profile/src/index.ts b/packages/profile/src/index.ts index e61bbd68..c910d318 100644 --- a/packages/profile/src/index.ts +++ b/packages/profile/src/index.ts @@ -15,4 +15,4 @@ export { extractProfile, } from './profileTokens'; -export { PublicProfile, PublicPersonProfile } from './types'; +export { PublicProfileBase, PublicProfile, PublicPersonProfile } from './types'; diff --git a/packages/profile/src/profile.ts b/packages/profile/src/profile.ts index 44641ef0..72d67bd2 100644 --- a/packages/profile/src/profile.ts +++ b/packages/profile/src/profile.ts @@ -36,7 +36,6 @@ const schemaDefinition: { [key: string]: any } = { /** * Represents a user profile - * */ export class Profile { _profile: { [key: string]: any }; diff --git a/packages/profile/src/types.ts b/packages/profile/src/types.ts index 88e8cb7b..2863b1d6 100644 --- a/packages/profile/src/types.ts +++ b/packages/profile/src/types.ts @@ -23,6 +23,7 @@ export interface PublicProfileBase { storage: string; }; }; + [k: string]: unknown; } export interface PublicPersonProfile extends PublicProfileBase { diff --git a/packages/wallet-sdk/package.json b/packages/wallet-sdk/package.json index 422bd1d5..37759711 100644 --- a/packages/wallet-sdk/package.json +++ b/packages/wallet-sdk/package.json @@ -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" }, diff --git a/packages/wallet-sdk/src/models/common.ts b/packages/wallet-sdk/src/models/common.ts index 5323b6e8..5daf4ef0 100644 --- a/packages/wallet-sdk/src/models/common.ts +++ b/packages/wallet-sdk/src/models/common.ts @@ -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. diff --git a/packages/wallet-sdk/src/models/profile.ts b/packages/wallet-sdk/src/models/profile.ts index 3aa1e43b..5d5aa514 100644 --- a/packages/wallet-sdk/src/models/profile.ts +++ b/packages/wallet-sdk/src/models/profile.ts @@ -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; diff --git a/packages/wallet-sdk/tests/mocks.ts b/packages/wallet-sdk/tests/mocks.ts index 1b38d5f8..99a221c1 100644 --- a/packages/wallet-sdk/tests/mocks.ts +++ b/packages/wallet-sdk/tests/mocks.ts @@ -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', }, }, -]); +]; diff --git a/packages/wallet-sdk/tests/models/profile.test.ts b/packages/wallet-sdk/tests/models/profile.test.ts index 4c5b6271..57f9a1b6 100644 --- a/packages/wallet-sdk/tests/models/profile.test.ts +++ b/packages/wallet-sdk/tests/models/profile.test.ts @@ -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); + }); +});