Skip to content

Commit

Permalink
feat(IT Wallet): [SIW-1574] Add wallet instance revocation (#6252)
Browse files Browse the repository at this point in the history
## Short description
This PR adds the feature to revoke the current wallet instance in-app.

## List of changes proposed in this pull request
- Created a new route with `ItwLifecycleWalletRevocationScreen`
- Added a new state to the eID issuance machine to handle revocation
- Added the revoke button to the eID info bottom sheet
- Updated `io-react-native-wallet`

## How to test
Press the button to revoke the wallet instance and check the network:
you should see a HTTP call to `/wallet/wallet-instances/current/status`.
Then check the Redux store to see the `features.itWallet` slice reset.


https://github.com/user-attachments/assets/89fb6b8b-a174-4101-a0d7-90f0b576112d

---------

Co-authored-by: LazyAfternoons <[email protected]>
  • Loading branch information
gispada and LazyAfternoons authored Oct 14, 2024
1 parent 464ade9 commit bc87f7e
Show file tree
Hide file tree
Showing 17 changed files with 233 additions and 12 deletions.
12 changes: 12 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3433,6 +3433,18 @@ features:
title: Certificato di autenticità
usageDescription: Quando ti viene richiesto, mostra il QR Code per attestare l'autenticità del documento.
certifiedLabel: Questo documento è certificato dall'ente emittente.
walletRevocation:
cta: Disattiva Documenti su IO
confirmScreen:
title: Vuoi davvero disattivare Documenti su IO?
subtitle: "Eliminerai i documenti che hai aggiunto al Portafoglio.\nSe cambi idea, potrai riattivare Documenti su IO in futuro."
action: Conferma e continua
loadingScreen:
title: Stiamo disattivando Documenti su IO...
subtitle: Attendi qualche secondo
failureScreen:
title: Si è verificato un errore imprevisto
subtitle: Non è stato possibile disattivare il servizio. Riprova.
support:
ticketList:
noTicket:
Expand Down
12 changes: 12 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3433,6 +3433,18 @@ features:
title: Certificato di autenticità
usageDescription: Quando ti viene richiesto, mostra il QR Code per attestare l'autenticità del documento.
certifiedLabel: Questo documento è certificato dall'ente emittente.
walletRevocation:
cta: Disattiva Documenti su IO
confirmScreen:
title: Vuoi davvero disattivare Documenti su IO?
subtitle: "Eliminerai i documenti che hai aggiunto al Portafoglio.\nSe cambi idea, potrai riattivare Documenti su IO in futuro."
action: Conferma e continua
loadingScreen:
title: Stiamo disattivando Documenti su IO...
subtitle: Attendi qualche secondo
failureScreen:
title: Si è verificato un errore imprevisto
subtitle: Non è stato possibile disattivare il servizio. Riprova.
support:
ticketList:
noTicket:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"@pagopa/io-react-native-integrity": "^0.3.0",
"@pagopa/io-react-native-jwt": "^1.2.0",
"@pagopa/io-react-native-login-utils": "1.0.6",
"@pagopa/io-react-native-wallet": "^0.19.0",
"@pagopa/io-react-native-wallet": "^0.20.0",
"@pagopa/io-react-native-zendesk": "^0.3.29",
"@pagopa/react-native-cie": "^1.3.0",
"@pagopa/ts-commons": "^10.15.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
IOStyles,
VStack,
H4,
Alert
Alert,
ButtonSolid
} from "@pagopa/io-app-design-system";
import * as O from "fp-ts/lib/Option";
import { constNull, pipe } from "fp-ts/lib/function";
Expand All @@ -16,7 +17,9 @@ import { useIOSelector } from "../../../../store/hooks";
import { itwCredentialsEidSelector } from "../../credentials/store/selectors";
import IOMarkdown from "../../../../components/IOMarkdown";
import { format } from "../../../../utils/dates";
import { useIONavigation } from "../../../../navigation/params/AppParamsList";
import { parseClaims, WellKnownClaim } from "../utils/itwClaimsUtils";
import { ITW_ROUTES } from "../../navigation/routes";
import { StoredCredential } from "../utils/itwTypesUtils";
import { ItwCredentialClaim } from "./ItwCredentialClaim";

Expand All @@ -29,14 +32,23 @@ export const ItwEidInfoBottomSheetTitle = () => (
</HStack>
);

export const ItwEidInfoBottomSheetContent = () => {
type Props = {
navigation: ReturnType<typeof useIONavigation>;
};

const ItwEidInfoBottomSheetContent = ({ navigation }: Props) => {
const eidOption = useIOSelector(itwCredentialsEidSelector);

const Content = ({ credential }: { credential: StoredCredential }) => {
const claims = parseClaims(credential.parsedCredential, {
exclude: [WellKnownClaim.unique_id, WellKnownClaim.content]
});

const navigateToWalletRevocationScreen = () =>
navigation.navigate(ITW_ROUTES.MAIN, {
screen: ITW_ROUTES.WALLET_REVOCATION_SCREEN
});

return (
<VStack space={24}>
<IOMarkdown
Expand Down Expand Up @@ -66,6 +78,12 @@ export const ItwEidInfoBottomSheetContent = () => {
"features.itWallet.presentation.bottomSheets.eidInfo.contentBottom"
)}
/>
<ButtonSolid
label={I18n.t("features.itWallet.walletRevocation.cta")}
fullWidth
color="danger"
onPress={navigateToWalletRevocationScreen}
/>
</VStack>
);
};
Expand All @@ -78,3 +96,9 @@ export const ItwEidInfoBottomSheetContent = () => {
)
);
};

const MemoizedItwEidInfoBottomSheetContent = React.memo(
ItwEidInfoBottomSheetContent
);

export { MemoizedItwEidInfoBottomSheetContent as ItwEidInfoBottomSheetContent };
19 changes: 19 additions & 0 deletions ts/features/itwallet/common/utils/itwRevocationUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { WalletInstance } from "@pagopa/io-react-native-wallet";
import { itwWalletProviderBaseUrl } from "../../../../config";
import { SessionToken } from "../../../../types/SessionToken";
import { createItWalletFetch } from "../../api/client";

/**
* Revoke the current wallet instance.
* @param sessionToken
*/
export const revokeCurrentWalletInstance = async (
sessionToken: SessionToken
): Promise<void> => {
const appFetch = createItWalletFetch(itwWalletProviderBaseUrl, sessionToken);

await WalletInstance.revokeCurrentWalletInstance({
walletProviderBaseUrl: itwWalletProviderBaseUrl,
appFetch
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,21 @@ export const ItwIssuanceEidFailureScreen = () => {
),
onPress: () => closeIssuance() // TODO: [SIW-1375] better retry and go back handling logic for the issuance process
}
},
[IssuanceFailureType.WALLET_REVOCATION_GENERIC]: {
title: I18n.t("features.itWallet.walletRevocation.failureScreen.title"),
subtitle: I18n.t(
"features.itWallet.walletRevocation.failureScreen.subtitle"
),
pictogram: "umbrellaNew",
action: {
label: I18n.t("global.buttons.retry"),
onPress: () => machineRef.send({ type: "revoke-wallet-instance" })
},
secondaryAction: {
label: I18n.t("global.buttons.close"),
onPress: () => machineRef.send({ type: "close" })
}
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from "react";
import { View } from "react-native";
import { Body, IOStyles } from "@pagopa/io-app-design-system";
import { OperationResultScreenContent } from "../../../../components/screens/OperationResultScreenContent";
import I18n from "../../../../i18n";
import { ItwEidIssuanceMachineContext } from "../../machine/provider";
import { selectIsLoading } from "../../machine/eid/selectors";
import LoadingScreenContent from "../../../../components/screens/LoadingScreenContent";
import { useItwDisableGestureNavigation } from "../../common/hooks/useItwDisableGestureNavigation";
import { useAvoidHardwareBackButton } from "../../../../utils/useAvoidHardwareBackButton";

const RevocationLoadingScreen = () => {
useItwDisableGestureNavigation();
useAvoidHardwareBackButton();

return (
<LoadingScreenContent
contentTitle={I18n.t(
"features.itWallet.walletRevocation.loadingScreen.title"
)}
>
<View style={[IOStyles.alignCenter, IOStyles.horizontalContentPadding]}>
<Body>
{I18n.t("features.itWallet.walletRevocation.loadingScreen.subtitle")}
</Body>
</View>
</LoadingScreenContent>
);
};

export const ItwLifecycleWalletRevocationScreen = () => {
const machineRef = ItwEidIssuanceMachineContext.useActorRef();
const isLoading = ItwEidIssuanceMachineContext.useSelector(selectIsLoading);

if (isLoading) {
return <RevocationLoadingScreen />;
}

return (
<OperationResultScreenContent
pictogram="attention"
title={I18n.t("features.itWallet.walletRevocation.confirmScreen.title")}
subtitle={I18n.t(
"features.itWallet.walletRevocation.confirmScreen.subtitle"
)}
action={{
label: I18n.t(
"features.itWallet.walletRevocation.confirmScreen.action"
),
accessibilityLabel: I18n.t(
"features.itWallet.walletRevocation.confirmScreen.action"
),
onPress: () => machineRef.send({ type: "revoke-wallet-instance" })
}}
secondaryAction={{
label: I18n.t("global.buttons.cancel"),
accessibilityLabel: I18n.t("global.buttons.cancel"),
onPress: () => machineRef.send({ type: "close" })
}}
/>
);
};
16 changes: 15 additions & 1 deletion ts/features/itwallet/machine/eid/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { useIODispatch } from "../../../../store/hooks";
import { assert } from "../../../../utils/assert";
import { itwCredentialsStore } from "../../credentials/store/actions";
import { itwStoreIntegrityKeyTag } from "../../issuance/store/actions";
import { itwLifecycleStateUpdated } from "../../lifecycle/store/actions";
import {
itwLifecycleStateUpdated,
itwLifecycleWalletReset
} from "../../lifecycle/store/actions";
import { ItwLifecycleState } from "../../lifecycle/store/reducers";
import { ITW_ROUTES } from "../../navigation/routes";
import { itwWalletInstanceAttestationStore } from "../../walletInstance/store/actions";
Expand Down Expand Up @@ -110,6 +113,12 @@ export const createEidIssuanceActionsImplementation = (
});
},

navigateToWalletRevocationScreen: () => {
navigation.navigate(ITW_ROUTES.MAIN, {
screen: ITW_ROUTES.WALLET_REVOCATION_SCREEN
});
},

closeIssuance: () => {
navigation.popToTop();
},
Expand Down Expand Up @@ -159,5 +168,10 @@ export const createEidIssuanceActionsImplementation = (
context
}: ActionArgs<Context, EidIssuanceEvents, EidIssuanceEvents>) => {
context.identification?.abortController?.abort();
},

resetWalletInstance: () => {
dispatch(itwLifecycleWalletReset());
toast.success(I18n.t("features.itWallet.issuance.eidResult.success.toast"));
}
});
10 changes: 9 additions & 1 deletion ts/features/itwallet/machine/eid/actors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
registerWalletInstance
} from "../../common/utils/itwAttestationUtils";
import { ensureIntegrityServiceIsReady } from "../../common/utils/itwIntegrityUtils";
import { revokeCurrentWalletInstance } from "../../common/utils/itwRevocationUtils";
import * as issuanceUtils from "../../common/utils/itwIssuanceUtils";
import { StoredCredential } from "../../common/utils/itwTypesUtils";
import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors";
Expand Down Expand Up @@ -140,5 +141,12 @@ export const createEidIssuanceActorsImplementation = (
callbackUrl: "" // This is not important in this phase, it will be set after completing the CIE auth flow
};
}
)
),

revokeWalletInstance: fromPromise(async () => {
const sessionToken = sessionTokenSelector(store.getState());
assert(sessionToken, "sessionToken is undefined");

await revokeCurrentWalletInstance(sessionToken);
})
});
5 changes: 5 additions & 0 deletions ts/features/itwallet/machine/eid/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export type Abort = {
type: "abort";
};

export type RevokeWalletInstance = {
type: "revoke-wallet-instance";
};

export type EidIssuanceEvents =
| Reset
| Start
Expand All @@ -84,4 +88,5 @@ export type EidIssuanceEvents =
| Close
| NfcEnabled
| Abort
| RevokeWalletInstance
| ErrorActorEvent;
3 changes: 2 additions & 1 deletion ts/features/itwallet/machine/eid/failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export enum IssuanceFailureType {
GENERIC = "GENERIC",
ISSUER_GENERIC = "ISSUER_GENERIC",
UNSUPPORTED_DEVICE = "UNSUPPORTED_DEVICE",
NOT_MATCHING_IDENTITY = "NOT_MATCHING_IDENTITY"
NOT_MATCHING_IDENTITY = "NOT_MATCHING_IDENTITY",
WALLET_REVOCATION_GENERIC = "WALLET_REVOCATION_GENERIC"
}

export type IssuanceFailure = {
Expand Down
28 changes: 28 additions & 0 deletions ts/features/itwallet/machine/eid/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const itwEidIssuanceMachine = setup({
navigateToCiePinScreen: notImplemented,
navigateToCieReadCardScreen: notImplemented,
navigateToNfcInstructionsScreen: notImplemented,
navigateToWalletRevocationScreen: notImplemented,
storeIntegrityKeyTag: notImplemented,
storeWalletInstanceAttestation: notImplemented,
storeEidCredential: notImplemented,
Expand All @@ -47,11 +48,13 @@ export const itwEidIssuanceMachine = setup({
setWalletInstanceToValid: notImplemented,
handleSessionExpired: notImplemented,
abortIdentification: notImplemented,
resetWalletInstance: notImplemented,
setFailure: assign(({ event }) => ({ failure: mapEventToFailure(event) }))
},
actors: {
onInit: fromPromise<OnInitActorOutput>(notImplemented),
createWalletInstance: fromPromise<string>(notImplemented),
revokeWalletInstance: fromPromise<void>(notImplemented),
getWalletAttestation: fromPromise<string, GetWalletAttestationActorParams>(
notImplemented
),
Expand Down Expand Up @@ -89,6 +92,12 @@ export const itwEidIssuanceMachine = setup({
on: {
start: {
target: "TosAcceptance"
},
close: {
actions: "closeIssuance"
},
"revoke-wallet-instance": {
target: "WalletInstanceRevocation"
}
}
},
Expand Down Expand Up @@ -140,6 +149,21 @@ export const itwEidIssuanceMachine = setup({
]
}
},
WalletInstanceRevocation: {
tags: [ItwTags.Loading],
invoke: {
src: "revokeWalletInstance",
onDone: {
actions: ["resetWalletInstance", "closeIssuance"]
},
onError: {
actions: assign(
setFailure(IssuanceFailureType.WALLET_REVOCATION_GENERIC)
),
target: "#itwEidIssuanceMachine.Failure"
}
}
},
WalletInstanceAttestationObtainment: {
description:
"This state obtains the wallet instance attestation and stores it in the context for later use in the issuance flow.",
Expand Down Expand Up @@ -410,6 +434,10 @@ export const itwEidIssuanceMachine = setup({
},
reset: {
target: "Idle"
},
"revoke-wallet-instance": {
actions: "navigateToWalletRevocationScreen",
target: "WalletInstanceRevocation"
}
}
},
Expand Down
1 change: 1 addition & 0 deletions ts/features/itwallet/navigation/ItwParamsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export type ItwParamsList = {
// PLAYGROUNDS
[ITW_ROUTES.PLAYGROUNDS]: undefined;
[ITW_ROUTES.IDENTITY_NOT_MATCHING_SCREEN]: undefined;
[ITW_ROUTES.WALLET_REVOCATION_SCREEN]: undefined;
};
7 changes: 7 additions & 0 deletions ts/features/itwallet/navigation/ItwStackNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { ItwIssuanceEidFailureScreen } from "../issuance/screens/ItwIssuanceEidF
import { ItwIssuanceEidPreviewScreen } from "../issuance/screens/ItwIssuanceEidPreviewScreen";
import { ItwIssuanceEidResultScreen } from "../issuance/screens/ItwIssuanceEidResultScreen";
import { ItwIdentityNotMatchingScreen } from "../lifecycle/screens/ItwIdentityNotMatchingScreen";
import { ItwLifecycleWalletRevocationScreen } from "../lifecycle/screens/ItwLifecycleWalletRevocationScreen";
import {
ItWalletIssuanceMachineProvider,
ItwCredentialIssuanceMachineContext,
Expand Down Expand Up @@ -179,6 +180,12 @@ const InnerNavigator = () => {
component={ItwIdentityNotMatchingScreen}
options={{ headerShown: false, gestureEnabled: false }}
/>

<Stack.Screen
name={ITW_ROUTES.WALLET_REVOCATION_SCREEN}
component={ItwLifecycleWalletRevocationScreen}
options={{ headerShown: false, gestureEnabled: false }}
/>
</Stack.Navigator>
);
};
Loading

0 comments on commit bc87f7e

Please sign in to comment.