fix(auth): unify role model to single EMPLOYER-inclusive enum - #14
Merged
merlik787-droi merged 1 commit intoAug 19, 2026
Merged
Conversation
- 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
Closed
6 tasks
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #4
This PR collapses three incompatible role definitions into a single uppercase source of truth aligned with the Prisma
Roleenum, addsEMPLOYERas 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.prismausedADMIN | LEARNER | INSTRUCTOR(uppercase, noEMPLOYER)src/types/user.types.tshadadmin | learner | instructor(lowercase values)src/middleware/auth.middleware.tsdeclaredtype UserRole = 'learner' | 'employer'(different set entirely)generateTokensigned the Prisma uppercase value into the JWT, butauthorize()compared it against lowercase strings — soauthorize('learner')always rejected a token carryingrole: 'LEARNER'. The employer B2B surface was entirely unreachable becauseEMPLOYERdidn't exist in the schema, andregister()wrote lowercase'learner'(a value rejected by Prisma's enum validation), causing a 500 on default signup.What was built
prisma/schema.prismaEMPLOYERadded toRoleenumprisma/migrations/20260819000001_add_employer_role/migration.sqlALTER TYPE "Role" ADD VALUE 'EMPLOYER'migrationsrc/types/user.types.tsUserRoleenum values changed to uppercase (ADMIN='ADMIN',LEARNER='LEARNER',INSTRUCTOR='INSTRUCTOR',EMPLOYER='EMPLOYER')src/middleware/auth.middleware.tsUserRoletype expanded to all four uppercase rolessrc/schemas/auth.schema.tsrolefield removed fromregisterSchemaentirelysrc/controllers/auth.controller.tsregister()always writesUserRole.LEARNER;roleno longer destructured from validated inputsrc/controllers/employer.controller.tsisEmployer()checks=== 'EMPLOYER'(uppercase)src/routes/v1/employer.routes.tsauthorize('EMPLOYER')tests/unit/role-model.test.tstests/unit/auth.middleware.test.tstests/unit/employer.controller.test.tsrole: 'employer'fixtures →'EMPLOYER'tests/unit/employer.routes.test.tsmakeTokensignature and calls updatedIntegration changes outside individual files
src/schemas/auth.schema.ts—rolefield deleted fromregisterSchema. Callers who sentrolein the body are silently ignored by schema parsing (not a breaking change for existing valid clients).src/middleware/auth.middleware.ts—UserRoletype 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
registerno longer reads arolevalue from the request body; created user's role isLEARNER(tests/unit/role-model.test.ts— "creates the user with role LEARNER when no role is supplied" and "even when client sends role: EMPLOYER")registerpersists a valid member of the PrismaRoleenum 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)EMPLOYERreceives a JWT whoseroleclaim lets them passauthorize('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)LEARNERis 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)tests/unit/role-model.test.ts, updatedtests/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; fulldocs/API.mdprose update is a documentation-only follow-on that does not affect runtime behaviorTest plan
pnpm test:ci— 290/290 passing (14 new tests for this feature intests/unit/role-model.test.ts)pnpm build(tsc) — no new type errorspnpm lint— no new errors or warnings versus baseDATABASE_URL=... npx prisma generate— Prisma client generated successfully withEMPLOYERinRoleenumEnv vars / Notes
No new env vars. The migration
20260819000001_add_employer_rolemust be applied before deploy:Existing users have
role: 'LEARNER'or'ADMIN'or'INSTRUCTOR'in the database; theADD VALUEmigration is non-destructive and backward-compatible. Any user who needs theEMPLOYERrole must be updated via a direct database write or a future admin endpoint — there is deliberately no API path to self-assign it.