feat: auth by default, theme toggle, CoderPad startup, review fixes

- Require a session on every route; public routes opt out with @allow_anonymous
- Split password hashing and pepper loading into passwords.py
- Add a system/light/dark theme toggle backed by light-dark() colors
- Ignore stale 401s from an earlier session, PATCH only changed book fields,
  and block overlapping journal-entry saves
- Add bin/start and CoderPad Vite server settings for the pad's start/restart
- Rewrite README as a mise onboarding guide; expand .gitignore
- Include review-round fixes and tests
This commit is contained in:
2026-10-02 16:49:14 -05:00
parent 9d7b0e805c
commit 7f5d034a1f
46 changed files with 1797 additions and 497 deletions
+31 -3
View File
@@ -1,9 +1,10 @@
import hashlib
import os
import re
from app import app
from auth import hash_password
from tests.support import ApiTestCase, db_execute
from app import create_app
from passwords import hash_password
from tests.support import ApiTestCase, app, db_execute
class AuthTests(ApiTestCase):
@@ -156,3 +157,30 @@ class AuthTests(ApiTestCase):
"pbkdf2_sha256$600000$"
)
)
class AuthByDefaultTests(ApiTestCase):
PUBLIC = frozenset({"health", "auth.register", "auth.login", "auth.logout"})
def test_only_the_expected_endpoints_allow_anonymous_access(self):
public = {
endpoint
for endpoint, view in app.view_functions.items()
if getattr(view, "allow_anonymous", False)
}
self.assertEqual(public, self.PUBLIC)
def test_every_other_route_rejects_anonymous_requests(self):
for rule in app.url_map.iter_rules():
if rule.endpoint in self.PUBLIC or rule.endpoint == "static":
continue
path = re.sub(r"<[^>]+>", "1", rule.rule)
for method in rule.methods - {"HEAD", "OPTIONS"}:
with self.subTest(method=method, path=path):
self.assertEqual(self.call(method, path).status_code, 401)
def test_a_new_route_requires_login_without_any_decorator(self):
fresh = create_app()
fresh.add_url_rule("/api/new-thing", "new_thing", lambda: {"ok": True})
response = self.call("GET", "/api/new-thing", client=fresh.test_client())
self.assertEqual(response.status_code, 401)