Skip to content

Commit

Permalink
feat(passport): account mask address
Browse files Browse the repository at this point in the history
  • Loading branch information
szkl committed Nov 11, 2023
1 parent 62e4562 commit e0f5e45
Show file tree
Hide file tree
Showing 27 changed files with 735 additions and 149 deletions.
42 changes: 27 additions & 15 deletions apps/passport/app/components/applications/claims.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import { Disclosure } from '@headlessui/react'
import { useState } from 'react'

import passportLogoURL from '~/assets/PassportIcon.svg'
import { HiOutlineMail } from 'react-icons/hi'
import { TbCrown } from 'react-icons/tb'
import { Modal } from '@proofzero/design-system/src/molecules/modal/Modal'
import warningImg from '~/assets/warning.svg'
import InputText from '~/components/inputs/InputText'
import { startCase } from 'lodash'
import { HiOutlineExternalLink, HiOutlineX } from 'react-icons/hi'
import { EmailMaskedPill } from '@proofzero/design-system/src/atoms/pills/EmailMaskPill'

export const ConfirmRevocationModal = ({
title,
Expand Down Expand Up @@ -171,12 +173,14 @@ export const ClaimsMobileView = ({ scopes }: { scopes: any[] }) => {
const RowView = ({
account,
appAskedFor,
masked = false,
whatsBeingShared,
sourceOfData,
sourceOfDataIcon,
dropdown = true,
}: {
appAskedFor: string
masked: boolean
sourceOfData: string
sourceOfDataIcon: JSX.Element
dropdown?: boolean
Expand All @@ -203,6 +207,7 @@ export const ClaimsMobileView = ({ scopes }: { scopes: any[] }) => {
>
{appAskedFor}
</Text>
{masked && <EmailMaskedPill />}
{whatsBeingShared && (
<Text
size="sm"
Expand All @@ -229,6 +234,7 @@ export const ClaimsMobileView = ({ scopes }: { scopes: any[] }) => {
>
{appAskedFor}
</Text>
{masked && <EmailMaskedPill />}
{whatsBeingShared && (
<Text
size="sm"
Expand Down Expand Up @@ -421,11 +427,10 @@ export const ClaimsMobileView = ({ scopes }: { scopes: any[] }) => {
<RowView
key={i}
appAskedFor="Email"
whatsBeingShared={scope.account}
masked={scope.masked}
whatsBeingShared={scope.address}
sourceOfData={scope.account}
sourceOfDataIcon={
<img src={scope.icon} className="w-5 h-5 rounded-full" />
}
sourceOfDataIcon={<HiOutlineMail className="w-5 h-5" />}
dropdown={false}
/>
)
Expand Down Expand Up @@ -491,12 +496,14 @@ export const ClaimsWideView = ({ scopes }: { scopes: any[] }) => {
const RowView = ({
account,
appAskedFor,
masked = false,
whatsBeingShared,
sourceOfData,
sourceOfDataIcon,
dropdown = true,
}: {
appAskedFor: string
masked: boolean
sourceOfData: string
sourceOfDataIcon: JSX.Element
dropdown?: boolean
Expand Down Expand Up @@ -527,15 +534,19 @@ export const ClaimsWideView = ({ scopes }: { scopes: any[] }) => {
>
{appAskedFor}
</Text>
{masked && <EmailMaskedPill />}
</Disclosure.Button>
) : (
<Text
size="sm"
weight="medium"
className="text-gray-500 truncate"
>
{appAskedFor}
</Text>
<div class="flex space-x-1">
<Text
size="sm"
weight="medium"
className="text-gray-500 truncate"
>
{appAskedFor}
</Text>
{masked && <EmailMaskedPill />}
</div>
)}
</td>
<td className="px-6 py-3">
Expand Down Expand Up @@ -754,11 +765,12 @@ export const ClaimsWideView = ({ scopes }: { scopes: any[] }) => {
<RowView
key={i}
appAskedFor="Email"
whatsBeingShared={scope.account}
sourceOfData={scope.account}
sourceOfDataIcon={
<img src={scope.icon} className="w-5 h-5 rounded-full" />
masked={scope.masked}
whatsBeingShared={
scope.masked ? scope.address : scope.source.address
}
sourceOfData={scope.address}
sourceOfDataIcon={<HiOutlineMail className="w-5 h-5" />}
dropdown={false}
/>
)
Expand Down
79 changes: 70 additions & 9 deletions apps/passport/app/routes/authorize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ import type { PersonaData } from '@proofzero/types/application'

import Authorization from '@proofzero/design-system/src/templates/authorization/Authorization'
import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors'
import { getEmailIcon } from '@proofzero/utils/getNormalisedConnectedAccounts'
import {
decorateAccountDropdownItem,
getEmailIcon,
} from '@proofzero/utils/getNormalisedConnectedAccounts'
import { ThemeContext } from '@proofzero/design-system/src/contexts/theme'
import { AuthenticationScreenDefaults } from '@proofzero/design-system/src/templates/authentication/Authentication'
import { Helmet } from 'react-helmet'
Expand Down Expand Up @@ -397,13 +400,13 @@ export const action: ActionFunction = async ({ request, context }) => {
}

export default function Authorize() {
const loaderData = useLoaderData<LoaderData>()
const {
clientId,
appProfile,
scopeMeta,
state,
redirectOverride,
dataForScopes,
redirectUri,
profile,
prompt,
Expand All @@ -412,10 +415,11 @@ export default function Authorize() {

const userProfile = profile as UserProfile

const [dataForScopes, setDataForScopes] = useState(loaderData.dataForScopes)
const {
connectedEmails,
personaData,
requestedScope,
connectedEmails,
connectedAccounts,
connectedSmartContractWallets,
} = dataForScopes as DataForScopes
Expand All @@ -434,15 +438,60 @@ export default function Authorize() {
}
return selected
})

const [maskEmail, setMaskEmail] = useState<boolean>(false)
useEffect(() => {
if (!maskEmail) return
if (selectedEmail?.mask) return
setMaskEmailCallback()
}, [maskEmail, selectedEmail])

const setMaskEmailCallback = async () => {
if (!maskEmail) return

const accountURN = selectedEmail?.value
if (!accountURN) return
if (selectedEmail.mask) return

const response = await fetch('/create/account-mask', {
body: JSON.stringify({ accountURN, clientId }),
headers: { 'Content-Type': 'application/json' },
method: 'POST',
})

let maskedAccount = selectedEmail
const maskAccount = await response.json()

setDataForScopes((state) => ({
...state,
connectedAccounts: connectedAccounts.map((ca) => {
if (ca.value !== accountURN) return ca
return {
...ca,
mask: decorateAccountDropdownItem(maskAccount),
}
}),
connectedEmails: connectedEmails.map((ce) => {
if (ce.value !== accountURN) return ce
maskedAccount = {
...ce,
mask: decorateAccountDropdownItem(maskAccount),
}
return maskedAccount
}),
}))
setSelectedEmail(maskedAccount)
}

const [selectedConnectedAccounts, setSelectedConnectedAccounts] = useState<
Array<DropdownSelectListItem> | Array<AuthorizationControlSelection>
>(() => {
if (persona.connected_accounts === AuthorizationControlSelection.ALL) {
return [AuthorizationControlSelection.ALL]
} else {
return connectedAccounts?.length
? connectedAccounts.filter((acc) =>
persona.connected_accounts?.includes(acc.value)
? connectedAccounts.filter(
(acc) => persona.connected_accounts?.includes(acc.value)
)
: []
}
Expand All @@ -454,8 +503,8 @@ export default function Authorize() {
return [AuthorizationControlSelection.ALL]
} else {
return connectedSmartContractWallets?.length
? connectedSmartContractWallets.filter((acc) =>
persona.erc_4337?.includes(acc.value)
? connectedSmartContractWallets.filter(
(acc) => persona.erc_4337?.includes(acc.value)
)
: []
}
Expand Down Expand Up @@ -510,7 +559,9 @@ export default function Authorize() {
}

if (requestedScope.includes('email') && selectedEmail) {
personaData.email = selectedEmail.value
personaData.email = maskEmail
? selectedEmail.mask?.value
: selectedEmail.value
}

if (
Expand All @@ -521,7 +572,12 @@ export default function Authorize() {
personaData.connected_accounts = AuthorizationControlSelection.ALL
} else {
personaData.connected_accounts = selectedConnectedAccounts.map(
(account) => (account as DropdownSelectListItem).value
(account) => {
const item = account as DropdownSelectListItem
if (!maskEmail) return item.value
if (item.value === selectedEmail?.value) return item.mask?.value
return item.value
}
)
}
}
Expand Down Expand Up @@ -640,10 +696,13 @@ export default function Authorize() {
// Substituting subtitle with icon
// on the client side
return {
address: email.address,
type: email.type,
icon: getEmailIcon(email.subtitle!),
title: email.title,
selected: email.selected,
value: email.value,
mask: email.mask,
}
}) ?? []
}
Expand All @@ -661,6 +720,8 @@ export default function Authorize() {
}}
selectEmailCallback={setSelectedEmail}
selectedEmail={selectedEmail}
maskEmail={maskEmail}
setMaskEmail={setMaskEmail}
connectedAccounts={connectedAccounts ?? []}
selectedConnectedAccounts={selectedConnectedAccounts}
addNewAccountCallback={() => {
Expand Down
61 changes: 61 additions & 0 deletions apps/passport/app/routes/create/account-mask.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { json } from '@remix-run/cloudflare'
import type { ActionFunction } from '@remix-run/cloudflare'

import { EmailAccountType, NodeType } from '@proofzero/types/account'
import { type AccountURN, AccountURNSpace } from '@proofzero/urns/account'
import { generateHashedIDRef } from '@proofzero/urns/idref'
import { getRollupReqFunctionErrorWrapper } from '@proofzero/utils/errors'

import { getCoreClient } from '~/platform.server'
import { getValidatedSessionContext } from '~/session.server'
import { BadRequestError } from '@proofzero/errors'

export const action: ActionFunction = getRollupReqFunctionErrorWrapper(
async ({ request, context }) => {
const { jwt } = await getValidatedSessionContext(
request,
context.authzQueryParams,
context.env,
context.traceSpan
)

const { accountURN, clientId } = await request.json<{
accountURN: AccountURN
clientId: string
}>()

if (typeof accountURN !== 'string')
throw new BadRequestError({ message: 'missing account urn' })

const coreClient = getCoreClient({ context, jwt, accountURN })
const address = await coreClient.account.getMaskedAddress.query({
clientId,
})
const qc = {
alias: address,
source: accountURN,
}
const rc = { node_type: NodeType.Email, addr_type: EmailAccountType.Mask }

const maskedAccountURN = AccountURNSpace.componentizedUrn(
generateHashedIDRef(EmailAccountType.Mask, address),
rc,
qc
)

const maskedAccountCoreClient = getCoreClient({
context,
jwt,
accountURN: maskedAccountURN,
})

await maskedAccountCoreClient.account.resolveIdentity.query({ jwt })
await maskedAccountCoreClient.account.setSourceAccount.mutate(accountURN)

return json({
baseUrn: AccountURNSpace.getBaseURN(maskedAccountURN),
qc,
rc,
})
}
)
12 changes: 7 additions & 5 deletions apps/passport/app/routes/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import noImg from '~/assets/noImg.svg'
import { getCoreClient } from '~/platform.server'

import type { AccountURN } from '@proofzero/urns/account'
import type { NodeType } from '@proofzero/types/account'
import { EmailAccountType, type NodeType } from '@proofzero/types/account'
import type {
LoaderFunction,
MetaFunction,
Expand Down Expand Up @@ -68,10 +68,12 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
identity: identityURN,
})

const accountTypeUrns = identityProfile?.accounts.map((a) => ({
urn: a.baseUrn,
nodeType: a.rc.node_type,
})) as { urn: AccountURN; nodeType: NodeType }[]
const accountTypeUrns = identityProfile?.accounts
.filter((a) => a.rc.addr_type !== EmailAccountType.Mask)
.map((a) => ({
urn: a.baseUrn,
nodeType: a.rc.node_type,
})) as { urn: AccountURN; nodeType: NodeType }[]

const accounts = accountTypeUrns.map((atu) => atu.urn)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
}

const coreClient = getCoreClient({ context })
return await coreClient.authorization.getAuthorizedAppScopes.query({
return coreClient.authorization.getAuthorizedAppScopes.query({
clientId,
identityURN,
})
Expand Down
3 changes: 1 addition & 2 deletions apps/passport/app/routes/userinfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper(
const { origin: issuer } = new URL(request.url)

const coreClient = getCoreClient({ context })
const result = await coreClient.authorization.getUserInfo.query({
return coreClient.authorization.getUserInfo.query({
access_token,
issuer,
})
return result
}
)
Loading

0 comments on commit e0f5e45

Please sign in to comment.