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(IT Wallet): [SIW-1824] Show alert if wallet instance is revoked #6547

Open
wants to merge 38 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f1fc15b
chore: add to wallet instance state also revocation props, initialize…
RiccardoMolinari95 Dec 10, 2024
50ec519
chore: add hook to display alert when wi is revoked, add test
RiccardoMolinari95 Dec 12, 2024
4e3857f
refactor: moved update wi status after reset
RiccardoMolinari95 Dec 13, 2024
c7694d0
refactor: deleted env ITW_MINIMUM_INTEGRITY_REQUIREMENTS and instead …
RiccardoMolinari95 Dec 13, 2024
19d9176
refactor: create type WalletInstanceStatus and WalletInstanceRevocati…
RiccardoMolinari95 Dec 13, 2024
7071c48
chore: fix test
RiccardoMolinari95 Dec 13, 2024
8582fb7
Merge commit 'a6a71349f95d7b8cb01262d578ae83f9c25b74d1' into SIW-1824…
RiccardoMolinari95 Dec 13, 2024
40bb43c
refactor: create selector folder for wallet instance, add test Wallet…
RiccardoMolinari95 Dec 13, 2024
efee04b
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 13, 2024
cdb4447
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 16, 2024
dd2346a
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 16, 2024
e20bee2
refactor: removed unused env
RiccardoMolinari95 Dec 16, 2024
0d1a844
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 16, 2024
06939b7
chore: typo
RiccardoMolinari95 Dec 17, 2024
260a57a
chore: typo
RiccardoMolinari95 Dec 17, 2024
e37c2fc
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 17, 2024
1728866
chore: add revocation object in wallet instance global state, includi…
RiccardoMolinari95 Dec 17, 2024
54168ff
test: update snapshot
RiccardoMolinari95 Dec 17, 2024
9a2fefc
refactor: revocation state moved alert shown
RiccardoMolinari95 Dec 17, 2024
2c2b0c3
test: update snapshot
RiccardoMolinari95 Dec 17, 2024
72bf28c
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 17, 2024
c788250
chore: add FAQ link url for NEW_WALLET_INSTANCE_CREATED alert
RiccardoMolinari95 Dec 17, 2024
e03ed75
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 17, 2024
0c6fc9b
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 18, 2024
bfb16f4
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 18, 2024
24e0e5f
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 18, 2024
b6fa8d4
refactor: refactor wi revocation alert
RiccardoMolinari95 Dec 18, 2024
8ddd9ed
chore: not persist wallet instance status
RiccardoMolinari95 Dec 18, 2024
70ec8cb
chore: persist wallet instance status, deleted itwWalletInstanceSetAl…
RiccardoMolinari95 Dec 18, 2024
545a54f
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 18, 2024
fe44716
fix: lint
RiccardoMolinari95 Dec 18, 2024
2ee86b0
refactor: prettify
RiccardoMolinari95 Dec 18, 2024
dfa16d8
refactor: openWebUrl instead linking
RiccardoMolinari95 Dec 18, 2024
bb5fcfa
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 18, 2024
d92a539
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 19, 2024
1261408
refactor
RiccardoMolinari95 Dec 19, 2024
84ce577
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 19, 2024
fe8f6bc
Merge branch 'master' into SIW-1824-alert-after-wi-revocation
RiccardoMolinari95 Dec 19, 2024
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
2 changes: 2 additions & 0 deletions .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ ITW_IDP_HINT_TEST=YES
ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs'
# ITW Documents on IO URL
ITW_DOCUMENTS_ON_IO_URL='https://io.italia.it/documenti-su-io'
# ITW Minimum Integrity Requirements FAQ URL
ITW_MINIMUM_INTEGRITY_REQUIREMENTS='https://io.italia.it/documenti-su-io/faq/#n1_12
mastro993 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ ITW_IDP_HINT_TEST=NO
ITW_IPZS_PRIVACY_URL='https://io.italia.it/informativa-ipzs'
# ITW Documents on IO URL
ITW_DOCUMENTS_ON_IO_URL='https://io.italia.it/documenti-su-io'
# ITW Minimum Integrity Requirements FAQ URL
ITW_MINIMUM_INTEGRITY_REQUIREMENTS='https://io.italia.it/documenti-su-io/faq/#n1_12
mastro993 marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 14 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3539,6 +3539,20 @@ features:
title: Dicci cosa ne pensi
content: Raccontaci la tua esperienza con la funzionalità Documenti su IO.
action: Inizia
walletInstanceRevoked:
alert:
cta: Scopri di più
closeButton: Chiudi
closeButtonAlt: Ho capito
revokedByWalletProvider:
title: Documenti su IO è stata disattivata
content: Per verificare i requisiti richiesti per continuare a usare la funzionalità sul tuo dispositivo, premi "Scopri di più".
newWalletInstanceCreated:
title: Documenti su IO è stata disattivata su questo dispositivo
content: Puoi usare i tuoi documenti su IO su un solo dispositivo alla volta per ragioni di sicurezza.
revokedByUser:
title: Hai disattivato Documenti su IO
content: Se cambi idea, potrai riattivare Documenti su IO in futuro.
support:
ticketList:
noTicket:
Expand Down
14 changes: 14 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3539,6 +3539,20 @@ features:
title: Dicci cosa ne pensi
content: Raccontaci la tua esperienza con la funzionalità Documenti su IO.
action: Inizia
walletInstanceRevoked:
alert:
cta: Scopri di più
closeButton: Chiudi
closeButtonAlt: Ho capito
revokedByWalletProvider:
title: Documenti su IO è stata disattivata
content: Per verificare i requisiti richiesti per continuare a usare la funzionalità sul tuo dispositivo, premi "Scopri di più".
newWalletInstanceCreated:
title: Documenti su IO è stata disattivata su questo dispositivo
content: Puoi usare i tuoi documenti su IO su un solo dispositivo alla volta per ragioni di sicurezza.
revokedByUser:
title: Hai disattivato Documenti su IO
content: Se cambi idea, potrai riattivare Documenti su IO in futuro.
support:
ticketList:
noTicket:
Expand Down
5 changes: 5 additions & 0 deletions ts/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,8 @@ export const itwDocumentsOnIOUrl: string = pipe(
t.string.decode,
E.getOrElse(() => "https://io.italia.it/documenti-su-io")
);
export const itwMinIntegrityReqURL: string = pipe(
mastro993 marked this conversation as resolved.
Show resolved Hide resolved
Config.ITW_MINIMUM_INTEGRITY_REQUIREMENTS,
t.string.decode,
E.getOrElse(() => "https://io.italia.it/documenti-su-io/faq/#n1_12")
);
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ exports[`featuresPersistor should match snapshot 1`] = `
"preferences": {},
"walletInstance": {
"attestation": undefined,
"isRevoked": false,
"revocationReason": undefined,
},
},
"landingBanners": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ exports[`itWalletReducer should match snapshot 1`] = `
"preferences": {},
"walletInstance": {
"attestation": undefined,
"isRevoked": false,
"revocationReason": undefined,
},
}
`;
14 changes: 14 additions & 0 deletions ts/features/itwallet/common/utils/itwAttestationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { itwWalletProviderBaseUrl } from "../../../../config";
import { SessionToken } from "../../../../types/SessionToken";
import { createItWalletFetch } from "../../api/client";
import { RevocationReason } from "../../walletInstance/store/reducers";
import { regenerateCryptoKey, WIA_KEYTAG } from "./itwCryptoContextUtils";
import {
generateIntegrityHardwareKeyTag,
Expand Down Expand Up @@ -91,3 +92,16 @@ export const getWalletInstanceStatus = (
walletProviderBaseUrl: itwWalletProviderBaseUrl,
appFetch: createItWalletFetch(itwWalletProviderBaseUrl, sessionToken)
});

/**
* Map an optional string `revocationReason` to a value of the `RevocationReason` enum.
* @param revocationReason - An optional string representing the reason for revocation.
* @returns A value of the `RevocationReason` enum if the string `revocationReason`
* matches one of the enum values, otherwise `undefined`.
*/
export const toRevocationReason = (
revocationReason?: string
): RevocationReason | undefined =>
Object.values(RevocationReason).includes(revocationReason as RevocationReason)
? (revocationReason as RevocationReason)
: undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ensureIntegrityServiceIsReady } from "../../common/utils/itwIntegrityUt
import { itwIntegrityKeyTagSelector } from "../../issuance/store/selectors";
import { itwLifecycleIsOperationalOrValid } from "../store/selectors";
import { itwIntegritySetServiceIsReady } from "../../issuance/store/actions";
import { itwUpdateWalletInstanceStatus } from "../../walletInstance/store/actions";
import { handleWalletInstanceResetSaga } from "./handleWalletInstanceResetSaga";

export function* getStatusOrResetWalletInstance(integrityKeyTag: string) {
Expand All @@ -20,6 +21,9 @@ export function* getStatusOrResetWalletInstance(integrityKeyTag: string) {
sessionToken
);

// Update wallet instance status
yield* put(itwUpdateWalletInstanceStatus(walletInstanceStatus));

if (walletInstanceStatus.is_revoked) {
yield* call(handleWalletInstanceResetSaga);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Alert, AlertButton, Linking } from "react-native";
import React from "react";
import { IOToast } from "@pagopa/io-app-design-system";
import { RevocationReason } from "../store/reducers";
import I18n from "../../../../i18n";
import { itwMinIntegrityReqURL } from "../../../../config";

const closeButtonText = I18n.t(
"features.itWallet.walletInstanceRevoked.alert.closeButton"
);
const alertCtaText = I18n.t(
"features.itWallet.walletInstanceRevoked.alert.cta"
);

/**
* Hook to monitor wallet instance status and display alerts if revoked.
* @param walletInstanceStatus - The status of the wallet instance, including whether it is revoked and the reason for revocation.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param walletInstanceStatus - The status of the wallet instance, including whether it is revoked and the reason for revocation.

*/
export const useItwWalletInstanceRevocationAlert = (walletInstanceStatus: {
isRevoked: boolean;
revocationReason?: RevocationReason;
}) => {
React.useEffect(() => {
if (walletInstanceStatus.isRevoked) {
showWalletRevocationAlert(walletInstanceStatus.revocationReason);
}
}, [walletInstanceStatus]);
};

/**
* Displays an alert based on the revocation reason.
*/
const showWalletRevocationAlert = (revocationReason?: RevocationReason) => {
switch (revocationReason) {
case RevocationReason.CERTIFICATE_REVOKED_BY_ISSUER:
showAlert(
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.title"
),
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.revokedByWalletProvider.content"
),
[
{ text: closeButtonText },
{
text: alertCtaText,
onPress: () => {
Linking.openURL(itwMinIntegrityReqURL).catch(() => {
IOToast.error(I18n.t("global.genericError"));
});
}
}
]
);
break;

case RevocationReason.NEW_WALLET_INSTANCE_CREATED:
showAlert(
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.title"
),
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.newWalletInstanceCreated.content"
),
[
{ text: closeButtonText },
{
text: alertCtaText,
onPress: () => {
// TODO: Add the correct URL
Linking.openURL("").catch(() => {
IOToast.error(I18n.t("global.genericError"));
});
}
}
]
);
break;
case RevocationReason.REVOKED_BY_USER:
showAlert(
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.revokedByUser.title"
),
I18n.t(
"features.itWallet.walletInstanceRevoked.alert.revokedByUser.content"
),
[
{
text: I18n.t(
"features.itWallet.walletInstanceRevoked.alert.closeButtonAlt"
)
}
]
);
break;
default:
break;
}
};

const showAlert = (
title: string,
message: string,
buttons: Array<AlertButton> = [{ text: closeButtonText }]
) => {
Alert.alert(title, message, buttons);
};
Comment on lines +103 to +109
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really need this? Why not using directly Alert.alert?

14 changes: 11 additions & 3 deletions ts/features/itwallet/walletInstance/store/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WalletInstanceData } from "@pagopa/io-react-native-wallet/lib/typescript/client/generated/wallet-provider";
mastro993 marked this conversation as resolved.
Show resolved Hide resolved
import { ActionType, createStandardAction } from "typesafe-actions";

/**
Expand All @@ -7,6 +8,13 @@ export const itwWalletInstanceAttestationStore = createStandardAction(
"ITW_WALLET_INSTANCE_ATTESTATION_STORE"
)<string>();

export type ItwWalletInstanceActions = ActionType<
typeof itwWalletInstanceAttestationStore
>;
/**
* This action update the Wallet Instance Status
*/
export const itwUpdateWalletInstanceStatus = createStandardAction(
"ITW_WALLET_INSTANCE__STATUS_UPDATE"
mastro993 marked this conversation as resolved.
Show resolved Hide resolved
)<WalletInstanceData>();

export type ItwWalletInstanceActions =
| ActionType<typeof itwWalletInstanceAttestationStore>
| ActionType<typeof itwUpdateWalletInstanceStatus>;
48 changes: 44 additions & 4 deletions ts/features/itwallet/walletInstance/store/reducers/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import * as O from "fp-ts/lib/Option";
import { flow } from "fp-ts/lib/function";
import { flow, pipe } from "fp-ts/lib/function";
import { PersistConfig, persistReducer } from "redux-persist";
import { createSelector } from "reselect";
import { getType } from "typesafe-actions";
import { Action } from "../../../../../store/actions/types";
import { GlobalState } from "../../../../../store/reducers/types";
import itwCreateSecureStorage from "../../../common/store/storages/itwSecureStorage";
import { isWalletInstanceAttestationValid } from "../../../common/utils/itwAttestationUtils";
import {
isWalletInstanceAttestationValid,
toRevocationReason
} from "../../../common/utils/itwAttestationUtils";
import { itwLifecycleStoresReset } from "../../../lifecycle/store/actions";
import { itwWalletInstanceAttestationStore } from "../actions";
import {
itwWalletInstanceAttestationStore,
itwUpdateWalletInstanceStatus
} from "../actions";

export enum RevocationReason {
CERTIFICATE_REVOKED_BY_ISSUER = "CERTIFICATE_REVOKED_BY_ISSUER",
NEW_WALLET_INSTANCE_CREATED = "NEW_WALLET_INSTANCE_CREATED",
REVOKED_BY_USER = "REVOKED_BY_USER"
}
mastro993 marked this conversation as resolved.
Show resolved Hide resolved

export type ItwWalletInstanceState = {
attestation: string | undefined;
isRevoked: boolean;
revocationReason?: RevocationReason;
};

export const itwWalletInstanceInitialState: ItwWalletInstanceState = {
attestation: undefined
attestation: undefined,
isRevoked: false,
revocationReason: undefined
};

const CURRENT_REDUX_ITW_WALLET_INSTANCE_STORE_VERSION = -1;
Expand All @@ -27,10 +43,19 @@ const reducer = (
switch (action.type) {
case getType(itwWalletInstanceAttestationStore): {
return {
...state,
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
...state,

I think it's correct to reset the state when we save a new WI: if there is already a status stored, it is for sure referred to a past WI.
What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Absolutely! Thanks for the suggestion

attestation: action.payload
};
}

case getType(itwUpdateWalletInstanceStatus): {
return {
...state,
isRevoked: action.payload.is_revoked,
revocationReason: toRevocationReason(action.payload.revocation_reason)
};
}

case getType(itwLifecycleStoresReset):
return { ...itwWalletInstanceInitialState };

Expand Down Expand Up @@ -62,4 +87,19 @@ export const itwIsWalletInstanceAttestationValidSelector = createSelector(
)
);

/* Selector to get the wallet instance status */
export const itwWalletInstanceStatusSelector = createSelector(
(state: GlobalState) => state.features.itWallet.walletInstance,
walletInstance =>
pipe(
O.fromNullable(walletInstance.isRevoked),
O.filter(Boolean),
O.map(() => ({
isRevoked: true,
revocationReason: walletInstance.revocationReason
})),
O.getOrElse(() => ({ isRevoked: false }))
)
);

export default persistedReducer;
5 changes: 5 additions & 0 deletions ts/features/wallet/components/WalletCardsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import {
import { WalletCardCategoryFilter } from "../types";
import { paymentsWalletUserMethodsSelector } from "../../payments/wallet/store/selectors";
import { cgnDetailSelector } from "../../bonus/cgn/store/reducers/details";
import { itwWalletInstanceStatusSelector } from "../../itwallet/walletInstance/store/reducers";
import { useItwWalletInstanceRevocationAlert } from "../../itwallet/walletInstance/hook/useItwWalletInstanceRevocationAlert";
import { WalletCardsCategoryContainer } from "./WalletCardsCategoryContainer";
import { WalletCardsCategoryRetryErrorBanner } from "./WalletCardsCategoryRetryErrorBanner";
import { WalletCardSkeleton } from "./WalletCardSkeleton";
Expand All @@ -46,6 +48,9 @@ const WalletCardsContainer = () => {
const selectedCategory = useIOSelector(selectWalletCategoryFilter);
const paymentMethodsStatus = useIOSelector(paymentsWalletUserMethodsSelector);
const cgnStatus = useIOSelector(cgnDetailSelector);
const walletInstanceStatus = useIOSelector(itwWalletInstanceStatusSelector);

useItwWalletInstanceRevocationAlert(walletInstanceStatus);

if (isLoading && cards.length === 0) {
return (
Expand Down
Loading
Loading