-
Notifications
You must be signed in to change notification settings - Fork 19
add(role): migration to create tenant_admin role and assign permissions #851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughAdds a tenant-scoped Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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
📒 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
ignoreDuplicatesfor 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_typeas:0 = non-admin,1 = admin(confirmed by validators insrc/validators/v1/user-role.js). Sincetenant_adminis an administrative role,user_type: 1is the correct and only valid choice. The existingsession_managerrole (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_rolestable. 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 inrole_permission_mapping, the migration succeeds silently with zero permissions copied totenant_adminroles, leaving them without necessary access rights.Confirm that:
- The 'admin' role exists in
user_rolesbefore this migration executes- The admin role has permissions in
role_permission_mappingfor the expected modules and endpoints- Permission IDs 35, 40, 22, 23, 26 exist and are properly configured
There was a problem hiding this 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
orgis queried from the database but declared withconst, creating a block-scoped variable that doesn't modify the outerorg(declared at line 160). After the override block exits, line 321 references the originalorgfrom 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
📒 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.
There was a problem hiding this 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
roleValidationis true, line 321 retrieves roles from the originalorg(line 163) rather than fetching roles corresponding to the overriddenorganization_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.organizationsincludes the necessary association and roles. If not, query the user-organization-role mapping table directly usingeffectiveOrgId.As per coding guidelines.
🧹 Nitpick comments (1)
src/middlewares/authenticator.js (1)
301-301: Remove redundant assignment of req.decodedToken.Line 301 sets
req.decodedTokenfor 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
📒 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
effectiveTenantCodefrom 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).
…otification and organization controllers
There was a problem hiding this 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 validatingtenantCodebefore role lookup.The role lookup now includes
tenant_codefor proper scoping. However, iftenantCodeisnullorundefined, 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
📒 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.jssrc/controllers/v1/organization.jssrc/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_ROLEalongsideORG_ADMIN_ROLEacross 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 ontenant_codeverified.An index on the
users.tenant_codecolumn is properly created in the migration file atsrc/database/migrations/20250506095633-update-relations.js:48. The addition oftenant_codeto 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:
- Tenant code is locked (line 228): Tenant admins cannot override their
tenant_codefrom the token; it remains fixed to their original tenant.- Organization lookups are scoped (line 244-251): Organization queries include both
codeand the tenant admin'stenant_code, preventing cross-tenant access.- 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_ROLEare safe because the middleware enforces tenant isolation at the request level.
…n-derived parameters
There was a problem hiding this 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
📒 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 beuser_id. Additionally, the current cleanup only removes individual session Redis keys but deletes the user prefix key (${redisUserKey}) separately. The suggested call touserHelper.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.
There was a problem hiding this 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_idbelongs totenant_codeand 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_idagainst an allowed set to prevent privilege escalation.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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.
There was a problem hiding this 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 accessesuser.iddirectly. Both refer to the sameuserobject from line 114. SincefindUserWithOrganizationshould return a consistent structure, the fallback touser_idappears unnecessary and creates inconsistency.Simplify to use
user.idconsistently:- user_id: user.id || user.user_id, + user_id: user.id,
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 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 newassignRolemethod for robust error handling and event broadcasting.Also applies to: 47-47
There was a problem hiding this 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 forupdatedUser.While unlikely, if the user is deleted between the mapping creation (line 155) and the fetch (line 176),
updatedUsercould be null, causing property access errors at lines 177 and 185. Although theauxErrorcatch 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.errorfor 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
📒 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
ForeignKeyConstraintErrorandUniqueConstraintErrorare properly imported and used in theassignRolemethod 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.
There was a problem hiding this 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_idfrom the request body. While safe (since validated at line 114), usinguser.idwould 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
📒 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
UniqueConstraintErroraddresses the race-condition handling discussed in previous reviews, andeventBodyDTOis 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.idconsistently without unnecessary fallbacks- Handles
ForeignKeyConstraintErrorto catch invalid role_id (including tenant isolation via composite FK)- Handles
UniqueConstraintErrorto 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 fromorganization_codein 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_codefrom the token to prevent scope bypass.
Summary by CodeRabbit
New Features
Behavior Changes
Localization