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

feat: credential selection in proof request #113

Merged
merged 8 commits into from
Jul 13, 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
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "biomejs.biome"
}
105 changes: 54 additions & 51 deletions apps/funke/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack } from 'expo-router'
import * as SplashScreen from 'expo-splash-screen'
import { useEffect, useState } from 'react'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

import { initializeAppAgent } from '.'
Expand Down Expand Up @@ -102,57 +103,59 @@ export default function HomeLayout() {

return (
<Provider>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
</GestureHandlerRootView>
</Provider>
)
}
2 changes: 2 additions & 0 deletions apps/funke/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ module.exports = (api) => {
disableExtraction: process.env.NODE_ENV === 'development',
},
],
// used for bottom sheet
'react-native-reanimated/plugin',
],
}
}
2 changes: 2 additions & 0 deletions apps/funke/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"prebuild": "APP_VARIANT=development expo prebuild --no-install"
},
"dependencies": {
"@gorhom/bottom-sheet": "^4.6.3",
"@hyperledger/anoncreds-react-native": "^0.2.2",
"@hyperledger/aries-askar-react-native": "^0.2.0",
"@hyperledger/indy-vdr-react-native": "^0.2.0",
Expand Down Expand Up @@ -43,6 +44,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.2",
"react-native-get-random-values": "~1.11.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "15.2.0"
Expand Down
110 changes: 58 additions & 52 deletions apps/paradym/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useEffect, useState } from 'react'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { initializeAppAgent } from '.'

import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { mediatorDid } from './constants'

void SplashScreen.preventAutoHideAsync()
Expand Down Expand Up @@ -143,58 +144,63 @@ export default function HomeLayout() {

return (
<Provider>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen options={{ presentation: 'modal', ...headerModalOptions }} name="notifications/didcomm" />
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/didcomm"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
</GestureHandlerRootView>
</Provider>
)
}
1 change: 1 addition & 0 deletions apps/paradym/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.2",
"react-native-get-random-values": "~1.11.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "15.2.0"
Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ function getW3cCredentialDisplay(
}
}

// Use background color from the JFF credential if not provided by the OID4VCI metadata
if (!credentialDisplay.backgroundColor && jffCredential.credentialBranding?.backgroundColor) {
credentialDisplay.backgroundColor = jffCredential.credentialBranding.backgroundColor
}

return {
...credentialDisplay,
// Last fallback, if there's really no name for the credential, we use a generic name
Expand Down
70 changes: 36 additions & 34 deletions packages/agent/src/format/formatPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,53 @@ export interface FormattedSubmission {
}

export interface FormattedSubmissionEntry {
name: string
/** can be either AnonCreds groupName or PEX inputDescriptorId */
inputDescriptorId: string
isSatisfied: boolean
credentialName: string
issuerName?: string

name: string
description?: string
requestedAttributes?: string[]
backgroundColor?: string

credentials: Array<{
id: string
credentialName: string
issuerName?: string
requestedAttributes?: string[]
backgroundColor?: string
}>
}

export function formatDifPexCredentialsForRequest(
credentialsForRequest: DifPexCredentialsForRequest
): FormattedSubmission {
const entries = credentialsForRequest.requirements.flatMap((requirement) => {
return requirement.submissionEntry.map((submission) => {
// FIXME: support credential selection from JFF branch
const [firstVerifiableCredential] = submission.verifiableCredentials
if (firstVerifiableCredential) {
// Credential can be satisfied
const { display, credential } = getCredentialForDisplay(firstVerifiableCredential.credentialRecord)

// TODO: support nesting
let requestedAttributes: string[]
if (firstVerifiableCredential.type === ClaimFormat.SdJwtVc) {
const { metadata, visibleProperties } = filterAndMapSdJwtKeys(firstVerifiableCredential.disclosedPayload)
requestedAttributes = [...Object.keys(visibleProperties), ...Object.keys(metadata)]
} else {
requestedAttributes = Object.keys(credential?.credentialSubject ?? {})
}

return {
name: submission.name ?? 'Unknown',
description: submission.purpose,
isSatisfied: true,
credentialName: display.name,
issuerName: display.issuer.name,
requestedAttributes,
backgroundColor: display.backgroundColor,
}
}
return requirement.submissionEntry.map((submission): FormattedSubmissionEntry => {
return {
inputDescriptorId: submission.inputDescriptorId,
name: submission.name ?? 'Unknown',
description: submission.purpose,
isSatisfied: false,
// fallback to submission name because there is no credential
credentialName: submission.name ?? 'Credential name',
isSatisfied: submission.verifiableCredentials.length >= 1,

credentials: submission.verifiableCredentials.map((verifiableCredential) => {
const { display, credential } = getCredentialForDisplay(verifiableCredential.credentialRecord)

// TODO: support nesting
let requestedAttributes: string[]
if (verifiableCredential.type === ClaimFormat.SdJwtVc) {
const { metadata, visibleProperties } = filterAndMapSdJwtKeys(verifiableCredential.disclosedPayload)
requestedAttributes = [...Object.keys(visibleProperties), ...Object.keys(metadata)]
} else {
requestedAttributes = Object.keys(credential?.credentialSubject ?? {})
}

return {
id: verifiableCredential.credentialRecord.id,
credentialName: display.name,
issuerName: display.issuer.name,
requestedAttributes,
backgroundColor: display.backgroundColor,
}
}),
}
})
})
Expand Down
Loading