mirror of
https://github.com/alexgo-io/stacks.js.git
synced 2026-01-12 17:52:41 +08:00
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:
1
package-lock.json
generated
1
package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -15,4 +15,4 @@ export {
|
||||
extractProfile,
|
||||
} from './profileTokens';
|
||||
|
||||
export { PublicProfile, PublicPersonProfile } from './types';
|
||||
export { PublicProfileBase, PublicProfile, PublicPersonProfile } from './types';
|
||||
|
||||
@@ -36,7 +36,6 @@ const schemaDefinition: { [key: string]: any } = {
|
||||
|
||||
/**
|
||||
* Represents a user profile
|
||||
*
|
||||
*/
|
||||
export class Profile {
|
||||
_profile: { [key: string]: any };
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface PublicProfileBase {
|
||||
storage: string;
|
||||
};
|
||||
};
|
||||
[k: string]: unknown;
|
||||
}
|
||||
|
||||
export interface PublicPersonProfile extends PublicProfileBase {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user