Repository: pagopa/pagopa-checkout-auth-service
Pull request #33: [CHK-3817] feat: add x-rpt-id header to all api
Author: login: scaminati-bv
Labels: name: size/small
name: patch
name: chart-patch
Created: 2025-03-19T09:24:27Z | Merged: 2025-03-26T10:17:20Z

<!--- 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
- update openapi to add `x-rpt-id` header
- change `x-rptId` to `x-rpt-id` into mdcFilter
- add empty `x-rpt-id` check on mdcFilter

<!--- Describe your changes in detail -->

#### Motivation and Context
The goal of this PR is to add `x-rpt-id` header to all api to add it in the mdc context.

<!--- Why is this change required? What problem does it solve? -->

#### 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. -->

#### Screenshots (if appropriate):

#### Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] 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! -->

- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.


Diff:
diff --git a/api-spec/v1/openapi.yaml b/api-spec/v1/openapi.yaml
index 660e0fcc..cb6694ee 100644
--- a/api-spec/v1/openapi.yaml
+++ b/api-spec/v1/openapi.yaml
@@ -25,9 +25,9 @@ paths:
       description: 'GET login endpoint with reCAPTCHA code'
       parameters:
         - in: header
-          name: x-rptid
+          name: x-rpt-id
           required: false
-          description: Optional RPTID for tracking login attempts
+          description: Optional RPT ID used to track login attempts to payment notices
           schema:
             type: string
       responses:
@@ -62,6 +62,14 @@ paths:
       operationId: authUsers
       summary: Get user information
       description: 'GET user information'
+      parameters:
+        - in: header
+          name: x-rpt-id
+          required: false
+          description: Optional RPT ID used to track get user information attempts to payment notices
+
+          schema:
+            type: string
       security:
         - bearerAuth: []
       responses:
@@ -102,6 +110,13 @@ paths:
       operationId: authLogout
       summary: Logout endpoint
       description: 'POST logout endpoint'
+      parameters:
+        - in: header
+          name: x-rpt-id
+          required: false
+          description: Optional RPT ID used to track logout attempts to payment notices
+          schema:
+            type: string
       security:
         - bearerAuth: []
       responses:
@@ -132,6 +147,13 @@ paths:
       operationId: authenticateWithAuthToken
       summary: Authentication endpoint
       description: 'POST authentication endpoint with auth code'
+      parameters:
+        - in: header
+          name: x-rpt-id
+          required: false
+          description: Optional RPT ID used to track auth token generation attempts to payment notices
+          schema:
+            type: string
       requestBody:
         $ref: '#/components/requestBodies/AuthRequest'
       responses:
@@ -178,6 +200,13 @@ paths:
       operationId: validateToken
       summary: Validate a token
       description: 'GET endpoint to validate a token'
+      parameters:
+        - in: header
+          name: x-rpt-id
+          required: false
+          description: Optional RPT ID used to track token validation attempts to payment notices
+          schema:
+            type: string
       security:
         - bearerAuth: []
       responses:
diff --git a/src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt b/src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt
index ea091f5b..fd59d8c9 100644
--- a/src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt
+++ b/src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt
@@ -30,7 +30,7 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
         xRptId: String?,
         exchange: ServerWebExchange?,
     ): Mono<ResponseEntity<LoginResponseDto>> {
-        logger.info("Received login request for rptId [{}]", xRptId)
+        logger.info("Received login request")
         return authenticationService.login().map { loginResponse ->
             ResponseEntity.ok(loginResponse)
         }
@@ -53,7 +53,11 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      * @formatter:on
      */
     @SuppressWarnings("kotlin:S6508")
-    override fun authLogout(exchange: ServerWebExchange): Mono<ResponseEntity<Void>> {
+    override fun authLogout(
+        xRptId: String?,
+        exchange: ServerWebExchange,
+    ): Mono<ResponseEntity<Void>> {
+        logger.info("Received logout request")
         return authenticationService.logout(exchange.request).map {
             ResponseEntity.noContent().build()
         }
@@ -66,7 +70,11 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      *   (status code 400) or Unauthorized (status code 401) or User not found (status code 404) or
      *   Internal server error (status code 500)
      */
-    override fun authUsers(exchange: ServerWebExchange): Mono<ResponseEntity<UserInfoResponseDto>> {
+    override fun authUsers(
+        xRptId: String?,
+        exchange: ServerWebExchange,
+    ): Mono<ResponseEntity<UserInfoResponseDto>> {
+        logger.info("Received authUsers request")
         return authenticationService.getUserInfo(exchange.request).map { userInfo ->
             ResponseEntity.ok(userInfo)
         }
@@ -82,6 +90,7 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      */
     override fun authenticateWithAuthToken(
         authRequestDto: Mono<AuthRequestDto>,
+        xRptId: String?,
         exchange: ServerWebExchange,
     ): Mono<ResponseEntity<AuthResponseDto>> =
         authRequestDto
@@ -110,7 +119,11 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      * @formatter:on
      */
     @SuppressWarnings("kotlin:S6508")
-    override fun validateToken(exchange: ServerWebExchange): Mono<ResponseEntity<Void>> {
+    override fun validateToken(
+        xRptId: String?,
+        exchange: ServerWebExchange,
+    ): Mono<ResponseEntity<Void>> {
+        logger.info("Received validate request")
         return authenticationService.validateAuthToken(exchange.request).map {
             ResponseEntity.ok().build()
         }
diff --git a/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt b/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
index 0bc6b5f0..29350f4c 100644
--- a/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
+++ b/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
@@ -11,7 +11,7 @@ class MDCFilter : WebFilter {
     private val utils = RequestTracingUtils()
 
     companion object {
-        const val HEADER_RPT_ID = "x-rptid"
+        const val HEADER_RPT_IDS = "x-rpt-id"
     }
 
     override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
@@ -21,13 +21,14 @@ class MDCFilter : WebFilter {
         val path = request.uri.path
 
         // extract header values
-        val rptId =
-            headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
+        val rptIds =
+            headers.getFirst(HEADER_RPT_IDS).takeIf { !it.isNullOrEmpty() }
+                ?: RequestTracingUtils.TracingEntry.RPT_IDS.defaultValue
 
         // create info
         val requestTracingInfo =
             RequestTracingUtils.RequestTracingInfo(
-                rptId = rptId,
+                rptIds = rptIds,
                 requestMethod = method,
                 requestUriPath = path,
             )
diff --git a/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/RequestTracingUtils.kt b/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/RequestTracingUtils.kt
index f7fbf549..f9c27aa0 100644
--- a/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/RequestTracingUtils.kt
+++ b/src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/RequestTracingUtils.kt
@@ -13,13 +13,13 @@ class RequestTracingUtils {
      * information are not taken from incoming request
      */
     enum class TracingEntry(val key: String, val defaultValue: String) {
-        RPT_ID("rptId", "{rptId-not-found}"),
+        RPT_IDS("rptIds", "{rpt-id-not-found}"),
         API_ID("apiId", "{api-id-not-found}"),
     }
 
     /** Request tracing info */
     data class RequestTracingInfo(
-        val rptId: String,
+        val rptIds: String,
         val requestMethod: String,
         val requestUriPath: String,
     )
@@ -39,11 +39,11 @@ class RequestTracingUtils {
         // starting context
         var context = reactorContext
 
-        // add RPT ID
+        // add RPT IDS
         context =
             putInReactorContextIfSetToDefault(
-                TracingEntry.RPT_ID,
-                requestTracingInfo.rptId,
+                TracingEntry.RPT_IDS,
+                requestTracingInfo.rptIds,
                 context,
             )
 
diff --git a/src/test/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginControllerTest.kt b/src/test/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginControllerTest.kt
index 64ddba67..63fd7eff 100644
--- a/src/test/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginControllerTest.kt
+++ b/src/test/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginControllerTest.kt
@@ -45,7 +45,27 @@ class AuthLoginControllerTest {
         webClient
             .get()
             .uri("/auth/login")
-            .header("127.0.0.1")
+            .header("x-rpt-id", "rpt-id-value")
+            .accept(MediaType.APPLICATION_JSON)
+            .exchange()
+            .expectStatus()
+            .is2xxSuccessful
+            .expectBody()
+            .jsonPath("$.urlRedirect")
+            .isEqualTo("https://mock.example.com/login?param=value")
+    }
+
+    @Test
+    fun `authLogin should return successful response when service returns login URL with empty x-rpt-id`() {
+        val loginResponse = LoginResponseDto()
+        loginResponse.urlRedirect = "https://mock.example.com/login?param=value"
+
+        whenever(authenticationService.login()).thenReturn(Mono.just(loginResponse))
+
+        webClient
+            .get()
+            .uri("/auth/login")
+            .header("x-rpt-id", "")
             .accept(MediaType.APPLICATION_JSON)
             .exchange()
             .expectStatus()


Reviews:
user: login: CianoDanilo
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T15:34:52Z
user: login: pietro-tota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:28:15Z
user: login: pietro-tota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:30:00Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:48:39Z
user: login: pietro-tota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:55:50Z
user: login: pietro-tota
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T16:57:31Z
user: login: CianoDanilo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T08:34:54Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T08:44:18Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T09:24:54Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T09:26:23Z
user: login: pietro-tota
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T10:17:02Z
user: login: CianoDanilo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T17:11:36Z
user: login: scaminati-bv
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:53:14Z
user: login: FrancescoDiTonnoJakala
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-25T13:25:29Z
user: login: CianoDanilo
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T14:29:31Z
user: login: pietro-tota
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:12:13Z

Review comments:
diff_hunk: @@ -62,6 +62,13 @@ paths:
       operationId: authUsers
       summary: Get user information
       description: 'GET user information'
+      parameters:
+        - in: header
+          name: x-rpt-id
+          required: false
+          description: Optional RPTID
path: api-spec/v1/openapi.yaml
user: login: pietro-tota
body: ```suggestion
          description: Optional RPT ID used to track login attempts to payment notices

```
please update other description fields too
created_at: 2025-03-21T16:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,7 @@ paths:
       description: 'GET login endpoint with reCAPTCHA code'
       parameters:
         - in: header
-          name: x-rptid
+          name: x-rpt-id
path: api-spec/v1/openapi.yaml
user: login: pietro-tota
body: ```suggestion
          name: x-rpt-ids

```
🚲  policy side we are populating this fields with a list of rpt ids (from 1 to 5) so a plural name will better fit here.
what do you think=
created_at: 2025-03-21T16:30:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,7 +25,7 @@ paths:
       description: 'GET login endpoint with reCAPTCHA code'
       parameters:
         - in: header
-          name: x-rptid
+          name: x-rpt-id
path: api-spec/v1/openapi.yaml
user: login: scaminati-bv
body: I agree with you. Done here 2dad9384292bba380ff425ce07508ecc11148f58
created_at: 2025-03-21T16:48:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,8 +21,8 @@ class MDCFilter : WebFilter {
         val path = request.uri.path
 
         // extract header values
-        val rptId =
-            headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
+        var rptId = headers.getFirst(HEADER_RPT_IDS)
+        if (rptId.isNullOrEmpty()) rptId = RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: pietro-tota
body: ```suggestion
        val rptIds = headers.getFirst(HEADER_RPT_IDS).let {
            if (!it.isNullOrEmpty()) {
                it
            } else {
                RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
            }
        }
```

🚲  less compact but produce a constant val instead of using a variable value that need to be smart cast later and that cannot be reassigned by error
created_at: 2025-03-21T16:55:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,7 @@ class MDCFilter : WebFilter {
     private val utils = RequestTracingUtils()
 
     companion object {
-        const val HEADER_RPT_ID = "x-rptid"
+        const val HEADER_RPT_IDS = "x-rpt-ids"
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: pietro-tota
body: Please update `RequestTracingUtils.TracingEntry.RPT_ID` to the plural version
created_at: 2025-03-21T16:57:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,8 +21,8 @@ class MDCFilter : WebFilter {
         val path = request.uri.path
 
         // extract header values
-        val rptId =
-            headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
+        var rptId = headers.getFirst(HEADER_RPT_IDS)
+        if (rptId.isNullOrEmpty()) rptId = RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: CianoDanilo
body: Or we can just go back to the previous implementation? 
(val rptId = headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue)
created_at: 2025-03-24T08:34:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,8 +21,8 @@ class MDCFilter : WebFilter {
         val path = request.uri.path
 
         // extract header values
-        val rptId =
-            headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
+        var rptId = headers.getFirst(HEADER_RPT_IDS)
+        if (rptId.isNullOrEmpty()) rptId = RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: scaminati-bv
body: @CianoDanilo I changed this implementation because both frontend and infra send empty header. With old code empty string is printed rather then default not found value.
created_at: 2025-03-24T08:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,8 +21,8 @@ class MDCFilter : WebFilter {
         val path = request.uri.path
 
         // extract header values
-        val rptId =
-            headers.getFirst(HEADER_RPT_ID) ?: RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
+        var rptId = headers.getFirst(HEADER_RPT_IDS)
+        if (rptId.isNullOrEmpty()) rptId = RequestTracingUtils.TracingEntry.RPT_ID.defaultValue
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: scaminati-bv
body: @pietro-tota I should have found a compact solution with elvis operator. :)
Done 6683074d65f2617e0606aaeff09d347668d9c4e1
created_at: 2025-03-24T09:24:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -11,7 +11,7 @@ class MDCFilter : WebFilter {
     private val utils = RequestTracingUtils()
 
     companion object {
-        const val HEADER_RPT_ID = "x-rptid"
+        const val HEADER_RPT_IDS = "x-rpt-ids"
path: src/main/kotlin/it/pagopa/checkout/authservice/mdcutilities/MDCFilter.kt
user: login: scaminati-bv
body: Done here 6683074d65f2617e0606aaeff09d347668d9c4e1 
created_at: 2025-03-24T09:26:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,7 +119,11 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      * @formatter:on
      */
     @SuppressWarnings("kotlin:S6508")
-    override fun validateToken(exchange: ServerWebExchange): Mono<ResponseEntity<Void>> {
+    override fun validateToken(
+        xRptIds: String?,
path: src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt
user: login: CianoDanilo
body: Is it ok to keep this variable name? Will Spring be able to inject the right value?
created_at: 2025-03-24T17:11:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -110,7 +119,11 @@ class AuthLoginController(@Autowired private val authenticationService: Authenti
      * @formatter:on
      */
     @SuppressWarnings("kotlin:S6508")
-    override fun validateToken(exchange: ServerWebExchange): Mono<ResponseEntity<Void>> {
+    override fun validateToken(
+        xRptIds: String?,
path: src/main/kotlin/it/pagopa/checkout/authservice/controllers/AuthLoginController.kt
user: login: scaminati-bv
body: Most likely not. Fixed here 94fdfc23f0808d23b8b27d57083c82b639b14474
created_at: 2025-03-25T09:53:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-03-25T09:55:46Z
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_pagopa-checkout-auth-service&pullRequest=33) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=pagopa_pagopa-checkout-auth-service&pullRequest=33&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_pagopa-checkout-auth-service&pullRequest=33&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_pagopa-checkout-auth-service&pullRequest=33&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_pagopa-checkout-auth-service&pullRequest=33&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_pagopa-checkout-auth-service&pullRequest=33&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=pagopa_pagopa-checkout-auth-service&pullRequest=33)


author_association: NONE