diff --git a/apps/easypid/src/app/(app)/_layout.tsx b/apps/easypid/src/app/(app)/_layout.tsx
index ac4fd2d5..9bfc7ca8 100644
--- a/apps/easypid/src/app/(app)/_layout.tsx
+++ b/apps/easypid/src/app/(app)/_layout.tsx
@@ -1,4 +1,4 @@
-import { Redirect, Stack, useGlobalSearchParams, useLocalSearchParams, usePathname, useRouter } from 'expo-router'
+import { Redirect, Stack, useGlobalSearchParams, usePathname, useRouter } from 'expo-router'
import { TypedArrayEncoder } from '@credo-ts/core'
import { useSecureUnlock } from '@easypid/agent'
@@ -135,7 +135,7 @@ export default function AppLayout() {
-
+
diff --git a/apps/easypid/src/app/(app)/federation.tsx b/apps/easypid/src/app/(app)/federation.tsx
new file mode 100644
index 00000000..70a8a0db
--- /dev/null
+++ b/apps/easypid/src/app/(app)/federation.tsx
@@ -0,0 +1,18 @@
+import { FunkeFederationDetailScreen } from '@easypid/features/wallet/FunkeFederationDetailScreen'
+import type { TrustedEntity } from '@package/agent'
+import { useLocalSearchParams } from 'expo-router'
+
+export default function Screen() {
+ const { entityId, trustedEntities, name, logo } = useLocalSearchParams()
+
+ const trustedEntitiesArray = JSON.parse(decodeURIComponent(trustedEntities as string)) as Array
+
+ return (
+
+ )
+}
diff --git a/apps/easypid/src/app/(app)/issuer.tsx b/apps/easypid/src/app/(app)/issuer.tsx
deleted file mode 100644
index 12e186cf..00000000
--- a/apps/easypid/src/app/(app)/issuer.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import { FunkeIssuerDetailScreen } from '@easypid/features/wallet/FunkeIssuerDetailScreen'
-import { useLocalSearchParams } from 'expo-router'
-
-export default function Screen() {
- const { host } = useLocalSearchParams()
-
- return
-}
diff --git a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx
index e7194bf8..9107e014 100644
--- a/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx
+++ b/apps/easypid/src/features/receive/FunkeCredentialNotificationScreen.tsx
@@ -352,7 +352,6 @@ export function FunkeCredentialNotificationScreen() {
logo={credentialDisplay.issuer.logo}
entityId={issuerMetadata?.credential_issuer as string}
lastInteractionDate={activities[0]?.date}
- approvalsCount={0}
onContinue={onCheckCardContinue}
/>
),
diff --git a/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx b/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx
index 7bc8909b..538d8096 100644
--- a/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx
+++ b/apps/easypid/src/features/receive/slides/VerifyPartySlide.tsx
@@ -1,4 +1,4 @@
-import type { DisplayImage } from '@package/agent'
+import type { DisplayImage, TrustedEntity } from '@package/agent'
import {
Circle,
@@ -25,8 +25,8 @@ interface VerifyPartySlideProps {
logo?: DisplayImage
backgroundColor?: string
lastInteractionDate?: string
- approvalsCount?: number
onContinue?: () => Promise
+ trustedEntities?: Array
}
export const VerifyPartySlide = ({
@@ -36,8 +36,8 @@ export const VerifyPartySlide = ({
logo,
backgroundColor,
lastInteractionDate,
- approvalsCount,
onContinue,
+ trustedEntities,
}: VerifyPartySlideProps) => {
const router = useRouter()
const { onNext, onCancel } = useWizard()
@@ -54,7 +54,9 @@ export const VerifyPartySlide = ({
}
const onPressVerifiedIssuer = withHaptics(() => {
- router.push(`/issuer?entityId=${entityId}`)
+ router.push(
+ `/federation?name=${encodeURIComponent(name ?? '')}&logo=${encodeURIComponent(logo?.url ?? '')}&entityId=${encodeURIComponent(entityId)}&trustedEntities=${encodeURIComponent(JSON.stringify(trustedEntities ?? []))}`
+ )
})
const onPressInteraction = withHaptics(() => {
@@ -93,15 +95,20 @@ export const VerifyPartySlide = ({
- {approvalsCount ? (
+ {trustedEntities && trustedEntities.length > 0 ? (
) : (
-
+
)}
credentialsForRequest && getOpenIdFedIssuerMetadata(credentialsForRequest.verifier.entityId),
- [credentialsForRequest]
- )
const lastInteractionDate = activities?.[0]?.date
+ const shouldUsePin = useShouldUsePinForSubmission(credentialsForRequest)
useEffect(() => {
if (credentialsForRequest) return
@@ -154,8 +146,8 @@ export function FunkeOpenIdPresentationNotificationScreen() {
entityId={credentialsForRequest?.verifier.entityId as string}
verifierName={credentialsForRequest?.verifier.name}
logo={credentialsForRequest?.verifier.logo}
+ trustedEntities={credentialsForRequest?.verifier.trustedEntities}
lastInteractionDate={lastInteractionDate}
- approvalsCount={fedDisplayData?.approvals.length}
onComplete={() => pushToWallet('replace')}
/>
)
diff --git a/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx b/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx
index 0e6010e8..eeea2449 100644
--- a/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx
+++ b/apps/easypid/src/features/share/FunkePresentationNotificationScreen.tsx
@@ -1,4 +1,4 @@
-import type { DisplayImage, FormattedSubmission } from '@package/agent'
+import type { DisplayImage, FormattedSubmission, TrustedEntity } from '@package/agent'
import { type SlideStep, SlideWizard } from '@package/app'
import { LoadingRequestSlide } from '../receive/slides/LoadingRequestSlide'
@@ -13,8 +13,7 @@ interface FunkePresentationNotificationScreenProps {
verifierName?: string
logo?: DisplayImage
lastInteractionDate?: string
- approvalsCount?: number
-
+ trustedEntities?: Array
submission?: FormattedSubmission
usePin: boolean
isAccepting: boolean
@@ -28,13 +27,13 @@ export function FunkePresentationNotificationScreen({
verifierName,
logo,
lastInteractionDate,
- approvalsCount,
usePin,
onAccept,
onDecline,
isAccepting,
submission,
onComplete,
+ trustedEntities,
}: FunkePresentationNotificationScreenProps) {
return (
),
},
diff --git a/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx b/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx
new file mode 100644
index 00000000..60396b4d
--- /dev/null
+++ b/apps/easypid/src/features/wallet/FunkeFederationDetailScreen.tsx
@@ -0,0 +1,104 @@
+import type { TrustedEntity } from '@package/agent'
+import {
+ Circle,
+ FlexPage,
+ Heading,
+ HeroIcons,
+ IconContainer,
+ Image,
+ MessageBox,
+ Paragraph,
+ ScrollView,
+ type ScrollViewRefType,
+ XStack,
+ YStack,
+} from '@package/ui'
+import { TextBackButton, useScrollViewPosition } from 'packages/app/src'
+import React, { useRef } from 'react'
+import { useSafeAreaInsets } from 'react-native-safe-area-context'
+
+interface FunkeFederationDetailScreenProps {
+ name: string
+ logo?: string
+ entityId?: string
+ trustedEntities?: Array
+}
+
+export function FunkeFederationDetailScreen({
+ name,
+ logo,
+ entityId,
+ trustedEntities = [],
+}: FunkeFederationDetailScreenProps) {
+ const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition()
+ const { bottom } = useSafeAreaInsets()
+ const scrollViewRef = useRef(null)
+
+ return (
+
+
+
+
+ About this party
+ }
+ />
+
+ {logo ? (
+
+
+
+ ) : (
+
+
+
+ )}
+ {name}
+
+
+
+ Trusted by
+
+ {trustedEntities.length > 0 ? (
+ <>A list of organizations and whether they have approved {name}.>
+ ) : (
+ <>There are no organizations that have approved {name}.>
+ )}
+
+
+
+ {trustedEntities.map((entity) => {
+ return (
+
+ {entity.logo_uri && (
+
+
+
+ )}
+
+
+ {entity.organization_name}
+
+ } />
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx b/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx
deleted file mode 100644
index 95ed1a50..00000000
--- a/apps/easypid/src/features/wallet/FunkeIssuerDetailScreen.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { getOpenIdFedIssuerMetadata } from '@easypid/utils/issuer'
-import {
- Circle,
- FlexPage,
- Heading,
- HeroIcons,
- Image,
- MessageBox,
- Paragraph,
- ScrollView,
- type ScrollViewRefType,
- Stack,
- XStack,
- YStack,
- useToastController,
-} from '@package/ui'
-import { useRouter } from 'expo-router'
-import { TextBackButton, useHaptics, useScrollViewPosition } from 'packages/app/src'
-import { useRef } from 'react'
-import { Linking } from 'react-native'
-import { useSafeAreaInsets } from 'react-native-safe-area-context'
-
-interface FunkeIssuerDetailScreenProps {
- host: string
-}
-
-export function FunkeIssuerDetailScreen({ host }: FunkeIssuerDetailScreenProps) {
- const toast = useToastController()
- const router = useRouter()
- const { withHaptics, errorHaptic } = useHaptics()
- const data = getOpenIdFedIssuerMetadata(host)
-
- if (!data) {
- router.back()
- errorHaptic()
- return toast.show('Currently unavailable.', {
- customData: {
- preset: 'warning',
- },
- })
- }
-
- const { handleScroll, isScrolledByOffset, scrollEventThrottle } = useScrollViewPosition()
- const { bottom } = useSafeAreaInsets()
- const scrollViewRef = useRef(null)
-
- const openDomain = withHaptics(() => {
- Linking.openURL(`https://${host}`)
- })
-
- return (
-
-
-
-
- About this party
- }
- />
-
-
-
-
-
- {data.display.name}
-
- {host}
-
-
-
-
-
- Approvals
- A list of entities that have approved {data.display.name}.
-
-
- {data.approvals.map((approval) => (
-
-
-
-
-
- {approval.name}
- Owned by {approval.ownedBy.display.name}
-
-
- ))}
-
-
-
-
- Trust marks
- Certifications that verify {data.display.name}'s security and quality standards.
-
-
- {data.certifications.map((certification) => (
-
- {certification}
-
- ))}
-
-
-
-
-
-
-
-
- )
-}
diff --git a/apps/easypid/src/utils/issuer.ts b/apps/easypid/src/utils/issuer.ts
deleted file mode 100644
index 15d8ac69..00000000
--- a/apps/easypid/src/utils/issuer.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-const DEFAULT_FUNKE_HOST = 'funke.animo.id'
-
-const FUNKE_ISSUER_DATA = {
- host: DEFAULT_FUNKE_HOST,
- display: {
- name: 'Animo',
- logo: {
- url: 'https://i.imgur.com/5kxpKzA.png',
- altText: 'Animo Solutions logo',
- },
- },
- approvals: [
- {
- id: '7c82e94d-d650-4230-9586-cc5e0bec1d88',
- name: 'Dutch Gov eID list',
- ownedBy: {
- did: 'did:web:dutch.gov',
- display: {
- name: 'Dutch Gov',
- logo: {
- url: 'https://i.imgur.com/lqfQV5g.png',
- altText: 'Dutch Gov logo',
- },
- isGovernment: true,
- },
- },
- },
- {
- id: '1e5b0c77-891f-4db6-b5b0-b174e7b38d30',
- name: 'Registered Businesses',
- ownedBy: {
- did: 'did:web:registered.businesses',
- display: {
- name: 'Registered Businesses',
- logo: {
- url: 'https://i.imgur.com/eoXTZS5.jpeg',
- altText: 'Registered Businesses logo',
- },
- },
- },
- },
- ],
- certifications: ['eIDAS compliant', 'ISO/IEC 27001'],
-}
-
-export const getOpenIdFedIssuerMetadata = (host: string) => {
- if (host === FUNKE_ISSUER_DATA.host) {
- return FUNKE_ISSUER_DATA
- }
-
- return null
-}
diff --git a/packages/agent/src/invitation/handler.ts b/packages/agent/src/invitation/handler.ts
index 584f882c..0944cd6e 100644
--- a/packages/agent/src/invitation/handler.ts
+++ b/packages/agent/src/invitation/handler.ts
@@ -55,6 +55,8 @@ import { getCredentialBindingResolver } from '../openid4vc/credentialBindingReso
import { extractOpenId4VcCredentialMetadata, setOpenId4VcCredentialMetadata } from '../openid4vc/displayMetadata'
import { BiometricAuthenticationError } from './error'
import { fetchInvitationDataUrl } from './fetchInvitation'
+import { TRUSTED_ENTITIES } from './trustedEntities'
+import type { TrustedEntity } from './trustedEntities'
export async function resolveOpenId4VciOffer({
agent,
@@ -448,20 +450,21 @@ export async function withTrustedCertificate(
}
export type CredentialsForProofRequest = Awaited>
-export const getCredentialsForProofRequest = async ({
- agent,
- data,
- uri,
- allowUntrustedFederation = true, // TODO: True for now
-}: {
+
+export type GetCredentialsForProofRequestOptions = {
agent: EitherAgent
- // Either data or uri can be provided
data?: string
uri?: string
allowUntrustedFederation?: boolean
-}) => {
- let requestUri: string
+}
+export const getCredentialsForProofRequest = async ({
+ agent,
+ data,
+ uri,
+ allowUntrustedFederation = true,
+}: GetCredentialsForProofRequestOptions) => {
+ let requestUri: string
let requestData = data
const { entityId = undefined, data: fromFederationData = null } = allowUntrustedFederation
@@ -495,6 +498,23 @@ export const getCredentialsForProofRequest = async ({
resolved.authorizationRequest.authorizationRequestPayload.client_metadata
}
+ let trustedEntities: Array = []
+ if (entityId) {
+ const resolvedChains = await agent.modules.openId4VcHolder.resolveOpenIdFederationChains({
+ entityId: entityId,
+ trustAnchorEntityIds: TRUSTED_ENTITIES,
+ })
+
+ trustedEntities = resolvedChains
+ .map((chain) => ({
+ entity_id: chain.trustAnchorEntityConfiguration.sub,
+ organization_name:
+ chain.trustAnchorEntityConfiguration.metadata?.federation_entity?.organization_name ?? 'Unknown entity',
+ logo_uri: chain.trustAnchorEntityConfiguration.metadata?.federation_entity?.logo_uri,
+ }))
+ .filter((entity, index, self) => self.findIndex((e) => e.entity_id === entity.entity_id) === index)
+ }
+
let formattedSubmission: FormattedSubmission
if (resolved.presentationExchange) {
formattedSubmission = formatDifPexCredentialsForRequest(
@@ -530,6 +550,7 @@ export const getCredentialsForProofRequest = async ({
}
: undefined,
name: clientMetadata?.client_name,
+ trustedEntities,
},
formattedSubmission,
} as const
diff --git a/packages/agent/src/invitation/index.ts b/packages/agent/src/invitation/index.ts
index 35c52ff0..f5b5f7bb 100644
--- a/packages/agent/src/invitation/index.ts
+++ b/packages/agent/src/invitation/index.ts
@@ -40,3 +40,5 @@ export {
} from './handler'
export { shareProof } from './shareProof'
export * from './error'
+
+export type { TrustedEntity } from './trustedEntities'
diff --git a/packages/agent/src/invitation/trustedEntities.ts b/packages/agent/src/invitation/trustedEntities.ts
new file mode 100644
index 00000000..0cd3b226
--- /dev/null
+++ b/packages/agent/src/invitation/trustedEntities.ts
@@ -0,0 +1,15 @@
+const BASE_URL = 'https://funke.animo.id/siop'
+
+export const TRUSTED_ENTITIES = [
+ `${BASE_URL}/0193687b-0c27-7b82-a686-ff857dc6bbb3`,
+ `${BASE_URL}/0193687f-20d8-720a-9139-ed939ba510fa`,
+ `${BASE_URL}/019368ed-3787-7669-b7f4-8c012238e90d`,
+ `${BASE_URL}/01936907-56a3-7007-a61f-44bff8b5d175`,
+ `${BASE_URL}/01936903-8879-733f-8eaf-6f2fa862099c`,
+] satisfies [string, ...string[]]
+
+export type TrustedEntity = {
+ entity_id: string
+ organization_name: string
+ logo_uri?: string
+}
diff --git a/packages/ui/src/components/InfoButton.tsx b/packages/ui/src/components/InfoButton.tsx
index 08a6c8da..caa9932d 100644
--- a/packages/ui/src/components/InfoButton.tsx
+++ b/packages/ui/src/components/InfoButton.tsx
@@ -21,6 +21,10 @@ const infoButtonVariants = {
icon: ,
accent: '$danger-500',
},
+ info: {
+ icon: ,
+ accent: '$grey-500',
+ },
// States
expired: {
@@ -95,7 +99,7 @@ export function InfoButton({
)}
-
+
{title}