fix: reveal plaintext service token on issue/rotate (HOTFIX-001)
- render_service_token_result no longer masks the token field; plaintext tokens are SHA-256 hashed in DB and irrecoverable once lost, so issue/ rotate must reveal them once at issuance (matches interface-contract.md) - Remove now-unused _mask_payload helper; keep mask_token_text for the auxiliary masked_token identifier line - Add one-time reveal warning to issue/rotate table output - 3 regression tests: JSON plaintext assertion, render unit test, table warning - Hotfix Exception Path record: docs/hotfixes/hotfix-001 - Total tests: 369 -> 371 passed Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1058,6 +1058,58 @@ def test_cli_service_token_issue_supports_json() -> None:
|
||||
assert payload["revoked"] is False
|
||||
assert payload["expires_at"] is not None
|
||||
assert payload["runtime_id"] is not None
|
||||
# HOTFIX-001 SC-1: JSON 출력의 token 필드는 마스킹되지 않은 평문이어야 한다.
|
||||
assert "*" not in payload["token"]
|
||||
stored = repo.get_service_token_by_name("nightly")
|
||||
assert payload["token"].startswith(stored.token_prefix)
|
||||
|
||||
|
||||
def test_render_service_token_result_does_not_mask_token() -> None:
|
||||
"""HOTFIX-001 SC-1/SC-2: render_service_token_result는 token을 평문으로 렌더링한다.
|
||||
|
||||
issue/rotate 모두 동일하게 render_service_token_result를 호출하므로 본 단위 테스트가
|
||||
두 경로(SC-1 issue, SC-2 rotate)의 렌더링 동작을 함께 커버한다.
|
||||
"""
|
||||
import io
|
||||
from rich.console import Console
|
||||
from app.cli.core.output import render_service_token_result
|
||||
from app.schemas.auth import ServiceTokenIssueCliResponse
|
||||
|
||||
plaintext = "ySusw9hQ2kPlAiNtExTtOkEnVaLuENotMaskedHere_Wa3w"
|
||||
response = ServiceTokenIssueCliResponse(
|
||||
token_id="540e005a-104b-451a-b11c-0eb03968b958",
|
||||
name="admin",
|
||||
expires_at=None,
|
||||
revoked=False,
|
||||
runtime_id="c544ab21-ce00-41cd-8327-5bf9103ebad8",
|
||||
daemon_id=None,
|
||||
token=plaintext,
|
||||
)
|
||||
|
||||
for fmt in ("json", "table"):
|
||||
buffer = io.StringIO()
|
||||
console = Console(file=buffer, force_terminal=False, width=200)
|
||||
render_service_token_result(console, fmt, response)
|
||||
output = buffer.getvalue()
|
||||
assert plaintext in output, f"{fmt}: 평문 토큰이 출력에 없음"
|
||||
assert "*" not in output, f"{fmt}: 마스킹 문자가 출력에 포함됨"
|
||||
|
||||
|
||||
def test_cli_service_token_issue_table_shows_reveal_warning() -> None:
|
||||
"""HOTFIX-001 SC-3: table 출력은 평문 토큰 + 1회 노출 경고를 표기한다."""
|
||||
repo = FakeRepo(_portfolio())
|
||||
repo.create_user("alice", permission="read")
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["service-token", "issue", "nightly", "--user", "alice"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
stored = repo.get_service_token_by_name("nightly")
|
||||
assert stored.token_prefix in result.stdout
|
||||
assert "다시 표시되지 않습니다" in result.stdout
|
||||
|
||||
|
||||
def test_cli_service_token_issue_rejects_conflicting_expiry_options() -> None:
|
||||
|
||||
Reference in New Issue
Block a user