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: background pid refresh #239

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
83 changes: 40 additions & 43 deletions apps/easypid/src/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { type CredentialDataHandlerOptions, DeeplinkHandler, useHaptics } from '
import { HeroIcons, IconContainer } from '@package/ui'
import { useEffect, useState } from 'react'
import { useTheme } from 'tamagui'
import { WithBackgroundPidRefresh } from '../../features/pid/WithBackPidRefresh'

const jsonRecordIds = [activityStorage.recordId]

Expand Down Expand Up @@ -99,48 +98,46 @@ export default function AppLayout() {
return (
<AgentProvider agent={secureUnlock.context.agent}>
<WalletJsonStoreProvider agent={secureUnlock.context.agent} recordIds={jsonRecordIds}>
<WithBackgroundPidRefresh>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
}}
name="(home)/scan"
/>
<Stack.Screen
name="notifications/openIdPresentation"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen
name="notifications/openIdCredential"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen
name="notifications/offlinePresentation"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen name="credentials/index" options={headerNormalOptions} />
<Stack.Screen name="credentials/[id]/index" options={headerNormalOptions} />
<Stack.Screen name="credentials/[id]/attributes" options={headerNormalOptions} />
<Stack.Screen name="credentials/requestedAttributes" options={headerNormalOptions} />
<Stack.Screen name="menu/index" options={headerNormalOptions} />
<Stack.Screen name="menu/feedback" options={headerNormalOptions} />
<Stack.Screen name="menu/settings" options={headerNormalOptions} />
<Stack.Screen name="menu/about" options={headerNormalOptions} />
<Stack.Screen name="activity/index" options={headerNormalOptions} />
<Stack.Screen name="activity/[id]" options={headerNormalOptions} />
<Stack.Screen name="pinConfirmation" options={headerNormalOptions} />
<Stack.Screen name="pinLocked" options={headerNormalOptions} />
<Stack.Screen name="issuer" options={headerNormalOptions} />
<Stack.Screen name="pidSetup" />
</Stack>
</WithBackgroundPidRefresh>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
}}
name="(home)/scan"
/>
<Stack.Screen
name="notifications/openIdPresentation"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen
name="notifications/openIdCredential"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen
name="notifications/offlinePresentation"
options={{
gestureEnabled: false,
}}
/>
<Stack.Screen name="credentials/index" options={headerNormalOptions} />
<Stack.Screen name="credentials/[id]/index" options={headerNormalOptions} />
<Stack.Screen name="credentials/[id]/attributes" options={headerNormalOptions} />
<Stack.Screen name="credentials/requestedAttributes" options={headerNormalOptions} />
<Stack.Screen name="menu/index" options={headerNormalOptions} />
<Stack.Screen name="menu/feedback" options={headerNormalOptions} />
<Stack.Screen name="menu/settings" options={headerNormalOptions} />
<Stack.Screen name="menu/about" options={headerNormalOptions} />
<Stack.Screen name="activity/index" options={headerNormalOptions} />
<Stack.Screen name="activity/[id]" options={headerNormalOptions} />
<Stack.Screen name="pinConfirmation" options={headerNormalOptions} />
<Stack.Screen name="pinLocked" options={headerNormalOptions} />
<Stack.Screen name="issuer" options={headerNormalOptions} />
<Stack.Screen name="pidSetup" />
</Stack>
</WalletJsonStoreProvider>
</AgentProvider>
)
Expand Down
9 changes: 0 additions & 9 deletions apps/easypid/src/features/pid/WithBackPidRefresh.tsx

This file was deleted.

67 changes: 0 additions & 67 deletions apps/easypid/src/hooks/useBackgroundPidRefresh.ts

This file was deleted.

12 changes: 6 additions & 6 deletions apps/easypid/src/use-cases/ReceivePidUseCaseFlow.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { AusweisAuthFlow, type AusweisAuthFlowOptions, sendCommand } from '@animo-id/expo-ausweis-sdk'
import type { MdocRecord } from '@credo-ts/core'
import type { AppAgent } from '@easypid/agent'
import {
type OpenId4VciRequestTokenResponse,
type OpenId4VciResolvedCredentialOffer,
type OpenId4VciResolvedOauth2RedirectAuthorizationRequest,
type SdJwtVcRecord,
acquireAuthorizationCodeAccessToken,
import type {
OpenId4VciRequestTokenResponse,
OpenId4VciResolvedCredentialOffer,
OpenId4VciResolvedOauth2RedirectAuthorizationRequest,
SdJwtVcRecord,
} from '@package/agent'
import { acquireAuthorizationCodeAccessToken } from '@package/agent/src/invitation/handler'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does importing from src work when built?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes because it's local build with expo. So the packages boundaries don't really matter here.

It's a but ugly and will refactor after the deadline (had to prevent require cyccle)


export interface ReceivePidUseCaseFlowOptions
extends Pick<AusweisAuthFlowOptions, 'onAttachCard' | 'onStatusProgress' | 'onCardAttachedChanged'> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ClaimFormat, MdocRecord, getJwkFromJson } from '@credo-ts/core'
import { SdJwtVcRecord } from '@credo-ts/core'
import type { AppAgent } from '@easypid/agent'
import type { OpenId4VciRequestTokenResponse, OpenId4VciResolvedCredentialOffer } from '@package/agent'
import {
type OpenId4VciRequestTokenResponse,
type OpenId4VciResolvedCredentialOffer,
SdJwtVcRecord,
acquireRefreshTokenAccessToken,
getRefreshCredentialMetadata,
receiveCredentialFromOpenId4VciOffer,
resolveOpenId4VciOffer,
setRefreshCredentialMetadata,
updateCredential,
} from '@package/agent'
} from '@package/agent/src/invitation/handler'
import { getBatchCredentialMetadata, setBatchCredentialMetadata } from '@package/agent/src/openid4vc/batchMetadata'
import {
getRefreshCredentialMetadata,
setRefreshCredentialMetadata,
} from '@package/agent/src/openid4vc/refreshMetadata'
import { updateCredential } from '@package/agent/src/storage/credential'
import { pidSchemes } from '../constants'
import { ReceivePidUseCaseFlow } from './ReceivePidUseCaseFlow'
import { C_PRIME_SD_JWT_MDOC_OFFER } from './bdrPidIssuerOffers'
Expand Down Expand Up @@ -55,9 +56,11 @@ export class RefreshPidUseCase {
public async retrieveCredentialsUsingExistingRecords({
sdJwt,
mdoc,
batchSize = 2,
}: {
sdJwt?: SdJwtVcRecord
mdoc?: MdocRecord
batchSize?: number
}) {
const existingRefreshMetadata =
(sdJwt ? getRefreshCredentialMetadata(sdJwt) : undefined) ??
Expand Down Expand Up @@ -94,7 +97,7 @@ export class RefreshPidUseCase {
resolvedCredentialOffer: this.resolvedCredentialOffer,
credentialConfigurationIdsToRequest,
clientId: RefreshPidUseCase.CLIENT_ID,
requestBatch: 2,
requestBatch: batchSize,
pidSchemes,
})

Expand Down
100 changes: 75 additions & 25 deletions packages/agent/src/batch.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
import { Mdoc, MdocRecord, SdJwtVcRecord, W3cCredentialRecord } from '@credo-ts/core'
import type { EitherAgent } from './agent'
import type { AppAgent } from '../../../apps/easypid/src/agent'
import { RefreshPidUseCase } from '../../../apps/easypid/src/use-cases/RefreshPidUseCase'
import type { EasyPIDAppAgent, EitherAgent } from './agent'
import { getCredentialCategoryMetadata } from './credentialCategoryMetadata'
import { decodeW3cCredential } from './format/credentialEncoding'
import { getBatchCredentialMetadata } from './openid4vc/batchMetadata'
import { getRefreshCredentialMetadata } from './openid4vc/refreshMetadata'
import { updateCredential } from './storage'

export async function refreshPid({
agent,
sdJwt,
mdoc,
batchSize,
}: { agent: AppAgent; sdJwt?: SdJwtVcRecord; mdoc?: MdocRecord; batchSize?: number }) {
console.log('refreshing PID')
const useCase = await RefreshPidUseCase.initialize({
agent,
})

await useCase.retrieveCredentialsUsingExistingRecords({
sdJwt,
mdoc,
batchSize,
})
}

/**
* If available, takes a credential from the batch.
*
Expand All @@ -14,31 +36,59 @@ export async function handleBatchCredential<CredentialRecord extends W3cCredenti
credentialRecord: CredentialRecord
): Promise<CredentialRecord> {
const batchMetadata = getBatchCredentialMetadata(credentialRecord)
if (!batchMetadata) return credentialRecord

// TODO: maybe we should also store the main credential in the additional credentials (and rename it)
// As right now the main one is mostly for display
const batchCredential = batchMetadata.additionalCredentials.pop()

// Store the record with the used credential removed. Even if the presentation fails we remove it, as we want to be careful
// if the presentation was shared
if (batchCredential) await updateCredential(agent, credentialRecord)

if (batchMetadata) {
const batchCredential = batchMetadata.additionalCredentials.pop()

if (batchCredential) {
// Store the record with the used credential removed. Even if the presentation fails we remove it, as we want to be careful
// if the presentation was shared
await updateCredential(agent, credentialRecord)

if (credentialRecord instanceof MdocRecord) {
return new MdocRecord({
mdoc: Mdoc.fromBase64Url(batchCredential as string),
}) as CredentialRecord
}
if (credentialRecord instanceof SdJwtVcRecord) {
return new SdJwtVcRecord({
compactSdJwtVc: batchCredential as string,
}) as CredentialRecord
}
if (credentialRecord instanceof W3cCredentialRecord) {
return new W3cCredentialRecord({
tags: { expandedTypes: [] },
credential: decodeW3cCredential(batchCredential),
}) as CredentialRecord
}
// Try to refrehs the pid if we ran out
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
// TODO: we should probably move this somewhere else at some point
const categoryMetadata = getCredentialCategoryMetadata(credentialRecord)
const refreshMetadata = getRefreshCredentialMetadata(credentialRecord)
if (
categoryMetadata?.credentialCategory === 'DE-PID' &&
refreshMetadata &&
batchMetadata.additionalCredentials.length === 0
) {
refreshPid({
agent: agent as EasyPIDAppAgent,
sdJwt: credentialRecord.type === 'SdJwtVcRecord' ? credentialRecord : undefined,
mdoc: credentialRecord.type === 'MdocRecord' ? credentialRecord : undefined,
// Get a batch of 5 for a single record type
batchSize: 5,
})
.catch((error) => {
// TOOD: we should handle the case where the refresh token is expired
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
agent.config.logger.error('Error refreshing pid', {
error,
})
})
.then(() => {
agent.config.logger.debug('Successfully refreshed PID')
})
}

if (batchCredential) {
if (credentialRecord instanceof MdocRecord) {
return new MdocRecord({
mdoc: Mdoc.fromBase64Url(batchCredential as string),
}) as CredentialRecord
}
if (credentialRecord instanceof SdJwtVcRecord) {
return new SdJwtVcRecord({
compactSdJwtVc: batchCredential as string,
}) as CredentialRecord
}
if (credentialRecord instanceof W3cCredentialRecord) {
return new W3cCredentialRecord({
tags: { expandedTypes: [] },
credential: decodeW3cCredential(batchCredential),
}) as CredentialRecord
}
}

Expand Down
Loading