Skip to content

feat: admin, security & access module (SEC-01..08, v14.5.0)#266

Merged
parth0025 merged 17 commits into
stagingfrom
feat/admin-security-v14.5.0
Jun 20, 2026
Merged

feat: admin, security & access module (SEC-01..08, v14.5.0)#266
parth0025 merged 17 commits into
stagingfrom
feat/admin-security-v14.5.0

Conversation

@parth0025

Copy link
Copy Markdown
Collaborator

Admin, Security & Access (v14.5.0)

The complete Admin, Security & Access module β€” 8 tasks (SEC-01..08), one per commit, each with tests/docs and tracker updates (AHE-3758..3765).

What's included

  • SEC-01 β€” Guest role + scoping (RBAC): roleType 4 guest; project/task/comment endpoints enforce guest project scope server-side (non-guests pass straight through). Guest branch in getProjectList.
  • SEC-02 β€” Enterprise SSO: SAML 2.0 + OIDC, per-company config (IdP secrets server-side only), JIT provisioning, admin Settings UI.
  • SEC-03 β€” Installable PWA: manifest + meta. ⚠️ Its service worker was withdrawn (see fix below).
  • SEC-04 β€” Audit logs: immutable audit_logs + retention cron + owner/admin read API & viewer. Hooks on member / SSO / SCIM / PTO changes.
  • SEC-05 β€” SCIM 2.0 provisioning: bearer token (company-in-token, bcrypt-hashed), full Users protocol + discovery, reuses the SSO JIT path; admin token UI.
  • SEC-06 β€” Offline mode: app-level (no service worker) β€” IndexedDB read-cache + write-queue + FIFO reconnect sync; hooked on the request failure path only so online behaviour is unchanged.
  • SEC-07 β€” Compliance posture: docs/compliance/ β€” whitepaper, SOC 2 / ISO 27001 / HIPAA control mapping, self-hosted shared-responsibility model, tracked gap register.
  • SEC-08 β€” Time-off / PTO: pto_entries + capacity engine (approved PTO reduces available capacity), CRUD + approval API + capacity endpoint, Settings β†’ Time Off.

Reliability fix included

  • fix(pwa): the SEC-03 service worker caused a reload loop (cache-first assets vs. dev HMR). Replaced with a self-unregistering kill-switch + removed registration, so affected browsers self-heal. SEC-06 delivers offline support without a worker.

Verification

  • 369 tests / 32 suites green β€” new pure-logic unit tests for guest, SSO, audit, SCIM, offline, and PTO rules.
  • Boot check: every new module loads + registers routes (new deps are lazy-loaded β€” the app boots even pre-npm install).
  • Frontend build: clean.
  • Pre-merge core-functionality audit: guest guards short-circuit for all non-guest roles (zero impact on existing users); only new endpoints added to auth middleware; schema-registry changes are additive.

Operational note

  • The production deploy must run npm install for the new lazy deps (openid-client, samlify, pdfmake). Core works without them; only SSO/SCIM/PDF features need them.

Scope: feature β†’ staging only. Promotion staging β†’ main (production) is intentionally left to a separate, gated step.

πŸ€– Generated with Claude Code

parth0025 and others added 5 commits June 20, 2026 14:36
Foundation for the guest/client role (external users restricted to their
assigned projects). Wiring these guards onto the detail endpoints follows.

- ROLE_GUEST (4) + guestProjectIds on company_users.
- Pure guestAccessRules (isGuest, guestAllowsProject) + 10 unit tests.
- Reusable guards in permissionGuard: requireGuestProjectAccess /
  requireGuestTaskAccess β€” these enforce for ALL clients (guests are web
  users), while every non-guest passes straight through untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- getProjectList: a guest (roleType 4) sees ONLY the projects in their
  guestProjectIds β€” short-circuits the public/private logic.
- requireGuestProjectAccess() guard on the project :id routes
  (detail / update / allTask / sprintFolder) β€” a guest hitting a non-assigned
  project now gets 403. Non-guests are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-01)

- GET /api/v1/task/:id -> requireGuestTaskAccess() (403 if the task's project
  isn't assigned to the guest).
- GET /api/v1/projectdata/taskData -> requireGuestProjectAccess() (by projectId).

Note: the query endpoints (tabSyncTask, task/find) and comments still need
controller-level query-scoping for guests (follow-up within SEC-01) β€” handled
carefully to avoid false denials on a guest's own assigned-project boards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tabSyncTask -> requireGuestProjectAccess by req.body.pid; task/find -> guests
  denied (arbitrary query, no project context β€” they use the scoped board).
- Comments save/update guarded by objId.projectId; paginated/searched reads by
  query.projectId. A guest only touches comments on assigned projects.
- updateMember now invalidates the cached roleType so a role/guest-projects
  change takes effect in the guards immediately (was 60s stale).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 API/UI verification cases for the guest/client role + the pre-existing
detail-endpoint access gap now closed for guests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 658b4021-7a66-4223-82a9-425e45dde1e7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/admin-security-v14.5.0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

parth0025 and others added 11 commits June 20, 2026 17:23
- New per-company sso_configs collection + admin config CRUD (owner/admin
  gated; secret-free public view for the login page).
- OIDC: /sso/oidc/initiate + /callback (openid-client, lazy-required) with
  state(CSRF)+nonce+PKCE, single-use 5-min cache.
- SAML: /sso/saml/initiate + /acs + /metadata (samlify, lazy-required;
  signature-validated).
- JIT provisioning mirrors the OAuth signup (global userAuth+users +
  company_users + notifications); reuses the session/JWT pipeline via a
  redirect-style finalizeSsoSession.
- Auth wiring: only /sso/config requires JWT; the login-flow routes are public.
- Deps added (lazy): openid-client, samlify, @authenio/samlify-node-saml2
  (run npm install on the server). 13 unit tests; full suite 319 green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Settings β†’ SSO page (SsoSettings.vue): provider/OIDC/SAML fields, enable +
  auto-provision toggles, and the login/redirect/ACS/metadata URLs to hand to
  the IdP. Owner/admin gated (server-side); configured via /api/v2/sso/config.
- Settings tab + route + env constant + i18n.
- .claude/test-cases/SSO.md (9 IdP/UI cases).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- manifest.webmanifest (standalone, theme color, logo icons) + a conservative
  service worker: network-first navigations with an offline shell fallback,
  cache-first hashed assets, NEVER caches /api or sockets, old caches purged
  on activate.
- index.html: manifest link, theme-color + apple mobile meta, apple-touch-icon,
  viewport-fit=cover, and a non-fatal SW registration.

The app now installs to a home screen and runs standalone; offline opens the
cached shell. Native apps + a deeper mobile UX pass remain follow-ups.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Immutable, insert-only audit trail scoped per company:

- audit_logs collection (8-edit registration): indexed on createdAt,
  actorId+createdAt, entityType+entityId+createdAt, action+createdAt.
- Modules/Audit/recorder.js: recordAudit / recordAuditFromReq are
  fire-and-forget and never throw, so a logging failure can never break
  the request that triggered it. runAuditRetentionForAllCompanies prunes
  rows older than AUDIT_RETENTION_DAYS (default 365) via a daily 02:00 cron.
- Modules/Audit/controller.js: GET /api/v1/audit-logs is owner/admin-gated
  (getRoleType + isPrivileged), with actor/entity/action/date filters and
  $facet pagination, newest first. JWT+companyId enforced in setMiddleware.
- helpers/auditRules.js (pure): normalizeAuditEntry bounds field lengths,
  requires action, rejects non-plain meta; retentionCutoff for the cron.
- First hooks: member.update (role / guest-project / status changes) and
  sso.config_update - both best-effort, wrapped so they cannot fail the
  mutation.

Full suite green (29 suites / 327 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings β†’ Audit Log (owner/admin tab): read-only, filterable, paginated
view onto the immutable trail.

- AuditLog.vue: action / entity-type / date-range filters, server-side
  $facet pagination (25/page), newest first, meta rendered compactly.
  Calls GET /api/v1/audit-logs (env.AUDIT_LOGS); 403 for non-privileged.
- Route (settings/audit-logs β†’ AuditLog) + Settings tab gated by
  settings.settings_security_permissions, beside SSO.
- i18n: settingslider "Audit Log" + full Audit block in en.js (other
  locales fall back to en).
- Self-namespaced button styles (audit-btn*) to avoid the global
  .btn_btn navy-on-navy trap.
- Test cases: .claude/test-cases/AuditLog.md (14 cases; AUD-14 unit-green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto provision/deprovision members from an IdP (Okta, Azure AD, OneLogin).

Backend (Modules/Scim):
- scim_configs collection (8-edit registration). The IdP bearer token is
  stored ONLY as a bcrypt hash + last4; the company is encoded into the
  token (base64url "companyId:secret") so SCIM requests β€” which carry no
  companyId header β€” resolve their tenant before the secret is verified.
- scimAuth middleware on /scim/v2/* (router-scoped, NOT JWT); also accepts
  application/scim+json.
- Protocol: GET/POST/GET{id}/PUT/PATCH/DELETE Users + ServiceProviderConfig
  /ResourceTypes/Schemas discovery. Create→provision, PATCH active:false /
  DELETE→deactivate (soft, this-company-only), filter by userName, paginated
  ListResponse.
- Reuses SSO jitProvisionUser, so a SCIM user and an SSO login for the same
  email are the SAME global user. Deactivation flips company_users
  status/isDelete + invalidates the role cache.
- Admin config: GET/PUT /api/v2/scim/config + POST /api/v2/scim/token
  (owner/admin), token shown once. SCIM mutations recorded to the SEC-04
  audit trail.
- helpers/scimRules.js is pure (token build/parse, email/filter parsing,
  Okta/Azure PATCH normalization, resource shaping) + 19 unit tests.

Frontend: Settings β†’ SCIM tab (ScimSettings.vue) β€” enable, default role,
SCIM base URL, generate/rotate token (shown once, copyable). i18n in en.js.

Verify: 30 suites / 346 tests green. Test cases: .claude/test-cases/ScimProvisioning.md (18).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The SEC-03 PWA worker cached build assets cache-first. Against the dev
server's HMR (non-hashed bundles) it served stale JS, so the HMR runtime
forced a full reload, got stale JS again, and looped. A registered worker
persists in the browser, so the loop survived later changes.

- service-worker.js is now a self-unregistering kill-switch: it claims
  clients, drops the alianhub-pwa caches, and unregisters. It has NO fetch
  handler, so the moment it activates the browser stops serving stale assets
  and the loop ends. Browsers re-check this script on navigation, so affected
  clients heal with no user action.
- index.html: registration replaced with a targeted unregister of any
  /service-worker.js registration (the firebase-messaging worker is left
  intact). Manifest + PWA meta stay (harmless; no worker = no install prompt).

A production-only PWA worker (https + non-localhost, hashed-asset aware) can
be reintroduced later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Offline mode WITHOUT a service worker β€” pure app code, so it behaves
identically on localhost/staging/production and cannot cause the kind of
asset-cache reload loop the withdrawn SEC-03 worker did.

- frontend/src/offline: offlineRules.js (pure whitelists + offline-error
  detection + synthetic responses, unit-tested), db.js (fail-soft IndexedDB
  read-cache + write-queue), index.js (cache, queue, FIFO reconnect sync,
  online/offline state).
- Hooked into services/index.js with ZERO change to online behaviour: it
  caches successful whitelisted GETs (fire-and-forget) and acts only on the
  request FAILURE path (which already rejected). Offline β†’ whitelisted GETs
  are served from cache; whitelisted writes (task update/PATCH, comment, time
  log) are queued and replayed FIFO on reconnect (or on the next successful
  request, so a recovered-but-still-"online" server also drains). Offline data
  is cleared on logout (no cross-account bleed).
- OfflineBanner.vue mounted in App.vue: "offline" / "syncing N…" status;
  hidden when online with nothing queued. i18n in en.js.

Verify: 31 suites / 356 tests green (incl. offline-rules); frontend
`npm run build` compiles clean. Test cases: .claude/test-cases/OfflineMode.md (16).

Done-when met: cached data viewable offline + queued writes sync on reconnect.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Published compliance documentation for the self-hosted model β€” maps real,
implemented AlianHub controls and tracks the gaps honestly.

- security-whitepaper.md β€” architecture, authn/z, tenant isolation, audit,
  data protection, hardening (cites shipped controls: bcrypt, 2FA, SSO,
  SCIM, RBAC + guest scoping, immutable audit logs, companyId isolation,
  Helmet, rate limiting).
- control-mapping.md β€” controls mapped to SOC 2 TSC, ISO/IEC 27001:2022
  Annex A, and the HIPAA Security Rule, each with responsibility
  (AlianHub / Operator / Shared) + status.
- shared-responsibility.md β€” responsibility matrix + operator hardening
  checklist (software vs. operator β€” the self-hosting nuance).
- gaps.md β€” 12-item gap register (encryption-at-rest, secret-at-rest,
  audit tamper-evidence, pen-test, SBOM, session revocation, automatic
  logoff, …) with owner, severity, status, target.
- README.md index; SECURITY.md links the pack. Publishes via GitBook from docs/.

Done-when met: compliance docs are published and gaps are tracked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.claude/test-cases/CompliancePosture.md β€” 10-point review checklist for the
compliance docs (coverage, accuracy, no over-claim, links, maintainability).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Track holidays/leave that reduce a user's available capacity.

Backend (Modules/Pto):
- pto_entries collection (8-edit registration; indexed userId+startDate, status).
- helpers/ptoRules.js (pure): validation, Mon–Fri working-day counting, range
  overlap, and computeAvailableCapacity β€” available = workingDays Γ— hours/day βˆ’
  APPROVED PTO hours in range (pending/rejected don't count; floors at 0).
- controller: create (members request own/pending; owner/admin may create for
  others + set status), list (own for members, team for admins), approve/reject
  (owner/admin only), soft-delete (own or admin), and GET /pto/capacity β€” the
  done-when. companyId-scoped; mutations recorded to the SEC-04 audit trail.
- routes + init; JWT+company via setMiddleware (prefix /api/v1/pto).

Frontend: Settings -> Time Off (TimeOff.vue) β€” request form, this-month
capacity card (working hours βˆ’ time off = available), team/own schedule with
approve/reject (admins) + delete. i18n in en.js.

Verify: 32 suites / 369 tests green (incl. 13 pto-rules); frontend build clean.
Test cases: .claude/test-cases/PtoCalendar.md (15).

Done-when met: PTO entries reduce a user's available capacity (feeds REP-06).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@parth0025 parth0025 force-pushed the feat/admin-security-v14.5.0 branch from 8d150d2 to 898f1c0 Compare June 20, 2026 11:54
@parth0025 parth0025 changed the title feat: Admin, Security & Access module (v14.5.0 β€” SEC-01..08) feat: admin, security & access module (SEC-01..08, v14.5.0) Jun 20, 2026
@authenio/samlify-node-saml2 (added in SEC-02) does not exist on npm, so
`npm ci` / `npm install` failed with a 404 β€” which would have broken the
build/deploy on every environment, not just the lint job. Switch to the
published, pure-JS validator @authenio/samlify-xsd-schema-validator@^1.0.5
(no native/binary deps) and refresh package-lock.json so `npm ci` resolves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@parth0025 parth0025 merged commit e499150 into staging Jun 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant