One sign-in for all of your Undip accounts. SSO, Kulon, and SIAP share a single door but split your data across three places, and none of them offer an API. YoDips takes your session once and turns everything into one dashboard and one REST API: Kulon assignments, SIAP grades and IRS, class schedules, even QR attendance scanning.
| Part | Stack | What it does |
|---|---|---|
backend/ |
NestJS + TypeScript | Core API: sign-in, Kulon/SIAP aggregation, encrypted sessions, push notifications |
web/ |
Vue 3 + Vite + Tailwind + shadcn-vue | Web dashboard: GPA, academic charts, tasks, schedule, profile, notifications |
mobile/ |
Kotlin + Jetpack Compose | Android app: everything the web has, plus QR attendance scanning |
extension/ |
Chrome/Edge MV3 (TypeScript) | The main way to sign in from a browser |
- Use it without building
- How to sign in
- Developing locally
- API overview
- Deployment
- Releasing
- Security notes
- Contributing
- License
No build steps needed if you just want to use it:
- Web: sso.crunchy.my.id
- Browser extension: YoDips on the Chrome Web Store
- Android: grab the APK from GitHub Releases. Install guide in
INSTALL.md
The extension targets Chromium (Manifest V3), so besides Chrome it installs straight from that link on Edge, Brave, Opera, Vivaldi, and other Chromium browsers. On Edge, first turn on Allow extensions from other stores in edge://extensions. An official Edge Add-ons listing is planned; Firefox is not supported yet because its extension platform works differently.
One rule across every device: your password stays between you and Undip. You sign in yourself, on your own device, and YoDips only reuses the session that already exists. It never asks for or stores your credentials.
- Install the YoDips extension.
- Open sso.crunchy.my.id and click Login via Extension.
- Sign in with your Undip account in the tab that opens. This is the regular SSO page, including MFA when prompted.
- That's it. Kulon and SIAP connect on their own, the tab closes, and your dashboard appears.
If a session expires later, the app quietly restores it the same way. No need to click anything.
- Install the app from Releases.
- Open it and sign in once on the SSO screen inside the app.
- Done. Kulon and SIAP connect automatically and your dashboard appears.
Notifications for new assignments and schedule changes arrive through Firebase after you sign in.
Each subproject stands alone; there is no root task runner.
# backend (NestJS) — http://localhost:3000
cd backend && npm install && npm run start:dev
# web (Vue 3) — http://localhost:5173
cd web && npm install && npm run dev
# extension (MV3 TS) — builds into dist/, load it unpacked at chrome://extensions
cd extension && npm install && npm run devMobile (Kotlin): open mobile/ in Android Studio, or cd mobile && ./gradlew :app:testDebugUnitTest && ./gradlew assembleDebug.
Every subproject carries its own test suite and CI runs all of them on every push. Run the ones covering what you changed; CONTRIBUTING.md documents the commands and the testing gotchas worth knowing before you write a spec.
Copy the templates and fill in your secrets:
cp backend/.env.example backend/.env
cp web/.env.example web/.envBackend variables that matter most:
| Variable | Meaning |
|---|---|
JWT_SECRET / JWT_EXPIRES_IN |
Token signing secret (openssl rand -hex 32) and lifetime. Never commit it |
CORS_ORIGIN |
Allowed frontend origins, comma-separated |
SESSION_BACKEND |
memory for dev/test, redis for production |
REDIS_URL / SESSION_ENC_KEY / SESSION_TTL_MS |
Required when SESSION_BACKEND=redis; sessions are AES-256-GCM encrypted |
CACHE_TTL_MS |
TTL for upstream scrape cache (default 5 minutes) |
NOTIFICATIONS_ENABLED / FIREBASE_SERVICE_ACCOUNT_JSON |
FCM push notifications (production) |
MS_*, CDP_URL, SSO_LOGIN_URL / SSO_DASHBOARD_URL, CHROME_PROFILE_DIR |
Legacy OIDC/Capture; required only in development/test, omitted from production runtime. CHROME_PATH is an optional browser override |
On the web side, VITE_API_BASE_URL points at the backend and VITE_EXTENSION_ID bakes in the extension ID used for SPA-extension detection.
- Per-user sessions sit behind a small
SessionStoreinterface: in-memory for dev, encrypted Redis for production. Restarting the backend while onmemorylogs everyone out. - Upstream scrape results are cached per user (Redis in production) so pages don't repeat expensive scrapes.
- Kulon/SIAP HTML and JSON parsers live in pure modules (
kulon-parse.ts,siap-parse.ts) with no fetch dependencies, which keeps them easy to test. - The extension separates its state machine (pure TS in
extension/src/core/) from the thinchrome.*adapter inbackground.ts.
All routes live under /api. Everything except the endpoints noted below requires an Authorization: Bearer <JWT> header.
| Method | Path | Purpose |
|---|---|---|
POST |
/auth/session/handoff |
Accept captured cookies, issue a JWT (no JWT guard by design, rate-limited) |
POST |
/auth/refresh |
Rotate the JWT |
GET |
/auth/me |
Identity + validity of the SSO/Kulon/SIAP sessions |
GET |
/kulon/courses |
Kulon courses with lecturer names merged in from SIAP |
GET |
/kulon/assignments/all |
Every assignment and quiz, completed ones included |
GET |
/kulon/assignments/:id/detail |
One assignment's details |
GET |
/kulon/courses/:id/content |
A course's materials |
GET |
/siap/profile · /irs · /khs |
Profile, IRS, KHS (GPA read straight from the official KHS footer) |
GET |
/siap/jadwal · /absen · /kehadiran/:id |
Class meetings, attendance summary, per-meeting history |
POST |
/siap/kehadiran |
Proxy a scanned attendance QR to SIAP |
GET |
/siap/lecturers · /notifications |
Lecturers per course code; SIAP notifications |
POST |
/siap/notifications/:id/unread |
Mark a notification unread |
POST |
/notifications/device |
Register a device's FCM token |
POST /auth/sso/capture and GET /auth/microsoft/* are dev/test only. The capture route is blocked with a 403 in production — the extension is the real login path, and there is no password-based login endpoint at all: the backend never receives a password.
Production runs the backend on Heroku and the web app on Vercel. A push to main that passes CI deploys both automatically through .github/workflows/deploy-backend.yml and deploy-web.yml. Production requires SESSION_BACKEND=redis with REDIS_URL + SESSION_ENC_KEY, and the custom domain must be registered on Heroku so requests don't die at Cloudflare with a 520. Runbooks live in tools/deploy/.
- Extension:
cd extension && npm run release <patch|minor|major>bumps the version, builds, tests, and produces the zip for upload to the Chrome Web Store (and Edge Add-ons). Keep icons 16/32/48/128 in the zip; the store rejects submissions without them. - Mobile:
node mobile/scripts/bump.mjs <X.Y.Z>bumpsversionCode/versionName. - Automatic: pushing a
v*tag runs.github/workflows/release.yml, which publishes a GitHub Release containing the signed APK, the extension zip, andSHA256SUMS.txt.
Your password never reaches the backend. Identity is always derived from the verified Kulon session, never from anything a client claims. JWTs carry server-side session references, not raw cookies. CORS allowlist, helmet, global ValidationPipe, OIDC state CSRF protection, and gitleaks/semgrep/npm-audit/trivy gates run in CI. Sessions are encrypted at rest when Redis backs them.
Found a vulnerability? Report it privately — see SECURITY.md. Please don't open a public issue for anything exploitable.
Bug reports, feature requests, and pull requests are welcome. CONTRIBUTING.md has the setup steps, the per-subproject test commands, the gotchas that cost real time, and the conventions a PR is expected to follow. Everyone participating is asked to follow the CODE_OF_CONDUCT.md.
MIT © Kemal Faza
YoDips is an independent student project and is not affiliated with or endorsed by Universitas Diponegoro.