Skip to content
This repository was archived by the owner on Apr 17, 2023. It is now read-only.

Identity settings in profile.json #1936

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 69 additions & 4 deletions app/js/account/store/account/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -41,7 +41,8 @@ function createAccount(
bitcoinPublicKeychain,
firstBitcoinAddress,
identityAddresses,
identityKeypairs
identityKeypairs,
identitySettings
} = getBlockchainIdentities(masterKeychain, identitiesToGenerate)

return {
Expand All @@ -51,7 +52,8 @@ function createAccount(
bitcoinPublicKeychain,
firstBitcoinAddress,
identityAddresses,
identityKeypairs
identityKeypairs,
identitySettings
}
}

Expand Down Expand Up @@ -547,6 +549,65 @@ function usedIdentityAddress() {
}
}

function refreshAllIdentitySettings(
api: { gaiaHubConfig: GaiaHubConfig },
ownerAddresses: Array<string>,
identityKeyPairs: Array<object>
) {
return dispatch => {
const promises: Array<Promise<*>> = 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)
.then((settings) => {
return dispatch(updateIdentitySettings(identityIndex, settings))
})
}

function updateAllIdentitySettings(settings) {
return {
type: types.UPDATE_ALL_IDENTITY_SETTINGS,
settings
}
}

function updateIdentitySettings(identityIndex, settings) {
return {
type: types.UPDATE_IDENTITY_SETTINGS,
identityIndex,
settings
}
}

const AccountActions = {
createAccount,
updateBackupPhrase,
Expand All @@ -569,7 +630,11 @@ const AccountActions = {
usedIdentityAddress,
displayedRecoveryCode,
newIdentityAddress,
updateEmail
updateEmail,
refreshAllIdentitySettings,
refreshIdentitySettings,
updateAllIdentitySettings,
updateIdentitySettings
}

export default AccountActions
21 changes: 19 additions & 2 deletions app/js/account/store/account/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const initialState = {
encryptedBackupPhrase: null, // persist
identityAccount: {
addresses: [],
keypairs: []
keypairs: [],
settings: []
},
bitcoinAccount: {
addresses: [],
Expand Down Expand Up @@ -43,6 +44,7 @@ function AccountReducer(state = initialState, action) {
publicKeychain: action.identityPublicKeychain,
addresses: action.identityAddresses,
keypairs: action.identityKeypairs,
settings: action.identitySettings,
addressIndex: 0
},
bitcoinAccount: {
Expand Down Expand Up @@ -258,13 +260,28 @@ 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_ALL_IDENTITY_SETTINGS:
return Object.assign({}, state, {
identityAccount: Object.assign({}, state.identityAccount, {
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
}
Expand Down
2 changes: 2 additions & 0 deletions app/js/account/store/account/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +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'
46 changes: 46 additions & 0 deletions app/js/account/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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'
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) {
Expand All @@ -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}/`
Expand Down Expand Up @@ -116,3 +122,43 @@ export function uploadProfile(
return uploadAttempt
})
}

export function uploadIdentitySettings(
api: { gaiaHubConfig: GaiaHubConfig, gaiaHubUrl: string},
identityKeyPair: { key: string, keyID: string },
settingsData: string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering why settingsData is a string? I would think it would be some kind of object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function expects the settings data to be already serialized. Although you could argue that the function itself should perform the serialization.

) {
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 => {
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')
}
})
}
8 changes: 7 additions & 1 deletion app/js/profiles/DefaultProfilePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ function mapStateToProps(state) {

function mapDispatchToProps(dispatch) {
return bindActionCreators(
Object.assign({}, IdentityActions, AccountActions),
Object.assign({}, AccountActions, IdentityActions),
dispatch
)
}
Expand All @@ -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,
Expand Down Expand Up @@ -112,6 +113,11 @@ export class DefaultProfilePage extends Component {
componentWillMount() {
logger.info('componentWillMount')
this.props.refreshIdentities(this.props.api, this.props.identityAddresses)
this.props.refreshAllIdentitySettings(
this.props.api,
this.props.identityAddresses,
this.props.identityKeypairs
)
}

componentWillReceiveProps(nextProps) {
Expand Down
5 changes: 4 additions & 1 deletion app/js/utils/account-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -455,6 +456,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) {
const identityKeyPair = deriveIdentityKeyPair(identityOwnerAddressNode)
identityKeypairs.push(identityKeyPair)
identityAddresses.push(identityKeyPair.address)
identitySettings.push({})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is no code using these settings yet, I'm trying to reason about how this will be used. Is identitySettings an array, where each item is an object that represents settings for the nth identity in the keychain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the schema:
image

logger.debug(`createAccount: identity index: ${addressIndex}`)
}

Expand All @@ -463,6 +465,7 @@ export function getBlockchainIdentities(masterKeychain, identitiesToGenerate) {
bitcoinPublicKeychain,
firstBitcoinAddress,
identityAddresses,
identityKeypairs
identityKeypairs,
identitySettings
}
}
3 changes: 2 additions & 1 deletion test/profiles/DefaultProfilePage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ function setup(accounts = []) {
encryptedBackupPhrase: 'onwards and upwards',
setDefaultIdentity: () => {},
identityKeypairs: [],
storageConnected: false
storageConnected: false,
refreshAllIdentitySettings: () => {}
}

const wrapper = shallow(<DefaultProfilePage {...props} />)
Expand Down