Feature Owner: scorevi (Sean Patrick Caintic)
Module: Auth & Security
Priority: P1
Sprint #12: Fully Implemented (PR #406)
Date: 2026-06-29
EXECUTIVE SUMMARY
What is this feature?
The "View As" system allows privileged users (Admins, Creators, Reviewers) to temporarily preview lower-role dashboards without changing their actual role. A wyzquests_view_as_role cookie stores the preview role, and every auth check validates it against a strict hierarchy.
Why does it matter?
Admins need to verify what learners see. Creators need to preview the reviewer interface. Without this, testing role-specific UX requires creating multiple accounts.
What's the MVP scope?VIEW_AS_HIERARCHY in lib/schemas/role-switch.schema.ts defines which roles each role can preview. getEffectiveRole() in authenticate.ts validates cookies against the hierarchy. RoleSwitcher UI component (290 lines). /api/user/switch-role and /api/user/available-roles endpoints.
1. USER PAIN POINT & SOLUTION
Current State (Without Feature)
To test the learner dashboard, an admin must sign out, create a learner account, sign in, test, sign out, and sign back in as admin. This is slow and disruptive.
Pain Point
Emotional: Frustration at the friction of switching contexts.
Functional: Multi-account management is tedious and error-prone. Cannot quickly verify how content appears to different roles.
Business Impact: Slower QA cycles. Admins and support staff waste time context-switching.
Future State (With Feature)
A dropdown in the sidebar lets privileged users instantly switch their view to any lower role. The cookie is secure (httpOnly, sameSite lax, 4h expiry) and validated server-side against a strict hierarchy to prevent privilege escalation.
Marketing Hook
"One account. Every perspective. Switch roles instantly."
2. 4D FRAMEWORK MAPPING
Diagnose
N/A — auth infrastructure.
Design
Creators can preview their quest as a learner would see it, verifying visual flow and branching.
Develop
Reviewers and admins can preview learner progress views and content rendering.
Deliver
N/A — infrastructure concern, but ensures admins can verify SCORM export behaves correctly for all roles.
3. USER FLOWS
Entry Point
Privileged user clicks the RoleSwitcher dropdown in their sidebar/dashboard header.
Success Criteria
Dashboard reloads showing the UI for the previewed role. A persistent indicator shows "Viewing as [Role]". User can reset to their actual role at any time.
Main Flow (Happy Path)
Admin opens RoleSwitcher dropdown — sees available roles per
VIEW_AS_HIERARCHY.Admin selects "Learner".
Client calls
POST /api/user/switch-rolewith{ viewAsRole: "LEARNER" }.Server validates: Admin can view as Learner → sets
wyzquests_view_as_role=LEARNERcookie (httpOnly, sameSite lax, maxAge 4h).Client reloads to
/learner.Layout calls
authenticateRole('LEARNER')→getEffectiveRole()reads cookie, returnseffectiveRole: "LEARNER".Dashboard renders as learner view.
Edge Cases
Invalid role in cookie:
getEffectiveRole()logs warning, ignores cookie, uses actual role.Privilege escalation attempt: Admin sets cookie to "ADMIN" while viewing as "LEARNER" →
VIEW_AS_HIERARCHY["LEARNER"]is[]→ cookie ignored.Agency owner:
AGENCY: []— agency owners cannot view as any lower roles.Reset: Client calls
POST /api/user/switch-rolewith{ viewAsRole: null }→ cookie deleted.Cookie expiration: After 4 hours, user automatically reverts to actual role.
Decision Points
IF user's actual role is in
VIEW_AS_HIERARCHYand requested role is in the allowed list → set cookie.ELSE → return 403
ApiResponseHelper.forbidden().
4. INFORMATION ARCHITECTURE
Primary Information (Always visible)
Current effective role (shown in RoleSwitcher badge).
"Viewing as [Role]" indicator when in preview mode.
Secondary Information
Available roles list (computed from
VIEW_AS_HIERARCHY[actualRole]).
Actions
Primary CTA: Select role from dropdown → switch view.
Secondary Actions: Reset to actual role ("Exit View As" button).
5. WIREFRAMES
[Excluded — existing UI: components/ui/RoleSwitcher.tsx (290 lines)]
6. WIREFLOWS
Excluded.
7. PROTOTYPE
[Excluded — existing implementation]
8. BACKEND SCHEMA
Database Tables
No dedicated table. Cookie-based state only.
Property | Value |
|---|---|
Name |
|
httpOnly |
|
secure |
|
sameSite |
|
maxAge |
|
path |
|
9. API ENDPOINTS
Method | Endpoint | Auth | File | Description |
|---|---|---|---|---|
POST |
| Clerk session |
| Validates role against |
GET |
| Clerk session |
| Returns |
Response shape (switch-role):
{ "success": true, "data": { "actualRole": "ADMIN", "viewingAsRole": "LEARNER", "isViewingAsOther": true, "redirectUrl": "/learner" } }
Response shape (available-roles):
{ "success": true, "data": { "actualRole": "ADMIN", "viewingAsRole": null, "isViewingAsOther": false, "canViewAs": ["AGENCY", "CREATOR", "REVIEWER", "LEARNER"], "hasAgencyMembership": false, "isAgencyOwner": false }}
10. DATA REQUIREMENTS
Frontend Needs
VIEW_AS_HIERARCHY(imported from schema) for client-side role availability.ROLE_DASHBOARD_PATHSfor redirect URLs.RoleSwitcher component state: current effective role, available roles, loading state.
API Calls Frontend Will Make
GET /api/user/available-roleson RoleSwitcher mount.POST /api/user/switch-roleon role selection.
Caching Strategy
Cookie is read on every request via getEffectiveRole(). No additional caching needed — cookie is the source of truth.
11. PERFORMANCE CONSIDERATIONSDatabase Optimization
N/A — cookie-based, no DB writes per switch.
API Response Time
switch-role: <50ms (sets cookie and returns).available-roles: <100ms (reads user role + agency membership, no heavy queries).
Who can access this feature?
Any authenticated user with at least one role in VIEW_AS_HIERARCHY that has non-empty allowed roles.
VIEW_AS_HIERARCHYis strictly enforced server-side:ADMIN→["AGENCY","CREATOR","REVIEWER","LEARNER"]AGENCY→[](cannot view as lower roles)CREATOR→["REVIEWER","LEARNER"]REVIEWER→["LEARNER"]LEARNER→[]
getEffectiveRole()validates cookie against hierarchy on every auth check.Invalid/malicious cookie values are silently ignored (logged as security warning).
Cookie is httpOnly (inaccessible to JavaScript).
Data Validation
viewAsRoleSchemavalidates incoming role withplatformRoleEnum.nullable().Response schemas:
viewAsRoleResponseSchema,availableRolesResponseSchema.
13. ERROR HANDLING
Error | Response |
|---|---|
Invalid role requested (not in hierarchy) | 403 |
Unauthenticated | 401 |
Valid but harmless cookie manipulation | Silent — cookie ignored, actual role used |
14. TESTING CHECKLIST
Happy Path
□ Admin switches to Learner → dashboard shows learner UI → indicator shows "Viewing as Learner".
□ Creator switches to Reviewer → reviewer dashboard renders correctly.
□ User resets to actual role → cookie deleted → dashboard reverts.
□ Cookie expires after 4 hours → user automatically returns to actual role.
Edge Cases
□ Admin viewing as Learner navigates to /admin → 403 (role mismatch).
□ Agency owner (AGENCY) attempts View As → dropdown shows no options.
□ Learner attempts View As → dropdown shows no options.
□ Malicious cookie value "SUPERADMIN" → ignored, actual role used.
□ Multiple tabs: switching role in one tab requires reload in others.
15. OPEN QUESTIONS
Should a user be allowed to remain in View As mode across sign-out/sign-in cycles?
Should the 4-hour cookie TTL be configurable?
16. OUT OF SCOPE (v1.1+)
"View As" for specific user (e.g., admin views a specific learner's dashboard).
Persistent View As mode across browser sessions.
17. SUCCESS METRICS
Zero privilege escalation incidents.
<100ms API response time for role switch.
100% of layout guards validate against effective role (not actual role).
18. DEPENDENCIES
This feature depends on:
0.1 Secure Login (Clerk session required).
lib/schemas/role-switch.schema.ts(90 lines):VIEW_AS_HIERARCHY, schemas.lib/auth/authenticate.ts(885 lines):getEffectiveRole().components/ui/RoleSwitcher.tsx(290 lines).ROLE_DASHBOARD_PATHSfor redirect URLs.
These features depend on this:
All layout auth guards indirectly (validate via effective role).
Admin/creator/reviewer testing workflows.
19. TIMELINE & OWNERSHIP
Implemented: PR #406.
Owner: scorevi (Sean Patrick Caintic)
Document Version
1.0 - Initial version - 2026-06-29 07:58 UTC
1.1 (Current Version) - Added Document Version section and update author to have full name - 2026-06-29 08:36 UTC