mirror of
https://github.com/NomaDamas/k-skill.git
synced 2026-06-24 02:04:11 +00:00
Honor explicit Kakao auth recovery overrides
The helper now treats manual auth overrides as a cache-bypassing recovery request and rejects invalid brute-force tuning flags at the CLI boundary so users get deterministic behavior instead of stale cached tuples or Python tracebacks. Regression coverage locks both paths before the PR follow-up lands. Constraint: The helper must remain a thin read-only wrapper around kakaocli auth recovery Rejected: Require --refresh whenever --user-id/--uuid is passed | worse UX than honoring overrides directly Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep explicit auth overrides ahead of cache reuse unless the CLI contract is redesigned and documented Tested: python3 -m unittest scripts.test_kakaotalk_mac; node --test scripts/skill-docs.test.js; npm run ci; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id 800000000 --workers 8 --chunk-size 2000000; python3 scripts/kakaotalk_mac.py chats --limit 1 --json; python3 scripts/kakaotalk_mac.py auth --cache-path <bad-json>; python3 scripts/kakaotalk_mac.py auth --refresh --max-user-id -1; python3 scripts/kakaotalk_mac.py auth --refresh --workers 2 --chunk-size 0 --max-user-id 10; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --user-id 999; python3 scripts/kakaotalk_mac.py auth --cache-path <temp-cache> --uuid <live-uuid> Not-tested: Manual override success with a truly alternate valid user_id/uuid pair on a multi-account local install
This commit is contained in:
parent
5631f58b14
commit
c81b710cae
2 changed files with 113 additions and 7 deletions
|
|
@ -502,7 +502,8 @@ def resolve_auth(
|
|||
workers: int | None,
|
||||
chunk_size: int,
|
||||
) -> ResolvedAuth:
|
||||
if not refresh:
|
||||
use_cache = not refresh and user_id_override is None and uuid_override is None
|
||||
if use_cache:
|
||||
cached = load_cached_auth(cache_path)
|
||||
if cached is not None:
|
||||
return cached
|
||||
|
|
@ -576,10 +577,10 @@ def build_passthrough_command(command: str, auth: ResolvedAuth, forwarded_args:
|
|||
|
||||
def main(argv: Sequence[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args, forwarded_args = parser.parse_known_args(argv)
|
||||
cache_path = Path(args.cache_path).expanduser()
|
||||
|
||||
try:
|
||||
args, forwarded_args = parser.parse_known_args(argv)
|
||||
cache_path = Path(args.cache_path).expanduser()
|
||||
if args.command == "auth":
|
||||
if forwarded_args:
|
||||
raise AuthResolutionError(f"Unexpected auth arguments: {' '.join(forwarded_args)}")
|
||||
|
|
@ -606,7 +607,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|||
)
|
||||
result = subprocess.run(build_passthrough_command(args.command, resolved, forwarded_args))
|
||||
return result.returncode
|
||||
except AuthResolutionError as exc:
|
||||
except (AuthResolutionError, ValueError) as exc:
|
||||
print(f"error: {exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
|
@ -634,9 +635,23 @@ def add_auth_options(parser: argparse.ArgumentParser) -> None:
|
|||
parser.add_argument("--cache-path", default=str(DEFAULT_CACHE_PATH))
|
||||
parser.add_argument("--user-id", type=int, help="Explicit Kakao user_id override.")
|
||||
parser.add_argument("--uuid", help="Explicit device UUID override.")
|
||||
parser.add_argument("--max-user-id", type=int, default=DEFAULT_MAX_USER_ID)
|
||||
parser.add_argument("--workers", type=int, default=None)
|
||||
parser.add_argument("--chunk-size", type=int, default=DEFAULT_CHUNK_SIZE)
|
||||
parser.add_argument("--max-user-id", type=non_negative_int, default=DEFAULT_MAX_USER_ID)
|
||||
parser.add_argument("--workers", type=positive_int, default=None)
|
||||
parser.add_argument("--chunk-size", type=positive_int, default=DEFAULT_CHUNK_SIZE)
|
||||
|
||||
|
||||
def non_negative_int(value: str) -> int:
|
||||
integer = int(value)
|
||||
if integer < 0:
|
||||
raise argparse.ArgumentTypeError("must be non-negative")
|
||||
return integer
|
||||
|
||||
|
||||
def positive_int(value: str) -> int:
|
||||
integer = int(value)
|
||||
if integer <= 0:
|
||||
raise argparse.ArgumentTypeError("must be positive")
|
||||
return integer
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
|||
|
||||
import hashlib
|
||||
import json
|
||||
import io
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
|
@ -156,6 +157,76 @@ class KakaoTalkMacHelperTests(unittest.TestCase):
|
|||
collect_state.assert_called_once_with(None)
|
||||
resolve_state.assert_called_once()
|
||||
|
||||
def test_resolve_auth_bypasses_cache_when_user_id_override_is_supplied(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
cache_path = Path(tempdir) / "auth-cache.json"
|
||||
database_path = Path(tempdir) / "kakaotalk.db"
|
||||
database_path.write_text("", encoding="utf-8")
|
||||
persistable = make_resolved_auth(database_path=database_path, source="cache")
|
||||
kakaotalk_mac.persist_auth_cache(persistable, cache_path)
|
||||
override_result = make_resolved_auth(user_id=999, database_path=database_path, source="candidate")
|
||||
|
||||
with (
|
||||
mock.patch.object(kakaotalk_mac, "collect_detection_state", return_value=mock.sentinel.state) as collect_state,
|
||||
mock.patch.object(kakaotalk_mac, "resolve_auth_state", return_value=override_result) as resolve_state,
|
||||
):
|
||||
resolved = kakaotalk_mac.resolve_auth(
|
||||
refresh=False,
|
||||
cache_path=cache_path,
|
||||
user_id_override=999,
|
||||
uuid_override=None,
|
||||
max_user_id=1000,
|
||||
workers=1,
|
||||
chunk_size=100,
|
||||
)
|
||||
|
||||
self.assertEqual(resolved, override_result)
|
||||
collect_state.assert_called_once_with(None)
|
||||
resolve_state.assert_called_once_with(
|
||||
mock.sentinel.state,
|
||||
verify_access=kakaotalk_mac.verify_database_access,
|
||||
cache_path=cache_path,
|
||||
user_id_override=999,
|
||||
max_user_id=1000,
|
||||
workers=1,
|
||||
chunk_size=100,
|
||||
)
|
||||
|
||||
def test_resolve_auth_bypasses_cache_when_uuid_override_is_supplied(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tempdir:
|
||||
cache_path = Path(tempdir) / "auth-cache.json"
|
||||
database_path = Path(tempdir) / "kakaotalk.db"
|
||||
database_path.write_text("", encoding="utf-8")
|
||||
persistable = make_resolved_auth(database_path=database_path, source="cache")
|
||||
kakaotalk_mac.persist_auth_cache(persistable, cache_path)
|
||||
override_result = make_resolved_auth(uuid="override-uuid", database_path=database_path, source="candidate")
|
||||
|
||||
with (
|
||||
mock.patch.object(kakaotalk_mac, "collect_detection_state", return_value=mock.sentinel.state) as collect_state,
|
||||
mock.patch.object(kakaotalk_mac, "resolve_auth_state", return_value=override_result) as resolve_state,
|
||||
):
|
||||
resolved = kakaotalk_mac.resolve_auth(
|
||||
refresh=False,
|
||||
cache_path=cache_path,
|
||||
user_id_override=None,
|
||||
uuid_override="override-uuid",
|
||||
max_user_id=1000,
|
||||
workers=1,
|
||||
chunk_size=100,
|
||||
)
|
||||
|
||||
self.assertEqual(resolved, override_result)
|
||||
collect_state.assert_called_once_with("override-uuid")
|
||||
resolve_state.assert_called_once_with(
|
||||
mock.sentinel.state,
|
||||
verify_access=kakaotalk_mac.verify_database_access,
|
||||
cache_path=cache_path,
|
||||
user_id_override=None,
|
||||
max_user_id=1000,
|
||||
workers=1,
|
||||
chunk_size=100,
|
||||
)
|
||||
|
||||
def test_render_auth_text_redacts_key_material(self) -> None:
|
||||
resolved = make_resolved_auth(key="super-secret-key", source="hash-recovery")
|
||||
|
||||
|
|
@ -178,5 +249,25 @@ class KakaoTalkMacHelperTests(unittest.TestCase):
|
|||
self.assertEqual(sorted(subcommands), ["auth", "chats", "messages", "schema", "search"])
|
||||
self.assertNotIn("query", subcommands)
|
||||
|
||||
def test_build_parser_rejects_negative_max_user_id(self) -> None:
|
||||
parser = kakaotalk_mac.build_parser()
|
||||
stderr = io.StringIO()
|
||||
|
||||
with self.assertRaises(SystemExit) as exit_context, mock.patch("sys.stderr", stderr):
|
||||
parser.parse_args(["auth", "--max-user-id", "-1"])
|
||||
|
||||
self.assertEqual(exit_context.exception.code, 2)
|
||||
self.assertIn("must be non-negative", stderr.getvalue())
|
||||
|
||||
def test_build_parser_rejects_non_positive_chunk_size(self) -> None:
|
||||
parser = kakaotalk_mac.build_parser()
|
||||
stderr = io.StringIO()
|
||||
|
||||
with self.assertRaises(SystemExit) as exit_context, mock.patch("sys.stderr", stderr):
|
||||
parser.parse_args(["auth", "--chunk-size", "0"])
|
||||
|
||||
self.assertEqual(exit_context.exception.code, 2)
|
||||
self.assertIn("must be positive", stderr.getvalue())
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue