Skip to content

Task#0000 To Make User Data Anonymized - #792

Open
Tusharmahajan12 wants to merge 11 commits into
tekdi:aspire-leadersfrom
Tusharmahajan12:new_aspjuly3
Open

Task#0000 To Make User Data Anonymized#792
Tusharmahajan12 wants to merge 11 commits into
tekdi:aspire-leadersfrom
Tusharmahajan12:new_aspjuly3

Conversation

@Tusharmahajan12

@Tusharmahajan12 Tusharmahajan12 commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added an authenticated endpoint for submitting user anonymization requests.
    • Supports batches of up to 100 validated email addresses with a required reason.
    • Removes personal information, archives accounts, invalidates active sessions, and synchronizes updates across connected services.
    • Improved detection of personal-information fields, including addresses, postal codes, and mobile numbers.
    • Added clear results for successful, missing, and previously anonymized accounts.
  • Bug Fixes
    • Added idempotency and partial-failure reporting.
    • Archived accounts can no longer log in.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This change adds an authenticated batch anonymization endpoint. It validates up to 100 email addresses, processes them with bounded concurrency, updates Keycloak and PostgreSQL, clears PII, synchronizes Elasticsearch, and reports per-email outcomes.

Changes

User anonymization

Layer / File(s) Summary
Request contract and endpoint
src/user/dto/user-anonymize.dto.ts, src/user/user.controller.ts, src/adapters/userservicelocator.ts, src/common/utils/api-id.config.ts, src/common/utils/response.messages.ts
Defines validated email and reason fields. Adds the authenticated POST /anonymize route, service contract, API identifier, and response messages.
Keycloak and PII utilities
src/common/utils/pii-fields.constant.ts, src/common/utils/keycloak.adapter.util.ts
Adds normalized PII field matching, configurable account state updates, and Keycloak session logout with structured responses.
Batch processing and account lifecycle
src/adapters/postgres/user-adapter.ts, src/auth/auth.service.ts
Deduplicates emails, processes up to five items concurrently, anonymizes matching users, clears PostgreSQL PII, synchronizes Elasticsearch, reports partial failures, and rejects archived users during login.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UserController
  participant PostgresUserService
  participant Keycloak
  participant PostgreSQL
  participant Elasticsearch
  UserController->>PostgresUserService: Submit validated emails and reason
  PostgresUserService->>Keycloak: Obtain shared token
  PostgresUserService->>Keycloak: Replace identity and invalidate sessions
  PostgresUserService->>PostgreSQL: Archive user and clear PII
  PostgresUserService->>Elasticsearch: Synchronize anonymized fields
  PostgresUserService-->>UserController: Return ordered per-email outcomes
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies user data anonymization, which is the primary change in the pull request.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (2)
src/user/dto/user-anonymize.dto.ts (1)

24-30: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Bound the length of reason.

reason is persisted to User.reason. That column is declared as @Column({ nullable: true }), which maps to a bounded varchar in Postgres. A long reason then fails at the database layer, after Keycloak and session invalidation already ran. Add @MaxLength to reject the value during validation.

♻️ Proposed refactor
-  `@IsString`()
-  `@IsNotEmpty`()
-  reason: string;
+  `@IsString`()
+  `@IsNotEmpty`()
+  `@MaxLength`(255)
+  reason: string;

Add MaxLength to the class-validator import list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/user/dto/user-anonymize.dto.ts` around lines 24 - 30, Update the reason
field in the anonymization DTO with a class-validator `@MaxLength` constraint
matching the User.reason database limit, and add MaxLength to the existing
class-validator imports so oversized values are rejected before persistence.
src/user/user.controller.ts (1)

322-322: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the response message constant.

Line 322 repeats the literal 'Users processed for anonymization.'. The same text now exists as API_RESPONSES.USER_ANONYMIZE_SUCCESSFULLY in src/common/utils/response.messages.ts, and PostgresUserService.anonymizeUsers returns that constant. Reference the constant so the documented description and the runtime message stay identical.

♻️ Proposed refactor
-  `@ApiOkResponse`({ description: 'Users processed for anonymization.' })
+  `@ApiOkResponse`({ description: API_RESPONSES.USER_ANONYMIZE_SUCCESSFULLY })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/user/user.controller.ts` at line 322, Update the `@ApiOkResponse` decorator
in the user controller to use API_RESPONSES.USER_ANONYMIZE_SUCCESSFULLY instead
of the duplicated literal, keeping the documented description aligned with
PostgresUserService.anonymizeUsers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/postgres/user-adapter.ts`:
- Around line 2972-2977: The documentation for anonymization must match the
Keycloak behavior: in src/adapters/postgres/user-adapter.ts lines 2972-2977,
remove “no session invalidation” and state that Keycloak sessions are
invalidated while the account’s enabled status remains unchanged; in
src/user/user.controller.ts lines 314-315, add session invalidation to the
Keycloak scope or remove the implementation detail and refer readers to the
adapter.
- Around line 3086-3102: The account-deletion flow currently updates Keycloak
before its Postgres writes, leaving systems inconsistent when database
operations fail. In the surrounding deletion method, reorder the operations so
the Postgres changes execute atomically in a transaction before
updateUserInKeyCloak; if retaining the current order, add compensation that
restores the Keycloak profile on Postgres failure and reports whether
restoration succeeded.
- Around line 3064-3071: Update the user lookup in the anonymization flow to use
the repository’s multi-row query instead of findOne, then iterate over every
matching user and aggregate each row’s outcome into the returned result.
Preserve the NOT_FOUND response when no users match, and ensure duplicate email
records cannot leave unanonymized PII behind.
- Around line 3117-3131: Update the account-anonymization flow around
updateBasicUserDetails to replace gender: null with a permitted User.gender enum
value, avoiding the non-null database constraint; also change the corresponding
gender value in the Elasticsearch payload so the index matches the persisted
user data.
- Around line 3008-3018: Bound concurrency in the batch around
anonymizeSingleUser instead of passing all emails directly to
Promise.allSettled. Process emails in sequential chunks with a small fixed batch
size, awaiting each chunk’s allSettled results before starting the next, while
preserving per-user failure isolation and collecting the existing settlement
outcomes.

In `@src/common/utils/keycloak.adapter.util.ts`:
- Around line 378-388: Set an explicit 10-second timeout on the Axios request
configuration used by the logout call, matching fetchNewToken. Also add the same
timeout to the existing configuration in updateUserInKeyCloak so both Keycloak
requests fail promptly when the service is unresponsive.

In `@src/common/utils/pii-fields.constant.ts`:
- Around line 11-30: Update isPiiCustomField and PII_CUSTOM_FIELD_NAME_PATTERNS
to normalize separator and casing differences so configured snake_case patterns
match camelCase names such as addressLine1 and postCode, while avoiding broad
substring matches such as mobile_notifications_opt_in and whatsapp_consent.
Replace the fixed pattern-only behavior with tenant-configurable exact-name or
explicitly bounded matching, preserving false for unrelated fields.

In `@src/user/dto/user-anonymize.dto.ts`:
- Around line 18-22: Normalize each value in the UserAnonymize DTO’s emails
field to lowercase during validation transformation, before
PostgresUserService.anonymizeSingleUser performs the exact email lookup.
Preserve the existing array and email validations so submitted mixed-case
addresses resolve to the lowercase values stored by saveUserToDatabase.

In `@src/user/user.controller.ts`:
- Around line 316-331: Update the anonymizeUsers route to apply RbacAuthGuard
before JwtAuthGuard, ensuring the RBAC policy rejects non-administrative roles
before anonymization executes; leave the handler and existing validation
unchanged.

---

Nitpick comments:
In `@src/user/dto/user-anonymize.dto.ts`:
- Around line 24-30: Update the reason field in the anonymization DTO with a
class-validator `@MaxLength` constraint matching the User.reason database limit,
and add MaxLength to the existing class-validator imports so oversized values
are rejected before persistence.

In `@src/user/user.controller.ts`:
- Line 322: Update the `@ApiOkResponse` decorator in the user controller to use
API_RESPONSES.USER_ANONYMIZE_SUCCESSFULLY instead of the duplicated literal,
keeping the documented description aligned with
PostgresUserService.anonymizeUsers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b23ecb49-8c5d-4a53-add4-62255377f463

📥 Commits

Reviewing files that changed from the base of the PR and between cb10b49 and 14b741e.

📒 Files selected for processing (8)
  • src/adapters/postgres/user-adapter.ts
  • src/adapters/userservicelocator.ts
  • src/common/utils/api-id.config.ts
  • src/common/utils/keycloak.adapter.util.ts
  • src/common/utils/pii-fields.constant.ts
  • src/common/utils/response.messages.ts
  • src/user/dto/user-anonymize.dto.ts
  • src/user/user.controller.ts

Comment thread src/adapters/postgres/user-adapter.ts
Comment thread src/adapters/postgres/user-adapter.ts Outdated
Comment thread src/adapters/postgres/user-adapter.ts Outdated
Comment thread src/adapters/postgres/user-adapter.ts
Comment thread src/adapters/postgres/user-adapter.ts Outdated
Comment thread src/common/utils/keycloak.adapter.util.ts
Comment thread src/common/utils/pii-fields.constant.ts Outdated
Comment thread src/user/dto/user-anonymize.dto.ts
Comment thread src/user/user.controller.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (2)
src/common/utils/pii-fields.constant.ts (2)

22-26: 🔒 Security & Privacy | 🟠 Major

Replace unordered token inclusion with bounded matching.

patternTokens.every((token) => tokens.has(token)) matches any field that contains a generic token. mobile_notifications_opt_in matches mobile, and address_verification_status matches address when the field type is missing or is not exactly lowercase "checkbox". The anonymizer can clear non-PII data.

Use exact normalized field names or explicit bounded combinations. Normalize fieldType before the set lookup when case variations are valid.

Also applies to: 40-54

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/utils/pii-fields.constant.ts` around lines 22 - 26, Update the PII
field-name matching logic around patternTokens, tokens, and NON_PII_FIELD_TYPES
to stop treating unordered token inclusion as a match. Use exact normalized
field names or explicitly bounded token combinations so names such as
mobile_notifications_opt_in and address_verification_status are not classified
as PII. Normalize fieldType before checking NON_PII_FIELD_TYPES, preserving the
checkbox exclusion regardless of valid casing.

1-20: 🔒 Security & Privacy | 🟠 Major

Split letter-digit boundaries in tokenize.

tokenize("addressLine1") returns ["address", "line1"], while the address_line pattern requires a line token. The detector therefore misses this PII field. tokenize("whatsApp") also returns ["whats", "app"], so a whatsapp pattern can be missed.

Split letter-digit boundaries and add normalized aliases for compound names. Add regression tests for addressLine1, postCode, and whatsApp.

Also applies to: 28-34

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/common/utils/pii-fields.constant.ts` around lines 1 - 20, Update tokenize
to split both letter-to-digit and digit-to-letter boundaries, then normalize
compound tokens so patterns such as address_line, post_code, and whatsapp match
addressLine1, postCode, and whatsApp. Extend PII_CUSTOM_FIELD_NAME_PATTERNS with
any required normalized aliases, and add regression tests covering all three
examples.
🧹 Nitpick comments (1)
src/adapters/postgres/user-adapter.ts (1)

3111-3115: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

One unexpected rejection discards the sibling rows' outcomes.

anonymizeUserRow returns status objects for handled failures, but updateUserInKeyCloak (line 3175) and logoutUserInKeyCloak (line 3195) run outside a try block. If either throws, Promise.all rejects immediately. The email then reports FAILED, even when other rows for the same email were anonymized. The response hides the partial state.

Use Promise.allSettled and map a rejection to a FAILED row result.

♻️ Proposed refactor
-    const rowResults = await Promise.all(
-      users.map((user) =>
-        this.anonymizeUserRow(user, reason, loggedInUserId, keycloakToken)
-      )
-    );
+    const settledRows = await Promise.allSettled(
+      users.map((user) =>
+        this.anonymizeUserRow(user, reason, loggedInUserId, keycloakToken)
+      )
+    );
+    const rowResults = settledRows.map((r) =>
+      r.status === 'fulfilled'
+        ? r.value
+        : {
+            status: 'FAILED',
+            message: r.reason?.message || 'Anonymization failed',
+          }
+    );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/adapters/postgres/user-adapter.ts` around lines 3111 - 3115, Update the
row aggregation around anonymizeUserRow to use Promise.allSettled instead of
Promise.all, mapping rejected promises to FAILED row results while preserving
fulfilled status objects. Ensure one rejection from updateUserInKeyCloak or
logoutUserInKeyCloak does not discard outcomes from sibling rows or hide partial
anonymization state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/adapters/postgres/user-adapter.ts`:
- Around line 3168-3169: Run Prettier on the affected user-adapter code,
including the added anonymization checks and blocks near the referenced symbols,
so quote style and line wrapping match repository formatting rules. Preserve the
existing logic and behavior while applying only formatter changes.
- Around line 3102-3109: Update anonymizeSingleUser to normalize the incoming
email to lowercase before calling usersRepository.find, matching
saveUserToDatabase's storage behavior. Use the normalized value for the lookup
while preserving the existing not-found response and anonymization flow.
- Around line 3211-3227: The anonymization update payload must clear all
remaining PII fields before marking the user archived. In the anonymization
method containing the shown User update, set district, state, address, pincode,
mobile_country_code, deviceId, and auto_tags to their sanitized empty or null
values, and ensure the corresponding Elasticsearch update uses the same values;
otherwise document an explicit permitted-retention reason for each field.

In `@src/user/dto/user-anonymize.dto.ts`:
- Line 2: Apply the repository formatter to the four reported string literals:
use double quotes for the class-transformer import and the "string" literal in
user-anonymize.dto.ts, and for the "checkbox" value and "$1_$2" replacement
string in pii-fields.constant.ts. Review each listed site for Google JavaScript
style and Node.js/NestJS formatting conformity; no other changes are needed.

---

Duplicate comments:
In `@src/common/utils/pii-fields.constant.ts`:
- Around line 22-26: Update the PII field-name matching logic around
patternTokens, tokens, and NON_PII_FIELD_TYPES to stop treating unordered token
inclusion as a match. Use exact normalized field names or explicitly bounded
token combinations so names such as mobile_notifications_opt_in and
address_verification_status are not classified as PII. Normalize fieldType
before checking NON_PII_FIELD_TYPES, preserving the checkbox exclusion
regardless of valid casing.
- Around line 1-20: Update tokenize to split both letter-to-digit and
digit-to-letter boundaries, then normalize compound tokens so patterns such as
address_line, post_code, and whatsapp match addressLine1, postCode, and
whatsApp. Extend PII_CUSTOM_FIELD_NAME_PATTERNS with any required normalized
aliases, and add regression tests covering all three examples.

---

Nitpick comments:
In `@src/adapters/postgres/user-adapter.ts`:
- Around line 3111-3115: Update the row aggregation around anonymizeUserRow to
use Promise.allSettled instead of Promise.all, mapping rejected promises to
FAILED row results while preserving fulfilled status objects. Ensure one
rejection from updateUserInKeyCloak or logoutUserInKeyCloak does not discard
outcomes from sibling rows or hide partial anonymization state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e5701a27-bf79-4d8a-bd67-8fb8fb39e69a

📥 Commits

Reviewing files that changed from the base of the PR and between 14b741e and 48cc7c7.

📒 Files selected for processing (3)
  • src/adapters/postgres/user-adapter.ts
  • src/common/utils/pii-fields.constant.ts
  • src/user/dto/user-anonymize.dto.ts

Comment thread src/adapters/postgres/user-adapter.ts Outdated
Comment thread src/adapters/postgres/user-adapter.ts Outdated
Comment thread src/adapters/postgres/user-adapter.ts
Comment thread src/user/dto/user-anonymize.dto.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/common/utils/pii-fields.constant.ts`:
- Around line 12-19: Update the PII_CUSTOM_FIELD_NAME_PATTERNS list to include
the "whatsapp" pattern, ensuring isPiiCustomField anonymizes text fields such as
whatsapp_number and whatsappPhone while the existing checkbox exclusion for
whatsapp_consent remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8d43429c-4e42-40ec-8a0d-8c86ff42e610

📥 Commits

Reviewing files that changed from the base of the PR and between 48cc7c7 and b313026.

📒 Files selected for processing (3)
  • src/adapters/postgres/user-adapter.ts
  • src/common/utils/pii-fields.constant.ts
  • src/user/dto/user-anonymize.dto.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/user/dto/user-anonymize.dto.ts
  • src/adapters/postgres/user-adapter.ts

Comment thread src/common/utils/pii-fields.constant.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/auth/auth.service.ts`:
- Around line 71-78: Run the configured formatter on the changed conditional and
error-message block in the authentication service, including the strings used to
build errorMessage, so it follows the repository’s double-quote style and clears
the Prettier lint errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d79f363-8cab-49de-9be4-6bf4bc157e97

📥 Commits

Reviewing files that changed from the base of the PR and between b313026 and 9c46d7e.

📒 Files selected for processing (1)
  • src/auth/auth.service.ts

Comment thread src/auth/auth.service.ts Outdated
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant