Skip to content

feat: add role-based field visibility enforcement at the serialization layer#916

Open
Xaxxoo wants to merge 1 commit into
rinafcode:mainfrom
Xaxxoo:feat/877-role-visibility-interceptor
Open

feat: add role-based field visibility enforcement at the serialization layer#916
Xaxxoo wants to merge 1 commit into
rinafcode:mainfrom
Xaxxoo:feat/877-role-visibility-interceptor

Conversation

@Xaxxoo

@Xaxxoo Xaxxoo commented Jun 27, 2026

Copy link
Copy Markdown

Closes #877

Summary

Replaces per-endpoint manual masking with a declarative, interceptor-driven approach so any new endpoint that returns a User object is automatically protected without extra code.

New files:

File Purpose
src/common/decorators/visible-to.decorator.ts @VisibleTo(...roles) property decorator; stores role lists in reflect-metadata on the class constructor
src/common/interceptors/role-visibility.interceptor.ts RoleVisibilityInterceptor registered as a global APP_INTERCEPTOR; strips restricted fields before the response leaves the process
src/common/interceptors/role-visibility.interceptor.spec.ts 12 unit tests

Modified files:

  • src/users/entities/user.entity.ts — adds @VisibleTo(UserRole.ADMIN) to refreshToken, passwordHistory, providerAccessToken, and providerRefreshToken
  • src/app.module.ts — registers RoleVisibilityInterceptor as a global interceptor

How it works:

  1. @VisibleTo(UserRole.ADMIN) on an entity field writes the allowed roles into reflect-metadata keyed by the class constructor.
  2. For every response, RoleVisibilityInterceptor resolves the viewer's role from request.user, walks the value recursively (supports arrays and {data:[]} / {items:[]} paginated shapes), and deletes any field whose @VisibleTo list does not include the viewer's role.
  3. Fields without any @VisibleTo annotation are always returned as-is — zero risk of breaking existing endpoints.

Test plan

  • Unauthenticated requests pass through unchanged
  • ADMIN receives all fields including @VisibleTo(ADMIN) ones
  • STUDENT role: secretToken (@VisibleTo(ADMIN)) is stripped
  • STUDENT role: internalScore (@VisibleTo(ADMIN, MODERATOR)) is stripped
  • STUDENT role: non-annotated public fields are preserved
  • MODERATOR role: sees @VisibleTo(ADMIN, MODERATOR) field but not @VisibleTo(ADMIN) field
  • Array responses: restriction applied to every element
  • Paginated { data: [] } shape: restriction applied inside array
  • Paginated { items: [] } shape: restriction applied inside array
  • Primitive and null values pass through unchanged
  • Plain objects without annotations return all fields
  • All 12 unit tests pass (npm test)

…yer (rinafcode#877)

Replace per-endpoint manual masking with a declarative, interceptor-driven
approach so any newly added field is automatically protected the moment it
carries a @VisibleTo annotation.

Changes:
- Add @VisibleTo(...roles) property decorator
  (src/common/decorators/visible-to.decorator.ts) that stores role lists in
  reflect-metadata on the entity constructor
- Add RoleVisibilityInterceptor (src/common/interceptors/role-visibility.interceptor.ts)
  registered globally as APP_INTERCEPTOR; strips @VisibleTo fields that the
  viewer's role is not permitted to see before the response reaches the client
- Supports single objects, arrays, and paginated {data:[]} / {items:[]} shapes
- Annotate User entity fields:
    @VisibleTo(UserRole.ADMIN) refreshToken
    @VisibleTo(UserRole.ADMIN) passwordHistory
    @VisibleTo(UserRole.ADMIN) providerAccessToken
    @VisibleTo(UserRole.ADMIN) providerRefreshToken
- 12 unit tests covering ADMIN/STUDENT/MODERATOR roles, arrays, paginated
  responses, primitives, null, and plain objects without annotations
@drips-wave

drips-wave Bot commented Jun 27, 2026

Copy link
Copy Markdown

@Xaxxoo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Great job so far

There’s just one blocker — the workflow is failing. Could you take a look and fix it so all checks pass?

Happy to review again once that’s done.

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.

Add role-based field visibility enforcement at the serialization layer

2 participants