Skip to content

Commit

Permalink
Merge pull request #899 from ensdomains/dentity-oauth-from-unconnecte…
Browse files Browse the repository at this point in the history
…d-fix

Fix enter verification process from unconnected state
  • Loading branch information
sugh01 authored Oct 30, 2024
2 parents fd77911 + 19807a5 commit 36e6041
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 89 deletions.
39 changes: 12 additions & 27 deletions e2e/specs/stateless/verifications.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,6 @@ test.describe('Verified records', () => {
await page.goto(`/${name}`)
// await login.connect()

await page.pause()

await expect(page.getByTestId('profile-section-verifications')).toBeVisible()

await profilePage.isRecordVerified('text', 'com.twitter')
Expand Down Expand Up @@ -201,8 +199,6 @@ test.describe('Verified records', () => {

await page.goto(`/${name}`)

await page.pause()

await expect(page.getByTestId('profile-section-verifications')).toBeVisible()

await profilePage.isRecordVerified('text', 'com.twitter', false)
Expand Down Expand Up @@ -600,8 +596,6 @@ test.describe('Verify profile', () => {
await profilePage.goto(name)
await login.connect()

await page.pause()

await expect(page.getByTestId('profile-section-verifications')).toBeVisible()

await profilePage.isRecordVerified('text', 'com.twitter')
Expand Down Expand Up @@ -629,7 +623,6 @@ test.describe('Verify profile', () => {
await profilePage.isRecordVerified('text', 'com.discord', false)
await profilePage.isRecordVerified('verification', 'dentity', false)
await profilePage.isPersonhoodVerified(false)
await page.pause()
})
})

Expand Down Expand Up @@ -744,22 +737,17 @@ test.describe('OAuth flow', () => {
await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`)
await login.connect()

await page.pause()

await expect(page.getByText('Verification failed')).toBeVisible()
await expect(
page.getByText(
'You must be connected as the Manager of this name to set the verification record. You can view and update the Manager under the Ownership tab.',
),
).toBeVisible()

await page.pause()
await login.switchTo('user2')

// Page should redirect to the profile page
await expect(page).toHaveURL(`/${name}`)

await page.pause()
})

test('Should show an error message if user is not logged in', async ({
Expand Down Expand Up @@ -787,22 +775,28 @@ test.describe('OAuth flow', () => {

await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`)

await page.pause()

await expect(page.getByText('Verification failed')).toBeVisible()
await expect(
page.getByText('You must be connected as 0x709...c79C8 to set the verification record.'),
).toBeVisible()

await page.locator('.modal').getByRole('button', { name: 'Done' }).click()

await page.pause()
await page.route(`${VERIFICATION_OAUTH_BASE_URL}/dentity/token`, async (route) => {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({
error_msg: 'Unauthorized',
}),
})
})

await login.connect('user2')
await page.reload()

// Page should redirect to the profile page
await expect(page).toHaveURL(`/${name}`)

await page.pause()
})

test('Should redirect to profile page without showing set verification record if it already set', async ({
Expand Down Expand Up @@ -850,15 +844,9 @@ test.describe('OAuth flow', () => {

await expect(page).toHaveURL(`/${name}`)
await expect(transactionModal.transactionModal).not.toBeVisible()

await page.pause()
})

test('Should show general error message if other problems occur', async ({
page,
login,
makeName,
}) => {
test('Should show general error message if other problems occur', async ({ page, makeName }) => {
const name = await makeName({
label: 'dentity',
type: 'legacy',
Expand All @@ -878,9 +866,6 @@ test.describe('OAuth flow', () => {
})

await page.goto(`/?iss=${DENTITY_ISS}&code=dummyCode`)
await login.connect('user')

await page.pause()

await expect(page.getByText('Verification failed')).toBeVisible()
await expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,86 +9,72 @@ import { VerificationProtocol } from '@app/transaction-flow/input/VerifyProfile/
import { ConfigWithEns, CreateQueryKey, QueryConfig } from '@app/types'
import { prepareQueryOptions } from '@app/utils/prepareQueryOptions'

import { getAPIEndpointForVerifier } from './utils/getAPIEndpointForVerifier'
import { type DentityFederatedToken } from '../useDentityToken/useDentityToken'

type UseVerificationOAuthParameters = {
verifier?: VerificationProtocol | null
code?: string | null
onSuccess?: (resp: UseVerificationOAuthReturnType) => void
token?: DentityFederatedToken
}

export type UseVerificationOAuthReturnType = {
export type UseDentityProfileReturnType = {
verifier: VerificationProtocol
name: string
owner: Hash
name?: string
owner?: Hash | null
manager?: Hash
primaryName?: string
address: Hash
resolverAddress: Hash
verifiedPresentationUri: string
address?: Hash
resolverAddress?: Hash
verifiedPresentationUri?: string
verificationRecord?: string
}

type UseVerificationOAuthConfig = QueryConfig<UseVerificationOAuthReturnType, Error>
type UseVerificationOAuthConfig = QueryConfig<UseDentityProfileReturnType, Error>

type QueryKey<TParams extends UseVerificationOAuthParameters> = CreateQueryKey<
TParams,
'getVerificationOAuth',
'standard'
>

export const getVerificationOAuth =
export const getDentityProfile =
(config: ConfigWithEns) =>
async <TParams extends UseVerificationOAuthParameters>({
queryKey: [{ verifier, code }, chainId],
}: QueryFunctionContext<QueryKey<TParams>>): Promise<UseVerificationOAuthReturnType> => {
// Get federated token from oidc worker
const url = getAPIEndpointForVerifier(verifier)
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ code }),
})
const json = await response.json()

const { name } = json as UseVerificationOAuthReturnType

if (!name)
queryKey: [{ token }, chainId],
}: QueryFunctionContext<QueryKey<TParams>>): Promise<UseDentityProfileReturnType> => {
if (!token || !token.name || !token.verifiedPresentationUri) {
return {
verifier,
...json,
verifier: 'dentity',
...token,
}
}

const { name } = token

// Get resolver address since it will be needed for setting verification record
const client = config.getClient({ chainId })
const records = await getRecords(client, { name, texts: [VERIFICATION_RECORD_KEY] })

// Get owner data to
const ownerData = await getOwner(client, { name })
const { owner, registrant, ownershipLevel } = ownerData || {}

const _owner = ownershipLevel === 'registrar' ? registrant : owner
const manager = ownershipLevel === 'registrar' ? owner : undefined

const userWithSetRecordAbility = manager ?? _owner
const primaryName = userWithSetRecordAbility
? await getName(client, { address: userWithSetRecordAbility })
: undefined

const data = {
...json,
verifier,
...token,
verifier: 'dentity' as const,
owner: _owner,
manager,
primaryName,
primaryName: primaryName?.name,
resolverAddress: records.resolverAddress,
verificationRecord: records.texts.find((text) => text.key === VERIFICATION_RECORD_KEY)?.value,
}
return data
}

export const useVerificationOAuth = <TParams extends UseVerificationOAuthParameters>({
export const useDentityProfile = <TParams extends UseVerificationOAuthParameters>({
enabled = true,
onSuccess,
gcTime,
staleTime,
scopeKey,
Expand All @@ -99,13 +85,13 @@ export const useVerificationOAuth = <TParams extends UseVerificationOAuthParamet
scopeKey,
functionName: 'getVerificationOAuth',
queryDependencyType: 'standard',
queryFn: getVerificationOAuth,
queryFn: getDentityProfile,
})

const preparedOptions = prepareQueryOptions({
queryKey: initialOptions.queryKey,
queryFn: initialOptions.queryFn,
enabled: enabled && !!params.verifier && !!params.code,
enabled: enabled && !!params.token,
gcTime,
staleTime,
retry: 0,
Expand Down
67 changes: 67 additions & 0 deletions src/hooks/verification/useDentityToken/useDentityToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { QueryFunctionContext, useQuery } from '@tanstack/react-query'

import { VERIFICATION_OAUTH_BASE_URL } from '@app/constants/verification'
import { useQueryOptions } from '@app/hooks/useQueryOptions'
import { CreateQueryKey, QueryConfig } from '@app/types'
import { prepareQueryOptions } from '@app/utils/prepareQueryOptions'

export type DentityFederatedToken = {
name: string
verifiedPresentationUri: string
}

type UseDentityTokenParameters = {
code?: string | null
}

export type UseDentityTokenReturnType = DentityFederatedToken

type UseVerificationOAuthConfig = QueryConfig<UseDentityTokenReturnType, Error>

type QueryKey<TParams extends UseDentityTokenParameters> = CreateQueryKey<
TParams,
'getDentityToken',
'independent'
>

export const getDentityToken = async <TParams extends UseDentityTokenParameters>({
queryKey: [{ code }],
}: QueryFunctionContext<QueryKey<TParams>>): Promise<UseDentityTokenReturnType> => {
// Get federated token from oidc worker
const url = `${VERIFICATION_OAUTH_BASE_URL}/dentity/token`
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify({ code }),
})
const json = await response.json()

return json as UseDentityTokenReturnType
}

export const useDentityToken = <TParams extends UseDentityTokenParameters>({
enabled = true,
gcTime,
scopeKey,
...params
}: TParams & UseVerificationOAuthConfig) => {
const initialOptions = useQueryOptions({
params,
scopeKey,
functionName: 'getDentityToken',
queryDependencyType: 'independent',
queryFn: getDentityToken,
})

const preparedOptions = prepareQueryOptions({
queryKey: initialOptions.queryKey,
queryFn: initialOptions.queryFn,
enabled: enabled && !!params.code,
gcTime,
staleTime: Infinity,
retry: 0,
})

const query = useQuery(preparedOptions)

return query
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,12 @@ import { useAccount } from 'wagmi'
import type { VerificationErrorDialogProps } from '@app/components/pages/VerificationErrorDialog'
import { DENTITY_ISS } from '@app/constants/verification'
import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory'
import { VerificationProtocol } from '@app/transaction-flow/input/VerifyProfile/VerifyProfile-flow'
import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider'

import { useVerificationOAuth } from '../useVerificationOAuth/useVerificationOAuth'
import { useDentityProfile } from '../useDentityProfile/useDentityProfile'
import { useDentityToken } from '../useDentityToken/useDentityToken'
import { dentityVerificationHandler } from './utils/dentityHandler'

const issToVerificationProtocol = (iss: string | null): VerificationProtocol | null => {
if (iss === DENTITY_ISS) return 'dentity'
return null
}

type UseVerificationOAuthHandlerReturnType = {
dialogProps: VerificationErrorDialogProps
}
Expand All @@ -32,14 +27,23 @@ export const useVerificationOAuthHandler = (): UseVerificationOAuthHandlerReturn

const { address: userAddress } = useAccount()

const isReady = !!createTransactionFlow && !!router && !!iss && !!code

const { data, isLoading, error } = useVerificationOAuth({
verifier: issToVerificationProtocol(iss),
const isReady = !!createTransactionFlow && !!router && !!iss && !!code && iss === DENTITY_ISS
const { data: dentityToken, isLoading: isDentityTokenLoading } = useDentityToken({
code,
enabled: isReady,
})

const isReadyToFetchProfile = !!dentityToken && !isDentityTokenLoading
const {
data,
isLoading: isDentityProfileLoading,
error,
} = useDentityProfile({
token: dentityToken,
enabled: isReadyToFetchProfile,
})

const isLoading = isDentityTokenLoading || isDentityProfileLoading
const [dialogProps, setDialogProps] = useState<VerificationErrorDialogProps>()
const onClose = () => setDialogProps(undefined)
const onDismiss = () => setDialogProps(undefined)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Hash } from 'viem'
import { createTransactionItem } from '@app/transaction-flow/transaction'
import { CreateTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider'

import { UseVerificationOAuthReturnType } from '../../useVerificationOAuth/useVerificationOAuth'
import { UseDentityProfileReturnType } from '../../useDentityProfile/useDentityProfile'

type Props = Pick<
UseVerificationOAuthReturnType,
UseDentityProfileReturnType,
'name' | 'verifier' | 'resolverAddress' | 'verifiedPresentationUri'
> & {
userAddress?: Hash
Expand Down
Loading

0 comments on commit 36e6041

Please sign in to comment.