From bd56e72bf1b3a99e46af02d639e1af2fc8ef7645 Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 24 Jun 2019 17:11:54 -0400 Subject: [PATCH 01/18] Update git ignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7b499e8f7..dc90e1a61 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ native/windows/BlockstackBrowser/Resources/node.exe native/windows/BlockstackBrowser/Resources/cors-proxy/corsproxy.js native/windows/BlockstackSetup/obj/ native/windows/BlockstackSetup/bin/ +native/windows/BlockstackSetup/*.msi native/macos/Blockstack/Blockstack/server/corsproxy.js native/macos/Blockstack/Blockstack/server/blockstackProxy.js native/macos/Blockstack/Blockstack/server/node From 8ab4b432900b46e33048ca0a656a80fb9d1fc789 Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 27 Jun 2019 00:30:48 -0400 Subject: [PATCH 02/18] Write and read encrypted settings files for each identity. --- app/js/account/store/account/actions.js | 26 +++++++++++++++-- app/js/account/store/account/reducer.js | 14 ++++++++- app/js/account/store/account/types.js | 1 + app/js/account/utils/index.js | 38 +++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 3 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 30266fe24..237137773 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -11,7 +11,7 @@ import { } from '@utils' import { isCoreEndpointDisabled } from '@utils/window-utils' import { transactions, config, network } from 'blockstack' - +import { fetchIdentitySettings } from '../../../account/utils' import roundTo from 'round-to' import * as types from './types' import log4js from 'log4js' @@ -547,6 +547,27 @@ function usedIdentityAddress() { } } +function refreshIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + ownerAddress: string, + identityKeyPair: { key: string } +) { + return dispatch => { + fetchIdentitySettings(api, ownerAddress, identityKeyPair) + .then((settings) => { + dispatch(updateIdentitySettings(identityIndex, settings)) + }) + } +} + +function updateIdentitySettings(identityIndex, settings) { + return { + type: types.UPDATE_IDENTITY_SETTINGS, + identityIndex, + settings + } +} + const AccountActions = { createAccount, updateBackupPhrase, @@ -569,7 +590,8 @@ const AccountActions = { usedIdentityAddress, displayedRecoveryCode, newIdentityAddress, - updateEmail + updateEmail, + refreshIdentitySettings } export default AccountActions diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 66c2c3910..9a21f4a81 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -11,6 +11,8 @@ const initialState = { addresses: [], keypairs: [] }, + identitySettings: { + }, bitcoinAccount: { addresses: [], balances: { total: 0.0 } @@ -43,6 +45,7 @@ function AccountReducer(state = initialState, action) { publicKeychain: action.identityPublicKeychain, addresses: action.identityAddresses, keypairs: action.identityKeypairs, + settings: {}, addressIndex: 0 }, bitcoinAccount: { @@ -258,13 +261,22 @@ function AccountReducer(state = initialState, action) { ...state.identityAccount.addresses, action.keypair.address ], - keypairs: [...state.identityAccount.keypairs, action.keypair] + keypairs: [...state.identityAccount.keypairs, action.keypair], + settings: [...state.identityAccount.settings, {}] }) }) case types.CONNECTED_STORAGE: return Object.assign({}, state, { connectedStorageAtLeastOnce: true }) + case types.UPDATE_IDENTITY_SETTINGS: + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: state.identityAccount.settings.map( + (settingsRow, i) => i === action.identityIndex ? action.newSettings : settingsRow + ) + }) + }) default: return state } diff --git a/app/js/account/store/account/types.js b/app/js/account/store/account/types.js index 2b4ecd189..7bd78f178 100644 --- a/app/js/account/store/account/types.js +++ b/app/js/account/store/account/types.js @@ -23,3 +23,4 @@ export const RECOVERY_CODE_VERIFIED = 'account/RECOVERY_CODE_VERIFIED' export const INCREMENT_IDENTITY_ADDRESS_INDEX = 'account/INCREMENT_IDENTITY_ADDRESS_INDEX' export const CONNECTED_STORAGE = 'account/CONNECTED_STORAGE' export const UPDATE_EMAIL_ADDRESS = 'account/UPDATE_EMAIL_ADDRESS' +export const UPDATE_IDENTITY_SETTINGS = 'account/UPDATE_IDENTITY_SETTINGS' diff --git a/app/js/account/utils/index.js b/app/js/account/utils/index.js index 550432406..988e261a9 100644 --- a/app/js/account/utils/index.js +++ b/app/js/account/utils/index.js @@ -4,6 +4,7 @@ import { parseZoneFile } from 'zone-file' import type { GaiaHubConfig } from './blockstack-inc' import { connectToGaiaHub, uploadToGaiaHub } from './blockstack-inc' +import { encryptContent, decryptContent } from 'blockstack' import { getTokenFileUrlFromZoneFile } from '@utils/zone-utils' import log4js from 'log4js' @@ -11,6 +12,7 @@ const logger = log4js.getLogger(__filename) export const BLOCKSTACK_INC = 'gaia-hub' const DEFAULT_PROFILE_FILE_NAME = 'profile.json' +const DEFAULT_IDENTITY_SETTINGS_FILE_NAME = 'settings.json' function getProfileUploadLocation(identity: any, hubConfig: GaiaHubConfig) { if (identity.zoneFile) { @@ -23,6 +25,10 @@ function getProfileUploadLocation(identity: any, hubConfig: GaiaHubConfig) { } } +function getSettingsUploadLocation(hubConfig: GaiaHubConfig) { + return `${hubConfig.url_prefix}${hubConfig.address}/${DEFAULT_IDENTITY_SETTINGS_FILE_NAME}` +} + // aaron-debt: this should be moved into blockstack.js function canWriteUrl(url: string, hubConfig: GaiaHubConfig): ?string { const readPrefix = `${hubConfig.url_prefix}${hubConfig.address}/` @@ -116,3 +122,35 @@ export function uploadProfile( return uploadAttempt }) } + +export function uploadIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig, gaiaHubUrl: string}, + identityKeyPair: { key: string, keyID: string }, + settingsData: string +) { + const publicKey = identityKeyPair.keyID + const encryptedSettingsData = encryptContent(settingsData, { publicKey }) + return connectToGaiaHub(api.gaiaHubUrl, identityKeyPair.key).then(identityHubConfig => { + const urlToWrite = getSettingsUploadLocation(identityHubConfig) + return tryUpload( + urlToWrite, + encryptedSettingsData, + identityHubConfig, + 'application/json' + ) + }) +} + +export function fetchIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + ownerAddress: string, + identityKeyPair: { key: string } +) { + const privateKey = identityKeyPair.key + const hubConfig = api.gaiaHubConfig + const url = `${hubConfig.url_prefix}${ownerAddress}/${DEFAULT_IDENTITY_SETTINGS_FILE_NAME}` + return fetch(url) + .then(response => response.text()) + .then(encryptedSettingsData => decryptContent(encryptedSettingsData, { privateKey })) + .then(decryptedSettingsData => JSON.parse(decryptedSettingsData)) +} From 3396a5f535184e25e9a7166b72da2a13780eba91 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 9 Jul 2019 12:47:07 -0400 Subject: [PATCH 03/18] Identity settings initialization. --- app/js/account/store/account/actions.js | 4 +++- app/js/account/store/account/reducer.js | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 237137773..e139ebcf3 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -549,6 +549,7 @@ function usedIdentityAddress() { function refreshIdentitySettings( api: { gaiaHubConfig: GaiaHubConfig }, + identityIndex: int, ownerAddress: string, identityKeyPair: { key: string } ) { @@ -591,7 +592,8 @@ const AccountActions = { displayedRecoveryCode, newIdentityAddress, updateEmail, - refreshIdentitySettings + refreshIdentitySettings, + updateIdentitySettings } export default AccountActions diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 9a21f4a81..8f4d0accd 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -9,9 +9,8 @@ const initialState = { encryptedBackupPhrase: null, // persist identityAccount: { addresses: [], - keypairs: [] - }, - identitySettings: { + keypairs: [], + settings: [] }, bitcoinAccount: { addresses: [], @@ -45,7 +44,7 @@ function AccountReducer(state = initialState, action) { publicKeychain: action.identityPublicKeychain, addresses: action.identityAddresses, keypairs: action.identityKeypairs, - settings: {}, + settings: [{}], addressIndex: 0 }, bitcoinAccount: { @@ -273,7 +272,7 @@ function AccountReducer(state = initialState, action) { return Object.assign({}, state, { identityAccount: Object.assign({}, state.identityAccount, { settings: state.identityAccount.settings.map( - (settingsRow, i) => i === action.identityIndex ? action.newSettings : settingsRow + (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow ) }) }) From 20fec3eaab1c2976208d8ba78bf6e483286c94c2 Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 10 Jul 2019 16:45:56 -0400 Subject: [PATCH 04/18] More identity settings initialization. --- app/js/account/store/account/actions.js | 6 ++++-- app/js/account/store/account/reducer.js | 2 +- app/js/utils/account-utils.js | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index e139ebcf3..73a1b536b 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -41,7 +41,8 @@ function createAccount( bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } = getBlockchainIdentities(masterKeychain, identitiesToGenerate) return { @@ -51,7 +52,8 @@ function createAccount( bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } } diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 8f4d0accd..26150a1d5 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -44,7 +44,7 @@ function AccountReducer(state = initialState, action) { publicKeychain: action.identityPublicKeychain, addresses: action.identityAddresses, keypairs: action.identityKeypairs, - settings: [{}], + settings: action.identitySettings, addressIndex: 0 }, bitcoinAccount: { diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index fc238ff9a..c7e2025fe 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -440,6 +440,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { const identityAddresses = [] const identityKeypairs = [] + const identitySettings = [] // We pre-generate a number of identity addresses so that we // don't have to prompt the user for the password on each new profile @@ -455,6 +456,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { const identityKeyPair = deriveIdentityKeyPair(identityOwnerAddressNode) identityKeypairs.push(identityKeyPair) identityAddresses.push(identityKeyPair.address) + identitySettings.push({}) logger.debug(`createAccount: identity index: ${addressIndex}`) } @@ -463,6 +465,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) { bitcoinPublicKeychain, firstBitcoinAddress, identityAddresses, - identityKeypairs + identityKeypairs, + identitySettings } } From b0cdbf9b868231110c00c2d35f924792ad00cf2d Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 11 Jul 2019 10:43:15 -0400 Subject: [PATCH 05/18] Identity settings refresh on profile page. --- app/js/account/store/account/actions.js | 45 +++++++++++++++++++++++-- app/js/account/store/account/reducer.js | 14 +++++--- app/js/account/store/account/types.js | 1 + app/js/account/utils/index.js | 14 ++++++-- app/js/profiles/DefaultProfilePage.js | 7 ++++ 5 files changed, 71 insertions(+), 10 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 73a1b536b..85e8585bf 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -549,17 +549,54 @@ function usedIdentityAddress() { } } +function refreshAllIdentitySettings( + api: { gaiaHubConfig: GaiaHubConfig }, + ownerAddresses: Array, + identityKeyPairs: Array +) { + return dispatch => { + const promises: Array> = ownerAddresses.map((address, index) => { + const promise: Promise<*> = new Promise((resolve, reject) => { + const keyPair = identityKeyPairs[index] + return fetchIdentitySettings(api, address, keyPair) + .then((settings) => { + resolve(settings) + }) + .catch(error => reject(error)) + }) + return promise + }) + + return Promise.all(promises) + .then(settings => { + return dispatch(updateAllIdentitySettings(settings)) + }) + .catch((error) => { + logger.error( + 'refreshIdentitySettings: error refreshing identity settings', + error + ) + return Promise.reject(error) + }) + } +} + function refreshIdentitySettings( api: { gaiaHubConfig: GaiaHubConfig }, identityIndex: int, ownerAddress: string, identityKeyPair: { key: string } ) { - return dispatch => { - fetchIdentitySettings(api, ownerAddress, identityKeyPair) + return dispatch => fetchIdentitySettings(api, ownerAddress, identityKeyPair) .then((settings) => { - dispatch(updateIdentitySettings(identityIndex, settings)) + return dispatch(updateIdentitySettings(identityIndex, settings)) }) +} + +function updateAllIdentitySettings(settings) { + return { + type: types.UPDATE_ALL_IDENTITY_SETTINGS, + settings } } @@ -594,7 +631,9 @@ const AccountActions = { displayedRecoveryCode, newIdentityAddress, updateEmail, + refreshAllIdentitySettings, refreshIdentitySettings, + updateAllIdentitySettings, updateIdentitySettings } diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 26150a1d5..6f343f9a1 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -268,14 +268,20 @@ function AccountReducer(state = initialState, action) { return Object.assign({}, state, { connectedStorageAtLeastOnce: true }) - case types.UPDATE_IDENTITY_SETTINGS: + case types.UPDATE_ALL_IDENTITY_SETTINGS: return Object.assign({}, state, { identityAccount: Object.assign({}, state.identityAccount, { - settings: state.identityAccount.settings.map( - (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow - ) + settings: action.settings }) }) + case types.UPDATE_IDENTITY_SETTINGS: + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: state.identityAccount.settings.map( + (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow + ) + }) + }) default: return state } diff --git a/app/js/account/store/account/types.js b/app/js/account/store/account/types.js index 7bd78f178..cc25721e5 100644 --- a/app/js/account/store/account/types.js +++ b/app/js/account/store/account/types.js @@ -23,4 +23,5 @@ export const RECOVERY_CODE_VERIFIED = 'account/RECOVERY_CODE_VERIFIED' export const INCREMENT_IDENTITY_ADDRESS_INDEX = 'account/INCREMENT_IDENTITY_ADDRESS_INDEX' export const CONNECTED_STORAGE = 'account/CONNECTED_STORAGE' export const UPDATE_EMAIL_ADDRESS = 'account/UPDATE_EMAIL_ADDRESS' +export const UPDATE_ALL_IDENTITY_SETTINGS = 'account/UPDATE_ALL_IDENTITY_SETTINGS' export const UPDATE_IDENTITY_SETTINGS = 'account/UPDATE_IDENTITY_SETTINGS' diff --git a/app/js/account/utils/index.js b/app/js/account/utils/index.js index 988e261a9..c59290bae 100644 --- a/app/js/account/utils/index.js +++ b/app/js/account/utils/index.js @@ -150,7 +150,15 @@ export function fetchIdentitySettings( const hubConfig = api.gaiaHubConfig const url = `${hubConfig.url_prefix}${ownerAddress}/${DEFAULT_IDENTITY_SETTINGS_FILE_NAME}` return fetch(url) - .then(response => response.text()) - .then(encryptedSettingsData => decryptContent(encryptedSettingsData, { privateKey })) - .then(decryptedSettingsData => JSON.parse(decryptedSettingsData)) + .then(response => { + if (response.ok) { + return response.text() + .then(encryptedSettingsData => decryptContent(encryptedSettingsData, { privateKey })) + .then(decryptedSettingsData => JSON.parse(decryptedSettingsData)) + } else if (response.status == 404) { + return {} + } else { + return Promise.reject('Could not fetch identity settings') + } + }) } diff --git a/app/js/profiles/DefaultProfilePage.js b/app/js/profiles/DefaultProfilePage.js index e7eb105bc..a516b8cf3 100644 --- a/app/js/profiles/DefaultProfilePage.js +++ b/app/js/profiles/DefaultProfilePage.js @@ -112,6 +112,13 @@ export class DefaultProfilePage extends Component { componentWillMount() { logger.info('componentWillMount') this.props.refreshIdentities(this.props.api, this.props.identityAddresses) + .then(() => { + this.props.refreshAllIdentitySettings( + this.props.api, + this.props.identityAddresses, + this.props.identityKeypairs + ) + }) } componentWillReceiveProps(nextProps) { From ce1c4a83c8d8e50ca24d37d3dc5f367b1b1fcfa0 Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 11 Jul 2019 16:55:56 -0400 Subject: [PATCH 06/18] Fix tests for DefaultProfilePage --- app/js/profiles/DefaultProfilePage.js | 17 ++++++++--------- test/profiles/DefaultProfilePage.test.js | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/js/profiles/DefaultProfilePage.js b/app/js/profiles/DefaultProfilePage.js index a516b8cf3..c6267c061 100644 --- a/app/js/profiles/DefaultProfilePage.js +++ b/app/js/profiles/DefaultProfilePage.js @@ -7,7 +7,7 @@ import { Person } from 'blockstack' import Modal from 'react-modal' import Image from '@components/Image' import { IdentityActions } from './store/identity' -import { AccountActions } from '../account/store/account' +import AccountActions from '../account/store/account' import SecondaryNavBar from '@components/SecondaryNavBar' import SocialAccountItem from './components/SocialAccountItem' import PGPAccountItem from './components/PGPAccountItem' @@ -66,7 +66,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return bindActionCreators( - Object.assign({}, IdentityActions, AccountActions), + Object.assign({}, AccountActions, IdentityActions), dispatch ) } @@ -79,6 +79,7 @@ export class DefaultProfilePage extends Component { createNewProfile: PropTypes.func.isRequired, updateProfile: PropTypes.func.isRequired, refreshIdentities: PropTypes.func.isRequired, + refreshAllIdentitySettings: PropTypes.func.isRequired, refreshSocialProofVerifications: PropTypes.func.isRequired, api: PropTypes.object.isRequired, identityAddresses: PropTypes.array.isRequired, @@ -112,13 +113,11 @@ export class DefaultProfilePage extends Component { componentWillMount() { logger.info('componentWillMount') this.props.refreshIdentities(this.props.api, this.props.identityAddresses) - .then(() => { - this.props.refreshAllIdentitySettings( - this.props.api, - this.props.identityAddresses, - this.props.identityKeypairs - ) - }) + this.props.refreshAllIdentitySettings( + this.props.api, + this.props.identityAddresses, + this.props.identityKeypairs + ) } componentWillReceiveProps(nextProps) { diff --git a/test/profiles/DefaultProfilePage.test.js b/test/profiles/DefaultProfilePage.test.js index ba657c8bd..074a8753b 100644 --- a/test/profiles/DefaultProfilePage.test.js +++ b/test/profiles/DefaultProfilePage.test.js @@ -26,7 +26,8 @@ function setup(accounts = []) { encryptedBackupPhrase: 'onwards and upwards', setDefaultIdentity: () => {}, identityKeypairs: [], - storageConnected: false + storageConnected: false, + refreshAllIdentitySettings: () => {} } const wrapper = shallow() From 8a8b14b3d173b785ecf5326e268c1c70b26a5aeb Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 11 Jul 2019 17:56:27 -0400 Subject: [PATCH 07/18] Fix import typo. --- app/js/profiles/DefaultProfilePage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/js/profiles/DefaultProfilePage.js b/app/js/profiles/DefaultProfilePage.js index c6267c061..d29a4b697 100644 --- a/app/js/profiles/DefaultProfilePage.js +++ b/app/js/profiles/DefaultProfilePage.js @@ -7,7 +7,7 @@ import { Person } from 'blockstack' import Modal from 'react-modal' import Image from '@components/Image' import { IdentityActions } from './store/identity' -import AccountActions from '../account/store/account' +import { AccountActions } from '../account/store/account' import SecondaryNavBar from '@components/SecondaryNavBar' import SocialAccountItem from './components/SocialAccountItem' import PGPAccountItem from './components/PGPAccountItem' From 59137b4b576e7b5eeaf5bad77ed85287c601043f Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 18 Jul 2019 11:10:45 -0400 Subject: [PATCH 08/18] Implementation of collection key generation and storage. --- app/js/auth/index.js | 169 ++++++++++++++--------- app/js/auth/utils.js | 15 +- app/js/common/store/selectors/account.js | 3 + app/js/store/reducers.js | 2 +- app/js/utils/account-utils.js | 61 ++++++++ 5 files changed, 186 insertions(+), 64 deletions(-) diff --git a/app/js/auth/index.js b/app/js/auth/index.js index 78b5f5c40..4ac1b644a 100644 --- a/app/js/auth/index.js +++ b/app/js/auth/index.js @@ -7,6 +7,7 @@ import { connect } from 'react-redux' import { randomBytes } from 'crypto' import { AuthActions } from './store/auth' import { IdentityActions } from '../profiles/store/identity' +import { AccountActions } from '../account/store/account' import { decodeToken, TokenSigner } from 'jsontokens' import { parseZoneFile } from 'zone-file' import queryString from 'query-string' @@ -19,7 +20,10 @@ import { updateQueryStringParameter, getPublicKeyFromPrivate } from 'blockstack' -import { AppsNode } from '@utils/account-utils' +import { AppsNode, CollectionsNode } from '@utils/account-utils' +import { + processCollectionScopes +} from '@utils/collection-utils' import { fetchProfileLocations, getDefaultProfileUrl @@ -31,7 +35,8 @@ import { uploadProfile } from '../account/utils' import { signProfileForUpload } from '@utils' import { validateScopes, - appRequestSupportsDirectHub + appRequestSupportsDirectHub, + getCollectionScopes } from './utils' import { selectApi, @@ -54,6 +59,7 @@ import { selectIdentityKeypairs, selectEmail, selectIdentityAddresses, + selectIdentitySettings, selectPublicKeychain } from '@common/store/selectors/account' import { formatAppManifest } from '@common' @@ -73,6 +79,7 @@ function mapStateToProps(state) { localIdentities: selectLocalIdentities(state), defaultIdentity: selectDefaultIdentity(state), identityKeypairs: selectIdentityKeypairs(state), + identitySettings: selectIdentitySettings(state), appManifest: selectAppManifest(state), appManifestLoading: selectAppManifestLoading(state), appManifestLoadingError: selectAppManifestLoadingError(state), @@ -88,7 +95,7 @@ function mapStateToProps(state) { } function mapDispatchToProps(dispatch) { - const actions = Object.assign({}, AuthActions, IdentityActions) + const actions = Object.assign({}, AuthActions, IdentityActions, AccountActions) return bindActionCreators(actions, dispatch) } @@ -178,8 +185,11 @@ class AuthPage extends React.Component { scopes: { ...this.state.scopes, email: scopes.includes('email'), - publishData: scopes.includes('publish_data') - } + publishData: scopes.includes('publish_data'), + }, + collectionScopes: [ + ...getCollectionScopes(scopes) + ] }) this.props.verifyAuthRequestAndLoadManifest(authRequest) @@ -274,9 +284,11 @@ class AuthPage extends React.Component { const profile = identity.profile const privateKey = profileSigningKeypair.key const appsNodeKey = profileSigningKeypair.appsNodeKey + const collectionsNodeKey = profileSigningKeypair.collectionsNodeKey const salt = profileSigningKeypair.salt const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() + const collectionsNode = new CollectionsNode(HDNode.fromBase58(collectionsNodeKey), salt) let profileUrlPromise @@ -314,71 +326,46 @@ class AuthPage extends React.Component { profileUrlPromise.then(profileUrl => { // Add app storage bucket URL to profile if publish_data scope is requested if (this.state.scopes.publishData) { - let apps = {} - if (profile.hasOwnProperty('apps')) { - apps = profile.apps - } - if (storageConnected) { - const hubUrl = this.props.api.gaiaHubUrl - getAppBucketUrl(hubUrl, appPrivateKey) - .then(appBucketUrl => { - logger.debug( - `componentWillReceiveProps: appBucketUrl ${appBucketUrl}` - ) - apps[appDomain] = appBucketUrl - logger.debug( - `componentWillReceiveProps: new apps array ${JSON.stringify( - apps - )}` - ) - profile.apps = apps - const signedProfileTokenData = signProfileForUpload( - profile, - nextProps.identityKeypairs[identityIndex], - this.props.api - ) - logger.debug( - 'componentWillReceiveProps: uploading updated profile with new apps array' - ) - return uploadProfile( - this.props.api, - identity, - nextProps.identityKeypairs[identityIndex], - signedProfileTokenData - ) - }) - .then(() => { - this.completeAuthResponse( - privateKey, - blockchainId, - coreSessionToken, - appPrivateKey, - profile, - profileUrl - ) - }) - .catch(err => { - logger.error( - 'componentWillReceiveProps: add app index profile not uploaded', - err - ) - }) + const identityKeyPair = nextProps.identityKeypairs[identityIndex] + return this.handlePublishDataScope( + this.props.api, + profileUrl, + profile, + this.props.api.gaiaHubUrl, + identity, + identityKeyPair, + appDomain, + appPrivateKey + ) } else { logger.debug( 'componentWillReceiveProps: storage is not connected. Doing nothing.' ) } } else { - this.completeAuthResponse( - privateKey, - blockchainId, - coreSessionToken, - appPrivateKey, - profile, - profileUrl - ) + return {profileUrl, profile} } + }).then(({profileUrl, profile}) => { + const identitySettings = this.props.identitySettings[identityIndex] + return processCollectionScopes( + appPrivateKey, + this.state.collectionScopes, + collectionsNode, + this.props.api.gaiaHubConfig, + identitySettings + ).then(results => { + return {profileUrl, profile} + }) + }).then(({profileUrl, profile}) => { + this.completeAuthResponse( + privateKey, + blockchainId, + coreSessionToken, + appPrivateKey, + profile, + profileUrl + ) }) } else { logger.error( @@ -389,9 +376,67 @@ class AuthPage extends React.Component { getFreshIdentities = async () => { await this.props.refreshIdentities(this.props.api, this.props.addresses) + await this.props.refreshAllIdentitySettings( + this.props.api, + this.props.addresses, + this.props.identityKeypairs + ) this.setState({ refreshingIdentities: false }) } + handlePublishDataScope = ( + api, + profileUrl, + profile, + gaiaHubUrl, + identity, + identityKeyPair, + appDomain, + appPrivateKey + ) => { + let apps = {} + if (profile.hasOwnProperty('apps')) { + apps = profile.apps + } + + return getAppBucketUrl(gaiaHubUrl, appPrivateKey) + .then(appBucketUrl => { + logger.debug( + `componentWillReceiveProps: appBucketUrl ${appBucketUrl}` + ) + apps[appDomain] = appBucketUrl + logger.debug( + `componentWillReceiveProps: new apps array ${JSON.stringify( + apps + )}` + ) + profile.apps = apps + const signedProfileTokenData = signProfileForUpload( + profile, + identityKeyPair, + api + ) + logger.debug( + 'componentWillReceiveProps: uploading updated profile with new apps array' + ) + return uploadProfile( + api, + identity, + identityKeyPair, + signedProfileTokenData + ) + }) + .then(() => { + return { profileUrl, profile } + }) + .catch(err => { + logger.error( + 'componentWillReceiveProps: add app index profile not uploaded', + err + ) + }) + } + completeAuthResponse = ( privateKey, blockchainId, diff --git a/app/js/auth/utils.js b/app/js/auth/utils.js index 14f99b30b..b29fbf9bd 100644 --- a/app/js/auth/utils.js +++ b/app/js/auth/utils.js @@ -11,6 +11,8 @@ const VALID_SCOPES = { publish_data: true } +const COLLECTION_SCOPE_PREFIX = 'collection.' + export function appRequestSupportsDirectHub(requestPayload: Object): boolean { let version = '0' let supportsHubUrl = false @@ -39,7 +41,9 @@ export function validateScopes(scopes: Array): boolean { let valid = false for (let i = 0; i < scopes.length; i++) { const scope = scopes[i] - if (VALID_SCOPES[scope] === true) { + if (scope.startsWith(COLLECTION_SCOPE_PREFIX)) { + valid = true + } else if (VALID_SCOPES[scope] === true) { valid = true } else { return false @@ -47,3 +51,12 @@ export function validateScopes(scopes: Array): boolean { } return valid } + +export function getCollectionScopes(scopes: Array): Array { + const collectionScopes = scopes.filter(value => value.startsWith(COLLECTION_SCOPE_PREFIX)) + if (collectionScopes.length > 0) { + return collectionScopes.map(value => value.substr(COLLECTION_SCOPE_PREFIX.length)) + } else { + return [] + } +} \ No newline at end of file diff --git a/app/js/common/store/selectors/account.js b/app/js/common/store/selectors/account.js index 9c2f80d35..24363578f 100644 --- a/app/js/common/store/selectors/account.js +++ b/app/js/common/store/selectors/account.js @@ -4,6 +4,8 @@ const selectEncryptedBackupPhrase = ({ account }) => const selectIdentityAddresses = ({ account }) => account.identityAccount.addresses const selectIdentityKeypairs = ({ account }) => account.identityAccount.keypairs +const selectIdentitySettings = ({ account }) => + account.identityAccount.settings const selectConnectedStorageAtLeastOnce = ({ account }) => account.connectedStorageAtLeastOnce const selectEmail = ({ account }) => account.email @@ -18,6 +20,7 @@ export { selectEncryptedBackupPhrase, selectIdentityAddresses, selectIdentityKeypairs, + selectIdentitySettings, selectConnectedStorageAtLeastOnce, selectEmail, selectPublicKeychain, diff --git a/app/js/store/reducers.js b/app/js/store/reducers.js index cf38a1572..4946f9717 100644 --- a/app/js/store/reducers.js +++ b/app/js/store/reducers.js @@ -39,7 +39,7 @@ export function initializeStateVersion() { * and other state is regenerated. * @type {number} */ -export const CURRENT_VERSION: number = 18 +export const CURRENT_VERSION: number = 19 const AppReducer = combineReducers({ account: AccountReducer, diff --git a/app/js/utils/account-utils.js b/app/js/utils/account-utils.js index c7e2025fe..e1274e1cf 100644 --- a/app/js/utils/account-utils.js +++ b/app/js/utils/account-utils.js @@ -19,6 +19,8 @@ function hashCode(string) { const APPS_NODE_INDEX = 0 const SIGNING_NODE_INDEX = 1 const ENCRYPTION_NODE_INDEX = 2 +const COLLECTIONS_NODE_INDEX = 3 +const COLLECTION_IDENTIFIER_DEFAULT = 'default' export const MAX_TRUST_LEVEL = 99 export class AppNode { @@ -65,6 +67,59 @@ export class AppsNode { } } +export class CollectionNode { + constructor(hdNode) { + this.hdNode = hdNode + } + + getCollectionPrivateKey() { + return this.hdNode.keyPair.d.toBuffer(32).toString('hex') + } + + getAddress() { + return this.hdNode.getAddress() + } +} + +export class CollectionsNode { + constructor(hdNode, salt) { + this.hdNode = hdNode + this.salt = salt + } + + getNode() { + return this.hdNode + } + + getCollectionNode(collectionTypeName, collectionIdentifier = COLLECTION_IDENTIFIER_DEFAULT) { + const hash = crypto + .createHash('sha256') + .update(`${collectionTypeName}${collectionIdentifier}${this.salt}`) + .digest('hex') + const collectionIndex = hashCode(hash) + const collectionNode = this.hdNode.deriveHardened(collectionIndex) + return new CollectionNode(collectionNode) + } + + getCollectionEncryptionNode(collectionTypeName, encryptionIndex, collectionIdentifier = COLLECTION_IDENTIFIER_DEFAULT) { + const hash = crypto + .createHash('sha256') + .update(`${collectionTypeName}${collectionIdentifier}${encryptionIndex}${this.salt}`) + .digest('hex') + const collectionEncIndex = hashCode(hash) + const collectionEncNode = this.hdNode.deriveHardened(collectionEncIndex) + return new CollectionNode(collectionEncNode) + } + + toBase58() { + return this.hdNode.toBase58() + } + + getSalt() { + return this.salt + } +} + class IdentityAddressOwnerNode { constructor(ownerHdNode, salt) { this.hdNode = ownerHdNode @@ -91,6 +146,10 @@ class IdentityAddressOwnerNode { return new AppsNode(this.hdNode.deriveHardened(APPS_NODE_INDEX), this.salt) } + getCollectionsNode() { + return new CollectionsNode(this.hdNode.deriveHardened(COLLECTIONS_NODE_INDEX), this.salt) + } + getAddress() { return this.hdNode.getAddress() } @@ -244,11 +303,13 @@ export function deriveIdentityKeyPair(identityOwnerAddressNode) { const identityKey = identityOwnerAddressNode.getIdentityKey() const identityKeyID = identityOwnerAddressNode.getIdentityKeyID() const appsNode = identityOwnerAddressNode.getAppsNode() + const collectionsNode = identityOwnerAddressNode.getCollectionsNode() const keyPair = { key: identityKey, keyID: identityKeyID, address, appsNodeKey: appsNode.toBase58(), + collectionsNodeKey: collectionsNode.toBase58(), salt: appsNode.getSalt() } return keyPair From 09861fc2ae64e166a723bf977ad8497b8742a8f5 Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 12 Aug 2019 17:59:14 -0400 Subject: [PATCH 09/18] Clean up auth processing and collection settings code. --- app/js/account/store/account/actions.js | 12 +- app/js/account/store/account/reducer.js | 28 ++- app/js/account/store/account/types.js | 1 + app/js/auth/index.js | 286 +++++++++++++----------- 4 files changed, 183 insertions(+), 144 deletions(-) diff --git a/app/js/account/store/account/actions.js b/app/js/account/store/account/actions.js index 85e8585bf..0da796d45 100644 --- a/app/js/account/store/account/actions.js +++ b/app/js/account/store/account/actions.js @@ -608,6 +608,15 @@ function updateIdentitySettings(identityIndex, settings) { } } +function setIdentityCollectionSetting(identityIndex, collectionName, collectionSettings) { + return { + type: types.SET_IDENTITY_COLLECTION_SETTINGS, + identityIndex, + collectionName, + collectionSettings + } +} + const AccountActions = { createAccount, updateBackupPhrase, @@ -634,7 +643,8 @@ const AccountActions = { refreshAllIdentitySettings, refreshIdentitySettings, updateAllIdentitySettings, - updateIdentitySettings + updateIdentitySettings, + setIdentityCollectionSetting } export default AccountActions diff --git a/app/js/account/store/account/reducer.js b/app/js/account/store/account/reducer.js index 6f343f9a1..8b130c739 100644 --- a/app/js/account/store/account/reducer.js +++ b/app/js/account/store/account/reducer.js @@ -274,14 +274,28 @@ function AccountReducer(state = initialState, action) { settings: action.settings }) }) - case types.UPDATE_IDENTITY_SETTINGS: - return Object.assign({}, state, { - identityAccount: Object.assign({}, state.identityAccount, { - settings: state.identityAccount.settings.map( - (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow - ) - }) + case types.UPDATE_IDENTITY_SETTINGS: + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: state.identityAccount.settings.map( + (settingsRow, i) => i === action.identityIndex ? action.settings : settingsRow + ) }) + }) + case types.SET_IDENTITY_COLLECTION_SETTINGS: + const newIdentitySettings = Object.assign({}, state.identityAccount.settings) + + const identitySettingsAtIndex = newIdentitySettings[action.identityIndex] + if (!identitySettingsAtIndex.collections) { + identitySettingsAtIndex.collections = {} + } + identitySettingsAtIndex.collections[action.collectionName] = action.collectionSettings + + return Object.assign({}, state, { + identityAccount: Object.assign({}, state.identityAccount, { + settings: newIdentitySettings + }) + }) default: return state } diff --git a/app/js/account/store/account/types.js b/app/js/account/store/account/types.js index cc25721e5..5063d031b 100644 --- a/app/js/account/store/account/types.js +++ b/app/js/account/store/account/types.js @@ -25,3 +25,4 @@ export const CONNECTED_STORAGE = 'account/CONNECTED_STORAGE' export const UPDATE_EMAIL_ADDRESS = 'account/UPDATE_EMAIL_ADDRESS' export const UPDATE_ALL_IDENTITY_SETTINGS = 'account/UPDATE_ALL_IDENTITY_SETTINGS' export const UPDATE_IDENTITY_SETTINGS = 'account/UPDATE_IDENTITY_SETTINGS' +export const SET_IDENTITY_COLLECTION_SETTINGS = 'account/SET_IDENTITY_COLLECTION_SETTINGS' diff --git a/app/js/auth/index.js b/app/js/auth/index.js index 4ac1b644a..fbb9246f9 100644 --- a/app/js/auth/index.js +++ b/app/js/auth/index.js @@ -31,7 +31,7 @@ import { import { getTokenFileUrlFromZoneFile } from '@utils/zone-utils' import { HDNode } from 'bitcoinjs-lib' import log4js from 'log4js' -import { uploadProfile } from '../account/utils' +import { uploadProfile, uploadIdentitySettings } from '../account/utils' import { signProfileForUpload } from '@utils' import { validateScopes, @@ -129,6 +129,8 @@ class AuthPage extends React.Component { loginToApp: PropTypes.func.isRequired, api: PropTypes.object.isRequired, identityKeypairs: PropTypes.array.isRequired, + refreshIdentitySettings: PropTypes.func.isRequired, + setIdentityCollectionSetting: PropTypes.func.isRequired, coreHost: PropTypes.string.isRequired, corePort: PropTypes.number.isRequired, appManifest: PropTypes.object, @@ -138,7 +140,7 @@ class AuthPage extends React.Component { noCoreSessionToken: PropTypes.func.isRequired, addresses: PropTypes.array.isRequired, publicKeychain: PropTypes.string.isRequired, - refreshIdentities: PropTypes.func.isRequired + refreshIdentities: PropTypes.func.isRequired, } constructor(props) { @@ -154,6 +156,7 @@ class AuthPage extends React.Component { storageConnected: this.props.api.storageConnected, processing: false, refreshingIdentities: true, + identitySettingsChanged: false, invalidScopes: false, sendEmail: false, blockchainId: null, @@ -230,148 +233,165 @@ class AuthPage extends React.Component { this.redirectUserToEchoReply() return } + } else { + logger.error( + 'componentWillReceiveProps: response already sent - doing nothing' + ) + } + } - const storageConnected = this.props.api.storageConnected - this.setState({ - storageConnected - }) - - const appDomain = this.state.decodedToken.payload.domain_name - const localIdentities = nextProps.localIdentities - const identityKeypairs = nextProps.identityKeypairs - if (!appDomain || !nextProps.coreSessionTokens[appDomain]) { - if (this.state.noCoreStorage) { - logger.debug( - 'componentWillReceiveProps: no core session token expected' - ) - } else { - logger.debug( - 'componentWillReceiveProps: no app domain or no core session token' - ) - return - } - } - - logger.info('componentWillReceiveProps') - - const coreSessionToken = nextProps.coreSessionTokens[appDomain] - let decodedCoreSessionToken = null - if (!this.state.noCoreStorage) { - logger.debug('componentWillReceiveProps: received coreSessionToken') - decodedCoreSessionToken = decodeToken(coreSessionToken) - } else { - logger.debug('componentWillReceiveProps: received no coreSessionToken') - } + sendAuthResponse = () => { + const storageConnected = this.props.api.storageConnected + const appDomain = this.state.decodedToken.payload.domain_name + const localIdentities = this.props.localIdentities + const identityKeypairs = this.props.identityKeypairs + if (!appDomain) { + logger.debug( + 'sendAuthResponse(): no app domain' + ) + } - const identityIndex = this.state.currentIdentityIndex + const identityIndex = this.state.currentIdentityIndex - const hasUsername = this.state.hasUsername - if (hasUsername) { - logger.debug(`login(): id index ${identityIndex} has no username`) - } + const hasUsername = this.state.hasUsername + if (hasUsername) { + logger.debug(`sendAuthResponse()(): id index ${identityIndex} has no username`) + } - // Get keypair corresponding to the current user identity - const profileSigningKeypair = identityKeypairs[identityIndex] - const identity = localIdentities[identityIndex] + // // Get keypair corresponding to the current user identity + const profileSigningKeypair = identityKeypairs[identityIndex] + const identity = localIdentities[identityIndex] - let blockchainId = null - if (decodedCoreSessionToken) { - blockchainId = decodedCoreSessionToken.payload.blockchain_id - } else { - blockchainId = this.state.blockchainId + let blockchainId = this.state.blockchainId + + const profile = identity.profile + const privateKey = profileSigningKeypair.key + const appsNodeKey = profileSigningKeypair.appsNodeKey + const collectionsNodeKey = profileSigningKeypair.collectionsNodeKey + const salt = profileSigningKeypair.salt + const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) + const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() + const collectionsNode = new CollectionsNode(HDNode.fromBase58(collectionsNodeKey), salt) + + let profileUrlPromise + + if (identity.zoneFile && identity.zoneFile.length > 0) { + const zoneFileJson = parseZoneFile(identity.zoneFile) + const profileUrlFromZonefile = getTokenFileUrlFromZoneFile(zoneFileJson) + if ( + profileUrlFromZonefile !== null && + profileUrlFromZonefile !== undefined + ) { + profileUrlPromise = Promise.resolve(profileUrlFromZonefile) } + } - const profile = identity.profile - const privateKey = profileSigningKeypair.key - const appsNodeKey = profileSigningKeypair.appsNodeKey - const collectionsNodeKey = profileSigningKeypair.collectionsNodeKey - const salt = profileSigningKeypair.salt - const appsNode = new AppsNode(HDNode.fromBase58(appsNodeKey), salt) - const appPrivateKey = appsNode.getAppNode(appDomain).getAppPrivateKey() - const collectionsNode = new CollectionsNode(HDNode.fromBase58(collectionsNodeKey), salt) - - let profileUrlPromise - - if (identity.zoneFile && identity.zoneFile.length > 0) { - const zoneFileJson = parseZoneFile(identity.zoneFile) - const profileUrlFromZonefile = getTokenFileUrlFromZoneFile(zoneFileJson) - if ( - profileUrlFromZonefile !== null && - profileUrlFromZonefile !== undefined - ) { - profileUrlPromise = Promise.resolve(profileUrlFromZonefile) + const gaiaBucketAddress = this.props.identityKeypairs[0].address + const identityAddress = this.props.identityKeypairs[identityIndex].address + const gaiaUrlBase = this.props.api.gaiaHubConfig.url_prefix + + if (!profileUrlPromise) { + // use default Gaia hub if we can't tell from the profile where the profile Gaia hub is + profileUrlPromise = fetchProfileLocations( + gaiaUrlBase, + identityAddress, + gaiaBucketAddress, + identityIndex + ).then(fetchProfileResp => { + if (fetchProfileResp && fetchProfileResp.profileUrl) { + return fetchProfileResp.profileUrl + } else { + return getDefaultProfileUrl(gaiaUrlBase, identityAddress) } - } - - const gaiaBucketAddress = nextProps.identityKeypairs[0].address - const identityAddress = nextProps.identityKeypairs[identityIndex].address - const gaiaUrlBase = nextProps.api.gaiaHubConfig.url_prefix - - if (!profileUrlPromise) { - // use default Gaia hub if we can't tell from the profile where the profile Gaia hub is - profileUrlPromise = fetchProfileLocations( - gaiaUrlBase, - identityAddress, - gaiaBucketAddress, - identityIndex - ).then(fetchProfileResp => { - if (fetchProfileResp && fetchProfileResp.profileUrl) { - return fetchProfileResp.profileUrl - } else { - return getDefaultProfileUrl(gaiaUrlBase, identityAddress) - } - }) - } + }) + } - profileUrlPromise.then(profileUrl => { - // Add app storage bucket URL to profile if publish_data scope is requested - if (this.state.scopes.publishData) { - if (storageConnected) { - const identityKeyPair = nextProps.identityKeypairs[identityIndex] - return this.handlePublishDataScope( - this.props.api, - profileUrl, - profile, - this.props.api.gaiaHubUrl, - identity, - identityKeyPair, - appDomain, - appPrivateKey - ) - } else { - logger.debug( - 'componentWillReceiveProps: storage is not connected. Doing nothing.' - ) - } + profileUrlPromise.then(profileUrl => { + // Add app storage bucket URL to profile if publish_data scope is requested + if (this.state.scopes.publishData) { + if (storageConnected) { + const identityKeyPair = this.props.identityKeypairs[identityIndex] + return this.handlePublishDataScope( + this.props.api, + profileUrl, + profile, + this.props.api.gaiaHubUrl, + identity, + identityKeyPair, + appDomain, + appPrivateKey + ) } else { - return {profileUrl, profile} + logger.debug( + 'sendAuthResponse(): storage is not connected. Doing nothing.' + ) } - }).then(({profileUrl, profile}) => { - const identitySettings = this.props.identitySettings[identityIndex] - return processCollectionScopes( + } else { + return {profileUrl, profile} + } + }).then(({profileUrl, profile}) => { + // Refresh selected account's identity settings + return this.props.refreshIdentitySettings( + this.props.api, + identityIndex, + this.props.addresses[identityIndex], + this.props.identityKeypairs[identityIndex] + ).then(() => + // Generate and write collection Gaia hub config and encryption keys + // to app storage bucket before sending auth response + processCollectionScopes( appPrivateKey, this.state.collectionScopes, collectionsNode, this.props.api.gaiaHubConfig, - identitySettings - ).then(results => { - return {profileUrl, profile} - }) - }).then(({profileUrl, profile}) => { - this.completeAuthResponse( - privateKey, - blockchainId, - coreSessionToken, - appPrivateKey, - profile, - profileUrl + this.props.identitySettings[identityIndex], + this.updateIdentityCollectionSettings ) + ).then(results => { + if (this.state.identitySettingsChanged) { + // Upload identity settings if modified + this.uploadIdentitySettings() + } + return {profileUrl, profile} }) - } else { - logger.error( - 'componentWillReceiveProps: response already sent - doing nothing' + }).then(({profileUrl, profile}) => { + // Generate and send the auth response + const authResponse = this.generateAuthResponse( + privateKey, + blockchainId, + appPrivateKey, + profile, + profileUrl ) - } + + logger.info( + `sendAuthResponse(): id index ${this.state.currentIdentityIndex} is logging in` + ) + + this.setState({ responseSent: true }) + redirectUserToApp(this.state.authRequest, authResponse) + }) + } + + updateIdentityCollectionSettings = (collectionName, settings) => { + this.setState({ + identitySettingsChanged: true + }) + const identityIndex = this.state.currentIdentityIndex + return this.props.setIdentityCollectionSetting(identityIndex, collectionName, settings) + } + + uploadIdentitySettings = () => { + const identityIndex = this.state.currentIdentityIndex + const identitySigner = this.props.identityKeypairs[identityIndex] + var newIdentitySettings = this.props.identitySettings[identityIndex] + + // TODO: Make identity settings and profile upload more resistant to corruption/loss + return uploadIdentitySettings( + this.props.api, + identitySigner, + JSON.stringify(newIdentitySettings) + ) } getFreshIdentities = async () => { @@ -437,10 +457,9 @@ class AuthPage extends React.Component { }) } - completeAuthResponse = ( + generateAuthResponse = ( privateKey, blockchainId, - coreSessionToken, appPrivateKey, profile, profileUrl @@ -494,7 +513,7 @@ class AuthPage extends React.Component { profileResponseData, blockchainId, metadata, - coreSessionToken, + '', appPrivateKey, undefined, transitPublicKey, @@ -504,13 +523,7 @@ class AuthPage extends React.Component { ) this.props.clearSessionToken(appDomain) - - logger.info( - `login(): id index ${this.state.currentIdentityIndex} is logging in` - ) - - this.setState({ responseSent: true }) - redirectUserToApp(this.state.authRequest, authResponse) + return authResponse } closeModal() { @@ -603,6 +616,7 @@ class AuthPage extends React.Component { noCoreStorage: true }) this.props.noCoreSessionToken(appDomain) + return this.sendAuthResponse() } else { logger.info('login(): No storage access requested.') this.setState({ From db4c701c019573f6405994041f14fbfbbfddaada Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 12 Aug 2019 18:23:14 -0400 Subject: [PATCH 10/18] Collections utility functions. --- app/js/utils/collection-utils.js | 140 +++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 app/js/utils/collection-utils.js diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js new file mode 100644 index 000000000..4557d9fd4 --- /dev/null +++ b/app/js/utils/collection-utils.js @@ -0,0 +1,140 @@ +import { + connectToGaiaHub, + uploadToGaiaHub, + encryptContent, + decryptContent, + hexStringToECPair, + GAIA_HUB_COLLECTION_KEY_FILE_NAME +} from 'blockstack' + +const DEFAULT_NEW_COLLECTION_SETTING_ARRAY = [{ + identifier: 'default', + encryptionKeyIndex: 0 +}] + +export function getCollectionEncryptionIndex(collectionName, settings, updateIdentityCollectionSettings) { + if(!settings.collection + || !settings.collection[collectionName] + || settings.collections[collectionName].length > 0) { + const defaultCollectionSettings = DEFAULT_NEW_COLLECTION_SETTING_ARRAY + updateIdentityCollectionSettings(collectionName, defaultCollectionSettings) + return Promise.resolve(defaultCollectionSettings[0].encryptionKeyIndex) + } else { + const collectionSetting = settings.collections[collectionName] + return Promise.resolve(collectionSetting[0].encryptionKeyIndex) + } +} + +export function fetchOrCreateCollectionKeys(scopes, node, settings, updateIdentityCollectionSettings) { + const collectionKeyPromises = scopes.map((scope) => + getCollectionEncryptionIndex(scope, settings, updateIdentityCollectionSettings) + .then((encryptionKeyIndex) => { + const collectionEncryptionPrivateKey = + node.getCollectionEncryptionNode(scope, encryptionKeyIndex).getCollectionPrivateKey() + return Promise.resolve(collectionEncryptionPrivateKey) + }) + ) + return Promise.all(collectionKeyPromises) +} + +export function getCollectionGaiaHubConfigs(scopes, node, gaiaHubUrl) { + const hubConfigPromises = scopes.map((scope) => { + const collectionPrivateKey = + node.getCollectionNode(scope).getCollectionPrivateKey() + return connectToGaiaHub(gaiaHubUrl, collectionPrivateKey) + }) + + return Promise.all(hubConfigPromises) +} + +function writeCollectionKeysToAppStorage(appPrivateKey, hubConfig, keyFile) { + const compressedAppPrivateKey = `${appPrivateKey}01` + const keyPair = hexStringToECPair(compressedAppPrivateKey) + const publicKey = keyPair.publicKey.toString('hex') + const encryptedKeyFile = encryptContent(JSON.stringify(keyFile), { publicKey }) + + return uploadToGaiaHub( + GAIA_HUB_COLLECTION_KEY_FILE_NAME, + encryptedKeyFile, + hubConfig, + 'application/json' + ) +} + +function getAppCollectionKeyFile(appPrivateKey, gaiaHubBucketUrl, appBucketAddress) { + const keyFileUrl = `${gaiaHubBucketUrl}${appBucketAddress}/${GAIA_HUB_COLLECTION_KEY_FILE_NAME}` + return fetch(keyFileUrl) + .then(response => { + if (response.ok) { + return response.text() + .then(encryptedKeyFile => decryptContent(encryptedKeyFile, { privateKey: appPrivateKey })) + .then(decryptedKeyFile => JSON.parse(decryptedKeyFile)) + } else if (response.status === 404) { + return {} + } else { + return Promise.reject('Could not get collection key file') + } + }) +} + +function updateAppCollectionKeys( + collectionScopes, + appPrivateKey, + gaiaHubUrl, + collectionKeys, + collectionHubConfigs +) { + return connectToGaiaHub(gaiaHubUrl, appPrivateKey).then(hubConfig => + getAppCollectionKeyFile(appPrivateKey, hubConfig.url_prefix, hubConfig.address) + .then((keyFile) => { + collectionScopes.map((scope, index) => { + keyFile[scope] = { + encryptionKey: collectionKeys[index], + hubConfig: collectionHubConfigs[index] + } + return true + }) + return keyFile + }) + .then(keyFile => writeCollectionKeysToAppStorage(appPrivateKey, hubConfig, keyFile)) + ) +} + +export function processCollectionScopes( + appPrivateKey, + collectionScopes, + collectionsNode, + gaiaHubConfig, + identitySettings, + updateIdentityCollectionSettings +) { + const encryptionKeyPromise = + fetchOrCreateCollectionKeys( + collectionScopes, + collectionsNode, + identitySettings, + updateIdentityCollectionSettings + ) + const hubConfigsPromise = + getCollectionGaiaHubConfigs(collectionScopes, collectionsNode, gaiaHubConfig.server) + return Promise.all([encryptionKeyPromise, hubConfigsPromise]) + .then(results => { + const collectionKeys = results[0] + const collectionHubConfigs = results[1] + return updateAppCollectionKeys( + collectionScopes, + appPrivateKey, + gaiaHubConfig.server, + collectionKeys, + collectionHubConfigs + ) + }) +} + + + + + + + + From ae7c2eb5343e722f532b5f1792bd32b6b75df37b Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 12 Aug 2019 18:24:30 -0400 Subject: [PATCH 11/18] Fix lint. --- app/js/auth/index.js | 31 ++++++++++++++++--------------- app/js/auth/utils.js | 2 +- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/js/auth/index.js b/app/js/auth/index.js index fbb9246f9..f6ccabaf6 100644 --- a/app/js/auth/index.js +++ b/app/js/auth/index.js @@ -129,7 +129,9 @@ class AuthPage extends React.Component { loginToApp: PropTypes.func.isRequired, api: PropTypes.object.isRequired, identityKeypairs: PropTypes.array.isRequired, + identitySettings: PropTypes.array.isRequired, refreshIdentitySettings: PropTypes.func.isRequired, + refreshAllIdentitySettings: PropTypes.func.isRequired, setIdentityCollectionSetting: PropTypes.func.isRequired, coreHost: PropTypes.string.isRequired, corePort: PropTypes.number.isRequired, @@ -140,7 +142,7 @@ class AuthPage extends React.Component { noCoreSessionToken: PropTypes.func.isRequired, addresses: PropTypes.array.isRequired, publicKeychain: PropTypes.string.isRequired, - refreshIdentities: PropTypes.func.isRequired, + refreshIdentities: PropTypes.func.isRequired } constructor(props) { @@ -188,7 +190,7 @@ class AuthPage extends React.Component { scopes: { ...this.state.scopes, email: scopes.includes('email'), - publishData: scopes.includes('publish_data'), + publishData: scopes.includes('publish_data') }, collectionScopes: [ ...getCollectionScopes(scopes) @@ -227,7 +229,7 @@ class AuthPage extends React.Component { window.location = redirectURI } - componentWillReceiveProps(nextProps) { + componentWillReceiveProps() { if (!this.state.responseSent) { if (this.state.echoRequestId) { this.redirectUserToEchoReply() @@ -262,7 +264,7 @@ class AuthPage extends React.Component { const profileSigningKeypair = identityKeypairs[identityIndex] const identity = localIdentities[identityIndex] - let blockchainId = this.state.blockchainId + const blockchainId = this.state.blockchainId const profile = identity.profile const privateKey = profileSigningKeypair.key @@ -325,13 +327,14 @@ class AuthPage extends React.Component { logger.debug( 'sendAuthResponse(): storage is not connected. Doing nothing.' ) + return false } } else { - return {profileUrl, profile} + return profileUrl } - }).then(({profileUrl, profile}) => { + }).then((profileUrl) => // Refresh selected account's identity settings - return this.props.refreshIdentitySettings( + this.props.refreshIdentitySettings( this.props.api, identityIndex, this.props.addresses[identityIndex], @@ -347,14 +350,14 @@ class AuthPage extends React.Component { this.props.identitySettings[identityIndex], this.updateIdentityCollectionSettings ) - ).then(results => { + ).then(() => { if (this.state.identitySettingsChanged) { // Upload identity settings if modified this.uploadIdentitySettings() } - return {profileUrl, profile} + return profileUrl }) - }).then(({profileUrl, profile}) => { + ).then((profileUrl) => { // Generate and send the auth response const authResponse = this.generateAuthResponse( privateKey, @@ -384,7 +387,7 @@ class AuthPage extends React.Component { uploadIdentitySettings = () => { const identityIndex = this.state.currentIdentityIndex const identitySigner = this.props.identityKeypairs[identityIndex] - var newIdentitySettings = this.props.identitySettings[identityIndex] + const newIdentitySettings = this.props.identitySettings[identityIndex] // TODO: Make identity settings and profile upload more resistant to corruption/loss return uploadIdentitySettings( @@ -446,9 +449,7 @@ class AuthPage extends React.Component { signedProfileTokenData ) }) - .then(() => { - return { profileUrl, profile } - }) + .then(() => profileUrl) .catch(err => { logger.error( 'componentWillReceiveProps: add app index profile not uploaded', @@ -616,7 +617,7 @@ class AuthPage extends React.Component { noCoreStorage: true }) this.props.noCoreSessionToken(appDomain) - return this.sendAuthResponse() + this.sendAuthResponse() } else { logger.info('login(): No storage access requested.') this.setState({ diff --git a/app/js/auth/utils.js b/app/js/auth/utils.js index b29fbf9bd..3095091c2 100644 --- a/app/js/auth/utils.js +++ b/app/js/auth/utils.js @@ -59,4 +59,4 @@ export function getCollectionScopes(scopes: Array): Array { } else { return [] } -} \ No newline at end of file +} From 7937c54e2e2d66d40fc5bdca1b4a908be8e297f8 Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 12 Aug 2019 23:39:43 -0400 Subject: [PATCH 12/18] Fix tests. --- test/account/store/account/actions.test.js | 3 +++ test/profiles/store/identity/actions/async.test.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/test/account/store/account/actions.test.js b/test/account/store/account/actions.test.js index d7aef8aa2..a6f8734d0 100644 --- a/test/account/store/account/actions.test.js +++ b/test/account/store/account/actions.test.js @@ -160,6 +160,9 @@ describe('AccountActions', () => { appsNodeKey: 'xprvA1y4zBndD83n6PWgVH6ivkTpNQ2WU1UGPg9hWa2q8sCANa7YrYMZFHWMhrbpsarx' + 'XMuQRa4jtaT2YXugwsKrjFgn765tUHu9XjyiDFEjB7f', + collectionsNodeKey: + 'xprvA1y4zBndD83nEPDq9NxZFJ4VAN7pjhKdhKLzTpjRBqvRMosxtGiedSmzQNjazwPR' + + 'Fb6V1oBu2M8YPhm1SK18YkDxv1yYDG5gZGwrUUkKSvi', salt: 'c15619adafe7e75a195a1a2b5788ca42e585a3fd181ae2ff009c6089de54ed9e' } diff --git a/test/profiles/store/identity/actions/async.test.js b/test/profiles/store/identity/actions/async.test.js index c24664c30..cb1277f23 100644 --- a/test/profiles/store/identity/actions/async.test.js +++ b/test/profiles/store/identity/actions/async.test.js @@ -71,6 +71,8 @@ describe('Identity Store: Async Actions', () => { address: '13ssnrZTn4TJzQkwFZHajfeZXrGe6fQtrZ', appsNodeKey: 'xprv9zn1Mwu3hGDz9ytK4Pvp6ckfswdeMRngASKEcT9J4f2THGjo2UjyrMocunoohYQW2fMx9Cb21KvooM8pVrbuVVjHuqTJ2Kdru5VKGLR1MZa', + collectionsNodeKey: + 'xprv9zn1Mwu3hGDzJhReZ7415JbdqumsaT8pgJGgtC8W5wTEUUsxbvihEw6Ru74sKxayJKasbqXvpanMt7dngdGFkSgQE1wbYp6br6CqaDvxWMb', key: '5c21340bdc95b66c203f1080d6c83655137edbb2fcbbf35849e82b10b993b7ad', keyID: From 08071e032509e0f7be3e45140e75d3c3f4b52968 Mon Sep 17 00:00:00 2001 From: Ken Date: Mon, 26 Aug 2019 10:26:20 -0400 Subject: [PATCH 13/18] Change collections gaia auth token scope to ArchivalPrefix. --- app/js/utils/collection-utils.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js index 4557d9fd4..4c65f4cb9 100644 --- a/app/js/utils/collection-utils.js +++ b/app/js/utils/collection-utils.js @@ -12,6 +12,9 @@ const DEFAULT_NEW_COLLECTION_SETTING_ARRAY = [{ encryptionKeyIndex: 0 }] +const ARCHIVAL_GAIA_AUTH_SCOPE = 'putFileArchivalPrefix' +const COLLECTION_GAIA_PREFIX = 'collection' + export function getCollectionEncryptionIndex(collectionName, settings, updateIdentityCollectionSettings) { if(!settings.collection || !settings.collection[collectionName] @@ -41,7 +44,11 @@ export function getCollectionGaiaHubConfigs(scopes, node, gaiaHubUrl) { const hubConfigPromises = scopes.map((scope) => { const collectionPrivateKey = node.getCollectionNode(scope).getCollectionPrivateKey() - return connectToGaiaHub(gaiaHubUrl, collectionPrivateKey) + const scopes = [{ + scope: ARCHIVAL_GAIA_AUTH_SCOPE, + domain: COLLECTION_GAIA_PREFIX + }] + return connectToGaiaHub(gaiaHubUrl, collectionPrivateKey, "", scopes) }) return Promise.all(hubConfigPromises) From c4bb7dc1d8e48e89df532a7ad4c00822f9ae4e54 Mon Sep 17 00:00:00 2001 From: Ken Date: Wed, 4 Sep 2019 15:12:36 -0400 Subject: [PATCH 14/18] Fix issue with Gaia hub url property in api settings being undefined. --- app/js/auth/index.js | 2 +- app/js/utils/collection-utils.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/js/auth/index.js b/app/js/auth/index.js index f6ccabaf6..0674aca0e 100644 --- a/app/js/auth/index.js +++ b/app/js/auth/index.js @@ -346,7 +346,7 @@ class AuthPage extends React.Component { appPrivateKey, this.state.collectionScopes, collectionsNode, - this.props.api.gaiaHubConfig, + this.props.api.gaiaHubUrl, this.props.identitySettings[identityIndex], this.updateIdentityCollectionSettings ) diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js index 4c65f4cb9..a7c761e5c 100644 --- a/app/js/utils/collection-utils.js +++ b/app/js/utils/collection-utils.js @@ -111,7 +111,7 @@ export function processCollectionScopes( appPrivateKey, collectionScopes, collectionsNode, - gaiaHubConfig, + gaiaHubUrl, identitySettings, updateIdentityCollectionSettings ) { @@ -123,7 +123,7 @@ export function processCollectionScopes( updateIdentityCollectionSettings ) const hubConfigsPromise = - getCollectionGaiaHubConfigs(collectionScopes, collectionsNode, gaiaHubConfig.server) + getCollectionGaiaHubConfigs(collectionScopes, collectionsNode, gaiaHubUrl) return Promise.all([encryptionKeyPromise, hubConfigsPromise]) .then(results => { const collectionKeys = results[0] @@ -131,7 +131,7 @@ export function processCollectionScopes( return updateAppCollectionKeys( collectionScopes, appPrivateKey, - gaiaHubConfig.server, + gaiaHubUrl, collectionKeys, collectionHubConfigs ) From 5d368b71c080c27e13d38ce0f8e102d9fb711040 Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 12 Sep 2019 22:43:12 -0400 Subject: [PATCH 15/18] Fix console warnings --- app/js/auth/index.js | 2 +- app/js/utils/collection-utils.js | 4 +-- package-lock.json | 45 ++++++++++++++++++++------------ package.json | 2 +- 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/app/js/auth/index.js b/app/js/auth/index.js index 0674aca0e..8bc945c19 100644 --- a/app/js/auth/index.js +++ b/app/js/auth/index.js @@ -129,7 +129,7 @@ class AuthPage extends React.Component { loginToApp: PropTypes.func.isRequired, api: PropTypes.object.isRequired, identityKeypairs: PropTypes.array.isRequired, - identitySettings: PropTypes.array.isRequired, + identitySettings: PropTypes.object.isRequired, refreshIdentitySettings: PropTypes.func.isRequired, refreshAllIdentitySettings: PropTypes.func.isRequired, setIdentityCollectionSetting: PropTypes.func.isRequired, diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js index a7c761e5c..345b3360c 100644 --- a/app/js/utils/collection-utils.js +++ b/app/js/utils/collection-utils.js @@ -44,11 +44,11 @@ export function getCollectionGaiaHubConfigs(scopes, node, gaiaHubUrl) { const hubConfigPromises = scopes.map((scope) => { const collectionPrivateKey = node.getCollectionNode(scope).getCollectionPrivateKey() - const scopes = [{ + const gaiaScopes = [{ scope: ARCHIVAL_GAIA_AUTH_SCOPE, domain: COLLECTION_GAIA_PREFIX }] - return connectToGaiaHub(gaiaHubUrl, collectionPrivateKey, "", scopes) + return connectToGaiaHub(gaiaHubUrl, collectionPrivateKey, '', gaiaScopes) }) return Promise.all(hubConfigPromises) diff --git a/package-lock.json b/package-lock.json index dc9856a86..da446906d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2620,9 +2620,9 @@ } }, "blockstack": { - "version": "19.3.0", - "resolved": "https://registry.npmjs.org/blockstack/-/blockstack-19.3.0.tgz", - "integrity": "sha512-P/HRS5n+buTeIssxs1v479EpDZOFGpfiivRrv9UjbHj/FdSJLxC1onVD8Hiyfm0mB8y7Ah9qT2lGqKX9P6r7+g==", + "version": "20.0.0-alpha.2", + "resolved": "https://registry.npmjs.org/blockstack/-/blockstack-20.0.0-alpha.2.tgz", + "integrity": "sha512-5M8WR2NeVc1UEe0B5Qy/Jgfdg6KByNI04d+EAdE0ez9bgrwtggwBeLAm+ywnkt+7VDGNiltIrwswwlJ241KmSw==", "requires": { "@types/bn.js": "^4.11.5", "@types/elliptic": "^6.4.9", @@ -2631,7 +2631,7 @@ "bitcoinjs-lib": "^5.1.2", "bn.js": "^4.11.8", "cheerio": "^0.22.0", - "cross-fetch": "^2.2.2", + "cross-fetch": "^3.0.4", "elliptic": "^6.4.1", "form-data": "^2.3.3", "jsontokens": "^2.0.2", @@ -2680,9 +2680,9 @@ } }, "bitcoinjs-lib": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.1.5.tgz", - "integrity": "sha512-VAcdXH7cNocWwEF2dAVTozOZtDHB+FVZH9tc9OLbUKzRLiYxb3C5lvAzBmd1QJR3CtPRJELgw049MvM5fs2dow==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-5.1.6.tgz", + "integrity": "sha512-NgvnA8XXUuzpuBnVs1plzZvVOYsuont4KPzaGcVIwjktYQbCk1hUkXnt4wujIOBscNsXuu+plVbPYvtMosZI/w==", "requires": { "@types/node": "10.12.18", "bech32": "^1.1.2", @@ -2735,9 +2735,9 @@ } }, "query-string": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.2.tgz", - "integrity": "sha512-J3Qi8XZJXh93t2FiKyd/7Ec6GNifsjKXUsVFkSBj/kjLsDylWhnCz4NT1bkPcKotttPW+QbKGqqPH8OoI2pdqw==", + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.8.3.tgz", + "integrity": "sha512-llcxWccnyaWlODe7A9hRjkvdCKamEKTh+wH8ITdTc3OhchaqUZteiSCX/2ablWHVrkVIe04dntnaZJ7BdyW0lQ==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -4129,12 +4129,24 @@ } }, "cross-fetch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.3.tgz", - "integrity": "sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.4.tgz", + "integrity": "sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw==", "requires": { - "node-fetch": "2.1.2", - "whatwg-fetch": "2.0.4" + "node-fetch": "2.6.0", + "whatwg-fetch": "3.0.0" + }, + "dependencies": { + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "whatwg-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz", + "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q==" + } } }, "cross-spawn": { @@ -10519,7 +10531,8 @@ "node-fetch": { "version": "2.1.2", "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", - "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=", + "dev": true }, "node-forge": { "version": "0.7.5", diff --git a/package.json b/package.json index 78315ae2d..53cb08d94 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "bip39": "^2.2.0", "bitcoinjs-lib": "^3.2.0", "blockstack-ui": "^0.0.71", - "blockstack": "^19.3.0", + "blockstack": "^20.0.0-alpha.2", "body-parser": "^1.16.1", "bootstrap": "^4.0.0-beta", "browser-stdout": "^1.3.0", From f2263f9fa128426bcfc1ba32509b8e7f76f4c692 Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 12 Sep 2019 23:00:34 -0400 Subject: [PATCH 16/18] Use getPublicKeyFromPrivate in collection key generation. --- app/js/utils/collection-utils.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js index 345b3360c..bed4195d2 100644 --- a/app/js/utils/collection-utils.js +++ b/app/js/utils/collection-utils.js @@ -4,6 +4,7 @@ import { encryptContent, decryptContent, hexStringToECPair, + getPublicKeyFromPrivate, GAIA_HUB_COLLECTION_KEY_FILE_NAME } from 'blockstack' @@ -55,11 +56,9 @@ export function getCollectionGaiaHubConfigs(scopes, node, gaiaHubUrl) { } function writeCollectionKeysToAppStorage(appPrivateKey, hubConfig, keyFile) { - const compressedAppPrivateKey = `${appPrivateKey}01` - const keyPair = hexStringToECPair(compressedAppPrivateKey) - const publicKey = keyPair.publicKey.toString('hex') + const publicKey = getPublicKeyFromPrivate(appPrivateKey) const encryptedKeyFile = encryptContent(JSON.stringify(keyFile), { publicKey }) - + return uploadToGaiaHub( GAIA_HUB_COLLECTION_KEY_FILE_NAME, encryptedKeyFile, From eb9742ff41f3640fa82749c6a5fb1ccda799476d Mon Sep 17 00:00:00 2001 From: Ken Date: Thu, 12 Sep 2019 23:02:43 -0400 Subject: [PATCH 17/18] Fix lint. --- app/js/utils/collection-utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/js/utils/collection-utils.js b/app/js/utils/collection-utils.js index bed4195d2..d807a86b3 100644 --- a/app/js/utils/collection-utils.js +++ b/app/js/utils/collection-utils.js @@ -3,7 +3,6 @@ import { uploadToGaiaHub, encryptContent, decryptContent, - hexStringToECPair, getPublicKeyFromPrivate, GAIA_HUB_COLLECTION_KEY_FILE_NAME } from 'blockstack' From 39692ac68af3d7d23c7bfd3c9516565bf68960a1 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 17 Sep 2019 12:59:44 -0400 Subject: [PATCH 18/18] Bump version to v0.37.0 alpha 1. --- native/macos/Blockstack/Blockstack.xcodeproj/project.pbxproj | 1 + native/macos/Blockstack/Blockstack/Info.plist | 4 ++-- native/macos/Blockstack/BlockstackLauncher/Info.plist | 4 ++-- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/native/macos/Blockstack/Blockstack.xcodeproj/project.pbxproj b/native/macos/Blockstack/Blockstack.xcodeproj/project.pbxproj index 1f29575a0..47cd44ae9 100644 --- a/native/macos/Blockstack/Blockstack.xcodeproj/project.pbxproj +++ b/native/macos/Blockstack/Blockstack.xcodeproj/project.pbxproj @@ -353,6 +353,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); diff --git a/native/macos/Blockstack/Blockstack/Info.plist b/native/macos/Blockstack/Blockstack/Info.plist index 999ce4661..fa03262ad 100644 --- a/native/macos/Blockstack/Blockstack/Info.plist +++ b/native/macos/Blockstack/Blockstack/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.36.3 + 0.37.0 CFBundleURLTypes @@ -30,7 +30,7 @@ CFBundleVersion - 119 + 120 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/native/macos/Blockstack/BlockstackLauncher/Info.plist b/native/macos/Blockstack/BlockstackLauncher/Info.plist index 6b93a140c..dbc2c7900 100644 --- a/native/macos/Blockstack/BlockstackLauncher/Info.plist +++ b/native/macos/Blockstack/BlockstackLauncher/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.36.3 + 0.37.0 CFBundleVersion - 119 + 120 LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly diff --git a/package-lock.json b/package-lock.json index da446906d..3a215674d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "blockstack-browser", - "version": "0.36.3", + "version": "0.37.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 53cb08d94..e7a9bb870 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "blockstack-browser", "description": "The Blockstack browser", - "version": "0.36.3", + "version": "0.37.0-alpha.1", "author": "Blockstack PBC ", "dependencies": { "bigi": "^1.4.2",