TypeDrop
2026-05-31 Challenge
2026-05-31
Medium
Typed Permission-Based Access Control Engine
You're building the authorization layer for a multi-tenant SaaS platform. Unknown user session tokens arrive from an authentication provider; your engine must validate them, resolve role-based permissions via a typed policy registry, and produce a strongly-typed access decision report — with zero `any`.
Goals
- Define `Permission` as a branded template literal type combining `Resource` and `Action`, and implement `makePermission` without type assertions.
- Implement `validateSessionToken` to safely narrow `unknown` to `SessionToken`, throwing a typed `ValidationError` for every invalid field.
- Implement the generic `checkAccess` engine so its return type is `AccessReport<T>` — a mapped type that preserves the exact keys of the input request map.
- Implement `diffPermissions` to compute the symmetric difference and intersection of two roles' permission sets, returning fully typed `Permission[]` arrays.
challenge.ts
// Key types & main function signature
type Resource = "billing" | "reports" | "settings" | "users" | "audit_logs";
type Action = "create" | "read" | "update" | "delete";
type RoleId = "admin" | "analyst" | "billing_manager" | "viewer" | "support";
// TODO 1 — template literal + brand
type Permission = /* `${Resource}:${Action}` + unique symbol brand */ unknown;
type PolicyRegistry = Record<RoleId, ReadonlyArray<Permission>>;
type AccessDecision =
| { outcome: "granted"; matchedPermission: Permission }
| { outcome: "denied"; reason: "insufficient_permissions" | "token_expired" | "no_roles" }
| { outcome: "error"; error: ValidationError };
// TODO 6 — generic mapped type
type AccessReport<T extends Record<string, AccessRequest>> = unknown; // mapped over keys of T
// Main engine function — return type is fully inferred from T
function checkAccess<T extends Record<string, AccessRequest>>(
rawToken: unknown,
requests: T,
options?: EngineOptions
): AccessReport<T> { /* TODO 7 */ }
Hints (click to reveal)
Hints
- For the branded `Permission` type, combine a template literal type with an intersection of `{ readonly [__permissionBrand]: true }` — then return it from `makePermission` using a type-safe cast-free approach (hint: the function body can use string concatenation and the return type annotation does the heavy lifting).
- In `validateSessionToken`, use `typeof` and `Array.isArray` guards first, then narrow each field individually — store intermediate results in typed `const` bindings so TypeScript can follow the narrowing chain.
- For `AccessReport<T>`, the mapped type should look like `{ [K in keyof T]: AccessDecision }` — make sure to preserve the `readonly` modifier and that `T` is constrained to `Record<string, AccessRequest>`.
Useful resources
Or clone locally
git clone -b challenge/2026-05-31 https://github.com/niltonheck/typedrop.git