Skip to content

Commit

Permalink
[#IOPID-1900] add appinsights events for lollipop operations (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
arcogabbo authored Jun 13, 2024
1 parent 67dd10c commit 8e7186d
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 53 deletions.
5 changes: 5 additions & 0 deletions .changeset/tricky-wolves-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pagopa/io-session-manager": minor
---

Added appinsights event tracking on lollipop operations
1 change: 1 addition & 0 deletions apps/session-manager/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ export const newApp: (
expressLollipopMiddleware(
APIClients.fnLollipopAPIClient,
REDIS_CLIENT_SELECTOR,
appInsightsClient,
),
pipe(
toExpressHandler({
Expand Down
5 changes: 3 additions & 2 deletions apps/session-manager/src/controllers/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import { getRequestIDFromResponse } from "../utils/spid";
import { AssertionRef } from "../generated/backend/AssertionRef";
import { CreateNewProfileDependencies } from "../services/profile";
import { AccessToken } from "../generated/public/AccessToken";
import { AppInsightsDeps } from "../utils/appinsights";
import { SESSION_ID_LENGTH_BYTES, SESSION_TOKEN_LENGTH_BYTES } from "./session";
import { AuthenticationController } from ".";

Expand All @@ -94,14 +95,14 @@ export type AcsDependencies = RedisRepo.RedisRepositoryDeps &
FnLollipopRepo.LollipopApiDeps &
RevokeAssertionRefDeps &
CreateNewProfileDependencies &
NotificationsRepo.NotificationsueueDeps & {
NotificationsRepo.NotificationsueueDeps &
AppInsightsDeps & {
isLollipopEnabled: boolean;
getClientErrorRedirectionUrl: (
params: ClientErrorRedirectionUrlParams,
) => UrlFromString;
getClientProfileRedirectionUrl: (token: string) => UrlFromString;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
appInsightsTelemetryClient?: any;
allowedCieTestFiscalCodes: ReadonlyArray<FiscalCode>;
hasUserAgeLimitEnabled: boolean;
standardTokenDurationSecs: Second;
Expand Down
1 change: 0 additions & 1 deletion apps/session-manager/src/controllers/pagopa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { FnAppAPIRepositoryDeps } from "../repositories/fn-app-api";
import { WithExpressRequest } from "../utils/express";
import { WithUser } from "../utils/user";
import { ProfileService, RedisSessionStorageService } from "../services";
import { EmailAddress } from "../generated/backend/EmailAddress";
import { InitializedProfile } from "../generated/backend/InitializedProfile";
import { RedisRepositoryDeps } from "../repositories/redis";
import { PagoPAUser } from "../generated/pagopa/PagoPAUser";
Expand Down
12 changes: 5 additions & 7 deletions apps/session-manager/src/services/lollipop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { LcParams } from "../generated/lollipop-api/LcParams";
import { RedisRepositoryDeps } from "../repositories/redis";
import { ActivatedPubKey } from "../generated/lollipop-api/ActivatedPubKey";
import { AssertionTypeEnum } from "../generated/lollipop-api/AssertionType";
import { AppInsightsDeps } from "../utils/appinsights";
import { RedisSessionStorageService } from ".";

const LOLLIPOP_ERROR_EVENT_NAME = "lollipop.error.acs";
Expand Down Expand Up @@ -99,9 +100,8 @@ export const activateLolliPoPKey = (
fiscalCode: FiscalCode;
assertion: NonEmptyString;
getExpirePubKeyFn: IO<Date>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
appInsightsTelemetryClient?: any;
} & GenerateLCParamsDeps,
} & GenerateLCParamsDeps &
AppInsightsDeps,
): TE.TaskEither<Error, ActivatedPubKey> =>
pipe(
TE.tryCatch(
Expand Down Expand Up @@ -165,10 +165,8 @@ export const deleteAssertionRefAssociation: (
eventMessage: string,
) => RTE.ReaderTaskEither<
LollipopRevokeRepo.RevokeAssertionRefDeps &
RedisRepositoryDeps & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
appInsightsTelemetryClient?: any;
},
RedisRepositoryDeps &
AppInsightsDeps,
Error,
boolean
> = (fiscalCode, assertionRefToRevoke, eventName, eventMessage) => (deps) => {
Expand Down
4 changes: 4 additions & 0 deletions apps/session-manager/src/utils/appinsights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import {
import { hashFiscalCode } from "@pagopa/ts-commons/lib/hash";
import { User } from "../types/user";

export type AppInsightsDeps = {
appInsightsTelemetryClient?: appInsights.TelemetryClient;
};

const SESSION_TRACKING_ID_KEY = "session_tracking_id";
const USER_TRACKING_ID_KEY = "user_tracking_id";

Expand Down
133 changes: 90 additions & 43 deletions apps/session-manager/src/utils/lollipop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import {
import { NextFunction, Request, Response } from "express";
import { ulid } from "ulid";
import { Errors } from "io-ts";
import { sha256 } from "@pagopa/io-functions-commons/dist/src/utils/crypto";
import { withValidatedOrValidationError } from "../utils/responses";
import { NewPubKey } from "../generated/lollipop-api/NewPubKey";
import { FnLollipopRepo } from "../repositories";
import { FnLollipopRepo, RedisRepo } from "../repositories";
import { JwkPubKeyHashAlgorithmEnum } from "../generated/lollipop-api/JwkPubKeyHashAlgorithm";
import {
LollipopLocalsType,
Expand All @@ -54,11 +55,11 @@ import { generateLCParams } from "../services/lollipop";
import { AssertionRefSha384 } from "../generated/lollipop-api/AssertionRefSha384";
import { AssertionRefSha512 } from "../generated/lollipop-api/AssertionRefSha512";
import { DomainErrorTypes } from "../models/domain-errors";
import { RedisRepositoryDeps } from "../repositories/redis";
import { errorsToError } from "./errors";
import { ResLocals } from "./express";
import { withOptionalUserFromRequest } from "./user";
import { log } from "./logger";
import { AppInsightsDeps } from "./appinsights";

const getLoginErrorEventName = "lollipop.error.get-login";

Expand Down Expand Up @@ -141,6 +142,7 @@ export const lollipopLoginHandler =
properties: {
message: `Error calling reservePubKey endpoint: ${error.message}`,
},
tagOverrides: { samplingEnabled: "false" },
});
return error;
},
Expand All @@ -161,6 +163,7 @@ export const lollipopLoginHandler =
properties: {
message: `Error calling reservePubKey endpoint: ${e.message}`,
},
tagOverrides: { samplingEnabled: "false" },
});
return e;
},
Expand Down Expand Up @@ -208,6 +211,7 @@ export const lollipopLoginMiddleware =
appInsightsTelemetryClient,
)(req).then((_) => (LollipopLoginParams.is(_) ? undefined : _));

const LOLLIPOP_SIGN_ERROR_EVENT_NAME = "lollipop.error.sign";
const NONCE_REGEX = new RegExp(';?nonce="([^"]+)";?');
// Take the first occurrence of the field keyid into the signature-params
const KEY_ID_REGEX = new RegExp(';?keyid="([^"]+)";?');
Expand Down Expand Up @@ -255,27 +259,33 @@ const getKeyThumbprintFromSignature = (
const getAndValidateAssertionRefForUser =
(
fiscalCode: FiscalCode,
/* TODO: add event logging
* operationId: NonEmptyString,
*/
operationId: NonEmptyString,
keyThumbprint: Thumbprint,
): RTE.ReaderTaskEither<
{ redisClientSelector: RedisClientSelectorType },
RedisRepo.RedisRepositoryDeps & AppInsightsDeps,
IResponseErrorInternal | IResponseErrorForbiddenNotAuthorized,
AssertionRef
> =>
({ redisClientSelector }) =>
({ redisClientSelector, appInsightsTelemetryClient }) =>
pipe(
RedisSessionStorageService.getLollipopAssertionRefForUser({
redisClientSelector,
fiscalCode,
}),
// TODO: send error event if taskEither results to left
TE.mapLeft((err) => {
log.error(
"lollipopMiddleware|error reading the assertionRef from redis [%s]",
err.message,
);
appInsightsTelemetryClient?.trackEvent({
name: `An error occurs retrieving the assertion ref from Redis | ${err.message}`,
properties: {
fiscal_code: sha256(fiscalCode),
name: LOLLIPOP_SIGN_ERROR_EVENT_NAME,
operation_id: operationId,
},
tagOverrides: { samplingEnabled: "false" },
});
return ResponseErrorInternal("Error retrieving the assertionRef");
}),
TE.chainW(TE.fromOption(() => ResponseErrorForbiddenNotAuthorized)),
Expand All @@ -287,7 +297,18 @@ const getAndValidateAssertionRefForUser =
`${getAlgoFromAssertionRef(assertionRef)}-${keyThumbprint}`,
() => ResponseErrorForbiddenNotAuthorized,
),
// TODO: send error event if taskEither results to left
TE.mapLeft((error) => {
appInsightsTelemetryClient?.trackEvent({
name: `AssertionRef is different from stored one`,
properties: {
fiscal_code: sha256(fiscalCode),
name: LOLLIPOP_SIGN_ERROR_EVENT_NAME,
operation_id: operationId,
},
tagOverrides: { samplingEnabled: "false" },
});
return error;
}),
),
),
);
Expand Down Expand Up @@ -333,47 +354,57 @@ export const extractLollipopLocalsFromLollipopHeaders =
lollipopHeaders: LollipopRequiredHeaders,
fiscalCode?: FiscalCode,
): RTE.ReaderTaskEither<
FnLollipopRepo.LollipopApiDeps & RedisRepositoryDeps,
FnLollipopRepo.LollipopApiDeps &
RedisRepo.RedisRepositoryDeps &
AppInsightsDeps,
IResponseErrorInternal | IResponseErrorForbiddenNotAuthorized,
LollipopLocalsType
> =>
({ fnLollipopAPIClient, redisClientSelector }) =>
({ fnLollipopAPIClient, redisClientSelector, appInsightsTelemetryClient }) =>
pipe(
TE.of(getNonceOrUlid(lollipopHeaders["signature-input"])),
TE.bindTo("operationId"),
TE.bind("keyThumbprint", ({ operationId: _operationId }) =>
TE.bind("keyThumbprint", ({ operationId }) =>
pipe(
getKeyThumbprintFromSignature(lollipopHeaders["signature-input"]),
// TODO: send error event if either results to left
E.mapLeft(() =>
ResponseErrorInternal("Invalid assertionRef in signature params"),
),
E.mapLeft(() => {
appInsightsTelemetryClient?.trackEvent({
name: "AssertionRef in signature-input is missing or invalid",
properties: {
fiscal_code: fiscalCode ? sha256(fiscalCode) : undefined,
name: LOLLIPOP_SIGN_ERROR_EVENT_NAME,
operation_id: operationId,
},
tagOverrides: { samplingEnabled: "false" },
});
return ResponseErrorInternal(
"Invalid assertionRef in signature params",
);
}),
TE.fromEither,
),
),
TE.bind(
"assertionRefSet",
({ keyThumbprint, operationId: _operationId }) =>
pipe(
O.fromNullable(fiscalCode),
O.map((fc) =>
pipe(
getAndValidateAssertionRefForUser(
fc,
/* operationId, */
keyThumbprint,
)({ redisClientSelector }),
TE.map((assertionRef) => [assertionRef]),
),
),
O.getOrElse(() =>
TE.of([
`sha256-${keyThumbprint}` as AssertionRef,
`sha384-${keyThumbprint}` as AssertionRef,
`sha512-${keyThumbprint}` as AssertionRef,
]),
TE.bind("assertionRefSet", ({ keyThumbprint, operationId }) =>
pipe(
O.fromNullable(fiscalCode),
O.map((fc) =>
pipe(
getAndValidateAssertionRefForUser(
fc,
operationId,
keyThumbprint,
)({ redisClientSelector, appInsightsTelemetryClient }),
TE.map((assertionRef) => [assertionRef]),
),
),
O.getOrElse(() =>
TE.of([
`sha256-${keyThumbprint}` as AssertionRef,
`sha384-${keyThumbprint}` as AssertionRef,
`sha512-${keyThumbprint}` as AssertionRef,
]),
),
),
),
TE.bindW("lcParams", ({ assertionRefSet, operationId }) =>
pipe(
Expand Down Expand Up @@ -401,17 +432,17 @@ export const extractLollipopLocalsFromLollipopHeaders =
),
),
),
TE.chainFirst(({ operationId: _operationId, lcParams, keyThumbprint }) =>
TE.chainFirst(({ operationId, lcParams, keyThumbprint }) =>
pipe(
O.fromNullable(fiscalCode),
O.map(() => TE.of(true)),
O.getOrElse(() =>
pipe(
getAndValidateAssertionRefForUser(
lcParams.fiscal_code,
/* operationId, */
operationId,
keyThumbprint,
)({ redisClientSelector }),
)({ redisClientSelector, appInsightsTelemetryClient }),
TE.map(() => true),
),
),
Expand All @@ -430,14 +461,26 @@ export const extractLollipopLocalsFromLollipopHeaders =
...lollipopHeaders,
}) as LollipopLocalsType,
),
// TODO: info event of the sending data to the third party
TE.map((lcLocals) => {
appInsightsTelemetryClient?.trackEvent({
name: "Lollipop locals to be sent to third party api",
properties: {
...Object.keys(lcLocals),
name: "lollipop.locals.info",
},
tagOverrides: { samplingEnabled: "false" },
});
return lcLocals;
}),
);

export const expressLollipopMiddleware: (
lollipopApiClient: LollipopApiClient,
redisClientSelector: RedisClientSelectorType,
appInsightsTelemetryClient?: appInsights.TelemetryClient,
) => (req: Request, res: Response, next: NextFunction) => Promise<void> =
(fnLollipopAPIClient, redisClientSelector) => (req, res, next) =>
(fnLollipopAPIClient, redisClientSelector, appInsightsTelemetryClient) =>
(req, res, next) =>
pipe(
TE.tryCatch(
() =>
Expand All @@ -447,7 +490,11 @@ export const expressLollipopMiddleware: (
extractLollipopLocalsFromLollipopHeaders(
lollipopHeaders,
O.toUndefined(user)?.fiscal_code,
)({ fnLollipopAPIClient, redisClientSelector }),
)({
fnLollipopAPIClient,
redisClientSelector,
appInsightsTelemetryClient,
}),
TE.map((lollipopLocals) => {
// eslint-disable-next-line functional/immutable-data
res.locals = { ...res.locals, ...lollipopLocals };
Expand Down

0 comments on commit 8e7186d

Please sign in to comment.