Skip to content

Conversation

@nevil-mathew
Copy link
Collaborator

@nevil-mathew nevil-mathew commented Oct 22, 2025

Summary by CodeRabbit

  • New Features

    • Tenant-admin role provisioned across tenants via migration.
    • New admin endpoint to assign roles to users; assignments trigger user-update events, cache clears, and session invalidation.
  • Behavior Changes

    • Admins can override tenant and organization via headers and bypass permission checks.
    • Tenant-admins can override organization (org header required) but remain subject to permission checks.
    • Tenant-admins gain org-admin–equivalent access in relevant management and notification flows.
    • User listings and role lookups are now tenant-scoped.
  • Localization

    • Added messages for tenant-admin override requirements and invalid tenant/organization codes.

@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

Walkthrough

Adds a tenant-scoped tenant_admin role via a migration and constant, updates authenticator to distinguish admins vs tenant-admins (header overrides and permission flow), broadens controller role checks to include TENANT_ADMIN_ROLE, adds an admin assignRole API/service, and scopes role lookups/queries by tenant_code.

Changes

Cohort / File(s) Change Summary
Database migration
src/database/migrations/20251022160602-add-tenant-admin-role.js
New migration exporting up/down. up inserts tenant_admin user_roles per distinct (tenant_code, organization_id), copies admin permissions (excludes module 'admin' and permission IDs 35, 40) into role_permission_mapping within a transaction using ignoreDuplicates; down removes those mappings and roles transactionally.
Constants
src/constants/common.js
Added TENANT_ADMIN_ROLE: 'tenant_admin' to exported constants.
Localization
src/locales/en.json
Added i18n keys: ORG_CODE_REQUIRED_FOR_TENANT_ADMIN, INVALID_ORG_CODE_FOR_TENANT, USER_ORGANIZATION_NOT_FOUND, USER_ROLE_ALREADY_EXISTS, INVALID_ROLE_ID, USER_ROLE_ASSIGNED_SUCCESSFULLY; adjusted punctuation for surrounding entries.
Middleware: authenticator
src/middlewares/authenticator.js
Added isTenantAdmin detection and branching: admins may override tenantCode+orgCode and bypass permission checks; tenant-admins preserve token tenant_code, may override org via required orgCode (validated within their tenant), and continue permission validation. Error messages adapted.
Controllers: authorization expansions
src/controllers/v1/notification.js, src/controllers/v1/org-admin.js, src/controllers/v1/organization.js
Extended role checks to accept TENANT_ADMIN_ROLE alongside ADMIN_ROLE/ORG_ADMIN_ROLE in multiple endpoints; one admin check commented out in createUsers.
Database queries / services
src/database/queries/users.js, src/services/account.js
listUsers now ensures tenant_code is applied at the top-level where clause; AccountHelper.list role lookup now filters by tenant_code (tenant-scoped role lookup).
Admin API / service
src/controllers/v1/admin.js, src/services/admin.js
Added assignRole controller and AdminHelper.assignRole(params, body): validates user/org, prevents duplicate role assignments, creates mapping, maps DB FK/unique errors to specific messages, broadcasts user update event, clears cache and sessions, and returns success. Added imports for ForeignKeyConstraintError, UniqueConstraintError, and eventBodyDTO.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant A as Authenticator
  participant DB as Database
  participant Ctrl as Controller
  participant S as Service

  Note over C,A: Request carries token + optional headers (tenantCode, orgCode)
  C->>A: HTTP request with token & headers
  A->>A: decode token -> extract roles (isAdmin / isTenantAdmin)
  alt isAdmin
    A->>DB: if headers present, lookup org by tenantCode + orgCode
    DB-->>A: org found / not found
    alt org found
      A->>Ctrl: attach decodedToken (tenant_code, organization_id, organization_code) — bypass permission checks
    else
      A-->>C: INVALID_ORG_OR_TENANT_CODE
    end
  else isTenantAdmin
    Note right of A: tenant_code preserved from token
    opt orgCode header present
      A->>DB: lookup org by orgCode within token.tenant_code
      DB-->>A: org found / not found
      alt org found
        A->>A: set organization_id, organization_code
      else
        A-->>C: INVALID_ORG_CODE_FOR_TENANT
      end
    end
    A->>DB: fetch tenant_admin permissions scoped to tenant_code
    DB-->>A: permissions
    A->>Ctrl: attach decodedToken (with org info) — proceed to permission checks
  else
    A-->>C: unauthorized / invalid token
  end

  alt Controller calls assignRole
    Ctrl->>S: AdminHelper.assignRole(params, body)
    S->>DB: validate user & org, check existing role, insert mapping
    DB-->>S: success / FK error / unique error
    S->>S: build event, broadcast, clear cache, end sessions
    S-->>Ctrl: result
    Ctrl-->>C: response
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I planted a role in rows of code,
Hopped through headers with a token in tow,
Tenants keep their turf while admins roam wide,
Permissions copied, migrations applied,
A cheerful rabbit — roles set just right.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "add(role): migration to create tenant_admin role and assign permissions" accurately describes a significant and central component of the changeset: the database migration that creates the tenant_admin role per tenant-organization pair and assigns applicable permissions to it. The title is clear, specific, and directly related to multiple file changes including the migration script itself, the TENANT_ADMIN_ROLE constant, and permission-related updates. While the PR scope extends beyond just the migration to include middleware authentication changes, controller authorization updates, a new role assignment endpoint, and query modifications, the title appropriately focuses on the core database migration work as the primary deliverable, which is a reasonable and defensible characterization of the main change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-tenant-admin-role

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f968909 and 81b1915.

📒 Files selected for processing (1)
  • src/database/migrations/20251022160602-add-tenant-admin-role.js (1 hunks)
🔇 Additional comments (9)
src/database/migrations/20251022160602-add-tenant-admin-role.js (9)

4-18: LGTM: Transaction setup and organization query are correct.

The transaction handling and query structure properly retrieve active tenant-organization pairs while respecting soft-delete patterns.


20-24: LGTM: Graceful handling of empty result set.

The early return with transaction commit is appropriate and prevents unnecessary operations.


74-93: LGTM: Permission copying logic is well-structured.

The implementation correctly preserves permission metadata and uses ignoreDuplicates for idempotency.


95-102: LGTM: Clear logging and proper transaction commit.

The summary logs provide excellent audit trail information for the migration.


103-108: LGTM: Proper error handling with transaction rollback.

The error handling correctly rolls back changes and propagates failures.


129-132: LGTM: Proper transaction commit in down migration.

The commit and logging follow the correct pattern.


133-137: LGTM: Consistent error handling pattern.

The down migration's error handling correctly mirrors the up migration's approach.


29-45: No action required. The hardcoded user_type value is correct.

The codebase defines user_type as: 0 = non-admin, 1 = admin (confirmed by validators in src/validators/v1/user-role.js). Since tenant_admin is an administrative role, user_type: 1 is the correct and only valid choice. The existing session_manager role (20240119142709) uses the same value. The inline comment suggesting adjustment is misleading, but the value itself aligns with the documented convention.


49-72: Verify that the admin role exists with permissions before migration runs.

The migration queries for an 'admin' role but does not verify it exists in the user_roles table. Unlike other roles (public, session_manager) added via dedicated migrations, no migration creates the 'admin' role. If the role or its permissions don't exist in role_permission_mapping, the migration succeeds silently with zero permissions copied to tenant_admin roles, leaving them without necessary access rights.

Confirm that:

  1. The 'admin' role exists in user_roles before this migration executes
  2. The admin role has permissions in role_permission_mapping for the expected modules and endpoints
  3. Permission IDs 35, 40, 22, 23, 26 exist and are properly configured

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/middlewares/authenticator.js (1)

328-339: Verify code flow: roles are stale when organization override occurs.

The concern is valid. When an override is processed (lines 244 or 279), a new org is queried from the database but declared with const, creating a block-scoped variable that doesn't modify the outer org (declared at line 160). After the override block exits, line 321 references the original org from the token (line 163), not the overridden organization. This means permission checks at line 329 use roles from the original organization, not the overridden one.

The cases to verify:

  • tenant-admin with org override header (no tenant header): The code requires both orgCode and tenantCode for tenant admins (line 226-236). Without tenantCode, it throws an error.
  • tenant-admin sending tenant header (should error): Tenant admins ignore the tenant header and use their token's tenant_code (line 221), but if they also send org header without matching the required flow, behavior needs testing.
  • admin with both headers: Admin can override both; however, the new org queried isn't actually used for role derivation.
🧹 Nitpick comments (2)
src/middlewares/authenticator.js (2)

279-298: Admin override path looks correct; minor naming nit only.

Consider mirroring the “overrideOrg” naming here too for consistency.


301-341: Duplicate assignment of req.decodedToken.

You set req.decodedToken at Line 301 and again at Line 341. Keep a single assignment after all mutations to prevent divergence.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81b1915 and 25f2b4c.

📒 Files selected for processing (3)
  • src/constants/common.js (1 hunks)
  • src/locales/en.json (1 hunks)
  • src/middlewares/authenticator.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/middlewares/**

⚙️ CodeRabbit configuration file

This is security-sensitive middleware. Scrutinize for potential vulnerabilities.

Files:

  • src/middlewares/authenticator.js
🧠 Learnings (1)
📚 Learning: 2025-07-31T08:44:36.982Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#776
File: src/services/entities.js:18-23
Timestamp: 2025-07-31T08:44:36.982Z
Learning: In the ELEVATE-Project/user codebase, organizationCode and tenantCode parameters passed to service methods always come from req.decodedToken.organization_code and req.decodedToken.tenant_code, which are guaranteed to be present after token validation. Additional validation for these parameters in service methods is unnecessary as the token validation process ensures they are always available.

Applied to files:

  • src/middlewares/authenticator.js
🧬 Code graph analysis (1)
src/middlewares/authenticator.js (2)
src/services/organization-feature.js (4)
  • isAdmin (36-36)
  • common (14-14)
  • orgCode (38-38)
  • tenantCode (39-39)
src/controllers/v1/organization-feature.js (4)
  • isAdmin (27-27)
  • common (10-10)
  • orgCode (68-68)
  • tenantCode (69-69)
🔇 Additional comments (2)
src/constants/common.js (1)

44-44: Constant addition is correct and consistent.

Name and value align with role checks in authenticator.

src/middlewares/authenticator.js (1)

206-213: Role detection LGTM.

Explicit checks for ADMIN_ROLE and TENANT_ADMIN_ROLE are clear and safe.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/middlewares/authenticator.js (1)

310-326: Stale roles after organization override for tenant-admins.

When a tenant-admin overrides to a different organization (lines 244-261) and roleValidation is true, line 321 retrieves roles from the original org (line 163) rather than fetching roles corresponding to the overridden organization_id (line 260). This causes permission checks (line 328) to validate against roles from the wrong organization.

This was flagged in previous reviews but the role refresh was not implemented.

Fetch the user's roles for the effective organization after override:

 	if (roleValidation) {
 		/* Invalidate token when user role is updated, say from mentor to mentee or vice versa */
 		const user = await userQueries.findByPk(decodedToken.data.id)
 		if (!user) {
 			throw responses.failureResponse({
 				message: 'USER_NOT_FOUND',
 				statusCode: httpStatusCode.unauthorized,
 				responseCode: 'UNAUTHORIZED',
 			})
 		}
 
-		const roles = org.roles
+		// Fetch roles from the effective organization (after any admin/tenant-admin override)
+		const effectiveOrgId = decodedToken.data.organization_id
+		const userOrg = user.organizations?.find(o => o.id === effectiveOrgId)
+		const roles = userOrg?.roles || org.roles
 
 		//update the token role as same as current user role
-		decodedToken.data.roles = roles //TODO: Update this to get roles from the user org roles table
+		decodedToken.data.roles = roles
 		//decodedToken.data.organization_id = user.organization_id
 	}

Note: Verify that user.organizations includes the necessary association and roles. If not, query the user-organization-role mapping table directly using effectiveOrgId.

As per coding guidelines.

🧹 Nitpick comments (1)
src/middlewares/authenticator.js (1)

301-301: Remove redundant assignment of req.decodedToken.

Line 301 sets req.decodedToken for admin and tenant-admin users, and line 341 sets it again for all authenticated users. This is redundant for the admin/tenant-admin code path.

Consider removing line 301 since line 341 already handles the assignment:

-		req.decodedToken = decodedToken.data
-
 		// Only admin users bypass role and permission validation
 		// Tenant admins must go through permission checks
 		if (isAdmin) {

Also applies to: 341-341

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25f2b4c and d23f99d.

📒 Files selected for processing (2)
  • src/locales/en.json (1 hunks)
  • src/middlewares/authenticator.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/middlewares/**

⚙️ CodeRabbit configuration file

This is security-sensitive middleware. Scrutinize for potential vulnerabilities.

Files:

  • src/middlewares/authenticator.js
🧠 Learnings (1)
📚 Learning: 2025-07-31T08:44:36.982Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#776
File: src/services/entities.js:18-23
Timestamp: 2025-07-31T08:44:36.982Z
Learning: In the ELEVATE-Project/user codebase, organizationCode and tenantCode parameters passed to service methods always come from req.decodedToken.organization_code and req.decodedToken.tenant_code, which are guaranteed to be present after token validation. Additional validation for these parameters in service methods is unnecessary as the token validation process ensures they are always available.

Applied to files:

  • src/middlewares/authenticator.js
🧬 Code graph analysis (1)
src/middlewares/authenticator.js (3)
src/services/organization-feature.js (7)
  • isAdmin (36-36)
  • common (14-14)
  • orgCode (38-38)
  • tenantCode (39-39)
  • responses (11-11)
  • httpStatusCode (8-8)
  • organizationQueries (10-10)
src/helpers/userInvite.js (2)
  • orgCode (1075-1083)
  • organizationQueries (15-15)
src/controllers/v1/public.js (1)
  • tenantCode (8-8)
🔇 Additional comments (4)
src/locales/en.json (1)

196-197: LGTM!

The new localization keys are properly defined and align with the updated authentication flow for distinguishing tenant-admin vs admin error messaging.

src/middlewares/authenticator.js (3)

206-212: LGTM!

The role detection logic correctly identifies both admin and tenant-admin roles from the decoded token.


214-223: LGTM!

Header extraction is properly handled with safe defaults and trimming.


214-308: Good security posture for tenant isolation and validation.

The middleware properly enforces tenant isolation for tenant-admins by using effectiveTenantCode from the token (line 228), validates organization existence before allowing overrides (lines 244-249, 279-284), and appropriately limits permission bypass to admins only (line 305).

As per coding guidelines (security-sensitive middleware).

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/services/account.js (1)

1610-1615: Consider validating tenantCode before role lookup.

The role lookup now includes tenant_code for proper scoping. However, if tenantCode is null or undefined, the query may return unexpected results. Consider adding validation:

+		if (!tenantCode) {
+			return responses.failureResponse({
+				message: 'TENANT_CODE_REQUIRED',
+				statusCode: httpStatusCode.bad_request,
+				responseCode: 'CLIENT_ERROR',
+			})
+		}
 		let role = await roleQueries.findOne(
 			{ title: params.query.type.toLowerCase(), tenant_code: tenantCode },
 			{
 				attributes: ['id'],
 			}
 		)

As per coding guidelines.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f8a947 and 7ea3863.

📒 Files selected for processing (5)
  • src/controllers/v1/notification.js (1 hunks)
  • src/controllers/v1/org-admin.js (7 hunks)
  • src/controllers/v1/organization.js (5 hunks)
  • src/database/queries/users.js (1 hunks)
  • src/services/account.js (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/controllers/**

⚙️ CodeRabbit configuration file

These are API controllers. Focus on request/response handling, security (auth middleware usage), and proper status codes.

Files:

  • src/controllers/v1/org-admin.js
  • src/controllers/v1/organization.js
  • src/controllers/v1/notification.js
src/database/queries/**

⚙️ CodeRabbit configuration file

Review database queries for performance. Check for N+1 problems and ensure indexes can be used.

Files:

  • src/database/queries/users.js
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/account.js
🧬 Code graph analysis (1)
src/services/account.js (4)
src/controllers/v1/tenant.js (2)
  • params (26-26)
  • tenantCode (171-171)
src/middlewares/authenticator.js (1)
  • tenantCode (220-220)
src/controllers/v1/public.js (1)
  • tenantCode (8-8)
src/services/organization.js (1)
  • tenantCode (281-281)
🔇 Additional comments (4)
src/controllers/v1/org-admin.js (1)

24-29: LGTM! Consistent authorization expansion across all admin methods.

The authorization checks have been consistently expanded to include TENANT_ADMIN_ROLE alongside ORG_ADMIN_ROLE across all methods. This aligns with the PR's objective of introducing tenant-scoped admin capabilities while maintaining proper access controls.

Also applies to: 56-61, 85-90, 120-125, 152-157, 181-186, 211-216

src/controllers/v1/notification.js (1)

32-38: LGTM! Authorization properly expanded to include tenant admins.

The role check now includes TENANT_ADMIN_ROLE, allowing tenant administrators to manage notification templates. This is consistent with the authorization changes across other controllers in this PR.

src/database/queries/users.js (1)

101-104: Index on tenant_code verified.

An index on the users.tenant_code column is properly created in the migration file at src/database/migrations/20250506095633-update-relations.js:48. The addition of tenant_code to the main where clause will benefit from this index to prevent table scans, and the change strengthens tenant isolation.

src/controllers/v1/organization.js (1)

34-34: Tenant boundary enforcement verified—code changes are secure.

The authenticator middleware properly enforces multi-tenant isolation for tenant admins:

  1. Tenant code is locked (line 228): Tenant admins cannot override their tenant_code from the token; it remains fixed to their original tenant.
  2. Organization lookups are scoped (line 244-251): Organization queries include both code and the tenant admin's tenant_code, preventing cross-tenant access.
  3. Only admins can override tenants (line 263-298): Only full admins can change tenant_code; tenant admins cannot.

The authorization expansions in organization.js (lines 34, 71, 173, 219, 264) to include TENANT_ADMIN_ROLE are safe because the middleware enforces tenant isolation at the request level.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (3)
src/services/admin.js (1)

123-127: Clarify org scoping; enforce tenant and optional conflict checks.

Current selection allows matching by either token organization_code or provided organization_id. If both are present but refer to different orgs, this could assign under an unintended org; also no explicit tenant check on org object.

Consider:

  • Ensuring org.tenant_code === params.tenant_code.
  • If organization_id is provided, require it to match the org resolved by organization_code (when both exist).
    Example:
-const org = orgs.find(
-  (o) => o?.code === organization_code || String(o?.id) === String(organization_id || '')
-)
+const org = orgs.find((o) => o?.code === organization_code)
+if (organization_id && (!org || String(org.id) !== String(organization_id))) {
+  return responses.failureResponse({
+    message: 'INVALID_ORG_OR_TENANT_CODE',
+    statusCode: httpStatusCode.bad_request,
+    responseCode: 'CLIENT_ERROR',
+  })
+}
+if (!org || (org.tenant_code && org.tenant_code !== tenant_code)) {
+  return responses.failureResponse({
+    message: 'USER_ORGANIZATION_NOT_FOUND',
+    statusCode: httpStatusCode.bad_request,
+    responseCode: 'CLIENT_ERROR',
+  })
+}
src/controllers/v1/admin.js (1)

76-79: Add minimal input validation for body to fail fast.

Validate presence and basic type/format of user_id and role_id before delegating, returning 400 on bad input.

-const user = await adminService.assignRole(params, req.body)
+const { user_id, role_id } = req.body || {}
+if (!user_id || !role_id) {
+  throw responses.failureResponse({
+    message: 'VALIDATION_FAILED',
+    statusCode: httpStatusCode.bad_request,
+    responseCode: 'CLIENT_ERROR',
+  })
+}
+const user = await adminService.assignRole(params, req.body)
src/locales/en.json (1)

194-201: New keys align with service/controller usage; placeholder fixed.

Messages map to assignRole outcomes and authenticator checks. Looks good.

Optional: standardize spelling across file ("Organization" vs "Organisation") in future cleanup.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea3863 and 899e99b.

📒 Files selected for processing (3)
  • src/controllers/v1/admin.js (1 hunks)
  • src/locales/en.json (1 hunks)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/controllers/**

⚙️ CodeRabbit configuration file

These are API controllers. Focus on request/response handling, security (auth middleware usage), and proper status codes.

Files:

  • src/controllers/v1/admin.js
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🧬 Code graph analysis (2)
src/controllers/v1/admin.js (3)
src/controllers/v1/org-admin.js (4)
  • utilsHelper (12-12)
  • common (10-10)
  • responses (13-13)
  • httpStatusCode (11-11)
src/services/admin.js (10)
  • common (15-15)
  • responses (41-41)
  • httpStatusCode (16-16)
  • params (111-111)
  • user (58-58)
  • user (114-114)
  • user (317-320)
  • user (455-455)
  • user (566-566)
  • adminService (32-32)
src/helpers/responses.js (1)
  • error (28-28)
src/services/admin.js (6)
src/services/org-admin.js (11)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (31-31)
  • require (32-32)
  • userHelper (30-30)
  • user (371-374)
  • userOrganizationRoleQueries (21-21)
  • newValues (656-656)
  • utils (12-12)
  • eventBody (671-681)
src/services/account.js (22)
  • require (26-26)
  • require (27-27)
  • require (37-37)
  • require (41-41)
  • userHelper (40-40)
  • user (136-145)
  • user (703-703)
  • user (848-848)
  • user (898-898)
  • user (1051-1051)
  • user (1204-1213)
  • user (1385-1393)
  • user (1733-1733)
  • user (1956-1959)
  • user (2067-2067)
  • userOrganizationRoleQueries (36-36)
  • utils (25-25)
  • eventBody (604-623)
  • eventBody (1754-1769)
  • userSessionData (918-926)
  • userSessionData (2001-2009)
  • userSessionsService (31-31)
src/controllers/v1/admin.js (3)
  • params (71-74)
  • user (34-34)
  • user (77-77)
src/services/user.js (5)
  • user (48-51)
  • user (319-319)
  • user (440-440)
  • utils (12-12)
  • eventBody (191-201)
src/services/user-sessions.js (2)
  • id (120-120)
  • id (364-364)
src/helpers/userHelper.js (4)
  • userHelper (50-157)
  • userOrganizationRoleQueries (5-5)
  • utils (2-2)
  • userSessionsService (6-6)
🔇 Additional comments (4)
src/services/admin.js (2)

39-39: Import looks good.

eventBroadcaster is used later (e.g., updateOrganization/deactivateUpcomingSession). No issues.


181-198: Undefined variable userId; session cleanup is incomplete.

Line 185 uses undefined userId—should be user_id. Additionally, the current cleanup only removes individual session Redis keys but deletes the user prefix key (${redisUserKey}) separately. The suggested call to userHelper.removeAllUserSessions(user.id, tenant_code) removes session keys but not the user prefix key, leaving cleanup incomplete. Ensure both the user prefix key and all session data are properly cleared, or consolidate this logic into a dedicated utility that handles both.

 const redisUserKey = `${common.redisUserPrefix}${tenant_code}_${user.id.toString()}`
 await utils.redisDel(redisUserKey)
-const userSessionData = await userSessionsService.findUserSession(
-  {
-    user_id: userId,
-    ended_at: null,
-  },
-  {
-    attributes: ['id'],
-  }
-)
-const userSessionIds = userSessionData.map(({ id }) => id)
-/**
- * 1: Remove redis data
- * 2: Update ended_at in user-sessions
- */
-await userSessionsService.removeUserSessions(userSessionIds)
+// Remove all active sessions for this user
+await userHelper.removeAllUserSessions(user.id, tenant_code)
src/controllers/v1/admin.js (2)

61-69: Admin gate is correct and consistent with controller patterns.


71-74: Token-derived scoping passed correctly.

organization_code and tenant_code sourced from token and bundled as params. Good.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
src/services/admin.js (7)

10-12: Consolidate Sequelize imports (minor).

Import from sequelize once to avoid duplication and keep consistency.

-const { Op } = require('sequelize')
+const { Op, ForeignKeyConstraintError, UniqueConstraintError } = require('sequelize')
...
-const { ForeignKeyConstraintError, UniqueConstraintError } = require('sequelize')

Also applies to: 21-21


97-109: Add minimal input validation for required fields.

Guard against missing user_id/role_id early; fail fast.

 static async assignRole(params, body) {
   try {
     const { tenant_code, organization_code } = params
     const { user_id, role_id, organization_id } = body
+    if (!user_id || !role_id) {
+      return responses.failureResponse({
+        message: 'USER_ID_AND_ROLE_ID_REQUIRED',
+        statusCode: httpStatusCode.bad_request,
+        responseCode: 'CLIENT_ERROR',
+      })
+    }

123-127: Org resolution can bypass token org; confirm intended authorization.

Current predicate accepts either params.organization_code or body.organization_id. If both are present but refer to different orgs, this assigns in the body-specified org. Verify this is allowed for the acting role (e.g., tenant_admin) and tenant-scoped.

-const org = orgs.find(
-  (o) => o?.code === organization_code || String(o?.id) === String(organization_id || '')
-)
+const org = orgs.find((o) => o?.code === organization_code || String(o?.id) === String(organization_id || ''))
+// Optional: enforce match when both supplied
+if (organization_id && organization_code && org && org.code !== organization_code) {
+  return responses.failureResponse({
+    message: 'ORGANIZATION_MISMATCH',
+    statusCode: httpStatusCode.bad_request,
+    responseCode: 'CLIENT_ERROR',
+  })
+}

148-172: Consider idempotent success on UniqueConstraintError; optionally pre-validate role tenancy.

  • Treat duplicate assignment as success to make the operation idempotent.
  • Optional: ensure role_id belongs to tenant_code and is ACTIVE for clearer errors.
         if (error instanceof UniqueConstraintError) {
-          return responses.failureResponse({
-            message: 'USER_ROLE_ALREADY_EXISTS',
-            statusCode: httpStatusCode.bad_request,
-            responseCode: 'CLIENT_ERROR',
-          })
+          return responses.successResponse({
+            statusCode: httpStatusCode.ok,
+            message: 'USER_ROLE_ASSIGNED_SUCCESSFULLY',
+            result: {
+              mapping: { tenant_code, user_id: user.id || user.user_id, organization_code: org.code, role_id },
+              idempotent: true,
+            },
+          })
         }

Optional pre-check (before create):

// const role = await roleQueries.findOne({ id: role_id, tenant_code, status: common.ACTIVE_STATUS }, { attributes: ['id'] })
// if (!role) return responses.failureResponse({ message: 'INVALID_ROLE_ID', statusCode: httpStatusCode.bad_request, responseCode: 'CLIENT_ERROR' })

174-186: Event body: add safe fallbacks for timestamps (optional).

If updatedUser is nullish (edge failures), provide sensible defaults.

-args: { created_at: updatedUser.created_at, updated_at: updatedUser.updated_at },
+args: {
+  created_at: updatedUser?.created_at || user?.created_at || new Date(),
+  updated_at: updatedUser?.updated_at || new Date(),
+},

206-215: Response is fine; consider returning user snapshot for convenience (optional).

Current payload returns only mapping. If API consumers often need updated roles, consider including a minimal updated user roles array.


109-172: One more guard: restrict assignable roles if applicable (policy).

If only certain roles (e.g., ORG_ADMIN, TENANT_ADMIN) are assignable by this endpoint, validate role_id against an allowed set to prevent privilege escalation.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 899e99b and c47f705.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🧠 Learnings (1)
📚 Learning: 2025-09-12T11:02:51.008Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/dtos/organizationDTO.js:14-43
Timestamp: 2025-09-12T11:02:51.008Z
Learning: In the ELEVATE-Project/user codebase, oldValue and newValue in the changedValues array for organizationDTO.eventBodyDTO are guaranteed to not be undefined by the business logic, and filtering out falsy values (0, false, '') from changes is the intended behavior.

Applied to files:

  • src/services/admin.js
🧬 Code graph analysis (1)
src/services/admin.js (4)
src/services/org-admin.js (17)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (31-31)
  • require (32-32)
  • userHelper (30-30)
  • user (371-374)
  • userQueries (14-14)
  • userQueries (47-50)
  • userQueries (119-119)
  • userOrganizationRoleQueries (21-21)
  • updatedUser (651-654)
  • newValues (656-656)
  • utils (12-12)
  • eventBody (671-681)
  • redisUserKey (685-685)
  • common (10-10)
src/services/account.js (25)
  • require (26-26)
  • require (27-27)
  • require (37-37)
  • require (41-41)
  • userHelper (40-40)
  • UserTransformDTO (38-38)
  • user (136-145)
  • user (703-703)
  • user (848-848)
  • user (898-898)
  • user (1051-1051)
  • user (1204-1213)
  • user (1385-1393)
  • user (1733-1733)
  • user (1956-1959)
  • user (2067-2067)
  • userQueries (17-17)
  • userOrganizationRoleQueries (36-36)
  • utils (25-25)
  • eventBody (604-623)
  • eventBody (1754-1769)
  • redisUserKey (1710-1710)
  • redisUserKey (1992-1992)
  • common (16-16)
  • userSessionsService (31-31)
src/controllers/v1/admin.js (4)
  • params (71-74)
  • user (34-34)
  • user (77-77)
  • common (10-10)
src/helpers/userHelper.js (6)
  • userHelper (50-157)
  • userQueries (3-3)
  • userOrganizationRoleQueries (5-5)
  • utils (2-2)
  • common (1-1)
  • userSessionsService (6-6)
🔇 Additional comments (2)
src/services/admin.js (2)

47-47: LGTM: eventBodyDTO import is correct.
Matches usage pattern elsewhere.


39-39: No-op formatting change.
Nothing to act on.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/services/admin.js (1)

152-152: Consider removing unnecessary defensive fallback.

Line 152 uses user.id || user.user_id, but line 180 accesses user.id directly. Both refer to the same user object from line 114. Since findUserWithOrganization should return a consistent structure, the fallback to user_id appears unnecessary and creates inconsistency.

Simplify to use user.id consistently:

-			user_id: user.id || user.user_id,
+			user_id: user.id,
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c47f705 and 6a13a69.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🧠 Learnings (3)
📚 Learning: 2025-09-12T11:02:51.008Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/dtos/organizationDTO.js:14-43
Timestamp: 2025-09-12T11:02:51.008Z
Learning: In the ELEVATE-Project/user codebase, oldValue and newValue in the changedValues array for organizationDTO.eventBodyDTO are guaranteed to not be undefined by the business logic, and filtering out falsy values (0, false, '') from changes is the intended behavior.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-08-08T13:06:32.911Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:10-10
Timestamp: 2025-08-08T13:06:32.911Z
Learning: In ELEVATE-Project/user, avoid potential circular dependencies between src/helpers/userHelper.js and src/services/user-sessions by extracting shared session cleanup logic (finding active sessions, deleting Redis keys, ending sessions) into a dedicated utility (e.g., helpers/sessionCleanup.js) that both layers can consume.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-08-08T15:12:44.423Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:117-156
Timestamp: 2025-08-08T15:12:44.423Z
Learning: In ELEVATE-Project/user, prefer extracting shared session teardown (find active sessions, delete Redis keys, mark ended_at) into helpers/sessionCleanup.js so both helpers and services use it, preventing upward (helper→service) dependencies and avoiding circular imports.

Applied to files:

  • src/services/admin.js
🧬 Code graph analysis (1)
src/services/admin.js (4)
src/services/org-admin.js (13)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (31-31)
  • require (32-32)
  • userHelper (30-30)
  • user (371-374)
  • userQueries (14-14)
  • userQueries (47-50)
  • userQueries (119-119)
  • userOrganizationRoleQueries (21-21)
  • utils (12-12)
  • eventBody (671-681)
src/services/account.js (20)
  • require (26-26)
  • require (27-27)
  • require (37-37)
  • require (41-41)
  • userHelper (40-40)
  • user (136-145)
  • user (703-703)
  • user (848-848)
  • user (898-898)
  • user (1051-1051)
  • user (1204-1213)
  • user (1385-1393)
  • user (1733-1733)
  • user (1956-1959)
  • user (2067-2067)
  • userQueries (17-17)
  • userOrganizationRoleQueries (36-36)
  • utils (25-25)
  • eventBody (604-623)
  • eventBody (1754-1769)
src/controllers/v1/admin.js (3)
  • params (71-74)
  • user (34-34)
  • user (77-77)
src/helpers/userHelper.js (4)
  • userHelper (50-157)
  • userQueries (3-3)
  • userOrganizationRoleQueries (5-5)
  • utils (2-2)
🔇 Additional comments (1)
src/services/admin.js (1)

21-21: LGTM!

The added imports (ForeignKeyConstraintError, UniqueConstraintError, eventBodyDTO) are correctly used in the new assignRole method for robust error handling and event broadcasting.

Also applies to: 47-47

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/services/admin.js (2)

174-197: Consider defensive null check for updatedUser.

While unlikely, if the user is deleted between the mapping creation (line 155) and the fetch (line 176), updatedUser could be null, causing property access errors at lines 177 and 185. Although the auxError catch handles this gracefully without failing the request, a defensive check would make the intent clearer:

 		// Post-assignment operations (best-effort)
 		try {
 			const updatedUser = await userQueries.findUserWithOrganization({ id: user_id, tenant_code })
+			if (!updatedUser) {
+				console.warn('User not found during post-assignment operations, skipping event/cache updates')
+				return responses.successResponse({
+					statusCode: httpStatusCode.ok,
+					message: 'USER_ROLE_ASSIGNED_SUCCESSFULLY',
+					result: { mapping },
+				})
+			}
+
 			const newValues = utils.extractDelta(user, updatedUser)
 
 			const eventBody = eventBodyDTO({

196-196: Optional: Consider structured logging for better observability.

Lines 196 and 205 use console.error for error logging. While functional, structured logging (e.g., using a logging library) would provide better observability in production with log levels, context, and integration with monitoring tools.

Also applies to: 205-205

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a13a69 and 10cab5f.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🧠 Learnings (2)
📚 Learning: 2025-08-08T13:06:32.911Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:10-10
Timestamp: 2025-08-08T13:06:32.911Z
Learning: In ELEVATE-Project/user, avoid potential circular dependencies between src/helpers/userHelper.js and src/services/user-sessions by extracting shared session cleanup logic (finding active sessions, deleting Redis keys, ending sessions) into a dedicated utility (e.g., helpers/sessionCleanup.js) that both layers can consume.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-08-08T15:12:44.423Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:117-156
Timestamp: 2025-08-08T15:12:44.423Z
Learning: In ELEVATE-Project/user, prefer extracting shared session teardown (find active sessions, delete Redis keys, mark ended_at) into helpers/sessionCleanup.js so both helpers and services use it, preventing upward (helper→service) dependencies and avoiding circular imports.

Applied to files:

  • src/services/admin.js
🧬 Code graph analysis (1)
src/services/admin.js (3)
src/services/org-admin.js (13)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (31-31)
  • require (32-32)
  • userHelper (30-30)
  • user (371-374)
  • userQueries (14-14)
  • userQueries (47-50)
  • userQueries (119-119)
  • userOrganizationRoleQueries (21-21)
  • utils (12-12)
  • eventBody (671-681)
src/controllers/v1/admin.js (3)
  • params (71-74)
  • user (34-34)
  • user (77-77)
src/helpers/userHelper.js (4)
  • userHelper (50-157)
  • userQueries (3-3)
  • userOrganizationRoleQueries (5-5)
  • utils (2-2)
🔇 Additional comments (2)
src/services/admin.js (2)

21-21: LGTM! Constraint error imports added correctly.

Both ForeignKeyConstraintError and UniqueConstraintError are properly imported and used in the assignRole method to handle database constraint violations gracefully.

Also applies to: 47-47


109-146: LGTM! Validation logic is solid.

The method correctly validates:

  • User existence within tenant scope
  • Organization membership
  • Duplicate role assignment prevention

Error responses are consistent and informative.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/services/admin.js (1)

176-176: Use validated user.id instead of body user_id for consistency.

Line 176 re-fetches the user using user_id from the request body. While safe (since validated at line 114), using user.id would be clearer and more consistent, as it explicitly relies on the already-validated user object.

-			const updatedUser = await userQueries.findUserWithOrganization({ id: user_id, tenant_code })
+			const updatedUser = await userQueries.findUserWithOrganization({ id: user.id, tenant_code })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10cab5f and 33a2150.

📒 Files selected for processing (1)
  • src/services/admin.js (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/services/**

⚙️ CodeRabbit configuration file

This is core business logic. Please check for correctness, efficiency, and potential edge cases.

Files:

  • src/services/admin.js
🧠 Learnings (4)
📚 Learning: 2025-09-12T11:02:51.008Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/dtos/organizationDTO.js:14-43
Timestamp: 2025-09-12T11:02:51.008Z
Learning: In the ELEVATE-Project/user codebase, oldValue and newValue in the changedValues array for organizationDTO.eventBodyDTO are guaranteed to not be undefined by the business logic, and filtering out falsy values (0, false, '') from changes is the intended behavior.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-08-08T13:06:32.911Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:10-10
Timestamp: 2025-08-08T13:06:32.911Z
Learning: In ELEVATE-Project/user, avoid potential circular dependencies between src/helpers/userHelper.js and src/services/user-sessions by extracting shared session cleanup logic (finding active sessions, deleting Redis keys, ending sessions) into a dedicated utility (e.g., helpers/sessionCleanup.js) that both layers can consume.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-08-08T15:12:44.423Z
Learnt from: nevil-mathew
PR: ELEVATE-Project/user#790
File: src/helpers/userHelper.js:117-156
Timestamp: 2025-08-08T15:12:44.423Z
Learning: In ELEVATE-Project/user, prefer extracting shared session teardown (find active sessions, delete Redis keys, mark ended_at) into helpers/sessionCleanup.js so both helpers and services use it, preventing upward (helper→service) dependencies and avoiding circular imports.

Applied to files:

  • src/services/admin.js
📚 Learning: 2025-09-12T10:35:28.285Z
Learnt from: praveenKDass
PR: ELEVATE-Project/user#816
File: src/services/admin.js:704-704
Timestamp: 2025-09-12T10:35:28.285Z
Learning: In ELEVATE-Project/user admin.js, there's an inconsistency in organizationQueries.update usage: addOrgAdmin calls it without options (returns number), while deactivateOrg calls it with { returning: true, raw: true } (returns object). The validation logic 'if (orgRowsAffected === 0)' only works for the first case - the second case needs 'if (orgRowsAffected.rowsAffected === 0)' to properly validate updates.

Applied to files:

  • src/services/admin.js
🧬 Code graph analysis (1)
src/services/admin.js (3)
src/services/org-admin.js (9)
  • require (22-22)
  • require (23-23)
  • require (24-24)
  • require (31-31)
  • require (32-32)
  • userHelper (30-30)
  • user (371-374)
  • utils (12-12)
  • eventBody (671-681)
src/controllers/v1/admin.js (3)
  • params (71-74)
  • user (34-34)
  • user (77-77)
src/helpers/userHelper.js (2)
  • userHelper (50-157)
  • utils (2-2)
🔇 Additional comments (4)
src/services/admin.js (4)

21-21: LGTM - Imports support the new assignRole functionality.

The addition of UniqueConstraintError addresses the race-condition handling discussed in previous reviews, and eventBodyDTO is correctly imported for event broadcasting in the new method.

Also applies to: 47-47


148-172: LGTM - Role creation with comprehensive error handling.

The role assignment logic correctly:

  • Uses user.id consistently without unnecessary fallbacks
  • Handles ForeignKeyConstraintError to catch invalid role_id (including tenant isolation via composite FK)
  • Handles UniqueConstraintError to gracefully manage race conditions
  • Returns appropriate client error responses for constraint violations

All concerns from previous reviews have been properly addressed.


199-208: LGTM - Response handling follows established patterns.

The success response returns the created mapping with appropriate status and message. Error handling logs with context and rethrows for global handling, consistent with other methods in the service.


123-134: Confirm intended scope: Organization selection allows request body to override token context.

Line 124-126 uses OR logic that allows an admin to assign roles in ANY organization where the target user is a member, via body.organization_id, even if it differs from organization_code in their token. The matched org is then used (line 153) for role creation.

If this enables tenant-admin cross-organizational capabilities, confirm this is intentional. Otherwise, restrict matching to only organization_code from the token to prevent scope bypass.

@nevil-mathew nevil-mathew merged commit 1047843 into develop Oct 27, 2025
1 of 2 checks passed
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.

2 participants