This is a submission for the Permit.io Authorization Challenge: Permissions Redefined

What I Built 🛠️

Viskify is a full-stack hiring platform that lets candidates prove their skills and credentials with blockchain-signed Verifiable Credentials (VCs) and AI-graded SkillPasses. Recruiters can then filter talent by proof instead of promises, while issuers (universities / employers) sign off on credentials in a couple of clicks.
To keep security sane as the feature-set grows, I ripped out every hand-rolled if (role === …) check and moved all authorization logic to Permit.io. The result is a cleaner codebase, live-editable policies, and a demo that shows how externalized RBAC can power a real production workflow (candidate → issuer → recruiter → admin) with zero redeploys.

Persona Everyday pain Viskify fix
Candidate Re-uploading the same PDF certificates to each job portal One DID-backed profile + SkillPass quizzes
Recruiter Manual credential verification, résumé keyword soup Filter by on-chain VCs and quiz scores
Issuer Endless email chains for reference checks Dashboard to review and sign requests
Admin Fragile in-code ACLs Permit.io PDP enforcing policies centrally

Demo 🌐

Email Password Role
[email protected] myPassword admin
[email protected] myPassword candidate
[email protected] myPassword recruiter
[email protected] myPassword issuer

Want to see Permit in action?
Log in as [email protected], open */dashboard*, then hit /api/admin/stats in another tab – 200 OK.
Log out, switch to [email protected], hit the same endpoint – 401 Unauthorized.

Screenshots:
Landing page screenshot

Admin dashboard screenshot

Admin stats API call screenshot

Permit.io audit log screenshot


Project Repo 📦

https://github.com/syntaxsurge/viskify-permit-io

Key folders:

  • /permit – declarative base RBAC policy (base.yml).
  • /scripts/sync-permit.ts – idempotent seeding of roles, resources & demo users via the Permit SDK.
  • /app – Next.js 15 App Router code (Server Actions, Route Handlers, RSC).

The README explains the full architecture plus copy-paste setup commands.


My Journey 🛤️

  1. Prototype → pain: I started with classic role checks sprinkled across API routes. After adding a fourth role the code smelled.
  2. Enter Permit.io: Spun up the cloud PDP, wrote an RBAC policy in YAML, and deleted ~120 LOC of brittle guards. ✂️
  3. CLI FTW: The Permit CLI let me seed roles/resources and demo users straight from pnpm permit:cli; no dashboard clicking.
  4. Live tweaks: During testing a recruiter needed read-only access to a new talent_search resource. I toggled a checkbox in Permit and instantaneously the UI lit up – no redeploy, no PR.
  5. Takeaways:
  • Externalized auth turns “oops, forgot a check” bugs into policy edits.
  • Keeping policies next to code (/permit/*.yml) still feels GitOps-friendly.
  • The PDP latency is negligible (<2 ms in local tests).

Using Permit.io for Authorization 🔐

  • Policy definitionpermit/policies/base.yml declares roles (admin, candidate, recruiter, issuer), resources (dashboard, admin_stats, …) and actions.
  • Seedingpnpm permit:cli runs scripts/sync-permit.ts which:
  1. Creates the roles/resources via the SDK.
  2. Assigns granular permissions (only admins get admin_stats:read, everyone gets dashboard:view).
  3. Syncs four demo users and assigns their roles.

API routes use the same helper (assertPermission) before hitting the DB.

  • Local vs Cloud PDP – by default we hit https://cloudpdp.api.permit.io, but running the PDP Docker image locally (docker run permitio/pdp-v2 ...) and setting PERMIT_PDP_URL works identically.

Thank you to the Permit.io team & DEV community for a super-smooth DX and a fun challenge! 🎉