Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix enter verification process from unconnected state #899

Merged
merged 4 commits into from
Oct 30, 2024
Merged
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
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
Loading