Practitionist / Practitionist/elluminar_web

🔒 Enforce entitlements beyond the player page + tighten permissive catalogScope default

Open
#51 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug commerce tenancy
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:

  1. 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.
  2. catalogScope semantics 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

  1. Add scopeMode to the catalogScope JSON contract (ALL | ALLOWLIST, default ALL to preserve behavior) — additive, no schema change.
  2. 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.
  3. Lesson/resource API routes that serve media or downloads should reuse resolveCourseAccess (or a lighter assertCourseEntitled) instead of trusting the page redirect.
  4. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.