You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
패스키 목록·등록 API 호출과 가짜 성공 차단은 #1944/PR #1927의 독립 범위이며 실제 서버 등록 검증은 #555가 추적한다. 이 이슈에 중복된 목록·등록 구현과 미확정 삭제 UI·PIN 단계 폐기·앱 잠금 연계 계획을 제거한다.
PM 확인이 필요한 결정
패스키 삭제 제공 여부와 확인·실패·마지막 인증수단 처리, 자체 PIN 단계의 필요성, 앱 잠금과의 연결 범위를 PM에게 확인한다. #574/#572의 답과 모순되지 않아야 한다.
완료 기준
PM의 명시적인 답변과 적용 범위·근거 링크를 기록한다. 답변 전에는 구현·재개·테스트 완료 체크리스트를 예약하지 않는다. 결정 후 최신 코드와 계약을 대조해 실제 필요한 작업만 정의한다. 디자이너에게 질문하거나 태그하지 않으며, 미답변이나 과거 FE 재량 위임을 승인으로 해석하지 않는다.
정리 근거
2026-09-13 사용자 요청에 따라 미확정 구현 예약을 제거했다. 기능 구현 완료를 의미하지 않는다. 기존 댓글의 실측은 참고 자료이며, 상충하는 옛 구현 지시는 현재 범위를 대체하지 않는다.
실측 — 이 결선이 없으면 #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 로 걸려 있는 상태 그대로다.
#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(자리표시자) 해소만 먼저 떼는 것은 가능하다.
그 밖에 확인된 것
assetlinks.json 의 package_name 불일치(0823 지적, BE#228)는 해소됐다 — 지금은 com.afternote.afternote_fe 로 200 응답.
등록된 sha256_cert_fingerprints 2개의 출처는 여전히 미확인이다. 팀 공유 debug keystore 와 release keystore 로 지문을 다시 뽑아 대조해야 하는데, 그 대조는 패스키 로그인 — Credential Manager 통합 인증 연동 #764 에서 함께 처리한다.
develop 실측: 삭제 축은 서버 대기가 아니고, 마지막 패스키 삭제는 열람 관문을 깨뜨린다
코드 변경 없이 origin/develop7d87aa3f4, PR #1927 head 9a6c033eb, Afternote-BE main3593aa30c 를 읽어 확인한 내용입니다. 이 이슈의 완료 기준(PM 의 명시적 답변과 적용 범위·근거 링크 기록)은 그대로 미충족이고, 이 코멘트로 닫지 않습니다. 답변을 기다리는 동안 다음 착수자가 낡은 전제로 시작하거나 중복 착수하지 않도록 기록만 남깁니다.
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 응답을 그대로 노출할지도 같은 자리에서 정해야 합니다.
중복을 피해 요지만 적습니다. 이 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 에게 물을 때 시안을 먼저 다시 열어야 합니다.
Activity
OnlyReadOneBook commentedon Aug 26, 2026
실측 — 이 결선이 없으면 #1073 의 생체 관문도 함께 죽어 있다 (2026-08-26)
PR #1170(#1073, 애프터노트 열람 관문을
CryptoObject로 확정) 의 실서버 QA 중 확인한 사실이라, 이 이슈의 우선순위 근거로 남겨 둔다.애프터노트 탭의 지문 관문은 패스키가 등록된 사용자에게만 작동한다.
그리고
isPasskeyRegistered는 서버가 아니라 로컬 DataStore 플래그다(UserProfile.preferences_pb의is_passkey_registered,UserProfileDataSource.kt:53). 그 플래그를 세우는 곳이savePasskeyRegistered()인데, 이 이슈가 다루는 패스키 등록 흐름이 서버에 결선되지 않은 상태라 실제로 세워지는 경로가 없다.실측 (2026-08-26 16:2x KST, 전용 AVD
Afternote_QA_777API 35, 계정 QA823)GET /api/v1/users/passkeys→200 {"data": []}FingerprintLoginRoute가 그려지고, 「지문 인증하기」 →BiometricPrompt→ 지문 통과 → 목록 진입까지 정상 동작즉 지금 프로덕션 사용자 중 이 관문을 지나는 사람은 사실상 없다. #1170 은 「패스키가 붙는 순간 관문이 진짜 관문이 되게」 해 두는 성격이고, 실제로 관문이 서려면 이 이슈(등록 결선)가 선행돼야 한다. 서버 선행 2건(Afternote-BE#133 · #138)이
blocked_by로 걸려 있는 상태 그대로다.관련 QA 기록: #1170 (comment)
[-]feat(setting): 설정 패스키 관리·생성 화면 — 목록·등록·삭제[/-][+]설정 패스키 관리·생성 화면 — 목록·등록·삭제[/+]OnlyReadOneBook commentedon Aug 30, 2026
교차 참조 — 등록 흐름도 같은 서버 블로커에 걸린다 (실측 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.7OriginVerifierImpl은 집합 정확일치라BadOriginException이 난다. BE 가 쓰는 그 버전 jar 로 직접 돌려 확인했다.register와authenticate가 같은serverProperty()를 쓰므로,createCredential()로 만든 등록 응답도PASSKEY_VERIFICATION_FAILED로 떨어진다. 즉 이 이슈의 "등록 옵션 API → Credential ManagercreateCredential()→ 등록 검증 API" 갈래가 서버에서 막힌다.막히지 않는 부분 — 목록(
GET /api/v1/users/passkeys)·삭제(DELETE /api/v1/users/passkeys/{id})는 WebAuthn 검증을 타지 않으므로 지금도 붙일 수 있다. #555(자리표시자) 해소만 먼저 떼는 것은 가능하다.그 밖에 확인된 것
assetlinks.json의package_name불일치(0823 지적, BE#228)는 해소됐다 — 지금은com.afternote.afternote_fe로 200 응답.sha256_cert_fingerprints2개의 출처는 여전히 미확인이다. 팀 공유 debug keystore 와 release keystore 로 지문을 다시 뽑아 대조해야 하는데, 그 대조는 패스키 로그인 — Credential Manager 통합 인증 연동 #764 에서 함께 처리한다.origin/develop실측과 일치한다 —PassKeyMakingScreen은 로컬BiometricPrompt+PassKeyViewModel.savePasskeyRegistered의 DataStore 불리언이 전부이고 WebAuthn 호출은 0건이다. 패스키 등록의 BiometricPrompt 가 CryptoObject 없이 호출 — CodeQL 알림 1건 (#1073 설정 몫) #1166 의CryptoObject미사용도 그대로다.BE 에 "신뢰 origin 목록화 + Android
apk-key-hashorigin 등록" 요청은 #764 쪽에서 올린다. 착지되면 여기에도 알리겠다.[-]설정 패스키 관리·생성 화면 — 목록·등록·삭제[/-][+]패스키 삭제·자체 PIN·앱 잠금 연계 범위를 PM에게 확인한다[/+]OnlyReadOneBook commentedon Sep 13, 2026
develop 실측: 삭제 축은 서버 대기가 아니고, 마지막 패스키 삭제는 열람 관문을 깨뜨린다
코드 변경 없이
origin/develop7d87aa3f4, PR #1927 head9a6c033eb, Afternote-BEmain3593aa30c를 읽어 확인한 내용입니다. 이 이슈의 완료 기준(PM 의 명시적 답변과 적용 범위·근거 링크 기록)은 그대로 미충족이고, 이 코멘트로 닫지 않습니다. 답변을 기다리는 동안 다음 착수자가 낡은 전제로 시작하거나 중복 착수하지 않도록 기록만 남깁니다.1. 삭제 축은 FE 가 0줄이고, 서버는 이미 있다
목록 화면의 항목 우측에는 아이콘 두 장만 있고 클릭 핸들러가 없습니다(PasskeyListItem.kt:35-38). develop 의 PassKeyListScreen.kt:61-63 은 그 자리표시자 항목 하나를 그립니다.
develop 에는 패스키 데이터 계층 자체가 없습니다.
feature/setting/data와feature/setting/domain에 Passkey 관련 파일이 0건입니다. 계약이 처음 생기는 곳은 열린 PR #1927 이고, 거기에도 삭제는 없습니다.PasskeyRepository도getPasskeys·getRegistrationOptions·registerPasskey셋뿐입니다.feature/setting전체에 Retrofit@DELETE는 develop 과 #1927 양쪽 모두 0건입니다.서버 쪽은 반대입니다. BE
main의 UserPasskeyController.java:36-43 에DELETE /api/v1/users/passkeys/{passkeyId}가 이미 있습니다.blocked_by3건도 다시 확인했습니다. 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-hashorigin 을 파생합니다. 이 이슈의 2026-08-30 코멘트가 인용한Collections.singleton서술은 지금 BEmain과 다릅니다. 이슈만 열린 채 남아 있는 상태로 보입니다.즉 삭제 축은 서버가 막고 있는 것이 아니라, 제공 여부 결정만 남았습니다.
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. 본문에서 정정할 항목
본문 수정은 하지 않았습니다. 완료 기준을 좁히는 편집이 아니라 진단을 바꾸는 편집이고, 그 진단 자체가 PM 이 답할 대상이기 때문입니다. 정정은 이 코멘트로 남깁니다.
7. 이번에 확인하지 못한 것
PASSKEY_ANDROID_SHA256값이 앱 서명 지문과 실제로 맞는지. BE#279 는 코드 경로만 열어 둔 것이고, 값 대조는 패스키 로그인 — Credential Manager 통합 인증 연동 #764 쪽 과제로 남아 있습니다.OnlyReadOneBook commentedon Sep 18, 2026
PM 결정 대기 항목은 깃허브 이슈로 두지 않고 피그마 질문 한 자리에만 둔다(2026-09-18 결정). 이 이슈의 질문은 피그마 스레드에 걸려 있다: https://www.figma.com/design/UP9ZR186jHvRBicjA2SOea/?node-id=4327-64956#1932490741 (#574·#572 와 합쳐 하나로 물었다.) 답이 오면 그 답을 근거로 구현 이슈를 새로 만든다.