Skip to content

패스키 삭제·자체 PIN·앱 잠금 연계 범위를 PM에게 확인한다 #765

Closed as not planned

Description

@OnlyReadOneBook

작업 유형

enhancement — 신규 기능·기존 기능 개선

주 담당 모듈

setting — 설정

개요

패스키 목록·등록 API 호출과 가짜 성공 차단은 #1944/PR #1927의 독립 범위이며 실제 서버 등록 검증은 #555가 추적한다. 이 이슈에 중복된 목록·등록 구현과 미확정 삭제 UI·PIN 단계 폐기·앱 잠금 연계 계획을 제거한다.

PM 확인이 필요한 결정

패스키 삭제 제공 여부와 확인·실패·마지막 인증수단 처리, 자체 PIN 단계의 필요성, 앱 잠금과의 연결 범위를 PM에게 확인한다. #574/#572의 답과 모순되지 않아야 한다.

완료 기준

PM의 명시적인 답변과 적용 범위·근거 링크를 기록한다. 답변 전에는 구현·재개·테스트 완료 체크리스트를 예약하지 않는다. 결정 후 최신 코드와 계약을 대조해 실제 필요한 작업만 정의한다. 디자이너에게 질문하거나 태그하지 않으며, 미답변이나 과거 FE 재량 위임을 승인으로 해석하지 않는다.

정리 근거

2026-09-13 사용자 요청에 따라 미확정 구현 예약을 제거했다. 기능 구현 완료를 의미하지 않는다. 기존 댓글의 실측은 참고 자료이며, 상충하는 옛 구현 지시는 현재 범위를 대체하지 않는다.

Activity

  1. OnlyReadOneBook commented on Aug 26, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    실측 — 이 결선이 없으면 #1073 의 생체 관문도 함께 죽어 있다 (2026-08-26)

    PR #1170(#1073, 애프터노트 열람 관문을 CryptoObject 로 확정) 의 실서버 QA 중 확인한 사실이라, 이 이슈의 우선순위 근거로 남겨 둔다.

    애프터노트 탭의 지문 관문은 패스키가 등록된 사용자에게만 작동한다.

    // AfternoteNavGraphFingerprint.kt:27
    // 패스키 미등록 시 지문 화면 스킵
    if (isPasskeyRegistered == false) onAuthenticationSuccess()

    그리고 isPasskeyRegistered 는 서버가 아니라 로컬 DataStore 플래그다(UserProfile.preferences_pb 의 is_passkey_registered, UserProfileDataSource.kt:53). 그 플래그를 세우는 곳이 savePasskeyRegistered() 인데, 이 이슈가 다루는 패스키 등록 흐름이 서버에 결선되지 않은 상태라 실제로 세워지는 경로가 없다.

    실측 (2026-08-26 16:2x KST, 전용 AVD Afternote_QA_777 API 35, 계정 QA823)

    • 서버 GET /api/v1/users/passkeys → 200 {"data": []}
    • 앱에서 노트 탭 진입 → 지문 화면이 아예 뜨지 않고 목록으로 직행
    • 로컬 플래그를 수동으로 세운 뒤에야 FingerprintLoginRoute 가 그려지고, 「지문 인증하기」 → BiometricPrompt → 지문 통과 → 목록 진입까지 정상 동작

    즉 지금 프로덕션 사용자 중 이 관문을 지나는 사람은 사실상 없다. #1170 은 「패스키가 붙는 순간 관문이 진짜 관문이 되게」 해 두는 성격이고, 실제로 관문이 서려면 이 이슈(등록 결선)가 선행돼야 한다. 서버 선행 2건(Afternote-BE#133 · #138)이 blocked_by 로 걸려 있는 상태 그대로다.

    관련 QA 기록: #1170 (comment)

  2. changed the title [-]feat(setting): 설정 패스키 관리·생성 화면 — 목록·등록·삭제[/-] [+]설정 패스키 관리·생성 화면 — 목록·등록·삭제[/+] on Aug 28, 2026
  3. OnlyReadOneBook commented on Aug 30, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    교차 참조 — 등록 흐름도 같은 서버 블로커에 걸린다 (실측 2026-08-30)

    #764 착수 전 조사에서 나온 것인데 이 이슈에도 그대로 적용된다. 착수 전에 확인하시라고 남긴다. 상세 근거는 #764 코멘트에 있다.

    요지 — BE 는 패스키 검증 시 신뢰 origin 을 https://afternote.kro.kr 하나만 들고 있는데(PasskeyService.serverProperty() → new ServerProperty(new Origin(passkeyProperties.getOrigin()), …), 이 생성자는 Collections.singleton), Android 앱이 만드는 clientDataJSON.origin 은 android:apk-key-hash:<서명 인증서 SHA-256 의 base64url> 이다. webauthn4j 0.29.7 OriginVerifierImpl 은 집합 정확일치라 BadOriginException 이 난다. BE 가 쓰는 그 버전 jar 로 직접 돌려 확인했다.

    register 와 authenticate 가 같은 serverProperty() 를 쓰므로, createCredential() 로 만든 등록 응답도 PASSKEY_VERIFICATION_FAILED 로 떨어진다. 즉 이 이슈의 "등록 옵션 API → Credential Manager createCredential() → 등록 검증 API" 갈래가 서버에서 막힌다.

    막히지 않는 부분 — 목록(GET /api/v1/users/passkeys)·삭제(DELETE /api/v1/users/passkeys/{id})는 WebAuthn 검증을 타지 않으므로 지금도 붙일 수 있다. #555(자리표시자) 해소만 먼저 떼는 것은 가능하다.

    그 밖에 확인된 것

    BE 에 "신뢰 origin 목록화 + Android apk-key-hash origin 등록" 요청은 #764 쪽에서 올린다. 착지되면 여기에도 알리겠다.

  4. changed the title [-]설정 패스키 관리·생성 화면 — 목록·등록·삭제[/-] [+]패스키 삭제·자체 PIN·앱 잠금 연계 범위를 PM에게 확인한다[/+] on Sep 12, 2026
  5. OnlyReadOneBook commented on Sep 13, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    develop 실측: 삭제 축은 서버 대기가 아니고, 마지막 패스키 삭제는 열람 관문을 깨뜨린다

    코드 변경 없이 origin/develop 7d87aa3f4, PR #1927 head 9a6c033eb, Afternote-BE main 3593aa30c 를 읽어 확인한 내용입니다. 이 이슈의 완료 기준(PM 의 명시적 답변과 적용 범위·근거 링크 기록)은 그대로 미충족이고, 이 코멘트로 닫지 않습니다. 답변을 기다리는 동안 다음 착수자가 낡은 전제로 시작하거나 중복 착수하지 않도록 기록만 남깁니다.

    1. 삭제 축은 FE 가 0줄이고, 서버는 이미 있다

    목록 화면의 항목 우측에는 아이콘 두 장만 있고 클릭 핸들러가 없습니다(PasskeyListItem.kt:35-38). develop 의 PassKeyListScreen.kt:61-63 은 그 자리표시자 항목 하나를 그립니다.

    develop 에는 패스키 데이터 계층 자체가 없습니다. feature/setting/data 와 feature/setting/domain 에 Passkey 관련 파일이 0건입니다. 계약이 처음 생기는 곳은 열린 PR #1927 이고, 거기에도 삭제는 없습니다.

    // PR #1927 head 9a6c033eb, feature/setting/data/.../PasskeyApiService.kt
    @GET("users/passkeys")        suspend fun getPasskeys(): BaseResponse<List<PasskeyDto>>
    @POST("auth/passkey/register/options") suspend fun getRegistrationOptions(): BaseResponse<JsonElement>
    @POST("auth/passkey/register")         suspend fun registerPasskey(@Body credential: JsonElement): BaseResponse<PasskeyDto>

    PasskeyRepository 도 getPasskeys · getRegistrationOptions · registerPasskey 셋뿐입니다. feature/setting 전체에 Retrofit @DELETE 는 develop 과 #1927 양쪽 모두 0건입니다.

    서버 쪽은 반대입니다. BE main 의 UserPasskeyController.java:36-43 에 DELETE /api/v1/users/passkeys/{passkeyId} 가 이미 있습니다.

    blocked_by 3건도 다시 확인했습니다. Afternote-BE#133 · #138 은 2026-08-17 에 닫혔고, 열려 있는 것은 Afternote-BE#272 하나입니다. 그런데 BE#272 가 지목한 결함은 이미 코드에서 사라졌습니다. Afternote-BE#279 가 2026-09-09 에 머지되면서 PasskeyService.serverProperty() 가 origin 집합을 쓰고, PasskeyProperties.trustedOriginUrls() 가 assetlinks 지문에서 android:apk-key-hash origin 을 파생합니다. 이 이슈의 2026-08-30 코멘트가 인용한 Collections.singleton 서술은 지금 BE main 과 다릅니다. 이슈만 열린 채 남아 있는 상태로 보입니다.

    즉 삭제 축은 서버가 막고 있는 것이 아니라, 제공 여부 결정만 남았습니다.

    2. 마지막 인증수단 처리는 문구 결정이 아니라 관문 정합성 문제다

    서버는 마지막 패스키를 조건부로만 막습니다. PasskeyService.java:283-289 의 assertNotLastLoginMeans 는 비밀번호도 없고 소셜 연동도 없고 패스키가 1개 이하일 때만 CANNOT_UNLINK_LAST_CREDENTIAL 을 던집니다. 비밀번호나 소셜이 있는 계정은 마지막 패스키를 지울 수 있고, 서버는 성공을 돌려줍니다.

    FE 에는 그 성공을 반영할 경로가 없습니다. is_passkey_registered 를 false 로 되돌리는 호출이 프로덕션에 0건입니다. savePasskeyRegistered 는 develop PassKeyViewModel.kt:24-28 과 PR #1927 의 PassKeyViewModel.kt:111 어디에서도 true 로만 호출됩니다.

    그 로컬 플래그 하나가 두 곳을 동시에 켭니다.

    그래서 삭제 UI 를 붙이면, 서버에 자격 증명이 0개인데 열람 관문은 켜진 채 남는 상태가 생깁니다. PM 에게 물을 항목은 "마지막 인증수단이면 무엇을 보여 주나" 하나가 아니라, 삭제 성공 시 로컬 관문 플래그를 어떻게 되돌릴지가 FE 몫으로 따라붙는다는 점까지입니다. 서버 코드 1213 응답을 그대로 노출할지도 같은 자리에서 정해야 합니다.

    3. 자체 PIN 축은 #574 에 같은 실측을 적었다

    중복을 피해 요지만 적습니다. 이 PIN 단계는 아무 값도 저장하지 않고 대조하지도 않습니다. 근거와 커밋 대조는 #574 코멘트에 있습니다. 따라서 이 이슈의 "자체 PIN 단계의 필요성" 질문은 저장·대조되는 PIN 이 있다는 전제 위에서 쓸 수 없습니다. 물을 것은 대조 대상이 없는 이 단계를 남길지, 남긴다면 무엇과 대조할지 둘입니다.

    4. 앱 잠금 축은 잠금 게이트 자체가 develop 에 없다

    AppLockSetupRoute 는 설정에서 진입만 되고(SettingNavGraph.kt:55, :189-195) 완료 시 pin 을 버린 채 뒤로가기만 합니다. 콜드 스타트나 백그라운드 복귀를 막는 코드는 0건이고, app 모듈의 AppLock 참조는 AppNavigationActions.kt:278-284 의 화면 이동뿐입니다.

    그래서 "앱 잠금과의 연결 범위" 질문도 동작하는 앱 잠금이 있다는 전제를 쓸 수 없습니다. 앱 잠금 자체의 출시 범위·복구 정책 미결은 #572 에 있고, 오늘 도착한 시안도 거기에 정리돼 있습니다. 같은 질문을 여기서 새로 올리지 않고 그 스레드를 가리킵니다. 세 이슈(#572 · #574 · #765)의 코멘트는 전부 제가 남긴 것이고, PM 회신은 아직 0건입니다.

    5. 답이 떨어지면 손댈 곳은 develop 이 아니다

    열린 스택은 #1927(승인, head 9a6c033eb) → #1967 → #1962(draft) → develop 입니다. #1927 이 패스키 목록·등록을 실제 API 에 연결하고 가짜 성공을 없앱니다. develop 만 보고 범위를 잡으면 어긋납니다.

    열린 PR 62건의 본문을 직접 조회했고, #765 · #574 · #572 를 Closes 하거나 언급하는 PR 은 0건입니다. 중복 착수 위험은 그대로입니다.

    6. 본문에서 정정할 항목

    • "패스키 삭제 제공 여부" 가 서버 선행에 걸린 것처럼 읽히지만 아닙니다. 목록·삭제 API 는 닫힌 BE#138 로 이미 있고, 남은 BE#272 의 코드 수정도 BE#279 로 머지됐습니다(1번).
    • "마지막 인증수단 처리" 는 UX 문구 결정이 아니라 로컬 관문 플래그 정합성 문제입니다(2번).
    • "자체 PIN 단계의 필요성" 은 저장·대조되는 PIN 이 있다는 전제를 쓸 수 없습니다(3번).
    • "앱 잠금과의 연결 범위" 는 동작하는 앱 잠금이 있다는 전제를 쓸 수 없습니다(4번).

    본문 수정은 하지 않았습니다. 완료 기준을 좁히는 편집이 아니라 진단을 바꾸는 편집이고, 그 진단 자체가 PM 이 답할 대상이기 때문입니다. 정정은 이 코멘트로 남깁니다.

    7. 이번에 확인하지 못한 것

    • 패스키 목록 시안에 삭제 어피어런스가 있는지. 앵커는 BE#138 본문의 1119:12322 인데, 이번 세션은 Figma 인증이 없어 원본을 열지 못했습니다. 삭제 제공 여부를 PM 에게 물을 때 시안을 먼저 다시 열어야 합니다.
    • 배포된 서버의 PASSKEY_ANDROID_SHA256 값이 앱 서명 지문과 실제로 맞는지. BE#279 는 코드 경로만 열어 둔 것이고, 값 대조는 패스키 로그인 — Credential Manager 통합 인증 연동 #764 쪽 과제로 남아 있습니다.
  6. OnlyReadOneBook commented on Sep 18, 2026

    @OnlyReadOneBook
    CollaboratorAuthor

    PM 결정 대기 항목은 깃허브 이슈로 두지 않고 피그마 질문 한 자리에만 둔다(2026-09-18 결정). 이 이슈의 질문은 피그마 스레드에 걸려 있다: https://www.figma.com/design/UP9ZR186jHvRBicjA2SOea/?node-id=4327-64956#1932490741 (#574·#572 와 합쳐 하나로 물었다.) 답이 오면 그 답을 근거로 구현 이슈를 새로 만든다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions