Skip to content

Commit

Permalink
fix(IT Wallet): [SIW-1795] Problem with SPID Login Using Custom Tab (#…
Browse files Browse the repository at this point in the history
…6409)

> [!WARNING]
> Depends on #6455

## Short description
This PR addresses the issue encountered by some users when logging in
via SPID for PID issuance on ITWallet, specifically related to the
opening of the `CustomTab`.
Unified the authorization flows for SPID, Cie + PIN, and CieID.
Previously, SPID and CieID had a unique flow, while Cie + PIN followed
two separate flows (`startAuthFlow` and `completeAuthFlow`). Replaced
the opening of a `CustomTab` with a `WebView` for SPID authentication.

## List of changes proposed in this pull request
- Updated `io-react-native-wallet` to 0.26.0 ( authentication outside
from the package)
- Created a new screen `ItwSpidIdpLoginScreen` to handle the SPID
authentication process within a `WebView`. This screen loads the
`authUrl` generated by `startAuthFlow` and sends the `authRedirectURL`
back to the state machine when it contains `itWalletIssuanceRedirectUri`
- Refactored the XState machine states related to eID issuance for CieID
and SPID, aligning them with the existing flow for CIE + PIN
- Added `openUrlAndListenForAuthRedirect`, which opens the `authUrl` in
the browser for CieID and provides the `authRedirectURL`
- Created a new actor `getAuthRedirectUrl` used in the
`CieIDBuildAuthRedirectUrl` state of CieID, which utilizes
`openUrlAndListenForAuthRedirect` to complete the authentication

## How to test
To test the changes, try obtaining the eID using all three
authentication methods (SPID, Cie + PIN, and CieID) and ensure that the
eID is successfully obtained in each case. After successfully obtaining
the eID, proceed to obtain a credential and ensure that the process
completes without issues.



https://github.com/user-attachments/assets/2e9d6a20-bee1-470f-860e-3e6b96c9ca1f

---------

Co-authored-by: Riccardo.Molinari <[email protected]>
Co-authored-by: Gianluca Spada <[email protected]>
Co-authored-by: Federico Mastrini <[email protected]>
  • Loading branch information
4 people authored Nov 27, 2024
1 parent 681017f commit c8626c3
Show file tree
Hide file tree
Showing 19 changed files with 713 additions and 283 deletions.
2 changes: 1 addition & 1 deletion .env.local
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ ITW_EAA_PROVIDER_BASE_URL="https://pre.eaa.wallet.ipzs.it"
# Url used to verify a credential (trustmark)
ITW_EAA_VERIFIER_BASE_URL="https://pre.verify.wallet.ipzs.it"
ITW_GOOGLE_CLOUD_PROJECT_NUMBER="260468725946"
ITW_ISSUANCE_REDIRECT_URI="iowallet://cb"
ITW_ISSUANCE_REDIRECT_URI="https://wallet.io.pagopa.it/index.html"
ITW_ISSUANCE_REDIRECT_URI_CIE="iowalletcie://cb"
# Bypass the check that enforces the identity of the issued eID is the same as the authenticated user
ITW_BYPASS_IDENTITY_MATCH=YES
Expand Down
2 changes: 1 addition & 1 deletion .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ ITW_EAA_PROVIDER_BASE_URL="https://eaa.wallet.ipzs.it"
# Url used to verify a credential (trustmark)
ITW_EAA_VERIFIER_BASE_URL="https://verify.wallet.ipzs.it"
ITW_GOOGLE_CLOUD_PROJECT_NUMBER="260468725946"
ITW_ISSUANCE_REDIRECT_URI="iowallet://cb"
ITW_ISSUANCE_REDIRECT_URI="https://wallet.io.pagopa.it/index.html"
ITW_ISSUANCE_REDIRECT_URI_CIE="iowalletcie://cb"
# Bypass the check that enforces the identity of the issued eID is the same as the authenticated user
ITW_BYPASS_IDENTITY_MATCH=NO
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"@pagopa/io-react-native-integrity": "^0.3.0",
"@pagopa/io-react-native-jwt": "^1.3.0",
"@pagopa/io-react-native-login-utils": "^1.0.7",
"@pagopa/io-react-native-wallet": "^0.25.0",
"@pagopa/io-react-native-wallet": "^0.26.0",
"@pagopa/io-react-native-zendesk": "^0.3.29",
"@pagopa/react-native-cie": "^1.3.0",
"@pagopa/ts-commons": "^10.15.0",
Expand Down
183 changes: 52 additions & 131 deletions ts/features/itwallet/common/utils/itwIssuanceUtils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
import { generate } from "@pagopa/io-react-native-crypto";
import {
type AuthorizationContext,
createCryptoContextFor,
Credential,
AuthorizationDetail
} from "@pagopa/io-react-native-wallet";
import { type CryptoContext } from "@pagopa/io-react-native-jwt";
import { openAuthenticationSession } from "@pagopa/io-react-native-login-utils";
import uuid from "react-native-uuid";
import { URL } from "react-native-url-polyfill";
import {
itwPidProviderBaseUrl,
itWalletIssuanceRedirectUri,
itWalletIssuanceRedirectUriCie,
itwIdpHintTest
itwIdpHintTest,
itWalletIssuanceRedirectUriCie
} from "../../../../config";
import { type IdentificationContext } from "../../machine/eid/context";
import { StoredCredential } from "./itwTypesUtils";
Expand All @@ -34,30 +31,43 @@ const CIE_L3_REDIRECT_URI = "https://wallet.io.pagopa.it/index.html";
const CREDENTIAL_TYPE = "PersonIdentificationData";

// Different scheme to avoid conflicts with the scheme handled by io-react-native-login-utils's activity
const getRedirectUri = (identificationMode: IdentificationContext["mode"]) =>
identificationMode === "cieId"
? itWalletIssuanceRedirectUriCie
: itWalletIssuanceRedirectUri;
const getRedirectUri = (identificationMode: IdentificationContext["mode"]) => {
switch (identificationMode) {
case "cieId":
return itWalletIssuanceRedirectUriCie;
case "ciePin":
return CIE_L3_REDIRECT_URI;
default:
return itWalletIssuanceRedirectUri;
}
};

type StartCieAuthFlowParams = {
type StartAuthFlowParams = {
walletAttestation: string;
identification: IdentificationContext;
};

/**
* Function to start the authentication flow when using CIE + PIN. It must be invoked before
* reading the card to get the `authUrl` to launch the CIE web view, and other params that are needed later.
* After successfully reading the card, the flow must be completed invoking `completeCieAuthFlow`.
* Function to start the authentication flow. It must be invoked before
* proceeding with the authentication process to get the `authUrl` and other parameters needed later.
* After completing the initial authentication flow and obtaining the redirectAuthUrl from the WebView (CIE + PIN & SPID) or Browser (CIEID),
* the flow must be completed by invoking `completeAuthFlow`.
* @param walletAttestation - The wallet attestation.
* @returns Authentication params to use when completing the flow.
*/
const startCieAuthFlow = async ({
walletAttestation
}: StartCieAuthFlowParams) => {
const startAuthFlow = async ({
walletAttestation,
identification
}: StartAuthFlowParams) => {
const startFlow: Credential.Issuance.StartFlow = () => ({
issuerUrl: itwPidProviderBaseUrl,
credentialType: CREDENTIAL_TYPE
});

const idpHint = getIdpHint(identification);

const redirectUri = getRedirectUri(identification.mode);

const { issuerUrl, credentialType } = startFlow();

const { issuerConf } = await Credential.Issuance.evaluateIssuerTrust(
Expand All @@ -72,141 +82,66 @@ const startCieAuthFlow = async ({
credentialType,
{
walletInstanceAttestation: walletAttestation,
redirectUri: CIE_L3_REDIRECT_URI,
redirectUri,
wiaCryptoContext
}
);

const authzRequestEndpoint =
issuerConf.oauth_authorization_server.authorization_endpoint;

const params = new URLSearchParams({
client_id: clientId,
request_uri: issuerRequestUri,
idphint: getIdpHint({ mode: "ciePin", pin: "" }) // PIN is not needed for the hint
});
// Obtain the Authorization URL
const { authUrl } = await Credential.Issuance.buildAuthorizationUrl(
issuerRequestUri,
clientId,
issuerConf,
idpHint
);

return {
authUrl: `${authzRequestEndpoint}?${params}`,
authUrl,
issuerConf,
clientId,
codeVerifier,
credentialDefinition
credentialDefinition,
redirectUri
};
};

type CompleteCieAuthFlowParams = {
type CompleteAuthFlowParams = {
callbackUrl: string;
issuerConf: IssuerConf;
clientId: string;
codeVerifier: string;
walletAttestation: string;
redirectUri: string;
};

export type CompleteCieAuthFlowResult = Awaited<
ReturnType<typeof completeCieAuthFlow>
export type CompleteAuthFlowResult = Awaited<
ReturnType<typeof completeAuthFlow>
>;

/**
* Function to complete the CIE + PIN authentication flow. It must be invoked after `startCieAuthFlow`
* and after reading the card to get the final `callbackUrl`. The rest of the parameters are those obtained from
* `startCieAuthFlow` + the wallet attestation.
* Function to complete the authentication flow. It must be invoked after `startAuthFlow`
* and after obtaining the final `callbackUrl` from the WebView (CIE + PIN & SPID) or Browser (CIEID).
* The rest of the parameters are those obtained from `startAuthFlow` + the wallet attestation.
* @param walletAttestation - The wallet attestation.
* @param callbackUrl - The callback url from which the code to get the access token is extracted.
* @returns Authentication tokens.
*/
const completeCieAuthFlow = async ({
const completeAuthFlow = async ({
callbackUrl,
clientId,
codeVerifier,
issuerConf,
walletAttestation
}: CompleteCieAuthFlowParams) => {
const query = Object.fromEntries(new URL(callbackUrl).searchParams);
const { code } = Credential.Issuance.parseAuthorizationResponse(query);

await regenerateCryptoKey(DPOP_KEYTAG);
const dPopCryptoContext = createCryptoContextFor(DPOP_KEYTAG);
const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);

const { accessToken } = await Credential.Issuance.authorizeAccess(
issuerConf,
code,
clientId,
CIE_L3_REDIRECT_URI,
codeVerifier,
{
walletInstanceAttestation: walletAttestation,
wiaCryptoContext,
dPopCryptoContext
}
);

return { accessToken, dPoPContext: dPopCryptoContext };
};

type FullAuthFlowParams = {
walletAttestation: string;
identification: Exclude<IdentificationContext, { mode: "ciePin" }>;
};

/**
* Full authentication flow completely handled by `io-react-native-wallet`. The consumer of the library
* does not need to implement any authentication screen or logic. Only compatible with SPID and CieID.
* @param walletAttestation - The wallet attestation.
* @param identification - Object that contains details on the selected identification mode.
* @returns Authentication tokens and other params needed to get the PID.
*/
const startAndCompleteFullAuthFlow = async ({
walletAttestation,
identification
}: FullAuthFlowParams) => {
const authorizationContext: AuthorizationContext | undefined =
identification.mode === "spid"
? { authorize: openAuthenticationSession }
: undefined;

const idpHint = getIdpHint(identification);

const startFlow: Credential.Issuance.StartFlow = () => ({
issuerUrl: itwPidProviderBaseUrl,
credentialType: CREDENTIAL_TYPE
});

const { issuerUrl, credentialType } = startFlow();

const { issuerConf } = await Credential.Issuance.evaluateIssuerTrust(
issuerUrl
);

const redirectUri = getRedirectUri(identification.mode);

const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);

const { issuerRequestUri, clientId, codeVerifier, credentialDefinition } =
await Credential.Issuance.startUserAuthorization(
issuerConf,
credentialType,
{
walletInstanceAttestation: walletAttestation,
redirectUri,
wiaCryptoContext
}
);

redirectUri
}: CompleteAuthFlowParams) => {
const { code } =
await Credential.Issuance.completeUserAuthorizationWithQueryMode(
issuerRequestUri,
clientId,
issuerConf,
idpHint,
redirectUri,
authorizationContext,
identification.abortController?.signal
callbackUrl
);

await regenerateCryptoKey(DPOP_KEYTAG);
const dPopCryptoContext = createCryptoContextFor(DPOP_KEYTAG);
const wiaCryptoContext = createCryptoContextFor(WIA_KEYTAG);

const { accessToken } = await Credential.Issuance.authorizeAccess(
issuerConf,
Expand All @@ -221,13 +156,7 @@ const startAndCompleteFullAuthFlow = async ({
}
);

return {
accessToken,
dPoPContext: dPopCryptoContext,
credentialDefinition,
clientId,
issuerConf
};
return { accessToken, dPoPContext: dPopCryptoContext };
};

type PidIssuanceParams = {
Expand All @@ -240,9 +169,7 @@ type PidIssuanceParams = {

/**
* Function to get the PID, parse it and return it in {@link StoredCredential} format.
* It must be called after either one of the following:
* - `startCieAuthFlow` and `completeCieAuthFlow`
* - `startAndCompleteFullAuthFlow`
* It must be called after `startAuthFlow` and `completeAuthFlow`.
* @returns The stored credential.
*/
const getPid = async ({
Expand Down Expand Up @@ -289,12 +216,7 @@ const getPid = async ({
};
};

export {
startCieAuthFlow,
completeCieAuthFlow,
startAndCompleteFullAuthFlow,
getPid
};
export { startAuthFlow, completeAuthFlow, getPid };

/**
* Consts for the IDP hints in test for SPID and CIE and in production for CIE.
Expand Down Expand Up @@ -330,7 +252,6 @@ const SPID_IDP_HINTS: { [key: string]: string } = {
* In production for SPID the hint is retrieved from the IDP ID via the {@link getSpidProductionIdpHint} function,
* for CIE the hint is always the same and it's defined in the {@link CIE_HINT_PROD} constant.
* @param idCtx the identification context which contains the mode and the IDP ID if the mode is SPID
* @returns the IDP hint to be provided to the {@link openAuthenticationSession} function
*/
const getIdpHint = (idCtx: IdentificationContext) => {
const isSpidMode = idCtx.mode === "spid";
Expand Down
Loading

0 comments on commit c8626c3

Please sign in to comment.