Skip to content

Refactor: CopySensitiveValueUseCase의 자동 정리·반복 감지 정책 분리 #95

Description

@doyeonk429

📝 기능 설명

CopySensitiveValueUseCase가 두 정책을 한 덩어리로 수행하고 있어, 평문 값에는 적용하지 않아야 하는 상황에서 떼어낼 방법이 없습니다.

  1. 복사 후 30초 클립보드 자동 정리
  2. 짧은 시간 반복 복사를 비정상 접근으로 알림 (AbnormalAccessMonitor, 60초 5회)

조회 화면의 평문 필드(Redirect URL / Public Key / Host / Username / Scope / Renew Command)는 Secret.metadata에서 오는 암호화되지 않은 값입니다. 여기에 위 정책을 태우면:

  • 붙여넣기 전에 클립보드가 비어버립니다. Redirect URL을 복사해 브라우저로 옮기는 데 30초가 넘게 걸리는 것은 흔합니다.
  • 공개 정보를 여러 번 복사한 것이 비정상 접근 카운터에 쌓여 오탐 보안 경고가 뜹니다. 진짜 경고의 신뢰를 깎습니다.

#93에서 임시로 SecretClient.copyPlainValue를 두고 ClipboardServiceImpl().write()를 직접 호출하도록 우회했습니다. 앱 전체에서 UseCase를 건너뛰는 유일한 자리이므로 이 이슈에서 반드시 걷어내야 합니다.


채택 방향 — 정책 값 타입 + 호출부 선택

왜 두 정책을 같은 방식으로 파라미터화하면 안 되는가

상태의 수명이 다릅니다.

  • 자동 정리는 복사 한 건마다 독립된 타이머입니다. 공유 상태가 없으므로 호출마다 다른 값을 줘도 됩니다.
  • 반복 감지AbnormalAccessMonitortimestamps를 들고 여러 복사에 걸쳐 슬라이딩 윈도를 유지합니다. 호출마다 window/threshold가 달라지면 누적된 기록이 어느 창에 속하는지 말할 수 없어집니다.

따라서 자동 정리는 을, 반복 감지는 참여 여부만 호출부가 정합니다. 이 비대칭은 의도된 것이며 정책 타입 주석에 근거를 남깁니다.

현재 상태가 이미 비대칭입니다

정책 값 위치 바꾸려면
자동 정리 clipboardClearDelay (init 주입) 조립부에서 가능
반복 감지 private static let 2개 impl 파일을 열어야 함

"각각 수정하기 편하게"가 어려운 이유의 절반이 여기 있습니다. window/threshold를 static에서 걷어내 조립 시점으로 올려야 두 정책의 값이 같은 층위에 놓입니다.

구현 형태

RevealAuthPolicy와 같은 패턴입니다. 그때 적어둔 근거 — "값으로 분리해 둔 이유는 코드를 고치지 않고 동작을 바꾸기 위해서다" — 가 여기에도 그대로 적용됩니다.

public struct ClipboardCopyPolicy: Equatable, Sendable {

    /// 복사 후 이 시간이 지나도록 클립보드가 그대로면 정리한다. `nil`이면 정리하지 않는다.
    public var autoClearAfter: Duration?

    /// 반복 복사 감지에 이 복사를 포함할지.
    /// 임계·윈도 값이 아니라 참여 여부만 두는 것은 감지가 **복사 여러 건에 걸친 상태**를
    /// 보기 때문이다. 호출마다 창 크기가 달라지면 누적된 기록의 의미가 사라진다.
    public var participatesInRepeatDetection: Bool

    public static let sensitive = ClipboardCopyPolicy(
        autoClearAfter: .seconds(30),
        participatesInRepeatDetection: true
    )

    public static let plain = ClipboardCopyPolicy(
        autoClearAfter: nil,
        participatesInRepeatDetection: false
    )
}

UseCase는 정책마다 private 메서드 하나씩으로 갈라둡니다. "자동 정리를 고친다"가 메서드 하나를 여는 일이 되도록 하는 것이 목적입니다.

public func execute(_ value: String, policy: ClipboardCopyPolicy = .sensitive) async throws {
    let changeCount = try clipboardService.write(value)
    scheduleAutoClear(from: changeCount, after: policy.autoClearAfter)
    guard policy.participatesInRepeatDetection else { return }
    await notifyIfRepeated()
}

기본값은 반드시 .sensitive로 둡니다. 새 호출부가 정책을 빠뜨려도 안전한 쪽으로 떨어집니다. .plain을 기본값으로 두면 이 이점이 뒤집힙니다.

채택하지 않은 안

  • Bool 파라미터 두 개 (execute(value, autoClear: true, detectRepeat: false)) — 호출부가 읽히지 않고, 정책이 늘 때마다 시그니처가 늘어납니다.
  • 프로토콜로 협력자 분리 (ClipboardAutoClearing / CopyRepeatDetecting) — 각 정책이 독립 파일·독립 테스트가 되어 가장 깔끔하지만 타입이 셋 늘고 조립부가 복잡해집니다. 지금 impl이 30줄 남짓이라 과합니다.
    정책이 셋 이상으로 늘거나 정책 하나의 테스트가 무거워지면 그때 넘어가고, 정책 값 타입에서 이쪽으로 가는 것은 호출부를 건드리지 않고 가능합니다.

함께 따라오는 것

  • CopySensitiveValueUseCase라는 이름이 평문도 다루게 되면 틀려집니다. CopyToClipboardUseCase 정도로 변경합니다.
  • 설정 화면이 생기면 ClipboardCopyPolicyDependencyKey로 올려 RevealAuthPolicy와 같은 모양으로 맞출 수 있습니다.

✅ 작업 목록

  • ClipboardCopyPolicy 값 타입 추가 (autoClearAfter / participatesInRepeatDetection)
  • 반복 감지 window·threshold를 private static let에서 조립 시점 주입으로 이동
  • execute를 정책별 private 메서드로 분리 (scheduleAutoClear / notifyIfRepeated)
  • execute(_:policy:) 기본값을 .sensitive로 지정
  • CopySensitiveValueUseCaseCopyToClipboardUseCase 이름 변경
  • 정책별 단위 테스트 추가 (정리만 / 감지만 / 둘 다 / 둘 다 없음)
  • SecretClient.copyPlainValue 임시 경로 제거하고 .plain 정책으로 대체
  • 임시 표시 3곳 정리
    • Projects/DVPresentation/Sources/Dependencies/SecretClient.swiftcopyPlainValue doc
    • Projects/Devault/Sources/Composition/Dependencies/SecretClient+Live.swift — 조립부 주석
    • Projects/DVPresentation/Sources/Features/SecretDetail/SecretDetailFeature.swiftcopyPlainEffect doc
  • 조회 화면에서 평문·민감 필드 복사 동작 회귀 확인

🔗 참고 링크

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions