Repository: pagopa/io-auth-n-identity-domain
Pull request #269: [#IOPID-2877] Get Session fix
Author: login: salvatorelaiso
Created: 2025-03-28T08:32:04Z | Merged: 2025-04-07T12:19:34Z

<!--- Please always add a PR description as if nobody knows anything about the context these changes come from. -->
<!--- Even if we are all from our internal team, we may not be on the same page. -->
<!--- Write this PR as you were contributing to a public OSS project, where nobody knows you and you have to earn their trust. -->
<!--- This will improve our projects in the long run! Thanks. -->


#### List of Changes
<!--- Describe your changes in detail -->
- Update get session method to fallback on the expiration of the standard login

#### Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
When performing a login without lollipop key (e.g. emulated device) the get session method fails.

#### How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->
- Unit tests
- Locally tested with container

#### Screenshots (if appropriate):

#### Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Chore (nothing changes by a user perspective)
- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

#### Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My change requires a changeset, which I've added.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.



Diff:
diff --git a/.changeset/thirty-balloons-relate.md b/.changeset/thirty-balloons-relate.md
new file mode 100644
index 000000000..ababd5139
--- /dev/null
+++ b/.changeset/thirty-balloons-relate.md
@@ -0,0 +1,5 @@
+---
+"io-session-manager": patch
+---
+
+Get session state fallback on standard login expiration date
diff --git a/apps/io-session-manager/src/controllers/__tests__/session.spec.ts b/apps/io-session-manager/src/controllers/__tests__/session.spec.ts
index 49bc71a33..0f0b0df3d 100644
--- a/apps/io-session-manager/src/controllers/__tests__/session.spec.ts
+++ b/apps/io-session-manager/src/controllers/__tests__/session.spec.ts
@@ -8,6 +8,7 @@ import * as E from "fp-ts/Either";
 import * as O from "fp-ts/Option";
 import { ResponseSuccessJson } from "@pagopa/ts-commons/lib/responses";
 import { addSeconds } from "date-fns";
+import { withoutUndefinedValues } from "@pagopa/ts-commons/lib/types";
 import mockRes from "../../__mocks__/response.mocks";
 import {
   mockedInitializedProfile,
@@ -33,7 +34,6 @@ import {
 import { toExpectedResponse } from "../../__tests__/utils";
 import { RedisSessionStorageService, TokenService } from "../../services";
 import { UserIdentityWithTtl } from "../../generated/introspection/UserIdentityWithTtl";
-import { withoutUndefinedValues } from "@pagopa/ts-commons/lib/types";
 
 vi.setSystemTime(new Date(2025, 0, 1));
 
@@ -67,14 +67,27 @@ describe("getSessionState", () => {
     .spyOn(TokenService, "getNewTokenAsync")
     .mockResolvedValue(aZendeskSuffix);
 
-  const ttl = 1800;
+  const ttlStandard = 3600;
+  const mockGetSessionTtl = vi.spyOn(
+    RedisSessionStorageService,
+    "getSessionTtl",
+  );
+  mockGetSessionTtl.mockReturnValue(() => TE.right(ttlStandard));
+  const expectedExpirationDateStandard = addSeconds(
+    new Date(),
+    ttlStandard,
+  ).toISOString();
+
+  const ttlFast = 1800;
   const mockGetSessionRemainingTtlFast = vi.spyOn(
     RedisSessionStorageService,
     "getSessionRemainingTtlFast",
   );
-  mockGetSessionRemainingTtlFast.mockReturnValue(TE.right(ttl));
-
-  const expectedExpirationDate = addSeconds(new Date(), ttl).toISOString();
+  mockGetSessionRemainingTtlFast.mockReturnValue(TE.right(O.some(ttlFast)));
+  const expectedExpirationDateFast = addSeconds(
+    new Date(),
+    ttlFast,
+  ).toISOString();
 
   test("GIVEN a valid request WHEN lollipop is initialized for the user THEN it should return a correct session state", async () => {
     mockGet.mockResolvedValueOnce(anAssertionRef);
@@ -92,6 +105,7 @@ describe("getSessionState", () => {
     )();
 
     expect(mockGet).toBeCalledWith(`KEYS-${mockedUser.fiscal_code}`);
+    expect(mockGetSessionTtl).not.toHaveBeenCalled();
     // mockedInitializedProfile has the email validated, a new zendesksuffix
     // would not be created
     expect(mockGetNewTokenAsync).not.toHaveBeenCalled();
@@ -104,12 +118,13 @@ describe("getSessionState", () => {
       walletToken: mockedUser.wallet_token,
       zendeskToken: `${mockedUser.zendesk_token}${zendeskSuffixForCorrectlyRetrievedProfile}`,
       lollipopAssertionRef: anAssertionRef,
-      expirationDate: expectedExpirationDate,
+      expirationDate: expectedExpirationDateFast,
     });
   });
 
   test("GIVEN a valid request WHEN lollipop is NOT initialized for the user THEN it should return a correct session state", async () => {
     mockGet.mockResolvedValueOnce(null);
+    mockGetSessionRemainingTtlFast.mockReturnValueOnce(TE.right(O.none));
 
     await pipe(
       {
@@ -124,6 +139,7 @@ describe("getSessionState", () => {
     )();
 
     expect(mockGet).toBeCalledWith(`KEYS-${mockedUser.fiscal_code}`);
+    expect(mockGetSessionTtl).toHaveBeenCalledOnce();
     // mockedInitializedProfile has the email validated, a new zendesksuffix
     // would not be created
     expect(mockGetNewTokenAsync).not.toHaveBeenCalled();
@@ -135,7 +151,7 @@ describe("getSessionState", () => {
       spidLevel: mockedUser.spid_level,
       walletToken: mockedUser.wallet_token,
       zendeskToken: `${mockedUser.zendesk_token}${zendeskSuffixForCorrectlyRetrievedProfile}`,
-      expirationDate: expectedExpirationDate,
+      expirationDate: expectedExpirationDateStandard,
     });
   });
 
@@ -167,10 +183,41 @@ describe("getSessionState", () => {
     );
   });
 
+  test("GIVEN a valid request WHEN the user logged without lollipop key THEN it should return a correct session state with the standard login TTL", async () => {
+    mockGet.mockResolvedValueOnce(null);
+    mockGetSessionRemainingTtlFast.mockReturnValueOnce(TE.right(O.none));
+
+    await pipe(
+      {
+        fnAppAPIClient: {} as ReturnType<typeof FnAppAPIClient>,
+        redisClientSelector: mockRedisClientSelector,
+        req,
+        user: mockedUser,
+      },
+      getSessionState,
+      TE.map((response) => response.apply(res)),
+      TE.mapLeft((err) => expect(err).toBeFalsy()),
+    )();
+
+    expect(mockGet).toBeCalledWith(`KEYS-${mockedUser.fiscal_code}`);
+    expect(mockGetSessionTtl).toHaveBeenCalledOnce();
+
+    expect(res.status).toHaveBeenCalledWith(200);
+    expect(res.json).toHaveBeenCalledWith({
+      bpdToken: mockedUser.bpd_token,
+      fimsToken: mockedUser.fims_token,
+      myPortalToken: mockedUser.myportal_token,
+      spidLevel: mockedUser.spid_level,
+      walletToken: mockedUser.wallet_token,
+      zendeskToken: `${mockedUser.zendesk_token}${zendeskSuffixForCorrectlyRetrievedProfile}`,
+      expirationDate: expectedExpirationDateStandard,
+    });
+  });
+
   test("GIVEN a valid request WHEN an error occurs retrieving the user profile THEN it should return a correct session state", async () => {
     const expectedError = new Error("Network Error");
     mockGetProfile.mockReturnValueOnce(TE.left(expectedError));
-    mockGet.mockResolvedValueOnce(null);
+    mockGet.mockResolvedValueOnce(anAssertionRef);
 
     await pipe(
       {
@@ -195,7 +242,8 @@ describe("getSessionState", () => {
       spidLevel: mockedUser.spid_level,
       walletToken: mockedUser.wallet_token,
       zendeskToken: `${mockedUser.zendesk_token}${aZendeskSuffix}`,
-      expirationDate: expectedExpirationDate,
+      lollipopAssertionRef: anAssertionRef,
+      expirationDate: expectedExpirationDateFast,
     });
   });
 
@@ -401,6 +449,46 @@ describe("getSessionState", () => {
       }),
     );
   });
+
+  test.each`
+    scenario             | invalidStandardSessionTtl
+    ${"does not exists"} | ${-2}
+    ${"has no TTL"}      | ${-1}
+  `(
+    "GIVEN a valid request WHEN a Redis key $scenario THEN it should return an error",
+    async ({ invalidStandardSessionTtl }) => {
+      const aValidFilterReq = mockReq({
+        query: { fields: "(expirationDate)" },
+      }) as unknown as Request;
+
+      mockGetSessionRemainingTtlFast.mockReturnValueOnce(TE.right(O.none));
+      mockGetSessionTtl.mockReturnValueOnce(() =>
+        TE.right(invalidStandardSessionTtl),
+      );
+
+      await pipe(
+        {
+          fnAppAPIClient: {} as ReturnType<typeof FnAppAPIClient>,
+          redisClientSelector: mockRedisClientSelector,
+          req: aValidFilterReq,
+          user: mockedUser,
+        },
+        getSessionState,
+        TE.map((response) => response.apply(res)),
+        TE.mapLeft((err) => expect(err).toBeFalsy()),
+      )();
+
+      expect(mockGetSessionRemainingTtlFast).toHaveBeenCalledTimes(1);
+      expect(mockGetSessionTtl).toHaveBeenCalledTimes(1);
+      expect(res.json).toHaveBeenCalledWith(
+        expect.objectContaining({
+          detail: `Error retrieving the session TTL: Standard session TTL is negative: ${invalidStandardSessionTtl}`,
+          status: 500,
+          title: "Internal server error",
+        }),
+      );
+    },
+  );
 });
 
 describe("logout", () => {
diff --git a/apps/io-session-manager/src/controllers/session.ts b/apps/io-session-manager/src/controllers/session.ts
index db54b82b9..aae675e8d 100644
--- a/apps/io-session-manager/src/controllers/session.ts
+++ b/apps/io-session-manager/src/controllers/session.ts
@@ -113,6 +113,30 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () =>
+          pipe(
+            RedisSessionStorageService.getSessionTtl(deps.user.session_token)(
+              deps,
+            ),
+            // The following is unnecessary since the user session should be valid
+            // and the TTL should be positive; however, we keep it for safety since
+            // the underlying `RedisSessionStorageService.getSessionTtl` does not
+            // handle such case.
+            TE.chain(
+              TE.fromPredicate(
+                (response) => response >= 0,
+                (response) =>
+                  Error(`Standard session TTL is negative: ${response}`),
+              ),
+            ),
+          ),
+        (ttl) => TE.right(ttl),
+      ),
+    ),
     TE.mapLeft((error) =>
       ResponseErrorInternal(
         `Error retrieving the session TTL: ${error.message}`,
diff --git a/apps/io-session-manager/src/services/__tests__/redis-session-storage.spec.ts b/apps/io-session-manager/src/services/__tests__/redis-session-storage.spec.ts
index 5581eec87..0673d9bb6 100644
--- a/apps/io-session-manager/src/services/__tests__/redis-session-storage.spec.ts
+++ b/apps/io-session-manager/src/services/__tests__/redis-session-storage.spec.ts
@@ -601,7 +601,7 @@ describe("RedisSessionStorage#getSessionRemainingTtlFast", () => {
     mockTtl.mockResolvedValueOnce(expectedTTL);
 
     const result = await getSessionRemainingTtlFast(mockedDependencies)();
-    expect(result).toEqual(E.right(expectedTTL));
+    expect(result).toEqual(E.right(O.some(expectedTTL)));
 
     expect(mockTtl).toHaveBeenCalledTimes(1);
     expect(mockTtl).toBeCalledWith(`KEYS-${aValidUser.fiscal_code}`);
@@ -632,13 +632,11 @@ describe("RedisSessionStorage#getSessionRemainingTtlFast", () => {
     expect(mockTtl).toBeCalledWith(`KEYS-${aValidUser.fiscal_code}`);
   });
 
-  test("should fail on Redis special value '-2'", async () => {
+  test("should received Option none on Redis special value '-2'", async () => {
     mockTtl.mockResolvedValueOnce(-2);
 
     const result = await getSessionRemainingTtlFast(mockedDependencies)();
-    expect(result).toEqual(
-      E.left(Error(`${errorPrefix} -2 (key does not exist)`)),
-    );
+    expect(result).toEqual(E.right(O.none));
 
     expect(mockTtl).toHaveBeenCalledTimes(1);
     expect(mockTtl).toBeCalledWith(`KEYS-${aValidUser.fiscal_code}`);
diff --git a/apps/io-session-manager/src/services/redis-session-storage.ts b/apps/io-session-manager/src/services/redis-session-storage.ts
index 251fe490f..354a868d4 100644
--- a/apps/io-session-manager/src/services/redis-session-storage.ts
+++ b/apps/io-session-manager/src/services/redis-session-storage.ts
@@ -43,6 +43,9 @@ export const DEFAULT_LOLLIPOP_ASSERTION_REF_DURATION = (3600 *
   365 *
   2) as Second; // 2y default assertionRef duration on redis cache
 
+const REDIS_KEY_NOT_FOUND = -2;
+const REDIS_KEY_NO_EXPIRE = -1;
+
 const parseUser = (value: string): E.Either<Error, User> =>
   pipe(
     E.parseJSON(value, E.toError),
@@ -915,15 +918,17 @@ export const getSessionTtl: (
     );
 
 /**
- * Return the session remaining time to live in seconds
+ * Return the session remaining time to live in seconds if the Lollipop data exists,
+ * otherwise return an O.none
  *
  * @param fiscalCode
- * @returns The key ttl in seconds or an error if the key doesn't exist or has no expire
+ * @returns The key ttl in seconds or an O.none if the key doesn't exist
+ * or an error if the key has no expire
  */
 export const getSessionRemainingTtlFast: RTE.ReaderTaskEither<
   RedisRepo.RedisRepositoryDeps & { fiscalCode: FiscalCode },
   Error,
-  number
+  O.Option<number>
 > = (deps) =>
   pipe(
     TE.tryCatch(
@@ -936,19 +941,29 @@ export const getSessionRemainingTtlFast: RTE.ReaderTaskEither<
     TE.mapLeft((error) =>
       Error(`Error retrieving the session TTL: ${error.message}`),
     ),
-    TE.chain((ttl) =>
-      ttl === -2
-        ? TE.left(
-            Error("Error retrieving the session TTL: -2 (key does not exist)"),
-          )
-        : ttl === -1
-          ? TE.left(
-              Error(
-                "Error retrieving the session TTL: -1 (key exists but has no associated expire)",
+    TE.chain((ttl) => {
+      switch (ttl) {
+        case REDIS_KEY_NOT_FOUND:
+          return pipe(
+            TE.fromIO(() =>
+              log.warn(
+                "Error retrieving the session TTL: -2 (key does not exist)",
               ),
-            )
-          : TE.right(ttl),
-    ),
+            ),
+            TE.chain(() => TE.right(O.none)),
+          );
+
+        case REDIS_KEY_NO_EXPIRE:
+          return TE.left(
+            new Error(
+              "Error retrieving the session TTL: -1 (key exists but has no associated expire)",
+            ),
+          );
+
+        default:
+          return TE.right(O.some(ttl));
+      }
+    }),
   );
 
 /**


Reviews:
user: login: arcogabbo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T09:03:13Z
user: login: giamma1295
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T09:12:48Z
user: login: giamma1295
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T09:20:18Z
user: login: giamma1295
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T09:20:58Z
user: login: salvatorelaiso
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T09:59:28Z
user: login: salvatorelaiso
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T09:59:52Z
user: login: salvatorelaiso
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-07T10:19:44Z
user: login: arcogabbo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T10:22:10Z
user: login: arcogabbo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T10:37:03Z
user: login: giamma1295
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T10:41:08Z
user: login: giamma1295
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-07T10:41:19Z

Review comments:
diff_hunk: @@ -113,6 +114,14 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () => getSessionTtl(deps.user.session_token)(deps),
path: apps/io-session-manager/src/controllers/session.ts
user: login: arcogabbo
body: here we are not checking for negative numbers. for -2 however is not a valid use case. if the key doesn't exist this endpoint handler couldn't be reached giving the user `401`.

but for the TTL not existing on key you can still get an incoherent output.

for example, a test like this should pass instead of giving an incoherent Date in output:
```ts
  test("should give error when requesting an expirationDate for a key that doesn't have TTL", async () => {
    const aValidFilterReq = mockReq({
      query: { fields: "(expirationDate)" },
    }) as unknown as Request;

    // returning -1 on fallback
    mockGetSessionRemainingTtlFast.mockReturnValueOnce(TE.right(O.none));
    mockGetSessionTtl.mockReturnValueOnce(() => TE.right(-1));

    await pipe(
      {
        fnAppAPIClient: {} as ReturnType<typeof FnAppAPIClient>,
        redisClientSelector: mockRedisClientSelector,
        req: aValidFilterReq,
        user: mockedUser,
      },
      getSessionState,
      TE.map((response) => response.apply(res)),
      TE.mapLeft((err) => expect(err).toBeFalsy()),
    )();

    expect(mockGetSessionRemainingTtlFast).toHaveBeenCalledTimes(1);
    expect(mockGetSessionTtl).toHaveBeenCalledTimes(1);
    expect(res.json).toHaveBeenCalledWith(
      expect.objectContaining({
        detail: `Error`,
        status: 500,
        title: "Internal server error",
      }),
    );
  });

```
created_at: 2025-04-07T08:06:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +114,14 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () => getSessionTtl(deps.user.session_token)(deps),
path: apps/io-session-manager/src/controllers/session.ts
user: login: arcogabbo
body: I would also suggest to use this notation. we should in the future give a best practice about this and refactor methods accordingly
```suggestion
        () =>
          RedisSessionStorageService.getSessionTtl(deps.user.session_token)(
            deps,
          ),
```
created_at: 2025-04-07T08:10:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -937,17 +941,22 @@ export const getSessionRemainingTtlFast: RTE.ReaderTaskEither<
       Error(`Error retrieving the session TTL: ${error.message}`),
     ),
     TE.chain((ttl) =>
-      ttl === -2
-        ? TE.left(
-            Error("Error retrieving the session TTL: -2 (key does not exist)"),
+      ttl === REDIS_KEY_NOT_FOUND
+        ? pipe(
+            TE.fromIO(() =>
+              log.warn(
+                "Error retrieving the session TTL: -2 (key does not exist)",
+              ),
+            ),
+            TE.chain(() => TE.right<Error, O.Option<number>>(O.none)),
           )
-        : ttl === -1
+        : ttl === REDIS_KEY_NO_EXPIRE
           ? TE.left(
-              Error(
+              new Error(
                 "Error retrieving the session TTL: -1 (key exists but has no associated expire)",
               ),
             )
path: apps/io-session-manager/src/services/redis-session-storage.ts
user: login: giamma1295
body: nit-pick(non blocking), can we remove this nested ternary to improve code readability?
created_at: 2025-04-07T09:12:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +114,14 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () => getSessionTtl(deps.user.session_token)(deps),
path: apps/io-session-manager/src/controllers/session.ts
user: login: giamma1295
body: Correct, pay attention editing the `getSessionTtl` function cause it's used in `setPagoPaNoticeEmail` which make the @arcogabbo suggested check!
created_at: 2025-04-07T09:20:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -914,16 +914,20 @@ export const getSessionTtl: (
       E.toError,
     );
 
+const REDIS_KEY_NOT_FOUND = -2;
+const REDIS_KEY_NO_EXPIRE = -1;
path: apps/io-session-manager/src/services/redis-session-storage.ts
user: login: giamma1295
body: nit-pick: constants should be placed on top of a module!
created_at: 2025-04-07T09:20:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +114,14 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () => getSessionTtl(deps.user.session_token)(deps),
path: apps/io-session-manager/src/controllers/session.ts
user: login: salvatorelaiso
body: Adding [1c40cd4](https://github.com/pagopa/io-auth-n-identity-domain/pull/269/commits/1c40cd4c5adc4b9e91f64db5141076726bbf0f6d), [8b68f0a](https://github.com/pagopa/io-auth-n-identity-domain/pull/269/commits/8b68f0a46974a01d6ba1eafaa75999a342ef2421)

@giamma1295 with this change, we are not touching the existing function at all, just adding an extra check in `getSessionExpirationDate` which is only used in `getSessionState`.
created_at: 2025-04-07T09:59:28Z
author_association: COLLABORATOR
diff_hunk: @@ -914,16 +914,20 @@ export const getSessionTtl: (
       E.toError,
     );
 
+const REDIS_KEY_NOT_FOUND = -2;
+const REDIS_KEY_NO_EXPIRE = -1;
path: apps/io-session-manager/src/services/redis-session-storage.ts
user: login: salvatorelaiso
body: Resolved in [1c40cd4](https://github.com/pagopa/io-auth-n-identity-domain/pull/269/commits/1c40cd4c5adc4b9e91f64db5141076726bbf0f6d)
created_at: 2025-04-07T09:59:52Z
author_association: COLLABORATOR
diff_hunk: @@ -937,17 +941,22 @@ export const getSessionRemainingTtlFast: RTE.ReaderTaskEither<
       Error(`Error retrieving the session TTL: ${error.message}`),
     ),
     TE.chain((ttl) =>
-      ttl === -2
-        ? TE.left(
-            Error("Error retrieving the session TTL: -2 (key does not exist)"),
+      ttl === REDIS_KEY_NOT_FOUND
+        ? pipe(
+            TE.fromIO(() =>
+              log.warn(
+                "Error retrieving the session TTL: -2 (key does not exist)",
+              ),
+            ),
+            TE.chain(() => TE.right<Error, O.Option<number>>(O.none)),
           )
-        : ttl === -1
+        : ttl === REDIS_KEY_NO_EXPIRE
           ? TE.left(
-              Error(
+              new Error(
                 "Error retrieving the session TTL: -1 (key exists but has no associated expire)",
               ),
             )
path: apps/io-session-manager/src/services/redis-session-storage.ts
user: login: salvatorelaiso
body: What do you think about the approach in [8f4207c](https://github.com/pagopa/io-auth-n-identity-domain/pull/269/commits/8f4207c9a074fb396c0ff74c30e7f8a9780262d8)?

https://github.com/pagopa/io-auth-n-identity-domain/blob/8f4207c9a074fb396c0ff74c30e7f8a9780262d8/apps/io-session-manager/src/services/redis-session-storage.ts#L944-L965

created_at: 2025-04-07T10:19:43Z
author_association: COLLABORATOR
diff_hunk: @@ -113,6 +114,14 @@ const getSessionExpirationDate: RTE.ReaderTaskEither<
       ...deps,
       fiscalCode: deps.user.fiscal_code,
     }),
+    TE.chainW(
+      O.fold(
+        // If getSessionRemainingTtlFast returns None, we fallback to getSessionTtl
+        // i.e. when the Lollipop assertionRef is not found
+        () => getSessionTtl(deps.user.session_token)(deps),
path: apps/io-session-manager/src/controllers/session.ts
user: login: arcogabbo
body: @salvatorelaiso tagged change is what I meant with my suggestion.
created_at: 2025-04-07T10:22:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -937,17 +941,22 @@ export const getSessionRemainingTtlFast: RTE.ReaderTaskEither<
       Error(`Error retrieving the session TTL: ${error.message}`),
     ),
     TE.chain((ttl) =>
-      ttl === -2
-        ? TE.left(
-            Error("Error retrieving the session TTL: -2 (key does not exist)"),
+      ttl === REDIS_KEY_NOT_FOUND
+        ? pipe(
+            TE.fromIO(() =>
+              log.warn(
+                "Error retrieving the session TTL: -2 (key does not exist)",
+              ),
+            ),
+            TE.chain(() => TE.right<Error, O.Option<number>>(O.none)),
           )
-        : ttl === -1
+        : ttl === REDIS_KEY_NO_EXPIRE
           ? TE.left(
-              Error(
+              new Error(
                 "Error retrieving the session TTL: -1 (key exists but has no associated expire)",
               ),
             )
path: apps/io-session-manager/src/services/redis-session-storage.ts
user: login: giamma1295
body: perfect!
created_at: 2025-04-07T10:41:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: changeset-bot[bot]
created_at: 2025-03-28T08:32:09Z
body: ###  🦋  Changeset detected

Latest commit: 8f4207c9a074fb396c0ff74c30e7f8a9780262d8

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name               | Type  |
| ------------------ | ----- |
| io-session-manager | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/pagopa/io-auth-n-identity-domain/new/IOPID-2877?filename=.changeset/nasty-gorillas-cheat.md&value=---%0A%22io-session-manager%22%3A%20patch%0A---%0A%0A%5B%23IOPID-2877%5D%20Get%20Session%20fix%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-28T08:32:20Z
body: ## Jira Pull Request Link ##
This Pull Request refers to the following Jira issue [IOPID-2877](https://pagopa.atlassian.net/browse/IOPID-2877) <br>
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-04-02T11:16:38Z
body: Please retry analysis of this Pull-Request directly on SonarQube Cloud
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-04-07T10:19:53Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269) **Quality Gate passed for 'Session Manager'**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=pagopa_io-auth-n-identity-domain_session-manager&pullRequest=269)


author_association: NONE