Skip to content

후속 개선: OIDC scope(groups/address/organization)·SAML IdP-initiated/POST·i18n(서버+admin)·검증 공통화 - #75

Merged
mack-erel merged 8 commits into
mainfrom
feat/followups-remaining
Jul 5, 2026
Merged

후속 개선: OIDC scope(groups/address/organization)·SAML IdP-initiated/POST·i18n(서버+admin)·검증 공통화#75
mack-erel merged 8 commits into
mainfrom
feat/followups-remaining

Conversation

@mack-erel

Copy link
Copy Markdown
Owner

개요

개선 리포트 후속 검토에서 남았던 항목들을 배치로 처리했습니다(argon2·HSTS preload·의도적 설계 항목 제외). 각 배치는 독립 Opus 에이전트로 구현 후 게이트로 검증했습니다.

최종 검증: vitest 54 passed(9 files) · svelte-check 0 errors(1357 files) · prettier·eslint 통과 · bun run build(adapter-cloudflare) 성공.

변경 요약 (8 커밋)

OIDC

  • groups scope — 조직 멤버십(department/team/part)을 groups 배열로 매핑(id_token·userinfo). discovery 반영.
  • organization scope 화이트리스트 복구 — discovery/userinfo는 처리했으나 admin UI에서 설정 불가였던 것 해소.
  • address scope — users 주소 5컬럼 + address 클레임(formatted 조합, 전부 null이면 생략) + admin 입력 UI + discovery. 마이그레이션 생성(d1 0020, pg/mysql/sqlite 0003).
  • token per-client rate-limit — IP 외 client_id 기반 제한 추가. userinfo Vary: Authorization.

SAML

  • IdP-initiated(unsolicited) SSOGET /saml/sso?sp=<entityId>, InResponseTo 없는 Response(§3.4), 등록된 ACS로 POST.
  • HTTP-POST 바인딩 AuthnRequest — 미서명 지원. 서명 AuthnRequest는 enveloped XML-dsig 검증기 부재로 명시적 거부(가짜 통과 없음). SP-initiated Redirect 흐름은 출력·보안검증 100% 보존, 공통 함수로 세 흐름 공유.
  • metadata WantAuthnRequestsSigned SP 반영(동작·광고 불일치 해소) + Vary 헤더.

보안·정합

  • TOTP enroll catch — unique 위반만 409, 그 외 DB 에러는 500 전파(관측성 회복).
  • positions level — 빈 문자열 조용한 0 저장 대신 검증 실패.
  • SAML/LDAP URL·host 검증 공통화 — validation.ts로 SSRF 프리미티브 통합(link-local 중복 제거). saml-sps/ldap-providers는 UI 계약·audit·암호화 이유로 CRUD 팩토리 미적용(회귀 방지, 검증 함수만 공통화).

i18n

  • auth 서버 에러 메시지 i18n — i18n/core.ts(공통 lookup/폴백) + server.ts(translate) 로 locals.locale 기반 번역. 6개 auth 서버 파일 전환.
  • admin 콘솔 영어화 — en.json을 ko.json과 1:1 대칭(476키)으로 완성, admin svelte 11+개 t() 전환. locale=en 사용자가 admin을 영어로 사용.

⚠️ 머지 전 확인

  1. DB 마이그레이션 적용 필요(address scope)drizzle/0020(d1)·drizzle/{pg,mysql,sqlite}/0003이 users에 주소 5컬럼(순수 additive, nullable)을 추가합니다. 규칙상 생성만 했으니 사용하는 방언에 적용해 주세요(예: bun run db:migrate:pg). 프리뷰 선검증 권장.
  2. en.json 원어민 검수 권장 — admin 섹션 번역은 자동 작성입니다.
  3. SAML POST 바인딩 서명 AuthnRequest 미지원 — enveloped XML-dsig 검증기 신규 구현이 필요해 이번엔 거부 처리(미서명만 지원). 후속 과제.

🤖 Generated with Claude Code

mack-erel and others added 8 commits July 5, 2026 19:03
…imit + userinfo Vary

- groups scope: 활성 조직 멤버십(department/team/part code|name)을 groups 배열로 매핑
  (membershipToGroups). token id_token·userinfo 공유. discovery scopes/claims_supported 에 groups 추가.
- organization scope 를 admin ALLOWED_OIDC_SCOPES 에 추가(discovery/userinfo 는 이미 처리했으나
  admin UI 화이트리스트 누락으로 클라이언트 설정 불가였음).
- token 엔드포인트에 per-client rate-limit(token-client:${clientId}, 60/분) 추가 — 남용 client 격리.
- userinfo 응답에 Vary: Authorization 헤더(캐시 오염 방지).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- generateIdpMetadataXml 에 wantAuthnRequestsSigned 파라미터 추가(하드코딩 false 제거).
  ?sp=<entityId> 쿼리 시 findSp 로 SP 별 값 반영 — 동작(런타임)과 광고(metadata) 불일치 해소.
- saml/metadata 응답에 Vary 헤더(테넌트/SP별 캐시 키 분리, Cache-Control 유지).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- lib/server/db/errors.ts: 방언 무관 isUniqueViolation(err) 헬퍼(sqlite/d1 UNIQUE, pg 23505, mysql 1062).
- enroll/confirm: unique 위반만 409, 그 외 DB 에러는 재던져 500 전파(관측성 회복).
- admin/schemas.ts intField: 빈 문자열/공백을 undefined 로 정규화해 조용한 0 저장 대신 검증 실패.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- users 3방언 스키마에 nullable 주소 5컬럼(address_street/locality/region/postal_code/country).
- lib/oidc/claims.ts: buildAddressClaim — 하위 필드 매핑 + formatted 조합, 전부 null 이면 클레임 생략.
  token id_token·userinfo 공유. address scope 요청 시에만 발급.
- discovery scopes/claims_supported 에 address 추가.
- admin users/[id] updateProfile 에 주소 5필드 저장(tenant 스코프) + 편집 폼 입력.
- 마이그레이션 생성만(d1 0020, pg/mysql/sqlite 0003) — 적용은 사용자.

vitest 54 passed, svelte-check 0 errors, build 성공.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- IdP-initiated: GET /saml/sso?sp=<entityId> (SAMLRequest 없음) → findSp → 로그인 → 서비스권한
  게이트 → InResponseTo 없는 Response(SAML core §3.4) → 등록된 sp.acsUrl 로 POST auto-submit.
  response.ts 의 inResponseTo 를 옵셔널화(null 이면 Response·SubjectConfirmationData 의 InResponseTo 생략).
- HTTP-POST 바인딩: POST /saml/sso, SAMLRequest=base64(XML, deflate 없음). 미로그인 시 Redirect
  바인딩으로 재인코딩해 로그인 후 GET 재개.
  ※ POST 바인딩 서명 AuthnRequest 는 enveloped XML-dsig 검증기가 없어 명시적으로 거부(가짜 통과 없음,
    미서명만 지원). wantAuthnRequestsSigned SP 의 POST 요청도 거부.
- SP-initiated Redirect 흐름은 출력·보안검증 100% 보존. 공통 함수 추출(ssoPreflight/Destination/ACS/
  게이트·발급) 로 세 흐름 공유.
- replay-ID 소비를 "Assertion 발급 직전"으로 이동 — 1 AuthnRequest=1 Assertion 보장 유지하면서
  forceAuthn 재진입(동일 요청 복귀)이 replay 오탐되던 결함 수정.

svelte-check 0 errors, vitest 54 passed, build 성공.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- validation.ts 에 validateSamlUrl, validateLdapHost, validateLdapPort, isCloudMetadataHost,
  isLinkLocalHost 추가. ldap-providers/skins 에 중복되던 link-local(169.254/16) 정규식 통합.
- saml-sps/ldap-providers 는 CRUD 팩토리 미적용(의도적): UI 성공 계약({create:true}) 불일치,
  before/after diff audit(H-SAML-4)·bindPassword 암호화(H-ADMIN-4)가 팩토리 훅으로 표현 불가 →
  강제 적용 시 회귀 확정이라 검증 함수 중복 제거만 수행. 각 라우트 고유 보안 검증 전부 보존.

svelte-check 0 errors, vitest 54 passed, build 성공.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- i18n/core.ts: 클라이언트 t() 와 서버 translate() 가 공유하는 lookup/폴백(현재로케일→ko→key) 로직 추출.
- i18n/server.ts: translate(locale, key, params) — event.locals.locale 로 SSR/action 에서 번역.
- (auth)/{login,signup,find-id,find-password,mfa,reset-password}/+page.server.ts 의 사용자 노출
  fail/error 메시지를 키 기반 translate 로 전환(errors 공통 섹션 + 각 섹션 키). ko.json 에 키 추가.
  운영자향 로그(IDP_ISSUER_URL 미설정 등)는 제외.

vitest 54 passed, svelte-check 0 errors, eslint·prettier 통과.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- en.json 을 ko.json 과 1:1 대칭으로 완성(30개 최상위 섹션, 476키 커버). admin/users/oidc/saml/
  ldap/skins/signing_keys/조직/audit/profile/passkeys/mfa_manage 등 admin 섹션 영어 번역 추가.
  → locale=en 사용자가 admin 콘솔을 영어로 사용 가능(기존엔 ko 폴백).
- admin svelte 잔존 하드코딩 한국어를 t() 로 전환: dashboard, admin/login, 조직 CRUD placeholder,
  oidc-clients/[id], saml-sps/[id], users/[id] 서비스권한 섹션, skins guide 등 11+파일.
  신규 키는 ko.json 에도 한국어 값 추가(ko 회귀 없음).
- ※ en.json 번역은 자동 작성 — 배포 전 원어민 검수 권장.

vitest 54 passed, svelte-check 0 errors, eslint·prettier 통과, build 성공, JSON 유효.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@mack-erel
mack-erel merged commit 5953f6d into main Jul 5, 2026
6 checks passed
@mack-erel
mack-erel deleted the feat/followups-remaining branch July 5, 2026 11:12
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