diff --git a/packages/pn-personafisica-webapp/src/__mocks__/Auth.mock.ts b/packages/pn-personafisica-webapp/src/__mocks__/Auth.mock.ts index 70f5bedeea..c4f653b67d 100644 --- a/packages/pn-personafisica-webapp/src/__mocks__/Auth.mock.ts +++ b/packages/pn-personafisica-webapp/src/__mocks__/Auth.mock.ts @@ -9,7 +9,7 @@ import { store } from '../redux/store'; export const mockLogin = async (): Promise => { const mock = new MockAdapter(authClient); mock.onPost(AUTH_TOKEN_EXCHANGE()).reply(200, userResponse); - const action = store.dispatch(exchangeToken('mocked-token')); + const action = store.dispatch(exchangeToken({ spidToken: 'mocked-token' })); mock.reset(); mock.restore(); return action; @@ -23,7 +23,7 @@ export const mockAuthentication = () => { beforeAll(() => { mock = new MockAdapter(authClient); mock.onPost(AUTH_TOKEN_EXCHANGE()).reply(200, userResponse); - store.dispatch(exchangeToken('mocked-token')); + store.dispatch(exchangeToken({ spidToken: 'mocked-token' })); }); afterAll(() => { @@ -49,3 +49,9 @@ export const userResponse: User = { iss: 'https://spid-hub-test.dev.pn.pagopa.it', jti: 'mockedJTI004', }; + +export const userResponseWithRetrievalId: User = { + ...userResponse, + retrievalId: 'mocked-retrieval-id', + tppId: 'mocked-tpp-id', +}; \ No newline at end of file diff --git a/packages/pn-personafisica-webapp/src/api/auth/Auth.api.ts b/packages/pn-personafisica-webapp/src/api/auth/Auth.api.ts index 1211893b69..1bc2654702 100644 --- a/packages/pn-personafisica-webapp/src/api/auth/Auth.api.ts +++ b/packages/pn-personafisica-webapp/src/api/auth/Auth.api.ts @@ -1,24 +1,56 @@ -import { User } from "../../redux/auth/types"; -import { authClient } from "../apiClients"; -import { AUTH_TOKEN_EXCHANGE } from "./auth.routes"; +/* eslint-disable functional/immutable-data */ +import { AppRouteParams } from '@pagopa-pn/pn-commons'; + +import { User } from '../../redux/auth/types'; +import { authClient } from '../apiClients'; +import { AUTH_TOKEN_EXCHANGE } from './auth.routes'; + +interface TokenExchangeBody { + authorizationToken: string; + retrievalId?: string; + aarQRCodeValue?: string; +} + +export interface TokenExchangeRequest { + spidToken: string; + rapidAccess?: [AppRouteParams, string]; +} export const AuthApi = { - exchangeToken: (spidToken: string): Promise => - authClient.post(AUTH_TOKEN_EXCHANGE(), {authorizationToken: spidToken}) - .then((response) => ({ - sessionToken: response.data.sessionToken, - email: response.data.email, - name: response.data.name, - family_name: response.data.family_name, - uid: response.data.uid, - fiscal_number: response.data.fiscal_number, - mobile_phone: response.data.mobile_phone, - from_aa: response.data.from_aa, - aud: response.data.aud, - level: response.data.level, - iat: response.data.iat, - exp: response.data.exp, - iss: response.data.iss, - jti: response.data.jti - })) + exchangeToken: async ({ spidToken, rapidAccess }: TokenExchangeRequest): Promise => { + const body: TokenExchangeBody = { authorizationToken: spidToken }; + if (rapidAccess) { + const [param, value] = rapidAccess; + if (param === AppRouteParams.AAR) { + body.aarQRCodeValue = value; + } + if (param === AppRouteParams.RETRIEVAL_ID) { + body.retrievalId = value; + } + } + const response = await authClient.post(AUTH_TOKEN_EXCHANGE(), body); + const user: User = { + sessionToken: response.data.sessionToken, + email: response.data.email, + name: response.data.name, + family_name: response.data.family_name, + uid: response.data.uid, + fiscal_number: response.data.fiscal_number, + mobile_phone: response.data.mobile_phone, + from_aa: response.data.from_aa, + aud: response.data.aud, + level: response.data.level, + iat: response.data.iat, + exp: response.data.exp, + iss: response.data.iss, + jti: response.data.jti, + }; + if (response.data.retrievalId) { + user.retrievalId = response.data.retrievalId; + } + if (response.data.tppId) { + user.tppId = response.data.tppId; + } + return user; + }, }; diff --git a/packages/pn-personafisica-webapp/src/api/auth/__test__/Auth.api.test.ts b/packages/pn-personafisica-webapp/src/api/auth/__test__/Auth.api.test.ts index 034a64614a..b33a057f43 100644 --- a/packages/pn-personafisica-webapp/src/api/auth/__test__/Auth.api.test.ts +++ b/packages/pn-personafisica-webapp/src/api/auth/__test__/Auth.api.test.ts @@ -1,18 +1,40 @@ import MockAdapter from 'axios-mock-adapter'; -import { userResponse } from '../../../__mocks__/Auth.mock'; +import { AppRouteParams } from '@pagopa-pn/pn-commons'; + +import { userResponse, userResponseWithRetrievalId } from '../../../__mocks__/Auth.mock'; import { authClient } from '../../apiClients'; import { AuthApi } from '../Auth.api'; import { AUTH_TOKEN_EXCHANGE } from '../auth.routes'; describe('Auth api tests', () => { - it('exchangeToken', async () => { - const token = 'mocked-token'; - const mock = new MockAdapter(authClient); - mock.onPost(AUTH_TOKEN_EXCHANGE(), { authorizationToken: token }).reply(200, userResponse); - const res = await AuthApi.exchangeToken(token); - expect(res).toStrictEqual(userResponse); + + let mock: MockAdapter; + + beforeAll(() => { + mock = new MockAdapter(authClient); + }); + + afterEach(() => { mock.reset(); + }); + + afterAll(() => { mock.restore(); }); + + it('exchangeToken', async () => { + const spidToken = 'mocked-token'; + mock.onPost(AUTH_TOKEN_EXCHANGE(), { authorizationToken: spidToken }).reply(200, userResponse); + const res = await AuthApi.exchangeToken({ spidToken }); + expect(res).toStrictEqual(userResponse); + }); + + it('exchangeToken with rapidAccess', async () => { + const spidToken = 'mocked-token'; + const rapidAccess: [AppRouteParams, string] = [AppRouteParams.AAR, 'mocked-qr-code']; + mock.onPost(AUTH_TOKEN_EXCHANGE(), { authorizationToken: spidToken }).reply(200, userResponseWithRetrievalId); + const res = await AuthApi.exchangeToken({ spidToken, rapidAccess }); + expect(res).toStrictEqual(userResponseWithRetrievalId); + }); }); diff --git a/packages/pn-personafisica-webapp/src/navigation/RapidAccessGuard.tsx b/packages/pn-personafisica-webapp/src/navigation/RapidAccessGuard.tsx index 08d7e15093..5221e4f09f 100644 --- a/packages/pn-personafisica-webapp/src/navigation/RapidAccessGuard.tsx +++ b/packages/pn-personafisica-webapp/src/navigation/RapidAccessGuard.tsx @@ -68,7 +68,7 @@ const RapidAccessGuard = () => { path = GET_DETTAGLIO_NOTIFICA_PATH(retrievalPayload.originId!); } - PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_RAPID_ACCESS); + PFEventStrategyFactory.triggerEvent(PFEventsType.SEND_RAPID_ACCESS, { source: param }); const state: NotificationDetailRouteState = { source: param }; navigate(path, { diff --git a/packages/pn-personafisica-webapp/src/navigation/SessionGuard.tsx b/packages/pn-personafisica-webapp/src/navigation/SessionGuard.tsx index 27419e5fb3..3d93ecea73 100644 --- a/packages/pn-personafisica-webapp/src/navigation/SessionGuard.tsx +++ b/packages/pn-personafisica-webapp/src/navigation/SessionGuard.tsx @@ -122,6 +122,7 @@ const SessionGuard = () => { ); const dispatch = useAppDispatch(); const navigate = useNavigate(); + const rapidAccess = useRapidAccessParam(); const sessionCheck = useSessionCheck(200, () => dispatch(logout())); const { hasApiErrors, hasSpecificStatusError } = useErrors(); const { WORK_IN_PROGRESS } = getConfiguration(); @@ -157,7 +158,7 @@ const SessionGuard = () => { const spidToken = getTokenParam(); if (spidToken) { AppResponsePublisher.error.subscribe('exchangeToken', manageUnforbiddenError); - await dispatch(exchangeToken(spidToken)); + await dispatch(exchangeToken({ spidToken, rapidAccess })); } }; void performStep(INITIALIZATION_STEPS.USER_DETERMINATION, doUserDetermination); diff --git a/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx b/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx index 486f10139e..a50425006e 100644 --- a/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx +++ b/packages/pn-personafisica-webapp/src/pages/NotificationDetail.page.tsx @@ -34,7 +34,6 @@ import { useIsCancelled, useIsMobile, } from '@pagopa-pn/pn-commons'; -import { EventNotificationSource } from '@pagopa-pn/pn-commons/src/models/MixpanelEvents'; import DomicileBanner from '../components/DomicileBanner/DomicileBanner'; import LoadingPageWrapper from '../components/LoadingPageWrapper/LoadingPageWrapper'; @@ -64,18 +63,6 @@ export type NotificationDetailRouteState = { source?: AppRouteParams; // indicates whether the user arrived to the notification detail page from the QR code }; -const getEventNotificationSource = ( - source: AppRouteParams | undefined -): EventNotificationSource => { - if (source === AppRouteParams.AAR) { - return 'QRcode'; - } - if (source === AppRouteParams.RETRIEVAL_ID) { - return '3Papp'; - } - return 'LISTA_NOTIFICHE'; -}; - const NotificationDetail: React.FC = () => { const { id, mandateId } = useParams(); const location = useLocation(); @@ -455,7 +442,7 @@ const NotificationDetail: React.FC = () => { notificationStatus: notification.notificationStatus, checkIfUserHasPayments, userPayments, - source: getEventNotificationSource(rapidAccessSource), + source: rapidAccessSource, timeline: notification.timeline, } as NotificationData); diff --git a/packages/pn-personafisica-webapp/src/redux/auth/actions.ts b/packages/pn-personafisica-webapp/src/redux/auth/actions.ts index 3282fc4539..0d5b037ffd 100644 --- a/packages/pn-personafisica-webapp/src/redux/auth/actions.ts +++ b/packages/pn-personafisica-webapp/src/redux/auth/actions.ts @@ -2,7 +2,7 @@ import { ConsentType, TosPrivacyConsent, parseError } from '@pagopa-pn/pn-common import { createAsyncThunk } from '@reduxjs/toolkit'; import { apiClient } from '../../api/apiClients'; -import { AuthApi } from '../../api/auth/Auth.api'; +import { AuthApi, TokenExchangeRequest } from '../../api/auth/Auth.api'; import { BffTosPrivacyActionBody, UserConsentsApiFactory, @@ -18,11 +18,11 @@ export enum AUTH_ACTIONS { * Exchange token action between selfcare and pn. * If token is valid, user info are set in sessionStorage */ -export const exchangeToken = createAsyncThunk( +export const exchangeToken = createAsyncThunk( 'exchangeToken', - async (spidToken, { rejectWithValue }) => { + async (request: TokenExchangeRequest, { rejectWithValue }) => { try { - return await AuthApi.exchangeToken(spidToken); + return await AuthApi.exchangeToken(request); } catch (e: any) { return rejectWithValue(parseError(e)); } diff --git a/packages/pn-personafisica-webapp/src/redux/auth/reducers.ts b/packages/pn-personafisica-webapp/src/redux/auth/reducers.ts index 12ba6c96ab..ace6fc16b8 100644 --- a/packages/pn-personafisica-webapp/src/redux/auth/reducers.ts +++ b/packages/pn-personafisica-webapp/src/redux/auth/reducers.ts @@ -25,6 +25,8 @@ const userDataMatcher = yup iss: yup.string().url(), jti: yup.string().matches(dataRegex.lettersNumbersAndDashs), mobile_phone: yup.string().matches(dataRegex.phoneNumber), + retrievalId: yup.string().optional(), + tppId: yup.string().optional(), }) .noUnknown(true); diff --git a/packages/pn-personafisica-webapp/src/redux/auth/types.ts b/packages/pn-personafisica-webapp/src/redux/auth/types.ts index ef99d733a2..e95fccbdd6 100644 --- a/packages/pn-personafisica-webapp/src/redux/auth/types.ts +++ b/packages/pn-personafisica-webapp/src/redux/auth/types.ts @@ -14,4 +14,6 @@ export interface User extends BasicUser { exp: number; iss: string; jti: string; + retrievalId?: string; // TODO verificare il nome + tppId?: string; // TODO verificare il nome } diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts index aa8e0c499c..7844545fb9 100644 --- a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/SendNotificationDetailStrategy.ts @@ -1,4 +1,5 @@ import { + AppRouteParams, Downtime, EventAction, EventCategory, @@ -13,7 +14,7 @@ import { TimelineCategory, TrackedEvent, } from '@pagopa-pn/pn-commons'; -import { EventNotificationSource } from '@pagopa-pn/pn-commons/src/models/MixpanelEvents'; +import { appRouteParamToEventSource } from '../../notification.utility'; export type NotificationData = { downtimeEvents: Array; @@ -21,7 +22,7 @@ export type NotificationData = { notificationStatus: NotificationStatus; checkIfUserHasPayments: boolean; userPayments: { pagoPaF24: Array; f24Only: Array }; - source: EventNotificationSource; + source: AppRouteParams | undefined; timeline: Array; }; @@ -65,8 +66,8 @@ export class SendNotificationDetailStrategy implements EventStrategy { contains_f24: hasF24 ? 'yes' : 'no', first_time_opening: timeline.findIndex((el) => el.category === TimelineCategory.NOTIFICATION_VIEWED) === -1, - source, + source: appRouteParamToEventSource(source) || 'LISTA_NOTIFICHE', }, }; } -} +} \ No newline at end of file diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts index e03aed1484..d9f7bb65bd 100644 --- a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/TechStrategy.ts @@ -1,15 +1,24 @@ import { + AppRouteParams, EventCategory, EventPropertyType, EventStrategy, TrackedEvent, } from '@pagopa-pn/pn-commons'; +import { EventNotificationSource } from '@pagopa-pn/pn-commons/src/models/MixpanelEvents'; + +import { appRouteParamToEventSource } from '../../notification.utility'; + +type Tech = { + source?: EventNotificationSource; +}; export class TechStrategy implements EventStrategy { - performComputations(): TrackedEvent { + performComputations(data?: { source: AppRouteParams }): TrackedEvent { return { [EventPropertyType.TRACK]: { event_category: EventCategory.TECH, + source: appRouteParamToEventSource(data?.source), }, }; } diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts index ddfaf2a6d6..6869f0ce73 100644 --- a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/SendNotificationDetailStrategy.test.ts @@ -1,4 +1,5 @@ import { + AppRouteParams, DowntimeStatus, EventAction, EventCategory, @@ -32,7 +33,7 @@ describe('Mixpanel - Notification detail Strategy', () => { pagoPaF24: paymentsData.pagoPaF24, f24Only: paymentsData.f24Only, }, - source: 'QRcode', + source: AppRouteParams.AAR, timeline: timeline, }; diff --git a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts index 6d65d987eb..a277e7768f 100644 --- a/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts +++ b/packages/pn-personafisica-webapp/src/utility/MixpanelUtils/Strategies/__test__/TechStrategy.test.ts @@ -1,4 +1,4 @@ -import { EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; +import { AppRouteParams, EventCategory, EventPropertyType } from '@pagopa-pn/pn-commons'; import { TechStrategy } from '../TechStrategy'; @@ -13,4 +13,16 @@ describe('Mixpanel - Tech Strategy', () => { }, }); }); + + it('should return tech event with additional properties', () => { + const strategy = new TechStrategy(); + + const techEvent = strategy.performComputations({ source: AppRouteParams.AAR }); + expect(techEvent).toEqual({ + [EventPropertyType.TRACK]: { + event_category: EventCategory.TECH, + source: 'QRcode', + }, + }); + }); }); diff --git a/packages/pn-personafisica-webapp/src/utility/notification.utility.ts b/packages/pn-personafisica-webapp/src/utility/notification.utility.ts index 530673f3df..5be05e2d90 100644 --- a/packages/pn-personafisica-webapp/src/utility/notification.utility.ts +++ b/packages/pn-personafisica-webapp/src/utility/notification.utility.ts @@ -1,4 +1,5 @@ -import { NotificationDetail } from '@pagopa-pn/pn-commons'; +import { AppRouteParams, NotificationDetail } from '@pagopa-pn/pn-commons'; +import { EventNotificationSource } from '@pagopa-pn/pn-commons/src/models/MixpanelEvents'; import { NotificationDetailForRecipient } from '../models/NotificationDetail'; import { Delegator } from '../redux/delegation/types'; @@ -40,3 +41,15 @@ export function parseNotificationDetailForRecipient( currentRecipientIndex, }; } + +export const appRouteParamToEventSource = ( + param: AppRouteParams | undefined +): EventNotificationSource | undefined => { + if (param === AppRouteParams.AAR) { + return 'QRcode'; + } + if (param === AppRouteParams.RETRIEVAL_ID) { + return '3Papp'; + } + return undefined; +};