Skip to content

feat(api,web): enforce 403 on unauthorized reads and close error-code gaps - #32

Merged
Icyretsz merged 4 commits into
devfrom
feat/error-codes-access-control
Nov 12, 2026
Merged

Icyretsz merged 4 commits into
devfrom
feat/error-codes-access-control

Conversation

@Icyretsz

Copy link
Copy Markdown
Collaborator

Summary

Unauthorized access to an existing private record now returns 403 Forbidden with a specific domain code, instead of being hidden behind a 404. The 404 remains for records that genuinely do not exist.

Backend

  • Register the missing domain codes in @researchos/error-codes and raise them as typed CodedHttpException instances.
  • Replace the remaining prose exceptions with machine-readable codes, so the frontend never receives English sentences.
  • Background jobs now distinguish a missing job (404) from one the caller may not access (403), keeping admin access to research-update jobs.

Frontend

  • Add English and Vietnamese translations for every new domain code.
  • The catalog spec keeps both locales and the code registry in step.

Verification

  • api: 448 tests passed
  • web: 85 tests passed
  • @researchos/error-codes: 6 tests passed
  • lint: 0 errors
  • openapi:check: document up to date

Icyretsz and others added 4 commits November 12, 2026 19:11
… gaps

Unauthorized access to an existing private record now returns 403 Forbidden
with a specific domain code instead of being hidden behind a 404. The
404 remains for records that genuinely do not exist.

Backend:
- register the missing domain codes in @researchos/error-codes and raise
  them as typed CodedHttpException instances
- replace the remaining prose exceptions with machine-readable codes so
  the frontend never receives English sentences
- background jobs: distinguish a missing job (404) from one the caller
  may not access (403), keeping admin access to research-update jobs

Frontend:
- add English and Vietnamese translations for every new domain code
- the catalog spec keeps both locales and the registry in step

Verified: api 448 tests, web 85 tests, error-codes 6 tests, lint clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
The e2e suite still asserted the old behaviour, where a job owned by
another caller was reported as missing (404). The job does exist, so the
callers without access are now denied with 403 and 404 is reserved for a
job that does not exist.

The owner, the non-owner admin and the non-admin are all denied on the
same existing job, so the three assertions move from 404 to 403. The
owner retry still conflicts (409) because the job is not failed.

Co-authored-by: Cursor <cursoragent@cursor.com>
The enum carried three values but only one of them described authority.
CORE_MEMBER was the value the policy read to decide who may manage a
group's membership; COLLABORATOR and STUDENT_MEMBER were labels that
granted nothing and were never read by a policy.

The type now says what the role does. MEMBER is a group member without
management rights and GROUP_LEADER is the member who may manage the
membership. The group's own leader is still research_groups.lead_id, so
a GROUP_LEADER membership row is the delegated equivalent and both are
accepted by the same rule.

- ResearchOS.dbml is the schema source; ResearchOS.sql is regenerated
  from it with the official exporter and the datamodel in schema.prisma
  follows, so the drift check stays green
- migration rebuilds the type: Postgres cannot rename or drop a value
  that is still in use, so the rows are cast through text onto the new
  type, with CORE_MEMBER becoming GROUP_LEADER and the rest MEMBER
- can_manage_opportunity names the new value
- the API, the seed script and the generated OpenAPI client follow

Co-authored-by: Cursor <cursoragent@cursor.com>
Prettier reformats the it.each table after the duplicate non-manager case
was dropped.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Icyretsz
Icyretsz merged commit 1359d4a into dev Nov 12, 2026
4 checks passed
@Icyretsz
Icyretsz deleted the feat/error-codes-access-control branch November 12, 2026 12:44
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.

1 participant