Skip to content

Commit

Permalink
feat: update the profile sagas deployEntityWithoutNewFiles to use the…
Browse files Browse the repository at this point in the history
… identity (#530)

* feat: update the profile sagas deployEntityWithoutNewFiles to use the identity

* feat: throw error when identity is not available

* fix: remove unused imports
  • Loading branch information
juanmahidalgo authored Dec 19, 2023
1 parent 67df674 commit 32aabbf
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 49 deletions.
62 changes: 26 additions & 36 deletions src/lib/entities.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ethers } from 'ethers'
import { Authenticator, AuthChain } from '@dcl/crypto'
import { Authenticator, AuthIdentity } from '@dcl/crypto'
import { Entity, EntityType } from '@dcl/schemas/dist/platform/entity'
import { ContentClient, createContentClient } from 'dcl-catalyst-client/dist/client/ContentClient'
import {
ContentClient,
createContentClient
} from 'dcl-catalyst-client/dist/client/ContentClient'
import { BuildEntityWithoutFilesOptions } from 'dcl-catalyst-client/dist/client/types'
import { buildEntityWithoutNewFiles } from 'dcl-catalyst-client/dist/client/utils/DeploymentBuilder'
import { getConnectedProvider } from './eth'
import { ProfileEntity } from './types'
import { PeerAPI } from './peer'
import { createFetchComponent } from '@well-known-components/fetch-component'
Expand All @@ -15,38 +16,23 @@ export class EntitiesOperator {
private catalystContentClientWithoutGbCollector: ContentClient | null // Undefined until initialization
private readonly peerAPI: PeerAPI

constructor(private peerUrl: string, private peerWithNoGbCollectorUrl?: string) {
this.catalystContentClient = createContentClient({ url: `${peerUrl}/content`, fetcher: createFetchComponent() })
constructor(
private peerUrl: string,
private peerWithNoGbCollectorUrl?: string
) {
this.catalystContentClient = createContentClient({
url: `${peerUrl}/content`,
fetcher: createFetchComponent()
})
this.catalystContentClientWithoutGbCollector = peerWithNoGbCollectorUrl
? createContentClient({ url: `${peerUrl}/content`, fetcher: createFetchComponent() })
? createContentClient({
url: `${peerUrl}/content`,
fetcher: createFetchComponent()
})
: null
this.peerAPI = new PeerAPI(peerUrl)
}

/**
* Uses the provider to request the user for a signature to
* deploy an entity.
*
* @param address - The address of the deployer of the entity.
* @param entityId - The entity id that it's going to be deployed.
*/
private async authenticateEntityDeployment(
address: string,
entityId: string
): Promise<AuthChain> {
const provider = await getConnectedProvider()
if (!provider)
throw new Error(
"The provider couldn't be retrieved when creating the auth chain"
)
const eth = new ethers.providers.Web3Provider(provider)

const personal = eth.getSigner(address)
const signature = await personal.signMessage(entityId)

return Authenticator.createSimpleAuthChain(entityId, address, signature)
}

/**
* Gets the first {@link ProfileEntity} out of multiple possible profile entities or
* returns the last one in case the given address has no profile entities.
Expand Down Expand Up @@ -80,7 +66,7 @@ export class EntitiesOperator {
hashesByKey: Map<string, string>,
entityType: EntityType,
pointer: string,
address: string
identity: AuthIdentity
): Promise<any> {
const options: BuildEntityWithoutFilesOptions = {
type: entityType,
Expand All @@ -90,13 +76,17 @@ export class EntitiesOperator {
timestamp: Date.now()
}

const catalystContentClient = this.catalystContentClientWithoutGbCollector ?? this.catalystContentClient
const catalystContentClient =
this.catalystContentClientWithoutGbCollector ?? this.catalystContentClient
const contentUrl = this.peerWithNoGbCollectorUrl ?? this.peerUrl

const entityToDeploy = await buildEntityWithoutNewFiles(createFetchComponent(), { contentUrl: `${contentUrl}/content`, ...options })
const entityToDeploy = await buildEntityWithoutNewFiles(
createFetchComponent(),
{ contentUrl: `${contentUrl}/content`, ...options }
)

const authChain: AuthChain = await this.authenticateEntityDeployment(
address,
const authChain = Authenticator.signPayload(
identity,
entityToDeploy.entityId
)

Expand Down
40 changes: 36 additions & 4 deletions src/modules/profile/sagas.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AuthIdentity } from '@dcl/crypto'
import { Avatar } from '@dcl/schemas/dist/platform/profile'
import { EntityType } from '@dcl/schemas/dist/platform/entity'
import { expectSaga } from 'redux-saga-test-plan'
Expand All @@ -7,7 +8,7 @@ import { profileFromLambda } from '../../tests/profileMocks'
import { ProfileEntity } from '../../lib/types'
import { PeerAPI } from '../../lib/peer'
import { dynamicDeepParametersEquality } from '../../tests/sagas'
import { createProfileSaga } from './sagas'
import { NO_IDENTITY_FOUND_ERROR_MESSAGE, createProfileSaga } from './sagas'
import { getHashesByKeyMap, lambdaProfileToContentProfile } from './utils'
import {
setProfileAvatarAliasFailure,
Expand All @@ -18,7 +19,12 @@ import {
setProfileAvatarDescriptionSuccess
} from './actions'

const profileSagas = createProfileSaga({ peerUrl: 'aURL' });
let mockAuthIdentity: AuthIdentity | undefined = {} as AuthIdentity

const profileSagas = createProfileSaga({
getIdentity: () => mockAuthIdentity,
peerUrl: 'aURL'
})
const address = 'anAddress'
const description = 'aDescription'
const errorMessage = 'anError'
Expand Down Expand Up @@ -94,7 +100,7 @@ describe('when handling the action to set the profile avatar description', () =>
getHashesByKeyMap(newAvatar),
EntityType.PROFILE,
address,
address
mockAuthIdentity
],
Promise.resolve(undefined)
)
Expand Down Expand Up @@ -130,6 +136,32 @@ describe('when handling the action to set the profile avatar alias', () => {
})
})

describe('when there is no identity available', () => {
beforeEach(() => {
mockAuthIdentity = undefined
})
afterAll(() => {
mockAuthIdentity = {} as AuthIdentity
})
it('should dispatch an action to signal that the request failed', () => {
return expectSaga(profileSagas)
.provide([
[
matchers.call.fn(PeerAPI.prototype.fetchProfile),
dynamicDeepParametersEquality(
[address, { useCache: false }],
Promise.resolve(profileFromLambda)
)
]
])
.put(
setProfileAvatarAliasFailure(address, NO_IDENTITY_FOUND_ERROR_MESSAGE)
)
.dispatch(setProfileAvatarAliasRequest(address, alias))
.silentRun()
})
})

describe('when deploying the entity fails', () => {
it('should dispatch an action to signal that the request failed', () => {
return expectSaga(profileSagas)
Expand Down Expand Up @@ -186,7 +218,7 @@ describe('when handling the action to set the profile avatar alias', () => {
getHashesByKeyMap(newAvatar),
EntityType.PROFILE,
address,
address
mockAuthIdentity
],
Promise.resolve(undefined)
)
Expand Down
28 changes: 19 additions & 9 deletions src/modules/profile/sagas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { takeLatest, put, call, takeEvery } from 'redux-saga/effects'
import { AuthIdentity } from '@dcl/crypto'
import { Avatar } from '@dcl/schemas/dist/platform/profile'
import { EntityType } from '@dcl/schemas/dist/platform/entity'
import { PeerAPI } from '../../lib/peer'
Expand Down Expand Up @@ -27,12 +28,16 @@ import {
import { getHashesByKeyMap, lambdaProfileToContentProfile } from './utils'
import { Profile } from './types'

export const NO_IDENTITY_FOUND_ERROR_MESSAGE = 'No identity found'

type CreateProfileSagaOptions = {
getIdentity: () => AuthIdentity | undefined
peerUrl: string
peerWithNoGbCollectorUrl?: string
}

export function createProfileSaga({
getIdentity,
peerUrl,
peerWithNoGbCollectorUrl
}: CreateProfileSagaOptions) {
Expand Down Expand Up @@ -134,15 +139,20 @@ export function createProfileSaga({
const profileMetadata: Profile = {
avatars: [newAvatar, ...profileWithContentHashes.avatars.slice(1)]
}

yield call(
[entities, 'deployEntityWithoutNewFiles'],
profileMetadata,
getHashesByKeyMap(newAvatar),
EntityType.PROFILE,
address,
address
)
const identity = getIdentity()

if (identity) {
yield call(
[entities, 'deployEntityWithoutNewFiles'],
profileMetadata,
getHashesByKeyMap(newAvatar),
EntityType.PROFILE,
address,
identity
)
} else {
throw new Error(NO_IDENTITY_FOUND_ERROR_MESSAGE)
}

return newAvatar
}
Expand Down

0 comments on commit 32aabbf

Please sign in to comment.