{"slug": "i-built-a-calendar-app-kids-control-even-parents-can-t-access-it", "title": "I Built a Calendar App Kids Control – Even Parents Can't Access It", "summary": "Developer Mihir Shaikh launched Kid Compass, a role-based family planner where children control their own schedules and chores while parents can only view and suggest, never edit or override entries. The app, built with Next.js 15, Clerk, and PostgreSQL on Vercel, serves as a testing target for PerfAI Security's broken access control review, enforcing strict authorization boundaries such as sibling isolation and field-level ownership.", "body_md": "**A calendar and chore planner kids run themselves — parents watch, suggest, and cheer from the sidelines. Never edit, never override.**\n\nKid Compass is a role-based family planner. Kids plan, log, and check off their own day — activities, chores, to-dos. Parents get full visibility and a way to encourage from the sidelines (comments, rewards), but never a way to edit a child's own entries.\n\nUnderneath the product, Kid Compass is also an authorization-testing app: every access-control boundary described below is a deliberate design decision, documented inline in [ src/lib/auth.ts](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts) with the reasoning behind it.\n\n[Roles & Permissions](#roles--permissions)[Tech Stack](#tech-stack)[Live App & Documentation](#live-app--documentation)[Security & PerfAI Testing](#security--perfai-testing)[Authorization Model](#authorization-model)[Getting Started](#getting-started)[Deployment](#deployment)[Project Notes](#project-notes)\n\nKid Compass has four roles, scoped per family:\n\n| Role | Can do |\n|---|---|\nFamily Admin |\nEverything a Guardian can, plus: manage membership, create invites, revoke guardian grants, toggle a child's \"maturity mode\" |\nGuardian |\nSee every child's schedule/chores/tasks in the family; leave suggestions; assign chores or drop them in the shared pool; set rewards |\nSecondary Guardian |\nSee only what an explicit, revocable grant allows — one child, optionally one entry kind, optionally a bounded time window |\nChild |\nCreate/edit/delete their own schedule blocks and tasks; mark entries private (if maturity mode is on); claim pool chores; mark their own tasks complete |\n\nA parent can always **see** what's planned. Only the child who owns an entry can ever check it off, edit it, or mark it done. Once maturity mode is on for a child, private entries are unreadable by anyone else — including a Family Admin.\n\n| Layer | Choice |\n|---|---|\n| Framework | Next.js 15 (App Router) |\n| Language | TypeScript |\n| UI | React, Tailwind CSS, Radix-based shadcn/ui |\n| Auth | Clerk |\n| Database | PostgreSQL (Neon) |\n| ORM | Prisma |\n| Validation | Zod |\n| Hosting | Vercel |\n| Built with |\n|\n\n| Resource | URL |\n|---|---|\n| Live app |\n|\n\n[https://kid-compass-teal.vercel.app/demo](https://kid-compass-teal.vercel.app/demo)[https://kid-compass-teal.vercel.app/docs.html](https://kid-compass-teal.vercel.app/docs.html)[https://kid-compass-teal.vercel.app/openapi.json](https://kid-compass-teal.vercel.app/openapi.json)[https://kid-compass-teal.vercel.app/openapi.yaml](https://kid-compass-teal.vercel.app/openapi.yaml)The `/demo`\n\nroute renders one seeded family's real data with no account required — including a private entry, shown correctly redacted, to demonstrate the core privacy guarantee live.\n\nKid Compass was purpose-built as a testing target for **PerfAI Security's** Broken Access Control review. The boundaries below are specifically the kind of thing a BOLA/IDOR-focused review is meant to probe:\n\n**Same-tier sibling isolation**— two children in one family hold the identical role (`CHILD`\n\n) and must be fully isolated from each other, despite sharing every other membership property.**Field-level ownership on a single row**— a chore's`title`\n\n/`notes`\n\n/`status`\n\nbelong to the child; its`rewardNote`\n\nbelongs to the guardian who assigned it. Two legitimate owners, two different fields, one database row.**Privacy that ignores a later setting change**— a private entry's visibility is baked in at creation time from the child's maturity-mode flag*at that moment*. Toggling the flag off later never retroactively exposes something written while it was on.**Grant-scoped access with no membership shortcut**— a secondary guardian's access comes entirely from an explicit, revocable grant row, never inferred from family membership alone.**Ownership validation on every mutating request**— task completion, block edits, and chore claims all re-verify the caller's relationship to the resource server-side, never trusting client-supplied IDs.\n\nReview questions this app is designed to be tested against:\n\n- Can one child access another child's private schedule?\n- Can a guardian modify data they should only be able to view?\n- Can a secondary guardian access data outside their granted scope?\n- Are private calendar entries actually protected at the API layer, not just hidden in the UI?\n- Are ownership checks enforced on every request, not just the ones the UI exercises?\n\nThe full authorization model is documented inline in [ src/lib/auth.ts](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts), and every endpoint's access rules are spelled out in the\n\n[API documentation](https://kid-compass-teal.vercel.app/docs.html).\n\nTwo layers:\n\n**Site-level**(`Role`\n\n`USER`\n\n/`ADMIN`\n\n) — moderation only (ban a user). Never grants access to any family's data.**Per-family**(`FamilyRole`\n\n`FAMILY_ADMIN`\n\n/`GUARDIAN`\n\n/`SECONDARY_GUARDIAN`\n\n/`CHILD`\n\n) — governs everything else.\n\nFull reasoning for every check lives in [ src/lib/auth.ts](/mihirshaikbugs/kid-compass/blob/main/src/lib/auth.ts) and the model comments in\n\n[.](/mihirshaikbugs/kid-compass/blob/main/prisma/schema.prisma)\n\n`prisma/schema.prisma`\n\n```\nnpm install\nnpm run db:push       # sync the schema to your local Postgres\nnpm run db:seed       # 3 fictional families with kids/chores/activities\nnpm run dev\n```\n\nRequires a local Postgres database and a `.env`\n\nwith `DATABASE_URL`\n\n/ `DIRECT_URL`\n\npointing at it (see `.env.example`\n\n). Clerk runs in **keyless dev mode** locally — no API keys needed to start; visiting the app for the first time auto-provisions temporary dev keys.\n\nTo promote a signed-up user to site-level `ADMIN`\n\n(moderation only — never grants access to family data):\n\n```\nnpm run make-admin -- <username>\n```\n\n**Database**— provision Postgres (e.g.[Neon](https://neon.tech)). Set`DATABASE_URL`\n\n(pooled) and`DIRECT_URL`\n\n(direct) in your host's env vars, then run`npm run db:push`\n\nand`npm run db:seed`\n\nagainst it.**Clerk**— create a production Clerk application, copy`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`\n\n/`CLERK_SECRET_KEY`\n\ninto your host's env vars, and point a webhook at`/api/webhooks/clerk`\n\n(`user.created`\n\n,`user.updated`\n\n,`user.deleted`\n\n) — copy the signing secret into`CLERK_WEBHOOK_SECRET`\n\n.**Deploy**— import the repo into Vercel (or any Next.js host), add the env vars from`.env.example`\n\n, deploy.`npm run build`\n\nruns`prisma generate`\n\nautomatically.- Run\n`npm run make-admin -- <username>`\n\nagainst production once your own account exists, if a site admin is needed.\n\nNotable issues found and fixed during development:\n\n**Redaction bug**— an early generic redaction helper spread every field through except the`isPrivate`\n\nflag itself, which would have leaked a private entry's title/location/notes to every guardian. Replaced with explicit, type-specific redaction functions with a hard field allowlist.**Dependency version mismatch**— a few UI components pulled in newer patch versions of shared Radix packages than the rest of the stack, breaking`next build`\n\non every route with an unrelated-looking`TypeError`\n\n. Fixed by pinning shared packages via`package.json`\n\noverrides.**UI/API mismatch on chore claiming**— the \"claim\" button rendered for guardians in the shared chore pool, even though only a child can claim (the API already rejected it correctly). Fixed by gating the button on the viewer's actual role.**Middleware gap**— signed-out visits to protected pages weren't redirecting to sign-in, they were throwing an uncaught error. Fixed with an explicit public-route matcher in`src/middleware.ts`\n\n.\n\nBuilt in a single session with [Claude Code](https://claude.com/claude-code).", "url": "https://wpnews.pro/news/i-built-a-calendar-app-kids-control-even-parents-can-t-access-it", "canonical_source": "https://github.com/mihirshaikbugs/kid-compass", "published_at": "2026-07-28 17:21:41+00:00", "updated_at": "2026-07-28 17:22:18.325445+00:00", "lang": "en", "topics": ["developer-tools", "ai-safety"], "entities": ["Kid Compass", "Mihir Shaikh", "PerfAI Security", "Next.js", "Clerk", "PostgreSQL", "Vercel", "Neon"], "alternates": {"html": "https://wpnews.pro/news/i-built-a-calendar-app-kids-control-even-parents-can-t-access-it", "markdown": "https://wpnews.pro/news/i-built-a-calendar-app-kids-control-even-parents-can-t-access-it.md", "text": "https://wpnews.pro/news/i-built-a-calendar-app-kids-control-even-parents-can-t-access-it.txt", "jsonld": "https://wpnews.pro/news/i-built-a-calendar-app-kids-control-even-parents-can-t-access-it.jsonld"}}