Skip to content

fix(auth): seed/setup 비밀번호 해싱을 argon2id 로 통일 — Workers PBKDF2 상한 회피 - #72

Merged
mack-erel merged 3 commits into
mainfrom
fix/seed-argon2id-workers-pbkdf2
Jul 2, 2026
Merged

fix(auth): seed/setup 비밀번호 해싱을 argon2id 로 통일 — Workers PBKDF2 상한 회피#72
mack-erel merged 3 commits into
mainfrom
fix/seed-argon2id-workers-pbkdf2

Conversation

@mack-erel

@mack-erel mack-erel commented Jul 2, 2026

Copy link
Copy Markdown
Owner

문제

PR #71(로그인 hang 수정)이 머지되어 hang 은 해소됐으나, 로그인이 비밀번호 검증 단계까지 도달하면서 다음 프로덕션 에러가 드러났습니다:

NotSupportedError: Pbkdf2 failed: iteration counts above 100000
are not supported (requested 600000).
    at verifyPbkdf2 → verifyPassword → authenticateLocalUser → POST /login

근본 원인

scripts/seed.ts 가 관리자 비밀번호를 PBKDF2 600,000회로 해싱했으나(pbkdf2$sha256:600000$...), Cloudflare Workers WebCrypto 는 PBKDF2 반복을 100,000 으로 제한합니다. 따라서 시드된 관리자는 Workers 에서 verifyPbkdf2deriveBitsNotSupportedError 를 던져 로그인 자체가 불가능했습니다. (scripts/setup.ts 는 100,000 을 써서 동작은 했으나 OWASP 미달 + seed 와 불일치.)

수정

1. seed/setup 해싱을 argon2id 로 통일

앱의 정규 해시인 argon2id(@hicaru/argon2-pure.js — 순수 JS, Workers 호환)를 seed/setup 이 그대로 재사용하도록 변경했습니다.

파일 변경
scripts/seed.ts hashPasswordPbkdf2(600k) 제거 → src/lib/server/auth/passwordhashPassword(argon2id) 재사용
scripts/setup.ts hashPasswordForSetup(100k PBKDF2) 제거 → 동일하게 hashPassword 재사용

효과: Workers 호환 + OWASP profile 5 즉시 적용 + PBKDF2 반복 상한 불일치 클래스 자체 제거 + 앱/스크립트 해시 경로 DRY.

2. 관리자 비밀번호 리셋 스크립트 추가

seed:pg 재실행은 idempotent 라 기존 password credential 을 덮어쓰지 않습니다. 따라서 위 수정만으로는 이미 600k PBKDF2 로 시드된 프로덕션 관리자가 복구되지 않습니다. 이를 위한 전용 스크립트를 추가했습니다.

파일 변경
scripts/reset-admin-password.ts 대상 사용자의 password credential secrethashPassword(argon2id)로 교체(UPDATE). 실행 후 verifyPassword 로 자체 검증. 방언 무관(scripts/lib/db 재사용)
package.json db:reset-admin[:pg|:mysql|:sqlite] 스크립트 추가

비밀번호는 shell 히스토리/프로세스 목록 노출을 피해 env 로 전달합니다:

DB_DIALECT=postgres \
RESET_ADMIN_USERNAME=admin \
RESET_ADMIN_PASSWORD='새-강력한-비밀번호' \
bun run db:reset-admin:pg

검증

  • end-to-end (임시 sqlite DB, 프로덕션 무영향): 마이그레이션 → argon2id 시드 → 600k PBKDF2 로 강제 교체(깨진 관리자 재현) → 리셋 스크립트 실행 → 교차검증. 결과: 새 비번 valid=true, 옛 비번/틀린 비번 valid=false, 저장 해시 $argon2id$.... 전 단계 PASS
  • hashPassword 스모크 — 스크립트 컨텍스트 import 성공, $argon2id$v=19$m=7168... 확인
  • DB_DIALECT=postgres tsc --noEmit — clean
  • DB_DIALECT=postgres bun run build — clean (adapter-cloudflare)
  • prettier --check + eslint — clean

⚠️ 배포 후 필수 조치

  1. 이 PR 머지 후 배포 (이후 시드는 argon2id 로 생성됨)
  2. 이미 600k PBKDF2 로 시드된 프로덕션 관리자 복구 — 다음 중 하나:
    • DB_DIALECT=postgres RESET_ADMIN_USERNAME=<관리자> RESET_ADMIN_PASSWORD='<새 비번>' bun run db:reset-admin:pg (권장)
    • 또는 기존 credential 삭제 후 IDP_BOOTSTRAP_ADMIN_* 설정하여 db:seed:pg 재실행

프로젝트 규칙상 원격 DB 적용 명령은 자동 실행하지 않으므로, 실제 리셋은 운영자가 값 확인 후 직접 실행해 주세요.

🤖 Generated with Claude Code

mack-erel and others added 3 commits July 2, 2026 13:11
프로덕션 로그인이 hang 수정 후 다음 에러로 이어짐:
  NotSupportedError: Pbkdf2 failed: iteration counts above 100000
  are not supported (requested 600000).

근본 원인: scripts/seed.ts 가 관리자 비밀번호를 PBKDF2 600,000회로
해싱했으나(pbkdf2$sha256:600000$...), Cloudflare Workers WebCrypto 는
PBKDF2 반복을 100,000 으로 제한한다. 따라서 시드된 관리자는 Workers 에서
verifyPbkdf2 의 deriveBits 가 throw 하여 로그인 자체가 불가능했다.
setup.ts 는 100,000 을 써서 동작은 했으나 OWASP 미달 + seed 와 불일치.

수정: 앱의 정규 해시인 argon2id(@hicaru/argon2-pure.js — 순수 JS,
Workers 호환)를 seed/setup 이 그대로 재사용하도록 변경.
- scripts/seed.ts: hashPasswordPbkdf2 제거, src/lib/server/auth/password
  의 hashPassword 재사용
- scripts/setup.ts: hashPasswordForSetup 제거, 동일하게 hashPassword 재사용

이로써 Workers 호환 + OWASP profile 5 즉시 적용 + PBKDF2 반복 상한
불일치 클래스 자체 제거 + 앱/스크립트 해시 경로 DRY.

주의: 이 수정은 이후 시드에만 적용된다. 이미 600k PBKDF2 로 시드된
프로덕션 관리자는 재시드/비밀번호 리셋이 필요하다.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
seed 재실행은 idempotent 라 기존 password credential 을 덮어쓰지 않는다.
따라서 과거 600k PBKDF2(Workers 검증 불가)로 시드된 관리자는 seed:pg
재실행으로는 복구되지 않는다. 이 전용 스크립트가 기존 사용자의 password
credential secret 만 argon2id 로 교체(UPDATE)해 로그인을 복구한다.

- scripts/reset-admin-password.ts: RESET_ADMIN_USERNAME|EMAIL + PASSWORD +
  (선택)TENANT_SLUG 를 env 로 받아(shell 히스토리/프로세스 목록 노출 회피)
  대상 사용자의 password credential 을 hashPassword(argon2id)로 교체.
  실행 후 verifyPassword 로 자체 검증(argon2id 형식 + 입력 비번 검증).
  방언 무관(scripts/lib/db openScriptDb 재사용).
- package.json: db:reset-admin[:pg|:mysql|:sqlite] 스크립트 추가.

검증(임시 sqlite DB, 프로덕션 무영향):
  마이그레이션 → argon2id 시드 → 600k PBKDF2 로 강제 교체(깨진 관리자 재현)
  → 리셋 스크립트 → 교차검증(새 비번 valid=true, 옛/틀린 비번 valid=false).
  전 단계 PASS. tsc(pg)/prettier/eslint clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@mack-erel
mack-erel merged commit af14863 into main Jul 2, 2026
6 checks passed
@mack-erel
mack-erel deleted the fix/seed-argon2id-workers-pbkdf2 branch July 2, 2026 04:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant