Skip to content

Commit

Permalink
feat: dcql and a lot of UI stuff (#227)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Nov 25, 2024
1 parent d3242ce commit d02e10d
Show file tree
Hide file tree
Showing 76 changed files with 2,373 additions and 2,665 deletions.
6 changes: 6 additions & 0 deletions apps/easypid/eas.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"build": {
"base": {
"node": "20.11.1"
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal",
"android": {
Expand All @@ -15,12 +19,14 @@
}
},
"preview": {
"extends": "base",
"distribution": "internal",
"env": {
"APP_VARIANT": "preview"
}
},
"production": {
"extends": "base",
"autoIncrement": true,
"distribution": "store",
"android": {
Expand Down
3 changes: 2 additions & 1 deletion apps/easypid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"start": "APP_VARIANT=development expo start -c --dev-client",
"android": "APP_VARIANT=development expo run:android",
"ios": "APP_VARIANT=development expo run:ios",
"prebuild": "APP_VARIANT=development expo prebuild --no-install"
"prebuild": "APP_VARIANT=development expo prebuild --no-install",
"eas-build-pre-install": "corepack enable"
},
"dependencies": {
"@animo-id/expo-ausweis-sdk": "catalog:",
Expand Down
4 changes: 3 additions & 1 deletion apps/easypid/src/agent/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export async function initializeAppAgent({
await wsp.createSalt()
await wsp.register()
}
if (getShouldUseCloudHsm()) shouldUseFallbackSecureEnvironment(true)

const shouldUseCloudHsm = getShouldUseCloudHsm()
if (shouldUseCloudHsm) shouldUseFallbackSecureEnvironment(true)
setFallbackSecureEnvironment(wsp)

return agent
Expand Down
1 change: 1 addition & 0 deletions apps/easypid/src/app/(app)/credentials/[id]/attributes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { FunkeCredentialDetailAttributesScreen } from '@easypid/features/wallet/
import { useLocalSearchParams } from 'expo-router'

export default function Screen() {
// TODO: this should take an id
const { attributes, metadata } = useLocalSearchParams<{
attributes: string
metadata: string
Expand Down
11 changes: 2 additions & 9 deletions apps/easypid/src/app/(app)/notifications/offlinePresentation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@ import { FunkeMdocOfflineSharingScreen } from '@easypid/features/share/FunkeMdoc
import { useLocalSearchParams } from 'expo-router'

export default function Screen() {
const { sessionTranscript, deviceRequest, requestedAttributes } = useLocalSearchParams()
const { sessionTranscript, deviceRequest } = useLocalSearchParams()

const sessionTranscriptArray = new Uint8Array(Buffer.from(sessionTranscript as string, 'base64'))
const deviceRequestArray = new Uint8Array(Buffer.from(deviceRequest as string, 'base64'))
const requestedAttributesArray = JSON.parse(requestedAttributes as string)

return (
<FunkeMdocOfflineSharingScreen
sessionTranscript={sessionTranscriptArray}
deviceRequest={deviceRequestArray}
requestedAttributes={requestedAttributesArray}
/>
)
return <FunkeMdocOfflineSharingScreen sessionTranscript={sessionTranscriptArray} deviceRequest={deviceRequestArray} />
}
2 changes: 1 addition & 1 deletion apps/easypid/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const trustedX509Certificates = [
animoFunkeRelyingPartyCertificate,
ubiqueRootCertificate,
oldAnimoFunkeRelyingPartyCertificate,
'MIIBKTCBz6ADAgECAhB6T2+09ZDXvmIu0tNzisQWMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4XDTcwMDEwMTAwMDAwMFoXDTI1MTEyMjA4MjIxMlowDTELMAkGA1UEBhMCTkwwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMxMC8wLQYDVR0RBCYwJIIiMTkxYi0xMDktMzctMTQ4LTE0OC5uZ3Jvay1mcmVlLmFwcDAKBggqhkjOPQQDAgNJADBGAiEA5oy5/mmbcGuLRlDyXPPVecmrKAaoqDIARdwepx4dIj0CIQCdpXI7GbjIVun1unF4OIzA3IzingADBsjEQvQwuD9s0Q==',
'MIIBKDCBzqADAgECAhAyWHL4SEss2wMO1QQybg/fMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4XDTcwMDEwMTAwMDAwMFoXDTI1MTEyMjA4MjIxMlowDTELMAkGA1UEBhMCTkwwOTATBgcqhkjOPQIBBggqhkjOPQMBBwMiAALcD1XzKepFxWMAOqV+ln1fybBt7DRO5CV0f9A6mRp2xaMwMC4wLAYDVR0RBCUwI4IhMDRkNy0yMTctMTIzLTE4LTI2Lm5ncm9rLWZyZWUuYXBwMAoGCCqGSM49BAMCA0kAMEYCIQDWjkAm/iLhGWcgKILW48f43vEUByvJd2R4lxdTdK9w+wIhALcZIgrH2h9SoXHjuI9ktOMbfVHxt59iq+lOKsC4yOUQ',
]

// https://gitlab.opencode.de/bmi/eudi-wallet/eidas-2.0-architekturkonzept/-/blob/main/architecture-proposal.md#pid-contents
Expand Down
2 changes: 2 additions & 0 deletions apps/easypid/src/crypto/WalletServiceProviderClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import {
import type { EasyPIDAppAgent } from 'packages/agent/src'
import { deriveKeypairFromPin } from './pin'

// TODO: should auto reset after X seconds
let __pin: Array<number> | undefined
export const setWalletServiceProviderPin = (pin?: Array<number>) => {
__pin = pin
}

export const getWalletServiceProviderPin = () => __pin

const GENERIC_RECORD_WALLET_SERVICE_PROVIDER_SALT_ID = 'GENERIC_RECORD_WALLET_SERVICE_PROVIDER_SALT_ID'
Expand Down
79 changes: 25 additions & 54 deletions apps/easypid/src/features/activity/FunkeActivityDetailScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ import { Circle, FlexPage, Heading, Paragraph, ScrollView, Stack, YStack } from
import React from 'react'
import { createParam } from 'solito'

import type { ClaimFormat } from '@credo-ts/core'
import { getPidDisclosedAttributeNames, isPidCredential, usePidCredential } from '@easypid/hooks'
import { getPidAttributesForDisplay } from '@easypid/hooks'
import { useCredentialsWithCustomDisplay } from '@easypid/hooks/useCredentialsWithCustomDisplay'
import { useCredentialsForDisplay } from '@package/agent'
import { CardWithAttributes, TextBackButton, activityInteractions } from '@package/app'
import { useScrollViewPosition } from '@package/app/src/hooks'
import { formatRelativeDate } from 'packages/utils/src'
Expand All @@ -21,10 +18,9 @@ export function FunkeActivityDetailScreen() {
const { params } = useParams()
const router = useRouter()
const { bottom } = useSafeAreaInsets()
const { pidCredentialForDisplay } = usePidCredential()

const { activities } = useActivities()
const { credentials } = useCredentialsWithCustomDisplay()
const { credentials } = useCredentialsForDisplay()
const activity = activities.find((activity) => activity.id === params.id)

if (!activity || activity.type === 'received') {
Expand Down Expand Up @@ -77,55 +73,30 @@ export function FunkeActivityDetailScreen() {
</Stack>
{activity.request.credentials && activity.request.credentials.length > 0 ? (
activity.request.credentials.map((activityCredential) => {
const credential = credentials.find((credential) => credential.id.includes(activityCredential.id))
if ('id' in activityCredential) {
const credential = credentials.find((credential) => credential.id === activityCredential.id)

if (!credential)
return (
<CardWithAttributes
key={activityCredential.id}
id={activityCredential.id}
name="Deleted credential"
textColor="$grey-100"
backgroundColor="$primary-500"
disclosedAttributes={activityCredential.disclosedAttributes ?? []}
disclosedPayload={activityCredential.disclosedPayload ?? {}}
disableNavigation={true}
/>
)
if (!credential) {
return (
<CardWithAttributes
id={activityCredential.id}
name="Deleted credential"
textColor="$grey-100"
backgroundColor="$primary-500"
formattedDisclosedAttributes={activityCredential.attributeNames}
disclosedPayload={activityCredential.attributes}
/>
)
}

const isExpired = credential.metadata.validUntil
? new Date(credential.metadata.validUntil) < new Date()
: false
const isExpired = credential.metadata.validUntil
? new Date(credential.metadata.validUntil) < new Date()
: false

const isNotYetActive = credential.metadata.validFrom
? new Date(credential.metadata.validFrom) > new Date()
: false
const isNotYetActive = credential.metadata.validFrom
? new Date(credential.metadata.validFrom) > new Date()
: false

if (isPidCredential(credential.metadata.type)) {
return (
<CardWithAttributes
key={pidCredentialForDisplay?.id}
id={pidCredentialForDisplay?.id as string}
name={pidCredentialForDisplay?.display.name as string}
issuerImage={pidCredentialForDisplay?.display.issuer.logo}
backgroundImage={pidCredentialForDisplay?.display.backgroundImage}
backgroundColor={pidCredentialForDisplay?.display.backgroundColor}
textColor={pidCredentialForDisplay?.display.textColor}
disclosedAttributes={getPidDisclosedAttributeNames(
activityCredential?.disclosedPayload ?? {},
credential?.claimFormat as ClaimFormat.SdJwtVc | ClaimFormat.MsoMdoc
)}
disclosedPayload={getPidAttributesForDisplay(
activityCredential?.disclosedPayload ?? {},
credential?.claimFormat as ClaimFormat.SdJwtVc | ClaimFormat.MsoMdoc
)}
isExpired={isExpired}
isNotYetActive={isNotYetActive}
/>
)
}

if (credential)
return (
<CardWithAttributes
key={credential.id}
Expand All @@ -135,13 +106,13 @@ export function FunkeActivityDetailScreen() {
textColor={credential.display.textColor}
backgroundColor={credential.display.backgroundColor}
backgroundImage={credential.display.backgroundImage}
disclosedAttributes={activityCredential.disclosedAttributes ?? []}
disclosedPayload={activityCredential.disclosedPayload ?? {}}
disableNavigation={activity.status !== 'success'}
formattedDisclosedAttributes={activityCredential.attributeNames}
disclosedPayload={activityCredential.attributes}
isExpired={isExpired}
isNotYetActive={isNotYetActive}
/>
)
}
})
) : (
<FailedReasonContainer reason={activity.request.failureReason ?? 'unknown'} />
Expand Down
119 changes: 80 additions & 39 deletions apps/easypid/src/features/activity/activityRecord.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { utils } from '@credo-ts/core'
import { type DisplayImage, type EasyPIDAppAgent, getWalletJsonStore, useWalletJsonRecord } from '@package/agent'
import {
type CredentialForDisplayId,
type CredentialsForProofRequest,
type DisplayImage,
type EasyPIDAppAgent,
type FormattedSubmission,
getDisclosedAttributeNamesForDisplay,
getUnsatisfiedAttributePathsForDisplay,
getWalletJsonStore,
useWalletJsonRecord,
} from '@package/agent'
import { useMemo } from 'react'
import type { AppAgent } from '../../agent'

export type ActivityType = 'shared' | 'received'
export type ActivityStatus = 'success' | 'failed' | 'stopped'
Expand All @@ -22,14 +33,23 @@ interface BaseActivity {
}
}

export interface PresentationActivityCredentialNotFound {
attributeNames: string[]
name?: string
}

export interface PresentationActivityCredential {
id: CredentialForDisplayId
name?: string
attributeNames: string[]
attributes: Record<string, unknown>
metadata: Record<string, unknown>
}

interface PresentationActivity extends BaseActivity {
type: 'shared'
request: {
credentials: Array<{
id: string
disclosedAttributes: string[]
disclosedPayload: Record<string, unknown>
}>
credentials: Array<PresentationActivityCredential | PresentationActivityCredentialNotFound>
name?: string
purpose?: string
failureReason?: SharingFailureReason
Expand All @@ -38,7 +58,7 @@ interface PresentationActivity extends BaseActivity {

interface IssuanceActivity extends BaseActivity {
type: 'received'
credentialIds: string[]
credentialIds: CredentialForDisplayId[]
}

export type Activity = PresentationActivity | IssuanceActivity
Expand Down Expand Up @@ -72,7 +92,7 @@ export const useActivities = ({ filters }: { filters?: { entityId?: string } } =

return [...record.activities]
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
.filter((activity) => activity.entity.id === filters?.entityId)
.filter((activity) => !filters?.entityId || activity.entity.id === filters?.entityId)
}, [record?.activities, filters?.entityId])

return {
Expand All @@ -89,7 +109,7 @@ export const addReceivedActivity = async (
host?: string
logo?: DisplayImage
backgroundColor?: string
credentialIds: string[]
credentialIds: CredentialForDisplayId[]
}
) => {
await activityStorage.addActivity(agent, {
Expand All @@ -98,53 +118,74 @@ export const addReceivedActivity = async (
type: 'received',
status: 'success',
entity: {
id: input.entityId,
name: input.name,
host: input.host,
logo: input.logo,
backgroundColor: input.backgroundColor,
},
credentialIds: input.credentialIds,
} as IssuanceActivity)
})
}

export const addSharedActivity = async (
agent: EasyPIDAppAgent,
input: {
status: ActivityStatus
entity: {
id: string
host: string
name?: string
logo?: DisplayImage
}
request: {
credentials: Array<{
id: string
disclosedAttributes: string[]
disclosedPayload: Record<string, unknown>
}>
name?: string
purpose?: string
failureReason?: SharingFailureReason
}
}
input: Omit<PresentationActivity, 'type' | 'date' | 'id'>
) => {
await activityStorage.addActivity(agent, {
...input,
id: utils.uuid(),
date: new Date().toISOString(),
type: 'shared',
status: input.status,
})
}

export function addSharedActivityForCredentialsForRequest(
agent: AppAgent,
credentialsForRequest: Pick<CredentialsForProofRequest, 'verifier' | 'formattedSubmission'>,
status: ActivityStatus
) {
return addSharedActivity(agent, {
status,
entity: {
id: input.entity.id,
name: input.entity.name,
host: input.entity.host,
logo: input.entity.logo,
id: credentialsForRequest.verifier.entityId,
host: credentialsForRequest.verifier.hostName,
name: credentialsForRequest?.verifier.name,
logo: credentialsForRequest.verifier.logo,
},
request: {
name: input.request.name,
purpose: input.request.purpose,
credentials: input.request.credentials,
failureReason: input.request.failureReason,
name: credentialsForRequest.formattedSubmission.name,
purpose: credentialsForRequest.formattedSubmission.purpose,
credentials: getDisclosedCredentialForSubmission(credentialsForRequest.formattedSubmission),
failureReason:
status === 'failed'
? !credentialsForRequest.formattedSubmission.areAllSatisfied
? 'missing_credentials'
: 'unknown'
: undefined,
},
} as PresentationActivity)
})
}

export function getDisclosedCredentialForSubmission(
formattedSubmission: FormattedSubmission
): Array<PresentationActivityCredentialNotFound | PresentationActivityCredential> {
return formattedSubmission.entries.map((entry) => {
if (!entry.isSatisfied) {
return {
name: entry.name,
attributeNames: getUnsatisfiedAttributePathsForDisplay(entry.requestedAttributePaths),
} satisfies PresentationActivityCredentialNotFound
}

// TODO: once we support selection we should update [0] to the selected credential
const credential = entry.credentials[0]

return {
id: credential.credential.id,
attributeNames: getDisclosedAttributeNamesForDisplay(credential),
attributes: credential.disclosed.attributes,
metadata: credential.disclosed.metadata as unknown as Record<string, unknown>,
} satisfies PresentationActivityCredential
})
}
Loading

0 comments on commit d02e10d

Please sign in to comment.