Skip to content

Commit e10b97e

Browse files
ChrisMattewgispadaRiccardoMolinari95
authored
feat: [SIW-1971,SIW-2255,SIW-2258] Remote presentation error handling (#6917)
> [!WARNING] > Depends on [this PR](pagopa/io-react-native-wallet#223) ## Short description This PR adds handling for certain potential errors in the remote presentation flow. ## List of changes proposed in this pull request - Implemented an error screen that allows users to retry the remote presentation if a communication error occurs during the auth/response step. - Implemented an error screen to inform the user when the RP fails to verify the Authorization Response. This screen will provide some useful details about the encountered error. - Implemented an error screen shown when the RP returns a non-compliant Request Object. ## Demo <details><summary>Auth Response Errors</summary> <p> | Communication Error | Verification Error| |--------|--------| |<video src="https://github.com/user-attachments/assets/ad045d7c-3cde-47ac-9654-1497ce21aded"></video>| <video src="https://github.com/user-attachments/assets/c6fa7a53-da0a-446b-830b-9b305888eb47"></video>| </p> </details> <details><summary>Non-Compliant Request Object</summary> <p> | Invalid Req Obj | Invalid Dcql Query | |--------|--------| |<video src="https://github.com/user-attachments/assets/d33fffb4-438d-4c2f-95ef-8163c1d5b7b9"></video>| <video src="https://github.com/user-attachments/assets/c6295b97-2af8-49c5-afb1-124b75e7ff1f"></video>| </p> </details> ## How to test ### Auth Response Error Using a proxy tool (I used Proxyman), intercept the POST request to `auth/response` to simulate a communication error (such as a 500 status code) or a validation error returned by the RP (status code 400 or 403), in order to display the two error screens shown in the demo. ### Non-Compliant Request Object - **Invalid Request Object:** - From the `node_modules/@pagopa/io-react-native-wallet/src/credential/presentation/05-verify-request-object.ts` file, make the `verifyRequestObject` function throw an `InvalidRequestObjectError`. - **Invalid Dcql Query:** - In the `ts/features/itwallet/presentation/remote/machine/actors.ts` file, modify the `requestObject.dcql_query` value before calling `evaluateDcqlQuery`. > [!NOTE] > The tests shown in the demo were performed using the test RP --------- Co-authored-by: Gianluca Spada <[email protected]> Co-authored-by: RiccardoMolinari95 <[email protected]>
1 parent b86b000 commit e10b97e

File tree

10 files changed

+186
-9
lines changed

10 files changed

+186
-9
lines changed

locales/en/index.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4743,6 +4743,25 @@
47434743
"primaryAction": "Inizia",
47444744
"secondaryAction": "Annulla"
47454745
},
4746+
"relyingParty": {
4747+
"genericError": {
4748+
"title": "Comunicazione interrotta",
4749+
"subtitle": "Si è verificato un problema che ha impedito la comunicazione con l’ente.\n\nGenera un nuovo QR Code e riprova.",
4750+
"primaryAction": "Riprova",
4751+
"secondaryAction": "Chiudi"
4752+
},
4753+
"invalidAuthResponse": {
4754+
"title": "Non è possibile continuare",
4755+
"subtitle": "Verifica di essere in possesso dei requisiti necessari per accedere ai servizi.",
4756+
"primaryAction": "Ho capito",
4757+
"secondaryAction": "Maggiori dettagli"
4758+
},
4759+
"invalidRequestObject": {
4760+
"title": "Non puoi continuare con la richiesta",
4761+
"subtitle": "L’ente che richiede la verifica non sta comunicando i dati necessari per continuare. Riprova più tardi.",
4762+
"primaryAction": "Ho capito"
4763+
}
4764+
},
47464765
"untrustedRpScreen": {
47474766
"title": "Questo ente non è verificato",
47484767
"subtitle": "Per la tua sicurezza, IO permette di condividere le tue informazioni solo con enti verificati e certificati.",

locales/it/index.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4743,6 +4743,25 @@
47434743
"primaryAction": "Inizia",
47444744
"secondaryAction": "Annulla"
47454745
},
4746+
"relyingParty": {
4747+
"genericError": {
4748+
"title": "Comunicazione interrotta",
4749+
"subtitle": "Si è verificato un problema che ha impedito la comunicazione con l’ente.\n\nGenera un nuovo QR Code e riprova.",
4750+
"primaryAction": "Riprova",
4751+
"secondaryAction": "Chiudi"
4752+
},
4753+
"invalidAuthResponse": {
4754+
"title": "Non è possibile continuare",
4755+
"subtitle": "Verifica di essere in possesso dei requisiti necessari per accedere ai servizi.",
4756+
"primaryAction": "Ho capito",
4757+
"secondaryAction": "Maggiori dettagli"
4758+
},
4759+
"invalidRequestObject": {
4760+
"title": "Non puoi continuare con la richiesta",
4761+
"subtitle": "L’ente che richiede la verifica non sta comunicando i dati necessari per continuare. Riprova più tardi.",
4762+
"primaryAction": "Ho capito"
4763+
}
4764+
},
47464765
"untrustedRpScreen": {
47474766
"title": "Questo ente non è verificato",
47484767
"subtitle": "Per la tua sicurezza, IO permette di condividere le tue informazioni solo con enti verificati e certificati.",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
"@pagopa/io-react-native-jwt": "^2.1.0",
6868
"@pagopa/io-react-native-login-utils": "^1.0.8",
6969
"@pagopa/io-react-native-secure-storage": "^0.2.0",
70-
"@pagopa/io-react-native-wallet": "^0.29.0",
70+
"@pagopa/io-react-native-wallet": "^0.30.0",
7171
"@pagopa/io-react-native-zendesk": "^0.3.30",
7272
"@pagopa/react-native-cie": "^1.4.2",
7373
"@pagopa/ts-commons": "^10.15.0",

ts/features/itwallet/common/hooks/useItwFailureSupportModal.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { clipboardSetStringWithFeedback } from "../../../../utils/clipboard";
3636
import { IssuanceFailure } from "../../machine/eid/failure";
3737
import { CredentialIssuanceFailure } from "../../machine/credential/failure";
3838
import { ItwFailure } from "../utils/ItwFailureTypes.ts";
39+
import { RemoteFailure } from "../../presentation/remote/machine/failure.ts";
3940

4041
const { isWalletProviderResponseError, isIssuerResponseError } = Errors;
4142

@@ -91,7 +92,11 @@ const extractErrorCode = (failure: Props["failure"]) => {
9192
const isDefined = <T,>(x: T | undefined | null | ""): x is T => Boolean(x);
9293

9394
type Props = {
94-
failure: IssuanceFailure | CredentialIssuanceFailure | ItwFailure;
95+
failure:
96+
| IssuanceFailure
97+
| CredentialIssuanceFailure
98+
| ItwFailure
99+
| RemoteFailure;
95100
credentialType?: string;
96101
supportChatEnabled: boolean;
97102
zendeskSubcategory: ZendeskSubcategoryValue;

ts/features/itwallet/presentation/remote/machine/actions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useIONavigation } from "../../../../../navigation/params/AppParamsList.ts";
22
import { ITW_REMOTE_ROUTES } from "../navigation/routes.ts";
33
import { ITW_ROUTES } from "../../../navigation/routes.ts";
4+
import ROUTES from "../../../../../navigation/routes.ts";
45

56
export const createRemoteActionsImplementation = (
67
navigation: ReturnType<typeof useIONavigation>
@@ -24,6 +25,10 @@ export const createRemoteActionsImplementation = (
2425
});
2526
},
2627

28+
navigateToBarcodeScanScreen: () => {
29+
navigation.navigate(ROUTES.BARCODE_SCAN);
30+
},
31+
2732
navigateToAuthResponseScreen: () => {
2833
navigation.navigate(ITW_REMOTE_ROUTES.MAIN, {
2934
screen: ITW_REMOTE_ROUTES.AUTH_RESPONSE

ts/features/itwallet/presentation/remote/machine/events.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export type GoToIdentificationMode = {
1313
type: "go-to-identification-mode";
1414
};
1515

16+
export type GoToBarcodeScan = {
17+
type: "go-to-barcode-scan";
18+
};
19+
1620
export type Back = {
1721
type: "back";
1822
};
@@ -34,6 +38,7 @@ export type RemoteEvents =
3438
| Start
3539
| GoToWalletActivation
3640
| GoToIdentificationMode
41+
| GoToBarcodeScan
3742
| Consent
3843
| ToggleCredential
3944
| Back

ts/features/itwallet/presentation/remote/machine/failure.ts

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Credential } from "@pagopa/io-react-native-wallet";
1+
import { Credential, Errors } from "@pagopa/io-react-native-wallet";
22
import { isDefined } from "../../../../../utils/guards.ts";
33
import { RemoteEvents } from "./events.ts";
44

@@ -8,9 +8,30 @@ export enum RemoteFailureType {
88
WALLET_INACTIVE = "WALLET_INACTIVE",
99
MISSING_CREDENTIALS = "MISSING_CREDENTIALS",
1010
EID_EXPIRED = "EID_EXPIRED",
11+
RELYING_PARTY_GENERIC = "RELYING_PARTY_GENERIC",
12+
RELYING_PARTY_INVALID_AUTH_RESPONSE = "RELYING_PARTY_INVALID_AUTH_RESPONSE",
13+
INVALID_REQUEST_OBJECT = "INVALID_REQUEST_OBJECT",
1114
UNTRUSTED_RP = "UNTRUSTED_RP",
1215
UNEXPECTED = "UNEXPECTED"
1316
}
17+
const { isRelyingPartyResponseError, RelyingPartyResponseErrorCodes: Codes } =
18+
Errors;
19+
20+
/**
21+
* Type that contains the possible error types thrown when the requested Request Object is invalid.
22+
*/
23+
type InvalidRequestObjectError =
24+
| Credential.Presentation.Errors.InvalidRequestObjectError
25+
| Credential.Presentation.Errors.DcqlError;
26+
27+
/**
28+
* Guard used to check if the error is of type `InvalidRequestObjectError`
29+
*/
30+
const isRequestObjectInvalidError = (
31+
error: unknown
32+
): error is InvalidRequestObjectError =>
33+
error instanceof Credential.Presentation.Errors.InvalidRequestObjectError ||
34+
error instanceof Credential.Presentation.Errors.DcqlError;
1435

1536
/**
1637
* Type that maps known reasons with the corresponding failure, in order to avoid unknowns as much as possible.
@@ -21,6 +42,9 @@ export type ReasonTypeByFailure = {
2142
missingCredentials: Array<string>;
2243
};
2344
[RemoteFailureType.EID_EXPIRED]: string;
45+
[RemoteFailureType.RELYING_PARTY_GENERIC]: Errors.RelyingPartyResponseError;
46+
[RemoteFailureType.RELYING_PARTY_INVALID_AUTH_RESPONSE]: Errors.RelyingPartyResponseError;
47+
[RemoteFailureType.INVALID_REQUEST_OBJECT]: InvalidRequestObjectError;
2448
[RemoteFailureType.UNTRUSTED_RP]: string;
2549
[RemoteFailureType.UNEXPECTED]: unknown;
2650
};
@@ -29,7 +53,7 @@ type TypedRemoteFailures = {
2953
[K in RemoteFailureType]: { type: K; reason: ReasonTypeByFailure[K] };
3054
};
3155

32-
/*
56+
/**
3357
* Union type of failures with the reason properly typed.
3458
*/
3559
export type RemoteFailure = TypedRemoteFailures[keyof TypedRemoteFailures];
@@ -62,6 +86,24 @@ export const mapEventToFailure = (event: RemoteEvents): RemoteFailure => {
6286
};
6387
}
6488

89+
if (isRelyingPartyResponseError(error, Codes.InvalidAuthorizationResponse)) {
90+
return {
91+
type: RemoteFailureType.RELYING_PARTY_INVALID_AUTH_RESPONSE,
92+
reason: error
93+
};
94+
}
95+
if (isRelyingPartyResponseError(error, Codes.RelyingPartyGenericError)) {
96+
return {
97+
type: RemoteFailureType.RELYING_PARTY_GENERIC,
98+
reason: error
99+
};
100+
}
101+
if (isRequestObjectInvalidError(error)) {
102+
return {
103+
type: RemoteFailureType.INVALID_REQUEST_OBJECT,
104+
reason: error
105+
};
106+
}
65107
return {
66108
type: RemoteFailureType.UNEXPECTED,
67109
reason: String(error)

ts/features/itwallet/presentation/remote/machine/machine.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const itwRemoteMachine = setup({
2828
navigateToClaimsDisclosureScreen: notImplemented,
2929
navigateToIdentificationModeScreen: notImplemented,
3030
navigateToAuthResponseScreen: notImplemented,
31+
navigateToBarcodeScanScreen: notImplemented,
3132
closePresentation: notImplemented
3233
},
3334
actors: {
@@ -209,6 +210,9 @@ export const itwRemoteMachine = setup({
209210
"go-to-identification-mode": {
210211
actions: "navigateToIdentificationModeScreen"
211212
},
213+
"go-to-barcode-scan": {
214+
actions: "navigateToBarcodeScanScreen"
215+
},
212216
close: {
213217
actions: "closePresentation"
214218
}

ts/features/itwallet/presentation/remote/screens/ItwRemoteFailureScreen.tsx

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import { useIONavigation } from "../../../../../navigation/params/AppParamsList.
1717
import { ITW_ROUTES } from "../../../navigation/routes.ts";
1818
import { useItwRemoteUntrustedRPBottomSheet } from "../hooks/useItwRemoteUntrustedRPBottomSheet.tsx";
1919
import { useItwDismissalDialog } from "../../../common/hooks/useItwDismissalDialog.tsx";
20+
import {
21+
useItwFailureSupportModal,
22+
ZendeskSubcategoryValue
23+
} from "../../../common/hooks/useItwFailureSupportModal.tsx";
24+
25+
const zendeskAssistanceErrors = [
26+
RemoteFailureType.RELYING_PARTY_INVALID_AUTH_RESPONSE
27+
];
2028

2129
export const ItwRemoteFailureScreen = () => {
2230
const failureOption =
@@ -48,6 +56,12 @@ const ContentView = ({ failure }: ContentViewProps) => {
4856
customBodyMessage: I18n.t(`${i18nNs}.walletInactiveScreen.alert.body`)
4957
});
5058

59+
const failureSupportModal = useItwFailureSupportModal({
60+
failure,
61+
supportChatEnabled: zendeskAssistanceErrors.includes(failure.type),
62+
zendeskSubcategory: ZendeskSubcategoryValue.IT_WALLET_PRESENTAZIONE_REMOTA
63+
});
64+
5165
const getOperationResultScreenContentProps =
5266
(): OperationResultScreenContentProps => {
5367
switch (failure.type) {
@@ -139,6 +153,69 @@ const ContentView = ({ failure }: ContentViewProps) => {
139153
}
140154
};
141155
}
156+
case RemoteFailureType.RELYING_PARTY_INVALID_AUTH_RESPONSE: {
157+
return {
158+
title: I18n.t(
159+
"features.itWallet.presentation.remote.relyingParty.invalidAuthResponse.title"
160+
),
161+
subtitle: I18n.t(
162+
"features.itWallet.presentation.remote.relyingParty.invalidAuthResponse.subtitle"
163+
),
164+
pictogram: "stopSecurity",
165+
action: {
166+
label: I18n.t(
167+
"features.itWallet.presentation.remote.relyingParty.invalidAuthResponse.primaryAction"
168+
),
169+
onPress: () => machineRef.send({ type: "close" })
170+
},
171+
secondaryAction: {
172+
label: I18n.t(
173+
"features.itWallet.presentation.remote.relyingParty.invalidAuthResponse.secondaryAction"
174+
),
175+
onPress: failureSupportModal.present
176+
}
177+
};
178+
}
179+
case RemoteFailureType.RELYING_PARTY_GENERIC: {
180+
return {
181+
title: I18n.t(
182+
"features.itWallet.presentation.remote.relyingParty.genericError.title"
183+
),
184+
subtitle: I18n.t(
185+
"features.itWallet.presentation.remote.relyingParty.genericError.subtitle"
186+
),
187+
pictogram: "umbrella",
188+
action: {
189+
label: I18n.t(
190+
"features.itWallet.presentation.remote.relyingParty.genericError.primaryAction"
191+
),
192+
onPress: () => machineRef.send({ type: "go-to-barcode-scan" })
193+
},
194+
secondaryAction: {
195+
label: I18n.t(
196+
"features.itWallet.presentation.remote.relyingParty.genericError.secondaryAction"
197+
),
198+
onPress: () => machineRef.send({ type: "close" })
199+
}
200+
};
201+
}
202+
case RemoteFailureType.INVALID_REQUEST_OBJECT: {
203+
return {
204+
title: I18n.t(
205+
"features.itWallet.presentation.remote.relyingParty.invalidRequestObject.title"
206+
),
207+
subtitle: I18n.t(
208+
"features.itWallet.presentation.remote.relyingParty.invalidRequestObject.subtitle"
209+
),
210+
pictogram: "umbrella",
211+
action: {
212+
label: I18n.t(
213+
"features.itWallet.presentation.remote.relyingParty.invalidRequestObject.primaryAction"
214+
),
215+
onPress: () => machineRef.send({ type: "close" })
216+
}
217+
};
218+
}
142219
case RemoteFailureType.UNTRUSTED_RP: {
143220
return {
144221
title: I18n.t(
@@ -174,6 +251,7 @@ const ContentView = ({ failure }: ContentViewProps) => {
174251
subtitleProps={{ textBreakStrategy: "simple" }}
175252
/>
176253
{bottomSheet}
254+
{failureSupportModal.bottomSheet}
177255
</>
178256
);
179257
};

yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3873,9 +3873,9 @@ __metadata:
38733873
languageName: node
38743874
linkType: hard
38753875

3876-
"@pagopa/io-react-native-wallet@npm:^0.29.0":
3877-
version: 0.29.0
3878-
resolution: "@pagopa/io-react-native-wallet@npm:0.29.0"
3876+
"@pagopa/io-react-native-wallet@npm:^0.30.0":
3877+
version: 0.30.0
3878+
resolution: "@pagopa/io-react-native-wallet@npm:0.30.0"
38793879
dependencies:
38803880
dcql: ^0.2.21
38813881
js-base64: ^3.7.7
@@ -3891,7 +3891,7 @@ __metadata:
38913891
"@pagopa/io-react-native-jwt": "*"
38923892
react: "*"
38933893
react-native: "*"
3894-
checksum: daebb39e3bb29d82a984abec5661ec03347209ee74638178849b1d5a4c0c0c7e947ed82cd0d002929c18a82a3511e4e0c2307f97997e36aada73e421d8e1873d
3894+
checksum: 4ffbb017900c41267ea896d4a6ac754452a2e81bde52c35b096a283908198606ad18843e11201a51e9699c7f6e02297f5a6c67869e93888038c20b0e4c17c075
38953895
languageName: node
38963896
linkType: hard
38973897

@@ -13675,7 +13675,7 @@ __metadata:
1367513675
"@pagopa/io-react-native-jwt": ^2.1.0
1367613676
"@pagopa/io-react-native-login-utils": ^1.0.8
1367713677
"@pagopa/io-react-native-secure-storage": ^0.2.0
13678-
"@pagopa/io-react-native-wallet": ^0.29.0
13678+
"@pagopa/io-react-native-wallet": ^0.30.0
1367913679
"@pagopa/io-react-native-zendesk": ^0.3.30
1368013680
"@pagopa/openapi-codegen-ts": ^14.0.0
1368113681
"@pagopa/react-native-cie": ^1.4.2

0 commit comments

Comments
 (0)