Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8f686fb
feat(rbac): guest role foundation + project-scope guards (SEC-01)
parth0025 Jun 20, 2026
bf9b3b5
feat(rbac): scope projects to assigned set for guests (SEC-01)
parth0025 Jun 20, 2026
5098ca8
feat(rbac): guard task detail + per-project task data for guests (SEC…
parth0025 Jun 20, 2026
55c4f9a
feat(rbac): scope task-query + comment endpoints for guests (SEC-01)
parth0025 Jun 20, 2026
76bb981
test(rbac): guest role test cases (SEC-01)
parth0025 Jun 20, 2026
3b6024a
feat(sso): enterprise SSO backend with SAML and OIDC (SEC-02)
parth0025 Jun 20, 2026
634b0a2
feat(sso): admin SSO config UI + settings page (SEC-02)
parth0025 Jun 20, 2026
7c58e26
feat(pwa): installable PWA — manifest + service worker (SEC-03)
parth0025 Jun 20, 2026
20ac6d1
feat(audit): tamper-evident audit logs with retention (SEC-04)
parth0025 Jun 20, 2026
e845d25
feat(audit): audit log viewer in Settings (SEC-04 frontend)
parth0025 Jun 20, 2026
de1b2dc
feat(scim): user provisioning via SCIM 2.0, paired with SSO (SEC-05)
parth0025 Jun 20, 2026
780f16e
fix(pwa): stop reload loop — withdraw the SEC-03 service worker
parth0025 Jun 20, 2026
685fdeb
feat(offline): work offline + sync on reconnect, app-level (SEC-06)
parth0025 Jun 20, 2026
759bd24
docs(compliance): posture pack for SOC 2 / ISO 27001 / HIPAA (SEC-07)
parth0025 Jun 20, 2026
986efb5
docs(compliance): review checklist for the SEC-07 posture pack
parth0025 Jun 20, 2026
898f1c0
feat(pto): time-off / PTO with capacity reduction (SEC-08)
parth0025 Jun 20, 2026
f4c480f
fix(sso): use a published samlify schema validator
parth0025 Jun 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .claude/test-cases/AuditLog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Audit Log — Test Cases

**Feature:** SEC-04 — tamper-evident audit logs with retention
**Backend:** `Modules/Audit/{recorder,controller,routes,init}.js`, `Modules/Audit/helpers/auditRules.js`, `audit_logs` collection, `GET /api/v1/audit-logs`, daily 02:00 retention cron (`cron.js`)
**Frontend:** `frontend/src/views/Settings/Audit/AuditLog.vue` (Settings → Audit Log tab)
**Hooks (initial):** `member.update` (Members controller), `sso.config_update` (SSO config)
**Unit-tested:** `tests/audit-rules.test.js` (`normalizeAuditEntry`, `retentionCutoff`) — in the Node suite (29 suites / 327 tests green).
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Precondition | Steps | Expected | Actual | Status |
|----|-------|--------------|-------|----------|--------|--------|
| AUD-01 | Member change is audited | Logged in as owner/admin | Settings → Members, change a member's role (or guest projects / status), save | A new row appears in Audit Log: action `member.update`, entity `member: <id>`, your name as actor, meta lists the changed fields | | ⬜ |
| AUD-02 | SSO config change is audited | Owner/admin; SSO module configured | Settings → SSO, toggle Enabled or edit a field, Save | A new `sso.config_update` row appears with meta `{provider, isEnabled}` | | ⬜ |
| AUD-03 | Owner/admin can view the log | Logged in as owner or admin | Open Settings → Audit Log | Table loads with recent events, newest first; pagination footer shows totals | | ⬜ |
| AUD-04 | Non-privileged user is blocked | Logged in as a normal member (roleType 3) | Call `GET /api/v1/audit-logs` (or open the tab if visible) | `403` with `Owner/admin only.`; no rows returned | | ⬜ |
| AUD-05 | Filter by action | Several events of different actions exist | Type `member.update` in Action, click Apply | Only `member.update` rows shown; total updates accordingly | | ⬜ |
| AUD-06 | Filter by entity type | Mixed entity events exist | Type `sso` in Entity type, Apply | Only `sso`-entity rows shown | | ⬜ |
| AUD-07 | Filter by date range | Events span multiple days | Set From/To to a window that excludes today, Apply | Only rows within the window shown; today's rows excluded | | ⬜ |
| AUD-08 | Pagination | More than 25 events exist | Click Next | Page 2 loads (next 25), newest-first order preserved; Prev returns to page 1 | | ⬜ |
| AUD-09 | Clear filters | Filters applied | Click Clear | All filters reset, page returns to 1, full list reloads | | ⬜ |
| AUD-10 | Logging never breaks the action | — | Force a recorder failure (e.g. bad companyId in a dev hook) and perform a member update | The member update still succeeds (HTTP 200); the audit write fails silently (fire-and-forget) | | ⬜ |
| AUD-11 | Immutable trail | Audit rows exist | Attempt to edit/delete a row via any normal API | No app endpoint mutates or deletes individual rows; only the retention cron prunes by age | | ⬜ |
| AUD-12 | Retention prune | Rows older than `AUDIT_RETENTION_DAYS` exist (default 365); set a small value in dev | Run `auditRecorder.runAuditRetentionForAllCompanies()` (or wait for the 02:00 cron) | Rows older than the cutoff are deleted; newer rows remain | | ⬜ |
| AUD-13 | Multi-tenant isolation | Two companies with audit rows | As company A's admin, open Audit Log | Only company A's events are visible; company B's are never returned | | ⬜ |
| AUD-14 | Entry normalization (unit) | — | `npx jest tests/audit-rules.test.js` | `action` required; over-long fields bounded; non-plain `meta` rejected; `retentionCutoff` correct — all green | | ✅ |

**Total:** 14 cases (1 unit-automated, 13 runtime/manual). First hooks are member + SSO changes; more mutation hooks can be added incrementally using `recordAuditFromReq`.
21 changes: 21 additions & 0 deletions .claude/test-cases/CompliancePosture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Compliance Posture — Review Checklist

**Feature:** SEC-07 — compliance posture (SOC 2 / ISO 27001 / HIPAA)
**Type:** docs / process (no code) — "tests" are a review checklist.
**Files:** `docs/compliance/{README,security-whitepaper,control-mapping,shared-responsibility,gaps}.md`, linked from `SECURITY.md`.
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Check | Expected | Status |
|----|-------|-------|----------|--------|
| COMP-01 | Docs published | `docs/compliance/` exists with 5 markdown files | All present; render on GitHub + sync to GitBook | ✅ |
| COMP-02 | Discoverable | SECURITY.md links the pack | Link to `docs/compliance/README.md` present | ✅ |
| COMP-03 | Whitepaper coverage | Read security-whitepaper.md | Covers authn, authz/RBAC, tenant isolation, audit, data protection, hardening, vuln mgmt | ✅ |
| COMP-04 | All three frameworks mapped | Read control-mapping.md | Separate tables for SOC 2 TSC, ISO 27001:2022 Annex A, HIPAA Security Rule | ✅ |
| COMP-05 | Shared-responsibility | Read shared-responsibility.md | Responsibility matrix + operator hardening checklist | ✅ |
| COMP-06 | Gaps tracked | Read gaps.md | Gap register with ID, owner, severity, status, target | ✅ |
| COMP-07 | Accuracy — controls real | Cross-check cited controls vs. code | Each cited control is actually implemented (bcrypt, 2FA, SSO, SCIM, RBAC, audit logs, companyId isolation, Helmet, rate limiting) | ⬜ |
| COMP-08 | Accuracy — no over-claim | Read framing | States AlianHub OSS is NOT certified; self-hosting is shared responsibility; no false certification claims | ✅ |
| COMP-09 | Links resolve | Click internal links | README ↔ whitepaper ↔ mapping ↔ shared-responsibility ↔ gaps ↔ SECURITY.md all resolve | ⬜ |
| COMP-10 | Maintainable | Read gaps.md footer | Defines review cadence + how to close a gap | ✅ |

**Total:** 10 checks. COMP-07 (control-accuracy cross-check) and COMP-09 (link rendering) to be confirmed by a human reviewer before publishing externally. The docs are intentionally honest about gaps rather than aspirational.
25 changes: 25 additions & 0 deletions .claude/test-cases/GuestRole.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Guest / Client Role — Test Cases

**Feature:** SEC-01 — external guests (roleType 4) restricted to assigned projects, enforced server-side
**Backend:** `Modules/Auth/helpers/guestAccessRules.js` (pure) + `requireGuestProjectAccess` / `requireGuestTaskAccess` in `Config/permissionGuard.js`; `guestProjectIds` on `company_users`; project-list scoping in `getProjectList`
**Guards applied:** project `:id` (detail/update/allTask/sprintFolder), project list, `task/:id`, `/projectdata/taskData`, `tabSyncTask` (by pid), `task/find` (guests denied), comments (by projectId)
**Assignment:** `PUT /api/v1/members {id, data:{roleType:4, guestProjectIds:[...]}}` (role cache invalidated on change)
**Unit tests:** `tests/guest-access-rules.test.js` (10 cases, all green)
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Precondition | Steps | Expected | Actual | Status |
|----|-------|--------------|-------|----------|--------|--------|
| GR-01 | Assign a guest | Owner/admin | `PUT /members` set `roleType:4` + `guestProjectIds:[P1]` | company_users updated; role cache invalidated (takes effect at once) | | ⬜ |
| GR-02 | Project list scoped | User is a guest assigned [P1] | `GET /api/v1/project` | Only P1 returned (not public/other projects) | | ⬜ |
| GR-03 | Project detail 403 | Guest assigned [P1] | `GET /api/v1/project/P2` (unassigned) | 403 Forbidden; `GET .../P1` → 200 | | ⬜ |
| GR-04 | Task detail 403 | Guest assigned [P1]; task T2 in P2 | `GET /api/v1/task/T2` | 403; a task in P1 → 200 | | ⬜ |
| GR-05 | Board sync scoped | Guest assigned [P1] | `POST /tabSyncTask {pid:P2}` | 403; `{pid:P1}` → 200 | | ⬜ |
| GR-06 | Arbitrary task query denied | Guest | `POST /api/v1/task/find` | 403 (guests use the scoped board, not free queries) | | ⬜ |
| GR-07 | Comments scoped | Guest assigned [P1] | view/post comments on P2 vs P1 | P2 → 403; P1 → allowed | | ⬜ |
| GR-08 | No admin nav | Logged in as a guest | open the app | No Settings/admin nav (checkPermission returns null for a guest's keys) | | ⬜ |
| GR-09 | Role change is immediate | Change a user's role to/from guest | retry a previously-allowed call | New scoping applies right away (no 60s cache lag) | | ⬜ |
| GR-10 | Non-guests unaffected | Owner / admin / member | normal usage | All guards pass through; behaviour byte-for-byte unchanged | | ⬜ |

**Total:** 10 cases (API/UI — run on the dev server). Guest-access rules covered by 10 automated unit tests.

**Follow-up (usability, not blocking):** an admin-facing "Guest" role option + per-guest project picker in the Members UI (assignment works via the members API today).
30 changes: 30 additions & 0 deletions .claude/test-cases/OfflineMode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Offline Mode — Test Cases

**Feature:** SEC-06 — work offline, sync on reconnect (app-level; NO service worker)
**Approach:** After the SEC-03 PWA worker was withdrawn (reload loop), offline mode is implemented purely in the app so it behaves identically on localhost / staging / production and can't cause a worker reload loop.
**Files:** `frontend/src/offline/{offlineRules.js,db.js,index.js}`, `frontend/src/components/offline/OfflineBanner.vue`, integration in `frontend/src/services/index.js`, mount + init in `frontend/src/App.vue`, i18n in `en.js`.
**How it works:** successful whitelisted GETs are cached in IndexedDB (fire-and-forget); on a request that fails because we're offline, whitelisted GETs are served from cache and whitelisted writes are queued; on reconnect (or the next successful request) the queue replays FIFO through the real request path.
**Online safety:** the request layer only calls the offline layer on the success side (non-blocking cache) and the failure path (which already rejected) — online behaviour is unchanged.
**Unit-tested:** `tests/offline-rules.test.js` (whitelists, offline-error detection, synthetic responses) — in the Node suite (31 suites / 356 tests green).
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Precondition | Steps | Expected | Actual | Status |
|----|-------|--------------|-------|----------|--------|--------|
| OFF-01 | Online unchanged | Normal use | Use the app online (projects, tasks, comments, time) | Identical to before — no banner, no behaviour change; GET responses get cached in the background | | ⬜ |
| OFF-02 | Offline banner | App loaded, then go offline (DevTools → Network → Offline) | Observe top of app | Amber "You're offline…" banner appears; disappears on reconnect | | ⬜ |
| OFF-03 | View cached data offline | Visited project/task lists while online, then go offline | Navigate back to those project/task views | Last-cached data is shown (not an error/blank) | | ⬜ |
| OFF-04 | Uncached data offline | Offline; open a view never loaded online | Navigate there | Fails gracefully as before (no cache to serve) — app does not crash | | ⬜ |
| OFF-05 | Queue a task edit offline | Offline | Change a task status / field (PATCH /api/v2/tasks or PUT /api/v1/task) | UI accepts it (queued); banner shows "N change(s) waiting to sync" | | ⬜ |
| OFF-06 | Queue a comment offline | Offline | Post a comment | Accepted + queued; counted in pending | | ⬜ |
| OFF-07 | Queue a time log offline | Offline | Log time (POST /api/v2/manualLogtime) | Accepted + queued | | ⬜ |
| OFF-08 | Sync on reconnect | Items queued offline | Go back online | Banner shows "Syncing N…"; queued writes replay FIFO; pending count → 0; server reflects the changes | | ⬜ |
| OFF-09 | Sync without an online event | Online but server was unreachable; items queued | Server recovers; make any successful request | Backlog drains opportunistically (a success proves connectivity) | | ⬜ |
| OFF-10 | Rejected write is dropped | A queued write the server will 4xx (e.g. stale) | Reconnect | That item is dropped (not retried forever); others still sync | | ⬜ |
| OFF-11 | FIFO order | Multiple queued writes to the same task | Reconnect | Replayed in the order they were made; final state matches the last edit | | ⬜ |
| OFF-12 | Logout clears offline data | Queued/cached data exists | Log out, then log in as a different user | No cached data or queued writes from the previous account remain (store cleared on logout) | | ⬜ |
| OFF-13 | Non-whitelisted untouched | — | Offline, trigger a non-whitelisted write (e.g. member/SSO settings) | Not queued — fails as before (only safe, common edits are queued) | | ⬜ |
| OFF-14 | Works everywhere | localhost / staging / production | Repeat OFF-02..08 in each | Same behaviour in all three; no service worker involved; no reload loop | | ⬜ |
| OFF-15 | IndexedDB unavailable | Private mode / blocked storage | Use offline | Fails soft — app still works online; offline features simply no-op | | ⬜ |
| OFF-16 | Rules (unit) | — | `npx jest tests/offline-rules.test.js` | All green — whitelists, offline-error detection, synthetic responses | | ✅ |

**Total:** 16 cases (1 unit-automated, 15 runtime/manual). Scope per AHE-3763 done-when: cached data viewable offline + queued writes sync on reconnect. Cold-starting a brand-new tab with no network (which needs a service-worker shell) is intentionally out of scope — that's what caused the SEC-03 loop; a production-only PWA shell can be added later if wanted.
18 changes: 18 additions & 0 deletions .claude/test-cases/PWA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Installable PWA — Test Cases

**Feature:** SEC-03 — installable Progressive Web App (interim before native apps)
**Files:** `frontend/public/manifest.webmanifest`, `frontend/public/service-worker.js`, `frontend/public/index.html`
**Note:** browser/runtime-verified (not in the Node suite). Served as static files at `/manifest.webmanifest` + `/service-worker.js` (scope `/`).
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Precondition | Steps | Expected | Actual | Status |
|----|-------|--------------|-------|----------|--------|--------|
| PWA-01 | Manifest valid | Built app | DevTools → Application → Manifest | Name/short_name/theme/icons/standalone load with no errors | | ⬜ |
| PWA-02 | Service worker registers | Built app over HTTPS (or localhost) | DevTools → Application → Service Workers | `service-worker.js` is activated + running | | ⬜ |
| PWA-03 | Installable | Chrome/Edge desktop or Android | Use the install / "Add to Home Screen" prompt | App installs + launches standalone (no browser chrome) | | ⬜ |
| PWA-04 | Offline shell | App loaded once, then go offline | Reload / navigate | The cached app shell loads (no dino); API calls fail gracefully (not cached) | | ⬜ |
| PWA-05 | API never cached | — | Inspect SW cache | No `/api/*` or `/socket*` responses cached; only shell + static assets | | ⬜ |
| PWA-06 | Update after deploy | New build deployed | Reload twice | Online navigations fetch fresh (network-first); old cache purged on the new SW activating | | ⬜ |
| PWA-07 | Mobile usability | Phone | Open core flows (projects, tasks, timesheet) | Usable on a phone screen (viewport-fit=cover; responsive) | | ⬜ |

**Total:** 7 cases (browser/device). Native iOS/Android apps + a deeper per-view mobile UX pass remain follow-ups.
28 changes: 28 additions & 0 deletions .claude/test-cases/PtoCalendar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Time-off / PTO — Test Cases

**Feature:** SEC-08 — time-off / PTO; approved entries reduce available capacity
**Backend:** `Modules/Pto/{helpers/ptoRules.js,controller.js,routes.js,init.js}`, `pto_entries` collection, endpoints `POST/GET /api/v1/pto`, `GET /api/v1/pto/capacity`, `PUT /api/v1/pto/:id/status`, `DELETE /api/v1/pto/:id`
**Frontend:** `frontend/src/views/Settings/TimeOff/TimeOff.vue` (Settings → Time Off)
**Capacity:** available hours = working days (Mon–Fri) × hours/day − approved PTO hours in range (feeds REP-06 capacity planning).
**Unit-tested:** `tests/pto-rules.test.js` (validation, working-day count, overlap hours, capacity math) — in the Node suite (32 suites / 369 tests green).
**Legend:** ✅ Pass · ❌ Fail · ⬜ Not run

| ID | Title | Precondition | Steps | Expected | Actual | Status |
|----|-------|--------------|-------|----------|--------|--------|
| PTO-01 | Request time off | Logged in | Settings → Time Off; pick type, dates, hours/day; Request | Entry created with status **Pending**; appears in the list | | ⬜ |
| PTO-02 | Validation | — | Submit with end before start, or missing dates | Rejected with a clear message; nothing created | | ⬜ |
| PTO-03 | Member sees only own | Member (role 3) with others' PTO present | Open Time Off | Only the member's own entries are listed | | ⬜ |
| PTO-04 | Admin sees team | Owner/admin | Open Time Off | All members' entries listed; Approve/Reject shown on pending | | ⬜ |
| PTO-05 | Approve | Admin; a pending entry | Click Approve | Status → **Approved**; recorded in the audit log (`pto.approved`) | | ⬜ |
| PTO-06 | Reject | Admin; a pending entry | Click Reject | Status → **Rejected**; not counted against capacity | | ⬜ |
| PTO-07 | Approve is admin-only | Member | `PUT /api/v1/pto/:id/status` | `403` Owner/admin only | | ⬜ |
| PTO-08 | Capacity reduced by approved PTO | An approved entry this month | View "My capacity"; or `GET /pto/capacity?from=&to=` | Available = working-hours − approved PTO hours (e.g. 184 − 40 = 144 for a 5-day leave in a 23-weekday month) | | ⬜ |
| PTO-09 | Pending/rejected don't reduce | Pending or rejected entries only | Check capacity | `ptoHours` = 0; available = full capacity | | ⬜ |
| PTO-10 | Weekends excluded | Leave spanning a weekend | Check capacity | Only Mon–Fri days within the leave count | | ⬜ |
| PTO-11 | Delete own | Member; own entry | Delete | Entry soft-removed (deletedStatusKey=1); gone from list | | ⬜ |
| PTO-12 | Delete others blocked | Member; another user's entry id | `DELETE /api/v1/pto/:id` | `403` — can only remove own | | ⬜ |
| PTO-13 | Tenant isolation | Two companies | List/capacity as company A | Only company A's PTO visible (companyId-scoped) | | ⬜ |
| PTO-14 | Capacity never negative | Leave longer than the range | Check capacity | `availableHours` floors at 0 | | ⬜ |
| PTO-15 | Rules (unit) | — | `npx jest tests/pto-rules.test.js` | All green — validation, working-day count, overlap hours, approved-only capacity reduction, non-negative floor | | ✅ |

**Total:** 15 cases (1 unit-automated, 14 runtime/manual). Done-when met: PTO entries reduce a user's available capacity. A visual month-grid calendar is a possible follow-up; the schedule list + capacity readout cover the requirement.
Loading
Loading