188 Commits

Author SHA1 Message Date
cad1ca00c7 Fix SECRET_KEY sentinel in backend/.env.example
Align with config.py check so the fatal safety exit triggers correctly
if this file is used verbatim in production.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:46:39 +08:00
c986028f51 Multi-stage Dockerfile: remove gcc/psql from runtime image (PT-11)
Convert to two-stage build: builder stage installs gcc and compiles
Python C extensions, runtime stage copies only the installed packages.
Removes gcc and postgresql-client from the production image, reducing
attack surface. postgresql-client was unused (healthchecks use urllib).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:43:43 +08:00
ab7e4a7c7e Backend pentest remediation (PT-03/05/06/07)
PT-03: Make UMBRA_URL configurable via env var (default http://localhost).
Replaces hardcoded http://10.0.69.35 in notification dispatch job and
ntfy test endpoint. Add UMBRA_URL to .env.example.

PT-05: Add explicit path="/" to session cookie for clarity.

PT-06: Add concurrent session limit (MAX_SESSIONS_PER_USER, default 10).
When exceeded, oldest sessions are revoked. New login always succeeds.

PT-07: Escape LIKE metacharacters (%, _) in admin audit log action
filter to prevent wildcard abuse.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:43:27 +08:00
7721bf5cec Harden nginx: real client IP, HSTS, custom dotfile 404 (PT-01/02/04)
PT-01: Add set_real_ip_from/real_ip_header/real_ip_recursive to restore
real client IP from X-Forwarded-For. Rate limiting now keys on actual
client IP instead of the Pangolin proxy IP.

PT-02: Add Strict-Transport-Security header (max-age 1 year) to both
the server block and static assets block.

PT-04: Replace bare 404 on dotfile requests with JSON response to
suppress nginx server identity disclosure in error pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:43:12 +08:00
ccfc5e151a Fix SECRET_KEY sentinel mismatch in .env.example (W-01)
The .env.example value didn't match the sentinel checked in config.py,
so copying .env.example verbatim to production would bypass the fatal
safety exit. Aligned to use the same default string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:21:32 +08:00
fee454fc33 Fix 503s behind reverse proxy: add uvicorn --proxy-headers
FastAPI trailing-slash redirects (307) were using http:// instead of
https:// because uvicorn wasn't reading X-Forwarded-Proto from the
reverse proxy. When Pangolin (TLS-terminating proxy) received the
http:// redirect it returned 503, breaking all list endpoints
(/events, /calendars, /settings, /projects, /people, /locations).

Adding --proxy-headers makes uvicorn honour X-Forwarded-Proto so
redirects use the correct scheme. --forwarded-allow-ips '*' trusts
headers from any IP since nginx sits on the Docker bridge network.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 17:17:39 +08:00
0c7d057654 Auto-derive COOKIE_SECURE from ENVIRONMENT setting
COOKIE_SECURE now defaults to None and auto-derives from ENVIRONMENT
(production → true, else false) via a Pydantic model_validator. Explicit
env var values are still respected as an override escape hatch. Adds a
startup log line showing the resolved value. Restructures .env.example
with clear sections and inline docs, removes redundant production
checklist block.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:38:54 +08:00
21aa670a39 Extract real client IP from proxy headers instead of Docker bridge IP
Nginx already forwards X-Forwarded-For and X-Real-IP, but the backend
read request.client.host directly — always returning 172.18.0.x. Added
get_client_ip() helper to audit service; updated all 13 call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 19:20:07 +08:00
f8c2df9328 Merge multi-user RBAC with login flow fixes, QA + pentest remediations
Multi-user RBAC (8 phases):
- user_id FKs on all 12 domain models (migrations 026-037)
- Per-user query scoping on all 14 routers
- Admin portal: IAM, system config, audit logs
- Registration flow with optional MFA enforcement
- Role-based authorization (admin/standard)

Login flow fixes:
- Block inactive user login (HTTP 403) before session creation
- Optimistic setQueryData eliminates login flicker
- Unified inline error alerts (401/403/423)
- Axios 401 interceptor excludes auth endpoints

Security hardening:
- Global CSRF middleware (X-Requested-With header check)
- extra="forbid" on all Pydantic input schemas
- max_length on all string fields, ge/le on path IDs
- Timing-safe login (M-02 dummy hash)
- Password reuse prevention on change-password
- Setup endpoint 500 fix (func.count vs scalar_one_or_none)

Verified: QA review (0 critical) + pentest (51+ test cases, 0 exploitable)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 03:02:28 +08:00
a313ce8b32 Update README for multi-user RBAC release
- Add multi-user RBAC, admin portal, and registration to features
- Update tech stack (37 migrations, CSRF middleware, RBAC)
- Expand security section with IDOR protection, CSRF, input validation,
  timing safety, inactive user blocking, password reuse prevention
- Update project structure (18 models, 13 schema modules, admin components)
- Add admin endpoints to API overview
- Note pentest verification (51+ test cases, 0 exploitable findings)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:58:52 +08:00
d269742aa2 Fix pentest findings: setup 500 error + password reuse prevention
- L-01: Setup endpoint used scalar_one_or_none() on unbounded User
  query, throwing 500 MultipleResultsFound when >1 user exists.
  Replaced with select(func.count()) for a reliable check.
- L-02: Change-password allowed reusing the same password, defeating
  must_change_password enforcement. Added equality check before
  accepting the new password.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:42:00 +08:00
1aeb725410 Fix issues from QA review: hash upgrade ordering, interceptor scope, guard
- W-01: Move is_active check before hash upgrade so disabled accounts
  don't get their password hash silently mutated on rejected login
- W-02: Narrow interceptor exclusion to specific auth endpoints instead
  of blanket /auth/* prefix (future-proofs against new auth routes)
- W-03: Add null guard on optimistic setQueryData to handle undefined
  cache gracefully instead of spreading undefined
- S-01: Clear loginError when switching from register back to login mode
- S-03: Add detail dict to auth.login_blocked_inactive audit event

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:13:48 +08:00
c4c06be148 Fix login error vanishing: exclude auth endpoints from 401 interceptor
The global axios 401 interceptor was firing window.location.href =
'/login' on every 401 response, including POST /auth/login with wrong
credentials. This caused a full page reload to /login, which remounted
the entire React tree and reset all LockScreen state (loginError,
username, password) before the user could see the error alert.

Fix: skip the redirect for /auth/* endpoints, which legitimately
return 401 for invalid credentials. The interceptor still redirects
to /login for expired sessions on protected API calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:43:18 +08:00
5426657b2e Fix login error alert disappearing due to browser autofill
After a failed login, the browser's password manager fires onChange
events on the username/password inputs (clearing or resetting them).
The onChange handlers were calling setLoginError(null), which wiped
the error alert immediately after it appeared.

Fix: remove setLoginError(null) from input onChange handlers. The
error now clears at the start of the next submit attempt via the
existing setLoginError(null) in handleCredentialSubmit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:37:57 +08:00
b2d81f7015 Block inactive user login + fix login flicker + inline error alerts
- Backend: reject is_active=False users with HTTP 403 after password
  verification but before session creation (prevents last_login_at
  update, lockout reset, and MFA token issuance for disabled accounts)
- Frontend: optimistic setQueryData on successful login eliminates the
  form flash between mutation success and auth query refetch
- LockScreen: replace lockoutMessage + toast.error with unified
  loginError inline alert for 401/403/423 responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 01:21:06 +08:00
c68fd69cdf Make temp password click-to-copy in reset password flow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 23:35:18 +08:00
8582b41b03 Add user profile fields + IAM search, email column, detail panel
Backend:
- Migration 037: add email, first_name, last_name to users table
- User model: add 3 profile columns
- Admin schemas: extend UserListItem/UserDetailResponse/CreateUserRequest
  with profile fields, email validator, name field sanitization
- _create_user_defaults: accept optional preferred_name kwarg
- POST /users: set profile fields, email uniqueness check, IntegrityError guard
- GET /users/{id}: join Settings for preferred_name, include must_change_password/locked_until

Frontend:
- AdminUser/AdminUserDetail types: add profile + detail fields
- useAdmin: add CreateUserPayload profile fields + useAdminUserDetail query
- CreateUserDialog: optional profile section (first/last name, email, preferred name)
- IAMPage: search bar filtering on username/email/name, email column in table,
  row click to select user with accent highlight
- UserDetailSection: two-column detail panel (User Info + Security & Permissions)
  with inline role editing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 22:40:20 +08:00
c3654dc069 Fix audit log target for deleted users + create user 500 error
1. Audit log: COALESCE target_username with detail JSON fallback so
   deleted users still show their username in the target column
   (target_user_id is SET NULL by FK cascade, but detail JSON
   preserves the username).

2. Create/get user: add exclude={"active_sessions"} to model_dump()
   calls — UserListItem defaults active_sessions=0, so model_dump()
   includes it, then the explicit active_sessions=N keyword argument
   causes a duplicate keyword TypeError. DB commit already happened,
   so the user exists but the response was a 500.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:44:10 +08:00
48e15fa677 UX polish for delete-user: username toast, hide self-delete
S-03: Delete toast now shows the deleted username from the API response
S-04: Delete button hidden for the current admin's own row (backend
still guards with 403, but no reason to show a dead button)

Adds username to auth status response so the frontend can identify
the current user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:30:43 +08:00
e7cb6de7d5 Add admin delete-user with full cascade cleanup
Migration 036 adds ondelete rules to 5 transitive FKs that would
otherwise block user deletion (calendar_events via calendars,
project_tasks via projects, todos via projects, etc.).

DELETE /api/admin/users/{user_id} with self-action guard, last-admin
guard, session revocation, and audit logging. Frontend gets a red
two-click confirm button in the IAM actions menu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:20:47 +08:00
c56830ddb0 Fix SyntaxError in admin.py: add default to Request params
Three functions (update_user_role, toggle_mfa_enforce, toggle_user_active)
had `request: Request` without a default after Path params with defaults,
causing Python SyntaxError on import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 18:31:25 +08:00
1ebc41b9d7 L-03: Session 7-day sliding window with 30-day hard ceiling
Reduce session expiry from 30 days to 7 days of inactivity while
preserving a 30-day absolute token lifetime for itsdangerous:

- SESSION_MAX_AGE_DAYS=7: sliding window for DB expires_at + cookie
- SESSION_TOKEN_HARD_CEILING_DAYS=30: itsdangerous max_age (prevents
  rejecting renewed tokens whose creation timestamp exceeds 7 days)
- get_current_user: silently extends expires_at and re-issues cookie
  when >1 day has elapsed since last renewal
- Active users never notice; 7 days of inactivity forces re-login;
  30-day absolute ceiling forces re-login regardless of activity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:45:15 +08:00
8e27f2920b M-02: Timing-safe login prevents username enumeration
When a login targets a non-existent username, run Argon2id against a
pre-computed dummy hash so response time (~60ms) matches wrong-password
attempts. Also reorder the login flow to run verify_password_with_upgrade
BEFORE the lockout check, preventing timing side-channels that could
distinguish locked accounts from wrong passwords.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:44:27 +08:00
2f58282c31 M-01+M-03: Add input validation and extra=forbid to all request schemas
- Add max_length constraints to all string fields in request schemas,
  matching DB column limits (title:255, description:5000, etc.)
- Add min_length=1 to required name/title fields
- Add ConfigDict(extra="forbid") to all request schemas to reject
  unknown fields (prevents silent field injection)
- Add Path(ge=1, le=2147483647) to all integer path parameters across
  all routers to prevent integer overflow → 500 errors
- Add max_length to TOTP inline schemas (code:6, mfa_token:256, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:43:55 +08:00
581efa183a H-01: Add global CSRF middleware for all mutating endpoints
Replace the admin-only verify_xhr dependency with a pure ASGI
CSRFHeaderMiddleware that validates X-Requested-With: XMLHttpRequest
on all POST/PUT/PATCH/DELETE requests globally. Pre-auth endpoints
(login, setup, register, totp-verify, enforce-setup/confirm) are
exempt. This closes the CSRF gap where non-admin routes accepted
requests without origin validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 15:39:42 +08:00
9f7bbbfcbb Add per-user active session counts to IAM user list
Move active_sessions field from UserDetailResponse into UserListItem
so GET /admin/users returns session counts. Uses a correlated subquery
to count non-revoked, non-expired sessions per user.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 13:26:32 +08:00
a128005ae5 Fix create-mode crash in detail panels (null entity access)
The desktop detail panels are pre-mounted (always in DOM, hidden with w-0).
useState(isCreating) only captures the initial value on mount (false), so
when isCreating later becomes true via props, isEditing stays false. The
view-mode branch then runs with a null entity, crashing on property access.

Fix: use (isEditing || isCreating) for all conditionals that gate between
edit/create form and view mode, ensuring the form always renders when
isCreating is true regardless of isEditing state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:57:19 +08:00
f07ce02576 Fix crash when creating new todo/reminder/event (null.priority)
All three DetailPanel components initialized isEditing=false even
when isCreating=true. The useEffect that flips it to true runs AFTER
the first render, so the view-mode branch executes with todo=null,
crashing on null.priority. Initialize isEditing from isCreating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:35:31 +08:00
0fc3f1a14b Allow dots in usernames (e.g. user.test)
Added . to the username character whitelist regex. No security
reason to exclude it — dots are standard in usernames.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:03:25 +08:00
e860723a2a Fix Edit Role submenu overflowing right edge of viewport
Submenu was positioned left-full (opening rightward) but the parent
dropdown is already at the right edge. Changed to right-full so it
opens leftward into available space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:35:03 +08:00
4fc85684ea Fix IAM user actions dropdown clipped by overflow-x-auto
The table wrapper's overflow-x-auto forced overflow-y to also clip,
hiding the 3-dot actions dropdown below the container boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:23:08 +08:00
2438cdcf25 Fix migration 034 failing on fresh DB: drop index not constraint
Migration 022 created a unique INDEX (ix_ntfy_sent_notification_key),
not a named unique CONSTRAINT. Migration 034 was trying to drop a
constraint name that only existed on upgraded DBs. Fixed to drop the
index instead, which works on both fresh and upgrade paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 06:06:13 +08:00
619e220622 Fix QA review #2: W-03/W-04, S-01 through S-04
W-03: Unify split transactions — _create_db_session() now uses flush()
      instead of commit(), callers own the final commit.
W-04: Time-bound dedup key fetch to 7-day purge window.
S-01: Type admin dashboard response with RecentLoginItem/RecentAuditItem.
S-02: Convert starred events index to partial index WHERE is_starred = true.
S-03: EventTemplate.created_at default changed to func.now() for consistency.
S-04: Add single-worker scaling note to weather cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 05:41:16 +08:00
72e00f3a69 Fix QA review #2: backup code flow, audit filters, schema hardening
C-01: verifyTotp now sends backup_code field when in backup mode
C-02: Backup code input filter allows alphanumeric chars (not digits only)
W-01: Audit log ACTION_TYPES aligned with actual backend action strings
W-02: Added extra="forbid" to SetupRequest and LoginRequest schemas

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:59:29 +08:00
72ac1d53fb Fix migration 030 failing on fresh DB with no admin user
Migration 006 seeds default calendar rows. On a fresh install, no users
exist when migration 030 runs, so the backfill SELECT returns NULL and
SET NOT NULL fails. Now deletes orphan calendars before enforcing the
constraint — account setup will recreate defaults for new users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:49:57 +08:00
cbf4663e8d Fix TS build errors and apply remaining QA fixes
Remove unused imports (UserCheck, Loader2, ShieldOff) and replace
non-existent SmartphoneOff icon with Smartphone in admin components.
Includes backend query fixes, performance indexes migration, and
admin page shared utilities extraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 04:42:23 +08:00
e57a5b00c9 Fix QA review findings: C-01 through C-04, W-01 through W-07, S-01/S-04/S-05/S-06
Critical fixes:
- C-01: Pass user_id to _mark_sent/_already_sent (ntfy crash)
- C-02: Align frontend HTTP methods with backend routes (PATCH->PUT,
  DELETE->POST, fix reset-password/enforce-mfa/disable-mfa paths)
- C-03: Add X-Requested-With to CORS allow_headers
- C-04: Replace scalar_one_or_none with func.count for auth/status

Warning fixes:
- W-01: Batch audit log into same transaction in create_user, setup, register
- W-02: Extract users array from UserListResponse wrapper in useAdminUsers
- W-03: Update password hint from "8 chars" to "12 chars" in CreateUserDialog
- W-04: Remove password input from reset flow, show returned temp password
- W-06: Remove unused actor_alias variable in admin_dashboard
- W-07: Resolve usernames in dashboard audit entries via JOIN, remove
  ip_address column from recent_logins (not tracked on User model)

Suggestions applied:
- S-01/S-06: Add extra="forbid" to all admin mutation schemas
- S-04: Add ondelete="SET NULL" to audit_log.actor_user_id FK
- S-05: Improve registration error message for better UX

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:19:04 +08:00
e5a7ce13e0 Merge multi-user RBAC implementation (Phases 1-8)
Complete multi-user integration for UMBRA:
- Database: role enum, system_config, audit_log, user_id FKs on all tables
- Auth: RBAC with require_role(), registration, MFA enforcement
- Routing: All 12 routers scoped by user_id
- Admin API: 14 endpoints for IAM, config, audit, dashboard
- Admin Portal: IAM page, config page, dashboard with stats
- Registration flow: LockScreen with register/MFA-enforce/password-change
- Security: SEC-01 through SEC-15 mitigations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:06:43 +08:00
d8bdae8ec3 Implement multi-user RBAC: database, auth, routing, admin API (Phases 1-6)
Phase 1: Add role, mfa_enforce_pending, must_change_password to users table.
Create system_config (singleton) and audit_log tables. Migration 026.

Phase 2: Add user_id FK to all 8 data tables (todos, reminders, projects,
calendars, people, locations, event_templates, ntfy_sent) with 4-step
nullable→backfill→FK→NOT NULL pattern. Migrations 027-034.

Phase 3: Harden auth schemas (extra="forbid" on RegisterRequest), add
MFA enforcement token serializer with distinct salt, rewrite auth router
with require_role() factory and registration endpoint.

Phase 4: Scope all 12 routers by user_id, fix dependency type bugs,
bound weather cache (SEC-15), multi-user ntfy dispatch.

Phase 5: Create admin router (14 endpoints), admin schemas, audit
service, rate limiting in nginx. SEC-08 CSRF via X-Requested-With.

Phase 6: Update frontend types, useAuth hook (role/isAdmin/register),
App.tsx (AdminRoute guard), Sidebar (admin link), api.ts (XHR header).

Security findings addressed: SEC-01, SEC-02, SEC-03, SEC-04, SEC-05,
SEC-06, SEC-07, SEC-08, SEC-12, SEC-13, SEC-15.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 19:06:25 +08:00
2ec70d9344 Add Phase 7 admin portal frontend (IAM, Config, Dashboard)
Creates 7 files: useAdmin hook with TanStack Query v5, AdminPortal
layout with horizontal tab nav, IAMPage with user table + stat cards
+ system settings, UserActionsMenu with two-click confirms, CreateUserDialog,
ConfigPage with paginated audit log + action filter, AdminDashboardPage
with stats + recent logins/actions tables.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:40:16 +08:00
464b8b911f Phase 8: Registration flow & MFA enforcement UI
- backend: add POST /auth/totp/enforce-setup and /auth/totp/enforce-confirm
  endpoints that operate on mfa_enforce_token (not session cookie), generate
  TOTP secret/QR/backup codes, verify confirmation code, enable TOTP, clear
  mfa_enforce_pending flag, and issue a full session cookie
- frontend: expand LockScreen to five modes — login, first-run setup,
  open registration, TOTP challenge, MFA enforcement setup (QR -> verify ->
  backup codes), and forced password change; all modes share AmbientBackground
  and the existing card layout; registration visible only when
  authStatus.registration_open is true

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 18:39:18 +08:00
b2ecedbc94 Support X-Forwarded-Proto from upstream reverse proxy
Add nginx map directive to prefer X-Forwarded-Proto header from
Traefik/Pangolin when present, falling back to $scheme for direct
internal HTTP access. Applied to both nginx.conf and proxy-params.conf.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 03:25:14 +08:00
132226120b Merge security/pentest-remediation-2026-02-26: remediate F-01, F-02, F-06 2026-02-26 02:43:11 +08:00
92efeba2ec Fix QA review findings: update docs and comments
- W-01: Update README.md security section to reflect removed in-memory
  rate limiter and add /setup to nginx rate-limited endpoint list
- W-02: Replace misleading ALLOW_LAN_NTFY reference with actionable
  guidance to edit _BLOCKED_NETWORKS directly
- S-04: Add comment explaining burst=3 on /api/auth/setup vs burst=5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 02:42:59 +08:00
a0b50a2b13 Remediate pentest findings F-01, F-02, F-06
- Remove ineffective in-memory IP rate limiter from auth.py (F-01):
  nginx limit_req_zone handles real-IP throttling, DB lockout is the per-user guard
- Block RFC 1918 + IPv6 ULA ranges in ntfy SSRF guard (F-02):
  prevents requests to Docker-internal services via user-controlled ntfy URL
- Rate-limit /api/auth/setup at nginx with burst=3 (F-06)
- Document production deployment checklist in .env.example (F-03/F-04/F-05)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 02:25:37 +08:00
6fc134d113 Update EntityDetailPanel to use 2-column grid layout
Add fullWidth field option to PanelField interface. Short fields
render in a grid grid-cols-2 layout; fullWidth fields (address, notes)
render below at full width. Add icons to People and Locations fields
for consistency with other detail panels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:59:30 +08:00
87a7a4ae32 Reformat detail panel view modes to 2-column grid layout
Match TaskDetailPanel gold standard: short fields use grid grid-cols-2
with icon+label headers, full-width fields (description) below the grid.
All grid slots render with "—" fallback to keep alignment consistent.

- Todo: Priority, Category, Due Date, Recurrence in grid
- Reminder: Status, Recurrence, Remind At, Snoozed Until in grid
- Calendar: Calendar, Starred, Start, End, Location, Recurrence in grid
- Task: Add "Updated X ago" footer (was missing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:52:15 +08:00
2e2466bfa6 Fix People search: alignment, focus ring, and name matching
- Wrap CategoryFilterBar in flex-1 min-w-0 so search aligns right
- Add first_name, last_name, nickname to People search filter
- Add ring-inset to all header search inputs (People, Todos,
  Locations, Reminders, Calendar) to prevent focus ring clipping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:35:40 +08:00
05d04c131e Always show search input in CategoryFilterBar
Remove the collapsible search behavior that hid the input behind an
icon when ≥4 categories existed. Search should always be visible and
match the standard header pattern (w-52 input with icon).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:30:02 +08:00
1e225145c9 Fix Todo search alignment: wrap CategoryFilterBar in flex-1 container
CategoryFilterBar's internal flex-1 spacer needs room to push search
right. Wrapping in flex-1 min-w-0 matches the Locations/People pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:25:41 +08:00