Skip to content

Prod - April release#18

Merged
jmgasper merged 22 commits into
masterfrom
dev
Apr 14, 2026
Merged

Prod - April release#18
jmgasper merged 22 commits into
masterfrom
dev

Conversation

@jmgasper
Copy link
Copy Markdown
Contributor

jmgasper and others added 21 commits March 31, 2026 10:15
What was broken
QA reported that M2M POST, PATCH, and DELETE requests against /v6/projects/{projectId}/members were still returning 403 responses after the earlier permission changes.

Root cause (if identifiable)
The earlier fix path added M2M write-scope handling in the permission layer, but the exact guarded HTTP flows for the project-member write endpoints were not covered by regression tests, so the QA scenario was not exercised directly.

What was changed
Added explicit project-member e2e coverage for client-credentials tokens with project-member write scope on POST, PATCH, and DELETE routes.
Verified the attached M2M token shape still resolves to the expected write permissions on the current dev baseline before adding the regression coverage.

Any added/updated tests
Updated test/project-member.e2e-spec.ts with M2M POST/PATCH/DELETE coverage.
Validated the updated spec with pnpm test:e2e -- --runInBand test/project-member.e2e-spec.ts.
What was broken
The v6 named-permission path no longer matched tc-project-service for several project read flows. Project Manager, Task Manager, Talent Manager, and related manager-tier roles could be blocked from listing projects, viewing projects, or reading project members, invites, and attachments unless they were explicit project members.

Root cause (if identifiable)
The earlier PM-3764 compatibility work restored many v5 response and M2M behaviors, but the Nest named-permission checks were narrower than the legacy v5 permission constants. QA therefore still hit access failures even after the broader compatibility and deployment fixes.

What was changed
Restored the legacy v5 project-read Topcoder role allowlist inside PermissionService for READ_PROJECT_ANY and VIEW_PROJECT.
Restored manager-tier read access for project members, invites, and attachments so the named-permission path matches the legacy service more closely.
Documented the restored legacy read-access behavior in docs/PERMISSIONS.md.

Any added/updated tests
Expanded PermissionService regression coverage for legacy project-read roles and manager-tier read access to project members, invites, and attachments.
Verified the affected project controller and service unit suites still pass.
PM-4211: add M2M project member write regression tests
PM-3764: restore legacy project read role parity
What was broken
The previous PM-3764 fix restored legacy read parity inside PermissionService, but some QA users could still be denied before those permission checks ran. Tokens carrying the legacy `topcoder_manager` role were excluded from controller-level `@Roles(...Object.values(UserRole))` allowlists, so the read-parity logic never executed for them.

Root cause (if identifiable)
PermissionService and the Swagger permission documentation still knew about the legacy `topcoder_manager` role, but the shared UserRole enum did not. Route-level role gates derive their allowlists from `Object.values(UserRole)`, which left the coarse auth layer out of sync with the PM-3764 compatibility logic.

What was changed
Added `UserRole.TOPCODER_MANAGER` for the legacy JWT role and updated the permission/documentation helpers to use the enum-backed value.
Kept the existing PM-3764 read-parity behavior intact while extending regression coverage for legacy `topcoder_manager` access through the route guard and read-permission paths.
Documented that `topcoder_manager` is accepted by both route guards and PermissionService.

Any added/updated tests
Added a TokenRolesGuard regression test covering `topcoder_manager` against `Object.values(UserRole)` route allowlists.
Expanded PermissionService regression coverage for `topcoder_manager` project, member, invite, and attachment read access.
Validated with `pnpm lint`, targeted auth regression tests, and `pnpm build`.
The full `pnpm test` suite still has the existing unrelated metadata event-bus failures on the current `dev` baseline.
What was broken
M2M POST, PATCH, and DELETE requests for project members could still return 403 even when the raw token payload carried project-member write scope.

Root cause (if identifiable)
The permission layer preferred user.scopes when they were present, while the route guard evaluated scopes from the raw token payload. If those two scope sources drifted, the guard could admit the request and the project-member service could still reject it.

What was changed
Merged normalized scopes from both user.scopes and the raw token payload in PermissionService so downstream permission checks use the same effective M2M grants that the auth guard sees.
Added a regression that covers create, update, and delete project-member permissions when the raw token payload is broader than user.scopes.

Any added/updated tests
Updated src/shared/services/permission.service.spec.ts with create, update, and delete project-member M2M regression coverage for mismatched scope sources.
PM-4211: merge M2M member scopes across auth layers
PM-3764: allow legacy topcoder_manager through route guards
What was broken
The earlier PM-4211 runtime fix handled M2M project-member writes, but the exact Auth0 client-credentials token shape from QA was still not covered by automated regression. That left the POST, PATCH, and DELETE member flows vulnerable to future regressions without a failing test.

Root cause (if identifiable)
The existing route e2e coverage mocked a simplified machine user, and JwtService coverage did not assert the non-numeric @Clients subject plus project-member write scope combination from the QA token.

What was changed
Added JwtService coverage for an Auth0 client-credentials subject carrying project-member write scope.
Added guarded project-member POST, PATCH, and DELETE regression coverage for an Auth0-shaped M2M principal whose raw token payload is broader than user.scopes.

Any added/updated tests
Updated src/shared/modules/global/jwt.service.spec.ts.
Updated test/project-member.e2e-spec.ts.
What was broken
Work stream, work, and work item read routes could still reject legitimate Projects API users before or during permission evaluation. The work-layer controllers only admitted a narrow role subset, and the named work-view permissions still excluded manager-tier legacy project-view access.

Root cause (if identifiable)
The earlier PM-3764 follow-ups restored legacy read parity for projects, members, invites, and attachments, but the work-layer endpoints were still using narrower auth rules than the legacy v5 projectView policy.

What was changed
Broadened the work-layer coarse role gate to accept all known human roles so project-member access reaches PermissionGuard.
Updated work-layer view permissions to allow manager-tier legacy project-view access and machine connect_project_admin scope, and aligned the Swagger/docs output with that behavior.
Documented the remaining PM-3764 work-layer read parity in the permissions docs.

Any added/updated tests
Added a TokenRolesGuard regression for Topcoder User tokens on work-layer routes.
Expanded PermissionService regression coverage for manager-tier work-layer reads and machine connect_project_admin scope.
Validated with pnpm lint, targeted auth/work regressions, and pnpm build.
Full pnpm test still has the existing unrelated metadata event-bus failures on the current dev baseline.
PM-3764: restore work-layer read parity
PM-4211: cover Auth0 M2M member token shape
PM-4720 Make copilot read endpoints public
PM-4720 Handle public user on Public copilot routes
What was broken
PM and TM users could list every project in Work Manager and open project records they were not part of.

Root cause
ProjectService treated PM/TM-style roles as global project readers when building the projects query and when resolving a project by id, so membership scoping was bypassed unless the client explicitly requested memberOnly filtering.

What was changed
Added a service-level global-read check that only bypasses membership scoping for admins, legacy manager roles, and authorized machine principals.
Applied that check to project listing, direct project fetches, and project response filtering so PM/TM callers must now be project members or pending invitees.

Any added/updated tests
Added ProjectService coverage for PM/TM project list scoping and for rejecting direct project access when the caller is not on the project.
Validated with pnpm test -- src/api/project/project.service.spec.ts, pnpm lint, and pnpm build.
The repo-wide pnpm test run still fails in unrelated metadata event-publishing specs.
PM-4847: scope PM/TM project access to memberships
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR bundles April production release updates focused on permission/read-parity adjustments, improved billing-account lookups, and expanded test coverage around Auth0/M2M token handling.

Changes:

  • Extend/adjust legacy role-based read access paths (projects, members/invites/attachments, work layer) and align M2M scope evaluation by merging user.scopes with token-claim scopes.
  • Add Billing Accounts API lookup for default billing account with Salesforce fallback, plus env/config updates and tests.
  • Add/expand tests for Auth0-shaped client-credentials tokens and updated Swagger auth documentation expectations.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/project-member.e2e-spec.ts Adds e2e coverage for M2M member write operations and Auth0-shaped scope behavior.
test/deployment-validation.e2e-spec.ts Updates deployment validation to accept new Billing Accounts API env var with fallback.
src/shared/utils/swagger.utils.spec.ts Adds Swagger auth doc test coverage for global project-read machine scopes.
src/shared/utils/permission-docs.utils.ts Updates documented role/scope summaries for some permissions (but needs alignment for READ_PROJECT_ANY).
src/shared/services/permission.service.ts Expands legacy read role logic, adjusts work-layer permission evaluation, and merges scope sources for M2M consistency.
src/shared/services/permission.service.spec.ts Adds unit coverage for new permission behaviors and merged-scope scenarios.
src/shared/services/billingAccount.service.ts Implements Billing Accounts API lookup with M2M auth + Salesforce fallback and adds response mapping utilities.
src/shared/services/billingAccount.service.spec.ts Introduces unit tests for Billing Accounts API success and Salesforce fallback.
src/shared/modules/global/jwt.service.spec.ts Adds unit test for extracting Auth0 client-credentials scopes for machine subjects.
src/shared/guards/tokenRoles.guard.spec.ts Adds coverage for legacy topcoder_manager and broadened work-layer role pass-through.
src/shared/enums/userRole.enum.ts Adds UserRole.TOPCODER_MANAGER enum value and defines MANAGER_ROLES.
src/shared/constants/roles.ts Broadens WORK_LAYER_ALLOWED_ROLES to all known roles for downstream permission enforcement.
src/shared/config/service-endpoints.config.ts Adds billingAccountsApiUrl and env var fallbacks for member/identity endpoints.
src/api/workstream/workstream.controller.ts Updates docstring to reflect new work-layer auth approach (pass-through + PermissionGuard).
src/api/project/project.service.ts Refactors global project read access decisions and tightens direct project visibility checks.
src/api/project/project.service.spec.ts Adds tests ensuring PM/TM roles are membership-scoped for listings and direct access is rejected when not on project.
src/api/copilot/copilot.utils.ts Makes admin/manager helpers tolerate undefined user for public routes.
src/api/copilot/copilot-opportunity.service.ts Allows user to be undefined and adjusts logic accordingly.
src/api/copilot/copilot-opportunity.controller.ts Marks opportunity browse/get endpoints @Public() and accepts @CurrentUser() as optional (but introduces an auth-context issue).
docs/PERMISSIONS.md Documents legacy read access behavior and role handling.
README.md Updates endpoint description and documents BILLING_ACCOUNTS_API_URL.
.env.example Adds BILLING_ACCOUNTS_API_URL.
Comments suppressed due to low confidence (2)

src/api/copilot/copilot-opportunity.controller.ts:70

  • @Public() short-circuits TokenRolesGuard before JWT validation, so @CurrentUser() will always be undefined even when a caller provides an Authorization: Bearer ... header. That breaks the endpoint’s documented behavior (e.g., “Admin and manager callers also receive minimal nested project metadata”) and makes @Roles/@Scopes metadata ineffective at runtime. If auth should be optional here, consider adding an “optional auth” path that still parses the token when present; if auth should be required, replace @Public() with @AnyAuthenticated()/@UseGuards accordingly.
  @Get('copilots/opportunities')
  @Public()
  @Roles(...Object.values(UserRole))
  @Scopes(
    Scope.PROJECTS_READ,
    Scope.PROJECTS_WRITE,
    Scope.PROJECTS_ALL,
    Scope.CONNECT_PROJECT_ADMIN,
  )

src/api/copilot/copilot-opportunity.controller.ts:121

  • Same issue as the list endpoint: @Public() bypasses JWT validation, so this handler cannot distinguish authenticated callers from anonymous ones (and will never see user even if a token is sent). Revisit the auth approach if you need optional-but-parsed authentication, otherwise require a validated token.
  @Get('copilot/opportunity/:id')
  @Get('copilots/opportunity/:id')
  @Public()
  // TODO [QUALITY]: Two route decorators (singular/plural) map to the same handler for legacy compatibility; document which route is canonical.
  @Roles(...Object.values(UserRole))
  @Scopes(
    Scope.PROJECTS_READ,
    Scope.PROJECTS_WRITE,
    Scope.PROJECTS_ALL,
    Scope.CONNECT_PROJECT_ADMIN,
  )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 236 to 240
case NamedPermission.READ_PROJECT_ANY:
return createSummary({
userRoles: ADMIN_AND_MANAGER_ROLES,
scopes: PROJECT_READ_SCOPES,
});
@jmgasper jmgasper merged commit 34a8011 into master Apr 14, 2026
8 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.

4 participants