Repository navigation
Refactor CLI ownership checks and fix bugs from full review - #15
Merged
Merged
Conversation
Single-task commands (update, complete, delete) now look tasks up through TaskManager._find_owned_task(), which only returns a task owned by the logged-in user, so a new command can't forget the check. Login checks use the existing @require_login decorator instead of per-method copies. This also closes a related leak: list-tasks while logged out printed every user's tasks; it now requires a login like the other commands.
- Rate limit keyed on the peer address, not the client-written X-Forwarded-For; the container trusts forwarded headers only from FORWARDED_ALLOW_IPS (default 127.0.0.1) instead of everyone. - Passwords over bcrypt's 72-byte limit are rejected at registration; login cuts input to 72 bytes so long passwords no longer 500 for unknown users only (which revealed which usernames exist). - Token revocation is atomic (SET NX / locked check-and-set) and refresh revokes before issuing, so parallel refreshes of one token yield one. - Concurrent registration of one username returns 400, not 500. - Due dates are converted to UTC before taking the date (PostgreSQL in a non-UTC session returned the previous day); naive timestamps from SQLite are returned as UTC. - PUT accepts null to clear description/email; required fields reject it. - Task search escapes LIKE wildcards. - JWT_ALGORITHM is limited to HS256/HS384/HS512.
- Migration: random one-time passwords instead of <username>@temp123; usernames that already exist are skipped with their tasks (no more tasks attached to someone else's account, re-runs are no-ops); created_at carried over, completed_at left unknown. - New PUT /api/users/me/password so those passwords can be replaced. - CLI tolerates extra keys in user records (send_reminders writes last_reminder_date, which crashed every later command). - Email reminders honour email_reminders_enabled and report failures; the cron job records only delivered reminders and skips bad rows. - Due dates are validated on add/update; a corrupt row no longer crashes listing or reminders. - tasks.json is written atomically; corrupt files give a clear error. - login works without a TTY and honours --email for existing users. - demo_api.py reads the paged list response and uses httpx. - Container runs task-manager from the writable /app/data.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #6. Refactors the CLI so ownership checks can't be forgotten, then fixes the bugs a full review of the project found.
Refactor
update,complete,delete) look tasks up throughTaskManager._find_owned_task(), which only returns the logged-in user's task.@require_logindecorator instead of per-method copies (it was defined but unused).list-taskswhile logged out printed every user's tasks.Security fixes
X-Forwarded-For, so a new header per request gave unlimited attempts. The limiter now keys on the peer address, and the container trusts forwarded headers only fromFORWARDED_ALLOW_IPS(default127.0.0.1; it was*).<username>@temp123, and JSON tasks could attach to an unrelated existing account with the same name. Accounts now get random one-time passwords, existing usernames are skipped along with their tasks, and the newPUT /api/users/me/passwordlets users replace the password.Correctness fixes
nullcan now cleardescription/email; required fields rejectnull.%and_literally.JWT_ALGORITHMis restricted to HMAC algorithms.send_remindersrun locked the user out (last_reminder_datecrashedUser(**data)).--duecorrupted the data file; bad rows crashed listing and the cron job for every user.tasks.jsonis written atomically; corrupt files give a clear error.loginworks without a TTY and honours--email.demo_api.pycrashed at step 5; the container CLI couldn't write its data.Verification
ruff,ruff format --check,mypy,bandit -ll,pip-audit: clean.alembic check: no drift.tests/test_api_hardening.pyandtests/test_cli_hardening.py. 31 of the 33 fail on the previous code; the other two guard behaviour that was already correct.demo_api.pyran end to end against a live server: all 12 steps, no 500s.task-manager loginworks in the container without a TTY; the API reports healthy.Not covered here: the Redis-backed limiter and denylist, which only run in CI's PostgreSQL+Redis job, and behaviour on a real PostgreSQL server in a non-UTC zone (tested by unit test only).