Practitionist / Practitionist/elluminar_web
🔒 Enforce entitlements beyond the player page + tighten permissive catalogScope default
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 0
- Avg merge
- 17h 2m
- Merged PRs (30d)
- 18
Description
Context
resolveCourseAccess(userId, courseId) (src/lib/commerce/entitlements.ts:15-81) is the central 3-step entitlement cascade (Enrollment → CATALOG-license seat w/ catalogScope → subscription libraryAccess), lazily materializing enrollment rows. It is called from exactly one place — the course player page (src/app/(learn)/learn/courses/[courseId]/page.tsx:56).
Consequences:
- Catalog browse/search (src/lib/catalog.ts) is identity-blind — org members see the whole marketplace with no indication of what their license covers; nothing stops deep-linking lesson routes other than the page-level check.
catalogScopesemantics are permissive: empty/absent allowlist = entire published marketplace (entitlements.ts:46). For a B2B contract that's often intended, but there is no way to express "deny by default, explicit list only" vs "all access", and /learn/org re-implements scope filtering inline for display (learn/org/page.tsx:90-95), risking drift from the authoritative resolver.
Proposed implementation
- Add
scopeModeto the catalogScope JSON contract (ALL | ALLOWLIST, default ALL to preserve behavior) — additive, no schema change. - Centralize scope-filtering: export
filterCoursesByLicense(courses, license)from entitlements.ts and use it in both /learn/org display AND any licensed-browse surface — single source of truth. - Lesson/resource API routes that serve media or downloads should reuse
resolveCourseAccess(or a lighterassertCourseEntitled) instead of trusting the page redirect. - Optionally annotate marketplace cards for entitled users ("Included in your company license") using batched seat/scope lookup.
Acceptance
- All course-content-serving routes enforce entitlements server-side
- Scope logic lives in exactly one module; /learn/org uses it
- ALLOWLIST mode tested incl. empty-list edge
Related: #35, epic #42.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/lib/commerce/entitlements.ts, especially resolveCourseAccess and its catalogScope handling, then compare the filtering in src/app/(learn)/learn/org/page.tsx with the routes serving course media or downloads. Trace the catalog surfaces and lesson/resource routes to identify every content path. Done means server-side entitlement checks cover those routes, /learn/org uses the shared scope logic, and ALLOWLIST behavior including an empty list is tested.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- authorization, backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 38/100