Feat/cookie - #11
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Confirmed runtime bugs exist in the new auth flow (browser enum string handling and RuntimeError escaping the AuthError boundary), plus a Playwright provider cleanup issue that can leak browser processes on exceptions.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR introduces a new noteforge.auth layer to centrally manage cookie import/validation/encrypted storage and integrates it into the media pipeline and CLI, while tightening test-time safety boundaries to prevent reading real browser credentials.
Changes:
- Add
AuthManager+ providers (browser/JSON/raw/Playwright) + validator + encrypted store, and wire media execution to use authenticated cookie leases with a single refresh retry. - Introduce
SubtitleAccessStatusso “no subtitles” can be distinguished from “anonymous request lacked access”. - Add
noteforge authCLI commands and expand tests to enforce “no real browser cookies in unit tests”.
File summaries
| File | Description |
|---|---|
| uv.lock | Adds Playwright and transitive deps to the lockfile. |
| pyproject.toml | Adds Playwright as a runtime dependency. |
| src/noteforge/auth/init.py | Exposes the new auth public API surface. |
| src/noteforge/auth/errors.py | Defines auth-specific exception types. |
| src/noteforge/auth/models.py | Adds auth platform/status/result models. |
| src/noteforge/auth/manager.py | Implements the auth lifecycle orchestration (load/validate/refresh/login/logout). |
| src/noteforge/auth/store.py | Adds AES-GCM + keyring backed encrypted cookie store (with legacy read support). |
| src/noteforge/auth/validator.py | Implements remote cookie validation for Bilibili/YouTube. |
| src/noteforge/auth/signing.py | Adds a CSRF-token helper extension point. |
| src/noteforge/auth/providers/init.py | Exports built-in cookie providers. |
| src/noteforge/auth/providers/base.py | Defines provider protocol + domain filtering helpers. |
| src/noteforge/auth/providers/browser.py | Imports cookies from local browsers via yt-dlp and filters by platform. |
| src/noteforge/auth/providers/json_file.py | Imports cookies from JSON exports and filters by platform. |
| src/noteforge/auth/providers/raw.py | Imports cookies from raw Cookie header text. |
| src/noteforge/auth/providers/playwright.py | Implements interactive login via Playwright and cookie extraction. |
| src/noteforge/media/cookies/service.py | Simplifies cookie service into a short-lived lease factory (no persistence/import). |
| src/noteforge/media/cookies/init.py | Removes CredentialInfo exports after cookie-service refactor. |
| src/noteforge/media/models.py | Adds SubtitleAccessStatus and simplifies AuthRequest. |
| src/noteforge/media/config.py | Updates docs to reflect auth is managed by AuthManager. |
| src/noteforge/media/init.py | Updates exports to reflect refactored cookie/auth surface. |
| src/noteforge/media/service.py | Integrates AuthManager and adds unified authenticated execution with one refresh retry. |
| src/noteforge/media/ytdlp/errors.py | Sanitizes user-facing yt-dlp error messages to avoid leaking context. |
| src/noteforge/cli/app.py | Registers the new noteforge auth Typer sub-app. |
| src/noteforge/cli/commands/auth.py | Adds auth login/status/logout commands. |
| src/noteforge/cli/commands/generate.py | Uses SubtitleAccessStatus to provide targeted preflight failures. |
| tests/conftest.py | Adds an autouse fixture preventing tests from reading real browser cookies/keyring. |
| tests/auth/test_auth.py | Adds unit tests for auth store/provider/validator/manager and media retry behavior. |
| tests/media/test_media.py | Injects an anonymous auth manager to keep media tests isolated from real cookies. |
| README.md | Updates docs to describe AuthManager behavior and noteforge auth usage. |
| README.zh-CN.md | Updates Chinese docs with auth lifecycle and CLI examples. |
Review details
Suppressed comments (3)
src/noteforge/auth/store.py:154
_load_legacy()解密旧版凭据失败时同样抛出RuntimeError,会导致调用AuthManager/CLI 时无法按AuthError统一处理。建议改为抛出AuthError(或其子类)。
except Exception as error:
raise RuntimeError("无法读取旧版加密 Cookie 存储。") from error
src/noteforge/auth/store.py:76
save()依赖缺失时抛出RuntimeError会让noteforge auth login/status等命令无法被except AuthError捕获并转成友好错误输出。建议将此类依赖/环境问题也统一包装为AuthError(或其子类)。
except ImportError as error:
raise RuntimeError("加密保存 Cookie 需要 cryptography。") from error
src/noteforge/auth/store.py:216
_vault_key()对环境变量解析失败或缺少 keyring 时抛出RuntimeError,上层 CLI 当前只捕获AuthError,会导致直接异常退出。建议在该方法内统一引入AuthError并用它替代这些RuntimeError。
except ValueError as error:
raise RuntimeError("Cookie Vault 密钥必须是十六进制。") from error
if len(key) != 32:
raise RuntimeError("Cookie Vault 密钥必须为 256 位。")
return key
- Files reviewed: 29/30 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| with sync_playwright() as playwright: | ||
| browser = playwright.chromium.launch(headless=False) | ||
| context = browser.new_context() | ||
| page = context.new_page() | ||
| page.goto(self.LOGIN_URLS[platform]) | ||
| deadline = monotonic() + self.timeout | ||
| while monotonic() < deadline: | ||
| jar = self._from_playwright(context.cookies()) | ||
| filtered = filter_cookies(jar, platform) | ||
| if self._has_session_marker(platform, filtered): | ||
| context.close() | ||
| browser.close() | ||
| return filtered | ||
| sleep(1) | ||
| context.close() | ||
| browser.close() | ||
| except InteractiveLoginError: |
| except Exception as error: | ||
| raise RuntimeError("无法读取加密 Cookie 存储。") from error |
| """统一注入认证,并在明确认证失败时最多刷新重试一次。""" | ||
|
|
||
| platform = AuthPlatform(adapter.platform.value) | ||
| browser = str(auth.browser) if auth is not None else None |
No description provided.