Skip to content

fix(auth): unify role model to single EMPLOYER-inclusive enum - #14

Merged
merlik787-droi merged 1 commit into
Kqirox:mainfrom
Magrexy:fix/issue-4-role-model-consistency
Aug 19, 2026
Merged

fix(auth): unify role model to single EMPLOYER-inclusive enum#14
merlik787-droi merged 1 commit into
Kqirox:mainfrom
Magrexy:fix/issue-4-role-model-consistency

Conversation

@Magrexy

@Magrexy Magrexy commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #4

This PR collapses three incompatible role definitions into a single uppercase source of truth aligned with the Prisma Role enum, adds EMPLOYER as a first-class member, and removes the attack surface that let callers self-assign elevated roles at registration. The critical design decision is treating the database enum as canonical and deriving every downstream comparison from it.

Why

Before this change the codebase had three independent role vocabularies:

  • prisma/schema.prisma used ADMIN | LEARNER | INSTRUCTOR (uppercase, no EMPLOYER)
  • src/types/user.types.ts had admin | learner | instructor (lowercase values)
  • src/middleware/auth.middleware.ts declared type UserRole = 'learner' | 'employer' (different set entirely)

generateToken signed the Prisma uppercase value into the JWT, but authorize() compared it against lowercase strings — so authorize('learner') always rejected a token carrying role: 'LEARNER'. The employer B2B surface was entirely unreachable because EMPLOYER didn't exist in the schema, and register() wrote lowercase 'learner' (a value rejected by Prisma's enum validation), causing a 500 on default signup.

What was built

File What it contains
prisma/schema.prisma EMPLOYER added to Role enum
prisma/migrations/20260819000001_add_employer_role/migration.sql ALTER TYPE "Role" ADD VALUE 'EMPLOYER' migration
src/types/user.types.ts UserRole enum values changed to uppercase (ADMIN='ADMIN', LEARNER='LEARNER', INSTRUCTOR='INSTRUCTOR', EMPLOYER='EMPLOYER')
src/middleware/auth.middleware.ts UserRole type expanded to all four uppercase roles
src/schemas/auth.schema.ts role field removed from registerSchema entirely
src/controllers/auth.controller.ts register() always writes UserRole.LEARNER; role no longer destructured from validated input
src/controllers/employer.controller.ts isEmployer() checks === 'EMPLOYER' (uppercase)
src/routes/v1/employer.routes.ts authorize('EMPLOYER')
tests/unit/role-model.test.ts New test file (14 tests) covering enum shape, authorize pass/reject, register always produces LEARNER
tests/unit/auth.middleware.test.ts Role value fixtures updated to uppercase
tests/unit/employer.controller.test.ts All role: 'employer' fixtures → 'EMPLOYER'
tests/unit/employer.routes.test.ts makeToken signature and calls updated

Integration changes outside individual files

  • src/schemas/auth.schema.tsrole field deleted from registerSchema. Callers who sent role in the body are silently ignored by schema parsing (not a breaking change for existing valid clients).
  • src/middleware/auth.middleware.tsUserRole type is now a strict union of all four uppercase enum values. Any external code passing lowercase roles will get a TypeScript error at compile time (intentional: catches stale consumers immediately).

Acceptance criteria coverage

  • register no longer reads a role value from the request body; created user's role is LEARNER (tests/unit/role-model.test.ts — "creates the user with role LEARNER when no role is supplied" and "even when client sends role: EMPLOYER")
  • register persists a valid member of the Prisma Role enum and does not fail with a validation error for default signup (tests/unit/role-model.test.ts — "returns a token whose role claim is LEARNER"; build passes confirming Prisma types accepted)
  • A user with database role EMPLOYER receives a JWT whose role claim lets them pass authorize('employer') (tests/unit/role-model.test.ts — "allows a user with EMPLOYER role to pass authorize('EMPLOYER')"; tests/unit/employer.routes.test.ts — EMPLOYER token gets 200 on /search)
  • A user with database role LEARNER is rejected 403 from employer routes (tests/unit/role-model.test.ts — "rejects a LEARNER from the EMPLOYER-only route with 403"; tests/unit/employer.routes.test.ts — LEARNER token gets 403)
  • Unit tests assert default-role registration and employer access, replacing inconsistent casing assertions (tests/unit/role-model.test.ts, updated tests/unit/auth.middleware.test.ts, tests/unit/employer.controller.test.ts, tests/unit/employer.routes.test.ts)
  • docs/API.md (or Swagger annotations) reflects canonical role values — Swagger annotations updated in source controllers; full docs/API.md prose update is a documentation-only follow-on that does not affect runtime behavior

Test plan

  • pnpm test:ci290/290 passing (14 new tests for this feature in tests/unit/role-model.test.ts)
  • pnpm build (tsc) — no new type errors
  • pnpm lint — no new errors or warnings versus base
  • DATABASE_URL=... npx prisma generate — Prisma client generated successfully with EMPLOYER in Role enum

Env vars / Notes

No new env vars. The migration 20260819000001_add_employer_role must be applied before deploy:

pnpm db:migrate

Existing users have role: 'LEARNER' or 'ADMIN' or 'INSTRUCTOR' in the database; the ADD VALUE migration is non-destructive and backward-compatible. Any user who needs the EMPLOYER role must be updated via a direct database write or a future admin endpoint — there is deliberately no API path to self-assign it.

- Add EMPLOYER to prisma/schema.prisma Role enum with migration
  (20260819000001_add_employer_role/migration.sql)
- Align UserRole enum values in src/types/user.types.ts to uppercase
  (ADMIN, LEARNER, INSTRUCTOR, EMPLOYER) matching the Prisma enum
- Remove role field from registerSchema so callers cannot self-assign
  roles; register() always persists Role.LEARNER
- Update src/middleware/auth.middleware.ts UserRole type to include all
  four canonical roles (uppercase) so authorize() comparisons are consistent
- Update isEmployer() check in employer.controller.ts to EMPLOYER
- Update employer.routes.ts to authorize('EMPLOYER')
- Update test fixtures in auth.middleware, employer.controller, and
  employer.routes tests to use uppercase role values
- Add tests/unit/role-model.test.ts covering: UserRole enum shape,
  authorize EMPLOYER/LEARNER, register always writes LEARNER regardless
  of client-supplied role field

Closes Kqirox#4

@merlik787-droi merlik787-droi 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.

LGTM

@merlik787-droi
merlik787-droi merged commit 7e4c36b into Kqirox:main Aug 19, 2026
1 check passed
P3az3 added a commit to P3az3/Orivex-Backend that referenced this pull request Aug 19, 2026
Use the uppercase EMPLOYER role in the persisted-plan test fixtures to
match the unified role enum from Kqirox#14, and mock prisma.quizQuestion.findMany
in the module completion webhook test now that completeModule grades
against server-side answer keys (Kqirox#16).
merlik787-droi pushed a commit that referenced this pull request Aug 19, 2026
* fix(employer): enforce plan tier from persisted employer record

Replace the client-controlled x-employer-plan header with a persisted
plan field on the User model (default starter). searchTalent and
contactCandidate now resolve the plan from the authenticated employer's
database row, so the per-plan page cap and the pro/enterprise contact
gate can no longer be bypassed by a spoofed header. Adds a Prisma
migration and tests for starter/pro/enterprise resolution and header
spoofing.

* test: fix CI failures from role-model and quiz-grading merges

Use the uppercase EMPLOYER role in the persisted-plan test fixtures to
match the unified role enum from #14, and mock prisma.quizQuestion.findMany
in the module completion webhook test now that completeModule grades
against server-side answer keys (#16).

---------

Co-authored-by: P3az3 <P3az3@users.noreply.github.com>
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.

Role model is inconsistent across three definitions: employer authorization can never succeed

2 participants