Skip to content

[2b] User and department management - #30

Open
andrmaz wants to merge 6 commits into
developfrom
cursor/user-department-management-70bf
Open

[2b] User and department management#30
andrmaz wants to merge 6 commits into
developfrom
cursor/user-department-management-70bf

Conversation

@andrmaz

@andrmaz andrmaz commented Aug 1, 2026

Copy link
Copy Markdown
Owner

What changed

Builds on #3 [2a] Organization CRUD to add admin department management and user-department assignment, plus session enrichment so downstream request paths can resolve department scope per user.

API (apps/api)

  • Departments admin API (apps/api/src/admin/departments/): GET/POST /api/admin/organizations/:organizationId/departments — create and list departments scoped to an organization, with 404 on unknown org and 409 on duplicate name within the org.
  • Users listing admin API (apps/api/src/admin/users/): GET /api/admin/organizations/:organizationId/users — lists users in an org, needed so the admin UI can pick a user to assign.
  • UserDepartment assignment API (apps/api/src/admin/user-departments/): GET/PUT /api/admin/users/:userId/departments — set-replaces a user's department assignments (add/remove departments in one call) and designates exactly one primary department. Validates that every departmentId belongs to the user's own organization, and clears existing primary flags before writing the new set inside a transaction so the DB's "one primary per user" partial unique index is never violated mid-write.
  • Session enrichment (apps/api/src/auth/): GET /api/me now returns departmentIds and primaryDepartmentId alongside the existing JWT claims (id, email, organizationId, role). This is resolved live from the UserDepartment table on every request rather than signed into the JWT, so admin-driven reassignment is visible immediately without waiting for token expiry/reissuance.
  • All new admin routes reuse the existing AdminRoleGuard (401 unauthenticated / 403 non-admin).

Web (apps/web)

  • /admin/departments — organization picker, department list, create-department form.
  • /admin/users — organization picker, user list, and a department-assignment form (checkboxes + primary radio) per selected user.
  • Added "Departments" / "Users" quick links from the Organizations admin table for discoverability.

Tests

  • Unit tests for DepartmentService, AdminUserService, UserDepartmentService (including primary-department resolution order, dedup, cross-org validation, and transaction shape), and UserService.getDepartmentAssignments.
  • Integration tests (supertest against a compiled Nest app with PrismaService mocked, matching the existing pattern) covering:
    • Department create/list authorization and validation paths.
    • User-department assignment authorization and validation paths.
    • Session enrichment (GET /api/me) reflecting a fresh assignment on an already-issued token, and the empty/no-assignment case.

Verification

  • pnpm test — all 152 API tests + existing shared package tests pass.
  • pnpm check-types / pnpm lint — clean across all workspaces.
  • pnpm --filter @cortex/web build — succeeds; new /admin/departments and /admin/users routes render.
  • Did not attempt a live end-to-end boot of @cortex/api — this repo has a documented, pre-existing gap (see docs/agents/cursor-cloud.md) where the compiled API can't start yet (db/client path resolution + missing Prisma driver adapter), unrelated to this change. Verified instead via the Jest integration tests, per that doc's guidance.

Acceptance criteria

  • Admin can create and list departments scoped to an organization.
  • Admin can assign a user to one or more departments.
  • Department assignment is reflected in the authenticated user's session/token claims (via GET /api/me).
  • Admin UI pages exist for departments and user assignment.
  • Integration tests cover assignment and session-enrichment paths.
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Added admin pages for managing organization departments and users.
    • Admins can create departments, view users, and assign multiple departments with a primary selection.
    • Added organization navigation and protected admin access.
    • Session information now includes department memberships and the primary department.
  • Validation & Access
    • Added input validation, organization scoping, duplicate handling, and authorization safeguards.
  • Tests
    • Added comprehensive unit and integration coverage for department, user, assignment, authentication, and session workflows.

- Add admin Departments API (create + list, scoped to an organization)
- Add admin Users listing API (scoped to an organization) to support the
  assignment UI
- Add admin UserDepartments API to assign a user to one or more
  departments via a set-replace PUT, with primary-department resolution
  and organization-scoped validation
- Enrich GET /api/me with departmentIds/primaryDepartmentId resolved
  live from the UserDepartment table, so admin-driven assignment changes
  are reflected in the authenticated user's session without requiring a
  token refresh
- Add Admin Web pages for departments (create/list) and user department
  assignment, with an organization picker shared across both
- Add unit and integration test coverage for department CRUD, user
  listing, assignment (including primary-department edge cases), and
  session enrichment

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bce3f17c-9a2b-475d-bd91-5c344bf04329

📥 Commits

Reviewing files that changed from the base of the PR and between 201ce29 and 197ed26.

📒 Files selected for processing (22)
  • .env.example
  • apps/api/src/admin/departments/department.service.ts
  • apps/api/src/admin/departments/departments.controller.ts
  • apps/api/src/admin/departments/departments.integration.spec.ts
  • apps/api/src/admin/guards/assert-admin-organization.ts
  • apps/api/src/admin/organizations/organization.service.ts
  • apps/api/src/admin/user-departments/user-department.service.spec.ts
  • apps/api/src/admin/user-departments/user-department.service.ts
  • apps/api/src/admin/user-departments/user-departments.controller.ts
  • apps/api/src/admin/user-departments/user-departments.integration.spec.ts
  • apps/api/src/admin/users/admin-users.controller.ts
  • apps/api/src/admin/users/admin-users.integration.spec.ts
  • apps/api/src/auth/auth.controller.ts
  • apps/api/src/auth/user.service.ts
  • apps/api/src/common/prisma-errors.ts
  • apps/web/app/admin/_components/OrgPicker.tsx
  • apps/web/app/admin/_lib/admin-auth.ts
  • apps/web/app/admin/layout.tsx
  • apps/web/app/auth/callback/route.ts
  • apps/web/app/auth/login/route.ts
  • apps/web/app/auth/logout/route.ts
  • turbo.json
📝 Walkthrough

Walkthrough

The PR adds organization-scoped department management, user-department assignment, admin UI workflows, and live department assignments to /api/me.

Changes

Department administration

Layer / File(s) Summary
Department API lifecycle
apps/api/src/admin/departments/*, apps/api/src/__mocks__/db-client.mock.ts, apps/api/src/admin/admin.module.ts
Adds department DTOs, organization-scoped listing and creation, admin guards, duplicate-name conflict handling, timestamp mapping, module wiring, and API tests.
User-department assignment API
apps/api/src/admin/user-departments/*, apps/api/src/admin/users/*, apps/api/src/admin/admin.module.ts
Adds organization-scoped user listing and transactional department assignment replacement with validation, deduplication, primary-department selection, and integration tests.
Session department data
apps/api/src/auth/*
Extends /api/me with current department IDs and the primary department ID. Tests verify assignments are reread for each request.
Department administration UI
apps/web/app/admin/departments/*, apps/web/app/admin/_components/OrgPicker.tsx, apps/web/app/admin/organizations/OrgTable.tsx
Adds organization selection, department listing, department creation, server actions, API helpers, loading states, error states, and navigation links.
User department administration UI
apps/web/app/admin/_lib/*, apps/web/app/admin/layout.tsx, apps/web/app/admin/users/*, apps/web/app/admin/organizations/api.ts
Adds admin access enforcement, authenticated API helpers, user browsing, department assignment forms, server actions, and assignment page states.

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

Possibly related issues

Possibly related PRs

  • andrmaz/cortex#23 — The PR extends its authentication code to include department data in /api/me.
  • andrmaz/cortex#26 — Both PRs update Prisma mocks for userDepartment lookups.
  • andrmaz/cortex#27 — The PR extends the admin module and organization administration infrastructure.

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.51% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary changes for user and department management.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/user-department-management-70bf

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.

@cursor cursor Bot mentioned this pull request Aug 1, 2026
5 tasks
@andrmaz
andrmaz marked this pull request as ready for review August 3, 2026 20:25
Comment thread apps/web/app/admin/users/api.ts Outdated
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Note

Unit test generation is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

Comment thread apps/web/app/admin/users/AssignDepartmentsForm.tsx
Comment thread apps/web/app/admin/users/page.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment (not approved): Cursor Bugbot completed as skipped and reported unresolved findings that need human attention. No eligible additional reviewers were available to assign on this repository.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ Unit tests committed locally. Commit: b4eb8000ed268cba28e8824a55c99300b9dbe4ba

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
apps/web/app/admin/departments/api.ts (1)

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

Align error handling with createDepartment.

fetchDepartments discards the response body on failure and throws only Failed to fetch departments: ${res.status}. createDepartment in the same file parses the error body for a specific message. Apply the same pattern here so a 404 "Organization not found" or similar backend message reaches the caller instead of a bare status code.

♻️ Proposed refactor
   if (!res.ok) {
-    throw new Error(`Failed to fetch departments: ${res.status}`);
+    const errBody = await parseJsonSafe<{ message?: string }>(res);
+    throw new Error(errBody?.message ?? `Failed to fetch departments: ${res.status}`);
   }
🤖 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 `@apps/web/app/admin/departments/api.ts` around lines 28 - 45, Update
fetchDepartments to handle non-OK responses using the same error-body parsing
pattern as createDepartment, extracting and propagating the backend’s specific
message while retaining the existing status-based fallback when no message is
available.
apps/api/src/admin/user-departments/user-departments.integration.spec.ts (1)

93-109: 🩺 Stability & Availability | 🔵 Trivial

Add a cross-organization authorization test once the ownership check exists.

The authorization block tests 401 and 403 but does not cover an admin token from one organization targeting a user in a different organization. Add this test once the organization-ownership check is implemented in user-department.service.ts (see the linked comment there).

🤖 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 `@apps/api/src/admin/user-departments/user-departments.integration.spec.ts`
around lines 93 - 109, Add an integration test in the authorization block that
issues an admin token for one organization, targets a user belonging to another
organization through the user-departments endpoint, and asserts the ownership
check rejects the request with the expected authorization status. Implement this
after the organization-ownership check in user-department service is available,
following the existing request and token setup patterns.
apps/web/app/admin/users/api.ts (1)

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

Consolidate duplicate admin API helpers.

apps/web/app/admin/users/api.ts, apps/web/app/admin/departments/api.ts, and apps/web/app/admin/organizations/api.ts all define the same getAdminToken, authHeaders, and parseJsonSafe helpers. Extract these into one shared admin API helper under apps/web/app/admin/_lib or a similar private layer to prevent drift and keep maintenance localized.

🤖 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 `@apps/web/app/admin/users/api.ts` around lines 5 - 26, Extract getAdminToken,
authHeaders, and parseJsonSafe into one shared private admin API helper under
the admin _lib layer, then update the users, departments, and organizations API
modules to import and reuse those functions. Remove the duplicate local
definitions while preserving their current behavior and types.
🤖 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 `@apps/api/src/admin/departments/department.service.ts`:
- Around line 10-17: Move the duplicated isPrismaUniqueConstraintError helper
into a shared Prisma error utility, then remove the local definitions from
department.service.ts and user.service.ts and import the shared function in both
callers. Preserve the existing P2002 classification behavior and update
references to use the shared symbol.

In `@apps/api/src/admin/departments/departments.controller.ts`:
- Around line 30-33: Update DepartmentsController authorization so every
department read and create operation validates that the authenticated user’s
organizationId matches the request organizationId before calling
DepartmentService.findAllByOrganization() or create(). Use the validated
`@AuthenticatedUser`() identity and preserve the existing AdminRoleGuard role
check, rejecting mismatches without accessing or modifying departments.

In `@apps/api/src/admin/user-departments/user-department.service.ts`:
- Around line 53-111: Require the authenticated admin’s organization membership
for all listed user and department operations: in
apps/api/src/admin/users/admin-users.controller.ts:24-37, compare
req.user.organizationId with :organizationId before findAllByOrganization; in
apps/api/src/admin/users/admin-user.service.ts:13-27, accept and enforce the
admin organization when listing users; in
apps/api/src/admin/user-departments/user-departments.controller.ts:34-66, read
req.user.organizationId and pass it to findForUser and assign; in
apps/api/src/admin/user-departments/user-department.service.ts:27-111, require
and validate that organization against the target user and departments before
reading or updating assignments.

In `@apps/api/src/admin/user-departments/user-departments.controller.ts`:
- Around line 54-62: Update the departmentIds validation in the controller to
require every array element to be a string before calling
UserDepartmentService.assign, while preserving the existing non-empty-array
check and controlled BadRequestException response for invalid payloads.

In `@apps/web/app/admin/_components/OrgPicker.tsx`:
- Around line 40-42: Update the organization selector around the empty option in
OrgPicker so users can clear an existing selection, either by removing the
disabled attribute or by adding an equivalent clear-selection control; ensure
the empty value can reach the existing basePath branch.

In `@apps/web/app/admin/users/api.ts`:
- Around line 64-85: Update assignUserDepartments to serialize an explicit
sentinel value for primaryDepartmentId when no primary department is selected,
rather than omitting the field. Preserve the provided department ID when
present, and ensure the request body always communicates either the selected
primary or the backend-supported clear value.

In `@apps/web/app/admin/users/AssignDepartmentsForm.tsx`:
- Around line 18-38: Ensure AssignDepartmentsForm resets its local checked and
primary state when the selected user changes; preferably key the
AssignDepartmentsForm instance by userId at the AssignmentPanel call site so it
remounts with the new currentAssignments.

---

Nitpick comments:
In `@apps/api/src/admin/user-departments/user-departments.integration.spec.ts`:
- Around line 93-109: Add an integration test in the authorization block that
issues an admin token for one organization, targets a user belonging to another
organization through the user-departments endpoint, and asserts the ownership
check rejects the request with the expected authorization status. Implement this
after the organization-ownership check in user-department service is available,
following the existing request and token setup patterns.

In `@apps/web/app/admin/departments/api.ts`:
- Around line 28-45: Update fetchDepartments to handle non-OK responses using
the same error-body parsing pattern as createDepartment, extracting and
propagating the backend’s specific message while retaining the existing
status-based fallback when no message is available.

In `@apps/web/app/admin/users/api.ts`:
- Around line 5-26: Extract getAdminToken, authHeaders, and parseJsonSafe into
one shared private admin API helper under the admin _lib layer, then update the
users, departments, and organizations API modules to import and reuse those
functions. Remove the duplicate local definitions while preserving their current
behavior and types.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 01deca4f-be10-4d5d-8748-03876129bd49

📥 Commits

Reviewing files that changed from the base of the PR and between 8d92b45 and 3c7f271.

📒 Files selected for processing (36)
  • apps/api/src/__mocks__/db-client.mock.ts
  • apps/api/src/admin/admin.module.ts
  • apps/api/src/admin/departments/department.dto.ts
  • apps/api/src/admin/departments/department.service.spec.ts
  • apps/api/src/admin/departments/department.service.ts
  • apps/api/src/admin/departments/departments.controller.ts
  • apps/api/src/admin/departments/departments.integration.spec.ts
  • apps/api/src/admin/user-departments/user-department.dto.ts
  • apps/api/src/admin/user-departments/user-department.service.spec.ts
  • apps/api/src/admin/user-departments/user-department.service.ts
  • apps/api/src/admin/user-departments/user-departments.controller.ts
  • apps/api/src/admin/user-departments/user-departments.integration.spec.ts
  • apps/api/src/admin/users/admin-user.service.spec.ts
  • apps/api/src/admin/users/admin-user.service.ts
  • apps/api/src/admin/users/admin-users.controller.ts
  • apps/api/src/admin/users/user.dto.ts
  • apps/api/src/auth/auth.controller.ts
  • apps/api/src/auth/auth.module.ts
  • apps/api/src/auth/auth.types.ts
  • apps/api/src/auth/session.integration.spec.ts
  • apps/api/src/auth/user.service.spec.ts
  • apps/api/src/auth/user.service.ts
  • apps/web/app/admin/_components/OrgPicker.tsx
  • apps/web/app/admin/departments/DeptForm.tsx
  • apps/web/app/admin/departments/DeptTable.tsx
  • apps/web/app/admin/departments/actions.ts
  • apps/web/app/admin/departments/api.ts
  • apps/web/app/admin/departments/page.tsx
  • apps/web/app/admin/departments/types.ts
  • apps/web/app/admin/organizations/OrgTable.tsx
  • apps/web/app/admin/users/AssignDepartmentsForm.tsx
  • apps/web/app/admin/users/UserTable.tsx
  • apps/web/app/admin/users/actions.ts
  • apps/web/app/admin/users/api.ts
  • apps/web/app/admin/users/page.tsx
  • apps/web/app/admin/users/types.ts

Comment thread apps/api/src/admin/departments/department.service.ts Outdated
Comment thread apps/api/src/admin/departments/departments.controller.ts
Comment thread apps/api/src/admin/user-departments/user-department.service.ts
Comment thread apps/api/src/admin/user-departments/user-departments.controller.ts
Comment thread apps/web/app/admin/_components/OrgPicker.tsx Outdated
Comment on lines +64 to +85
export async function assignUserDepartments(
userId: string,
departmentIds: string[],
primaryDepartmentId?: string,
): Promise<{ result?: UserDepartmentsResponse; error?: string }> {
const res = await fetch(
`${API_URL}/api/admin/users/${encodeURIComponent(userId)}/departments`,
{
method: "PUT",
headers: { ...authHeaders(), "Content-Type": "application/json" },
body: JSON.stringify({ departmentIds, primaryDepartmentId }),
signal: AbortSignal.timeout(5000),
},
);

if (!res.ok) {
const errBody = await parseJsonSafe<{ message?: string }>(res);
return { error: errBody?.message ?? "Failed to assign departments" };
}

return { result: (await res.json()) as UserDepartmentsResponse };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect the user-department DTO and service to see how primaryDepartmentId is validated/applied.
fd -a 'user-department.dto.ts|user-department.service.ts' apps/api/src/admin/user-departments -x cat -n {}

Repository: andrmaz/cortex

Length of output: 5875


🏁 Script executed:

#!/bin/bash
# Inspect the API route/controller dispatches and client callers around primaryDepartmentId to understand available forms and whether clear handling exists elsewhere.
set -e
echo "== route files =="
fd -a -t f '.*' apps/api/src/admin/user-departments apps/web/app/admin/users -g '*.ts' -g '*.tsx' | sed 's/^/FILE: /'
echo
echo "== relevant snippets =="
rg -n "assignUserDepartments|primaryDepartmentId|departments" apps/api/src/admin/user-departments apps/web/app/admin/users -g '*.ts' -g '*.tsx' -C 3

Repository: andrmaz/cortex

Length of output: 34673


Send a sentinel value when the user has no primary department.

An omitted primaryDepartmentId triggers default resolution by the existing primary or the first selected department; it does not clear isPrimary. The primary radio is required for each selected department, so admins have no way to leave the user without a primary unless the backend also accepts an explicit clear value.

🤖 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 `@apps/web/app/admin/users/api.ts` around lines 64 - 85, Update
assignUserDepartments to serialize an explicit sentinel value for
primaryDepartmentId when no primary department is selected, rather than omitting
the field. Preserve the provided department ID when present, and ensure the
request body always communicates either the selected primary or the
backend-supported clear value.

Comment thread apps/web/app/admin/users/AssignDepartmentsForm.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment (not approved): Cursor Bugbot previously reported unresolved findings (including a high-severity issue) that still need human attention. No reviewers were assigned because the only available collaborator is the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

- Gate admin routes with session JWT verification via admin layout
- Forward API calls with caller session token instead of env service token
- Reset AssignDepartmentsForm state when userId changes (key prop)
- Validate selected user belongs to org before showing assignment panel

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
@@ -0,0 +1,75 @@
import { adminAuthHeaders } from "../_lib/admin-auth";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: removed the process-wide CORTEX_ADMIN_TOKEN forwarding. API helpers now call adminAuthHeaders(), which runs requireAdminAccess() — reading the session JWT from the cortex_access_token cookie and verifying admin role via GET /api/me. An admin/layout.tsx gate blocks unauthenticated callers before any privileged fetch runs.


return (
<AssignDepartmentsForm
key={userId}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed stale form state: added key={userId} so AssignDepartmentsForm remounts (and resets checked/primary state) whenever the selected user changes or after a successful save + revalidation.

fetchUserDepartments(userId),
]);

const userInOrg = users.some((u) => u.id === userId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed org membership mismatch: AssignmentPanel now loads the org user list and returns an error banner when userId is not in that organization, instead of showing departments for one org against a user from another.

Comment thread apps/web/app/admin/_lib/admin-auth.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment (not approved): Cursor Bugbot completed as skipped and reported an unresolved high-severity finding that still needs human attention. No reviewers were assigned because the only available collaborator is the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@apps/web/app/admin/_lib/admin-auth.ts`:
- Around line 24-37: Wrap the fetch and response parsing in the admin-auth flow
with try/catch so network, timeout, abort, and malformed-JSON failures are
converted to AdminAccessError. Preserve the existing Invalid or expired session
and Admin role required errors, while rethrowing other failures as
AdminAccessError with a clear session-verification message.
- Around line 18-45: Repeated admin authentication checks trigger multiple
/api/me requests within one request. In
apps/web/app/admin/_lib/admin-auth.ts:18-45, wrap requireAdminAccess in React’s
per-request cache while preserving its validation and token-return behavior;
adminAuthHeaders should continue reusing requireAdminAccess. The call sites in
apps/web/app/admin/organizations/api.ts:1-1, 17-17, 33-33, 51-54, and 75-78
require no direct changes because the memoized requireAdminAccess call will
deduplicate their checks.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 25b684ad-48fe-4050-9277-e08a92871ae0

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7f271 and 201ce29.

📒 Files selected for processing (10)
  • apps/api/src/admin/departments/departments.integration.spec.ts
  • apps/api/src/admin/user-departments/user-departments.integration.spec.ts
  • apps/api/src/admin/users/admin-users.integration.spec.ts
  • apps/api/src/auth/user.service.spec.ts
  • apps/web/app/admin/_lib/admin-auth.ts
  • apps/web/app/admin/departments/api.ts
  • apps/web/app/admin/layout.tsx
  • apps/web/app/admin/organizations/api.ts
  • apps/web/app/admin/users/api.ts
  • apps/web/app/admin/users/page.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/app/admin/departments/api.ts
  • apps/web/app/admin/users/api.ts
  • apps/api/src/auth/user.service.spec.ts
  • apps/web/app/admin/users/page.tsx

Comment thread apps/web/app/admin/_lib/admin-auth.ts Outdated
Comment thread apps/web/app/admin/_lib/admin-auth.ts Outdated
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 1 file(s) based on 2 unresolved review comments.

Files modified:

  • apps/web/app/admin/_lib/admin-auth.ts

Commit: 51282e7fd73ec0db44148e647e0abf18e3650c18

The changes have been pushed to the cursor/user-department-management-70bf branch.

Time taken: 2m 16s

Fixed 1 file(s) based on 2 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment (not approved): an unresolved high-severity Cursor Bugbot finding still needs human attention. No reviewers were assigned because the only assignable collaborator is the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

- Extract shared isPrismaUniqueConstraintError helper
- Enforce org-scoped admin access on department/user endpoints
- Validate departmentIds elements are non-empty strings
- Allow clearing organization selection in OrgPicker
- Add integration tests for cross-org and invalid payloads

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>
* Narrows an unknown thrown value to a Prisma unique-constraint error (P2002).
* Avoids importing Prisma runtime types into the CommonJS API workspace.
*/
export function isPrismaUniqueConstraintError(err: unknown): boolean {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: moved isPrismaUniqueConstraintError into apps/api/src/common/prisma-errors.ts and updated department.service.ts, user.service.ts, and organization.service.ts to import the shared helper.

@Req() req: RequestWithUser,
@Param("organizationId") organizationId: string,
): Promise<DepartmentResponseDto[]> {
assertAdminOrganizationAccess(req.user.organizationId, organizationId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: DepartmentsController now calls assertAdminOrganizationAccess(req.user.organizationId, organizationId) before list/create operations. Added integration coverage for cross-org requests (403).

departmentId,
isPrimary: departmentId === primaryDepartmentId,
},
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: admin org membership is enforced end-to-end.

  • Controllers pass req.user.organizationId into user-department service methods.
  • ensureUserInOrganization rejects users outside the admin's org with 403.
  • AdminUsersController and DepartmentsController use the same org guard.
  • Integration tests cover cross-org access.

throw new BadRequestException("departmentIds must be a non-empty array");
}
if (
body.departmentIds.some((id) => typeof id !== "string" || id.length === 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: added validation that every departmentIds element is a non-empty string before calling the service. Integration test covers { departmentIds: [123] } → 400.

}}
style={selectStyle}
>
<option value="">Select an organization…</option>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: removed disabled from the empty option so admins can clear the organization selection; the existing onChange handler already navigates to basePath when the value is empty.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

Left a non-blocking comment (not approved): Cursor Bugbot completed as skipped, and an unresolved high-severity Bugbot finding still needs human attention. No reviewers were assigned because the only assignable collaborator is the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@andrmaz

andrmaz commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent Fix the unresolved high-severity Bugbot finding mentioned in #30 (review)

@cursor

cursor Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

CI is green and the fix is already on the branch. The Bugbot finding is resolved.

Summary

The high-severity Bugbot finding ("Admin cookie never set") was already fixed by a concurrent commit (197ed26, "fix(web): wire OAuth callback to set admin session cookie") that landed on the branch while I was working. Rather than push a competing fix, I discarded my own draft solution (a manual paste-a-token bridge) and instead thoroughly validated that the existing fix actually resolves the bug end-to-end, since redirect-wiring bugs like this one aren't caught by type-checking or unit tests.

What the landed fix does: it wires a real OAuth redirect chain — a new /auth/login route redirects to the API's GET /auth/google, and the API's callback now redirects back to a new /auth/callback route (instead of returning bare JSON) which sets the httpOnly cortex_access_token cookie and forwards to /admin/organizations. A /auth/logout route clears it, and the admin gate links to /auth/login.

How I verified it: I spun up a mock API server simulating /auth/google/auth/google/callback/api/me, built and ran the real web app against it, and drove the flow with a headless Chrome instance via puppeteer-core:

  • Unauthenticated request to /admin/organizations → correctly shows the gate with a "Sign in with Google" link.
  • Following that link through the full redirect chain → cookie gets set (httpOnly: true, sameSite: Lax) and lands on /admin/organizations, which renders real data.
  • Visiting /auth/logout → cookie is cleared and a fresh unauthenticated request shows the gate again.

I also re-ran pnpm check-types, pnpm lint, and pnpm test (175 API tests) against the current branch state — all pass, and CI on the PR is green. No further changes were needed from me.

View PR

Open in Web Open in Cursor 

The admin auth module read cortex_access_token from cookies but nothing
ever set it after removing CORTEX_ADMIN_TOKEN. Add login/callback/logout
routes in the web app, redirect the API Google callback to /auth/callback,
and show a sign-in link on the admin gate.

Co-authored-by: Andrea Mazzucchelli <andrmaz@users.noreply.github.com>

const API_URL = process.env["CORTEX_API_URL"] ?? "http://localhost:3001";

export async function setAccessTokenCookie(token: string): Promise<void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Fixed: the cortex_access_token cookie is now set after Google OAuth completes.

  • Added setAccessTokenCookie / clearAccessTokenCookie helpers here
  • /auth/login redirects to the API Google OAuth flow
  • /auth/callback reads accessToken and sets the httpOnly session cookie (8h, matching JWT expiry)
  • API GET /auth/google/callback now redirects to the web callback instead of returning JSON
  • Admin gate shows a "Sign in with Google" link when unauthenticated

Flow: /auth/login → API OAuth → /auth/callback?accessToken=… → cookie set → /admin/organizations

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 197ed26. Configure here.

const token = this.authService.issueToken(authenticatedUser);

callbackUrl.searchParams.set("accessToken", token);
res.redirect(callbackUrl.toString());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

JWT exposed in redirect URL

High Severity

The Google OAuth callback now redirects with the JWT in the accessToken query parameter. This exposes the session credential in browser history, referrer headers, and server/proxy access logs, creating a security risk.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 197ed26. Configure here.

const message =
error instanceof Error ? error.message : "Authentication failed";
callbackUrl.searchParams.set("error", message);
res.redirect(callbackUrl.toString());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

OAuth errors leak domain

Medium Severity

On OAuth failure, the googleCallback endpoint redirects with raw exception messages in the error query parameter. These messages can expose sensitive data, like a user's email domain, in URLs, logs, and browser history.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 197ed26. Configure here.


/** Starts Google OAuth on the API; callback redirects back to set the session cookie. */
export function GET(): never {
redirect(`${API_URL}/auth/google`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wrong default API port

High Severity

The web app defaults CORTEX_API_URL to http://localhost:3001. When this environment variable is unset, OAuth login and admin API calls fail because the API actually runs on port 4000.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 197ed26. Configure here.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Left a non-blocking comment (not approved): Cursor Bugbot completed as skipped and reported unresolved findings (including high-severity issues) that still need human attention. No reviewers were assigned because the only assignable collaborator is the PR author.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

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