CampusSync
PROTOTYPEMulti-tenant certificate verification for universities and recruiters.
Personal project · Sep–Dec 2025 · 165 commits
THE PROBLEM
Certificates are checked by hand and are easy to forge, so recruiters can't trust the ones they receive.
HOW IT WORKS
- Students upload certificates; Gemini vision extracts the fields and regular expressions normalise them; faculty approve.
- Approved certificates are issued as signed credentials (RS256 JWS in a W3C-VC-shaped JSON) with revocation and public verification.
THE PIPELINE
- Student uploadThe student uploads a certificate.
- Gemini visionExtracts the fields; regular expressions normalise them.
- Faculty approvalA reviewer verifies or rejects.
- Signed credentialRS256 JWS in a W3C-VC-shaped JSON, with revocation.
- Public verificationAnyone can check a credential.
ENGINEERING EVIDENCE
- 101 API route files (138 handlers), guarded by role and organisation checks.
- 91 passing tests across 6 files, and architecture documents in my-app/docs.
- A Sep 2026 security review of every route found and fixed real holes: role assignment and signup that did not require a session, credential issuing for arbitrary claims, unauthenticated document-status routes that used the service role, unsigned webhooks and open redirects. Dependency audit findings went from 71 to 2. safeRedirect.ts ↗
DECISIONS & INVESTIGATIONS
One external dependency is now on the critical path: a missing Gemini key or an unparseable reply gives a 500 with no fallback.
- user_roles row-level security: self-referencing policies replaced by a SECURITY DEFINER functionPARTIAL
Five policy names recur in every version; only the admin check changed.
WHAT ISN'T DONE
- Credentials are JWT-style, not full W3C proofs, and signing keys are held in memory.
- The review found that the early version was much less secure than the README suggested. The fixes are checked by typecheck, lint, the unit tests and a build, not against a live deployment.
- Rate limiting is in memory and per server instance.
- The database schema and row-level-security SQL are not in the repository (the migrations were removed from the main branch), so the policies could not be reviewed from the code.
NEXT STEPS
Each one comes from a gap listed above. It says what fixing the gap would take; it is not a promise.
- Put the database schema and row-level-security SQL back in the repository so the policies can be reviewed.
- Replace the in-memory rate limiter with a shared store.
- Keep signing keys outside process memory.
STACK
- Next.js
- TypeScript
- Supabase
- PostgreSQL
- Gemini
- Vitest