TypeDrop

2026-05-25 Challenge

2026-05-25 Easy

Typed User Permission Checker

You're building the access-control layer for a SaaS dashboard. Raw user session data arrives as `unknown` from a JWT-decode utility; your engine must validate it, derive a typed permission set from the user's role, and answer permission queries — with zero `any`.

Goals

  • Define `PermissionMap` as a mapped type over `Role` and populate `PERMISSIONS` using the `satisfies` operator.
  • Implement `parseSession` to validate `unknown` input into a typed `UserSession` via a discriminated-union `ParseResult` — no `any` or type assertions.
  • Write a `isRole` type-guard that narrows `unknown` to the `Role` union without casting.
  • Implement `can` and `checkAll` to answer permission queries against the typed permission table.
challenge.ts
/** Every discrete action a user might attempt. */
type Action =
  | "read:content"   | "write:content" | "delete:content"
  | "manage:users"   | "view:analytics" | "export:data";

type Role = "admin" | "editor" | "viewer";

/** Mapped type: every Role key is guaranteed present */
type PermissionMap = { readonly [R in Role]: ReadonlyArray<Action> };

/** Discriminated-union result from the session parser */
type ParseResult =
  | { ok: true;  session: UserSession }
  | { ok: false; reason: string };

// --- functions you must implement ---
declare function parseSession(raw: unknown): ParseResult;
declare function can(session: UserSession, action: Action): boolean;
declare function checkAll(
  session: UserSession,
  actions: ReadonlyArray<Action>
): Partial<Record<Action, boolean>>;
Hints (click to reveal)

Hints

  • A type-guard `isRole(v: unknown): v is Role` lets the compiler accept `rec['role']` as `Role` inside `parseSession` without any cast.
  • The `satisfies` operator validates an object's shape against a type while preserving its narrower literal types — perfect for `PERMISSIONS`.
  • `Object.fromEntries(actions.map(a => [a, can(session, a)]))` produces the batch report in one expression; you only need a light cast at the boundary.

Or clone locally

git clone -b challenge/2026-05-25 https://github.com/niltonheck/typedrop.git