The oidc-binding PUT/DELETE endpoints allowed any account.create holder
(non-admin role 'account-manager') to bind an attacker-controlled OIDC
identity to the admin account (or unbind admin, reopening the OIDC-only
bootstrap window) — full admin takeover.
Non-admin callers may now only manage bindings of non-privileged
accounts: requests targeting an is_admin user, the built-in
acc-mgr/deleted-user, or any holder of account.create / user.reset-apikey
are rejected with 403. Global admins remain unrestricted, so the
intended "account-manager binds normal users" capability is preserved.
Found by post-feature security audit. Verified locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In OIDC-only mode, before any admin is linked, an IdP user whose token
carries the configured admin role (default "admin"; OIDC_ADMIN_ROLE /
oidc_settings.admin_role) auto-connects to the unbound hf admin on
first OIDC sign-in, then the window self-closes once any admin is
bound. Roles are scanned across userinfo + the (unverified) access
token: realm_access.roles, resource_access.*.roles, roles/role/groups.
Adds admin_role to settings model/env/effective/API and to the wizard
bootstrap config. Replaces the manual admin-subject approach.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
init_wizard applies config['oidc'] on first init: creates the
oidc_settings row and, when admin_subject is given, binds the
bootstrap admin so OIDC-only deployments are reachable. Idempotent —
an existing row / admin binding is preserved (later admin edits via
the API survive restarts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Test points for OIDC login, user binding, HARBORFORGE_OIDC_ONLY mode,
and the admin OIDC settings page, with local verification status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persist OIDC config in a single-row oidc_settings table; non-empty DB
fields override the OIDC_* env vars (env = bootstrap default). The
Authlib client is rebuilt when config changes.
- GET/PUT /auth/oidc/settings — admin only, via JWT OR API key. The
API-key path is the recovery channel when OIDC-only mode is on and
OIDC is misconfigured (avoids total lockout).
- client_secret is write-only: never returned (has_client_secret bool),
preserved when the field is left blank on update.
- /auth/config, login/link/callback now use the effective (DB|env)
config so enabling OIDC needs no redeploy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Generic OIDC (Authlib discovery) Authorization Code flow; backend
issues the existing HS256 JWT on success. Unbound identities are
rejected (no auto-provisioning).
- User.oidc_issuer/oidc_subject (unique together) + startup migration.
- PUT/DELETE /users/{id}/oidc-binding (admin or account-manager;
JWT or API key; 409 on conflict). Self-link /auth/oidc/link
(non-OIDC_ONLY only). Public GET /auth/config.
- HARBORFORGE_OIDC_ONLY: /auth/token rejected, create/update ignore
password (passwordless users; API keys + OIDC still work).
- Dockerfile ARG/ENV HARBORFORGE_OIDC_ONLY; authlib+itsdangerous deps;
SessionMiddleware for OIDC state. Fixed _user_response to expose
the new binding fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the auth/RBAC/SSRF hardening in this branch: mandatory strong
SECRET_KEY (server refuses weak/default), admin-only + masked /api-keys,
admin-only /webhooks with SSRF guard, project role hierarchy, and auth
added to previously-open endpoints. Fixed stale Issues→tasks model.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Returns today's slots for all agents on a claw instance, keyed by
agent_id. Used by HF Plugin to maintain a local schedule cache
instead of per-agent heartbeat.
Also records heartbeat for all agents on the instance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add deleted-user as a built-in account (no permissions, cannot log in)
created during init_wizard, protected from deletion like acc-mgr
- On user delete, reassign all foreign key references to deleted-user
then delete the original user, instead of failing on IntegrityError
- API keys, notifications, and project memberships are deleted outright
since they're meaningless without the real user
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Change dependency from get_current_user (OAuth2 only) to
get_current_user_or_apikey, enabling account-manager API key
to reset user API keys for provisioning workflows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- resolve_slot_competition: selects highest-priority slot as winner,
marks remaining as Deferred with priority += 1 (capped at 99)
- defer_all_slots: defers all pending slots when agent is not idle
- CompetitionResult dataclass for structured return
- Full test coverage: winner selection, priority bumping, cap, ties,
empty input, single slot, already-deferred slots
- Add GET /calendar/dates endpoint that returns sorted future dates
with at least one materialized (real) slot
- Excludes skipped/aborted slots and pure plan-generated virtual dates
- Add DateListResponse schema
- PATCH /calendar/plans/{plan_id}: edit a recurring schedule plan
- Validates period-parameter hierarchy after merge
- Rejects edits to inactive (cancelled) plans
- Detaches future materialized slots so they keep old data
- Past materialized slots remain untouched
- POST /calendar/plans/{plan_id}/cancel: cancel (soft-delete) a plan
- Sets is_active=False
- Detaches future materialized slots (plan_id -> NULL)
- Preserves past materialized slots, returns their IDs
- Added SchedulePlanEdit and SchedulePlanCancelResponse schemas
- Add POST /calendar/slots/{slot_id}/cancel for real slot cancellation
- Add POST /calendar/slots/virtual/{virtual_id}/cancel for virtual slot cancellation
- Virtual cancel materializes the slot first, then marks as Skipped
- Both endpoints enforce past-slot immutability guard
- Both endpoints detach from plan (set plan_id=NULL)
- Status set to SlotStatus.SKIPPED on cancel
- Add TimeSlotCancelResponse schema
- Add TimeSlotEdit schema (partial update, all fields optional)
- Add TimeSlotEditResponse schema
- Add PATCH /calendar/slots/{slot_id} for editing real slots
- Add PATCH /calendar/slots/virtual/{virtual_id} for editing virtual slots
- Triggers materialization before applying edits
- Detaches from plan after edit
- Both endpoints enforce past-slot immutability, overlap detection, plan
detachment, and workload warnings
- Add GET /calendar/day endpoint with optional ?date= query param
- Returns unified CalendarDayResponse merging real slots + virtual plan slots
- New CalendarSlotItem schema supports both real (id) and virtual (virtual_id) slots
- Excludes inactive slots (skipped/aborted) from results
- All slots sorted by scheduled_at ascending
- Helper functions for real/virtual slot conversion
BE-CAL-007: Workload warning computation (already implemented in prior wave,
verified tests pass - 24/24). Computes daily/weekly/monthly/yearly scheduled
minutes and compares against user thresholds. Warnings are advisory only.
BE-CAL-008: New slot_immutability service with guards for:
- Forbid edit/cancel of past real slots (raises ImmutableSlotError)
- Forbid edit/cancel of past virtual slots
- Plan-edit/plan-cancel helper to identify past materialized slot IDs
that must not be retroactively modified
Tests: 19/19 passing.