Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fba3075
fix(security): 보안 Medium 5건 일괄 해소 — 타이밍 열거·계정 잠금·tenant 격리·code 세션 검사…
mack-erel Jul 6, 2026
5d71a4d
chore(db): 마이그레이션 4트랙 대칭화 — legacy code drop·client_skins 보정·email_ve…
mack-erel Jul 6, 2026
cad797c
refactor(db): runAtomic 공용 유틸 — refresh 회전 원자화·서명키 캐시
mack-erel Jul 6, 2026
dd145ad
feat(gc): 만료 데이터 GC 도입 — 10종 테이블 무한 성장 차단
mack-erel Jul 6, 2026
430bda9
feat(email): 이메일 인증 플로우 + 보안 알림 메일
mack-erel Jul 6, 2026
d48992c
test: P0 보안 핵심 로직 테스트 89개 추가 + parity 강화 + 커버리지 인프라
mack-erel Jul 6, 2026
de110d4
refactor(admin): users/[id] 도메인 분리 + 서버 에러 i18n / docs: 보안 문서 정리·시크릿 …
mack-erel Jul 6, 2026
8dc99cc
chore(db): 초대·계정삭제 스키마 4트랙 — invite_tokens·status·deletion_scheduled_at
mack-erel Jul 6, 2026
a94b8f8
feat(account): P6 세션 셀프서비스 — 활성 세션 목록·개별 폐기
mack-erel Jul 6, 2026
2731198
feat(admin): P7 계정 초대 플로우 — 비밀번호 없이 선생성 후 링크 가입
mack-erel Jul 6, 2026
5799c81
feat(account): P8 계정 삭제(소프트 삭제 + 유예 복구)
mack-erel Jul 6, 2026
e83b290
feat(auth): P9 시크릿 무중단 회전 — current/previous 이중 시크릿
mack-erel Jul 6, 2026
c1fa642
refactor(i18n): P10 잔존 한국어 서버 문자열 전량 i18n 전환
mack-erel Jul 6, 2026
dc485bf
test: P11 통합 테스트 — 후속 라운드 플로우 E2E 커버리지
mack-erel Jul 6, 2026
eb6fa9d
docs: 후속 2차 개선(round2) 계획·투두 문서
mack-erel Jul 6, 2026
e1da878
chore(db): 3차 마감 스키마 3트랙 — users 인덱스·이메일변경 토큰·organization config
mack-erel Jul 6, 2026
4db21b2
chore(i18n): 후속 3차 라운드 키 일괄 반영 (ko/en 대칭)
mack-erel Jul 6, 2026
57072a1
fix: 잔여 버그·보안·운영 마감 (P12)
mack-erel Jul 6, 2026
5bd3e42
refactor(ratelimit): 저장소 추상화 — RateLimitStore 인터페이스 + Node in-memory …
mack-erel Jul 6, 2026
e427fdd
feat(a11y): 인증 폼 에러 배너·로딩 상태·스킨 로그인 복구 (P14)
mack-erel Jul 6, 2026
428980f
feat(account): 메일 locale 인지·세션 철회 알림·프로필 이메일 변경 (P13)
mack-erel Jul 6, 2026
57e8e95
test(integration): SAML/LDAP/MFA·P12 로직·세션알림 통합 커버리지 심화 (P16)
mack-erel Jul 6, 2026
a898259
feat(oidc): organization 클레임 클라이언트별 노출 config + id_token 정합 (P17/P12)
mack-erel Jul 6, 2026
1368e4f
docs(admin): 운영 매뉴얼·스킨 placeholder 6종·백업코드 저잔량 경고 (P17)
mack-erel Jul 6, 2026
5f8dbe6
docs: 후속 3차 마감(round3) 계획·투두 문서
mack-erel Jul 6, 2026
b7f1ccd
Merge remote-tracking branch 'origin/feat/saml-email-followup' into f…
mack-erel Jul 6, 2026
2a84093
Merge remote-tracking branch 'origin/main' into feat/saml-email-followup
claude Jul 6, 2026
18433b9
fix(ci): 레이트리밋 테스트 타임아웃 상향 + gitleaks 오탐 3건 ignore
claude Jul 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
# - node: 순수 Node 서버 (adapter-node). platform 바인딩 없음 → pg/mysql 은
# DATABASE_URL 로 직결, 설정값은 process.env 로 읽는다. (D1 은 Workers 전용)
# 예) BUILD_TARGET=node DB_DIALECT=postgres bun run build && node build
#
# 레이트 리밋 저장소(RateLimitStore)는 배포 타깃에 따라 자동 선택된다:
# - cloudflare(Workers): 요청 간 상태를 공유할 수 없는 isolate 특성상 DB(rate_limits 테이블)
# 기반 공유 저장소를 사용한다.
# - node(adapter-node) : 프로세스 내 in-memory 저장소를 사용한다(핫패스 DB write 없음).
# ⚠️ in-memory 는 "단일 프로세스" 가정이다. 같은 앱을 여러 인스턴스로 수평 확장하면
# 각 인스턴스가 자기 카운터만 보므로 실질 한도가 인스턴스 수만큼 완화된다.
# 다중 인스턴스 Node 배포에서 엄격한 전역 한도가 필요하면 공유 저장소(Redis 등)로
# RateLimitStore 를 교체해야 한다(인터페이스는 열려 있으나 이번 버전엔 미포함).
BUILD_TARGET="cloudflare"

# DB_DIALECT: d1(기본) | sqlite | postgres | mysql — 배포 단위로 하나만 사용
Expand Down Expand Up @@ -71,11 +80,14 @@ IDP_DEFAULT_TENANT_NAME="My Organization"

# 서명 키 암호화 KEK — 최소 32자 랜덤 문자열
# 생성: openssl rand -base64 32
# ⚠️ 프로덕션 필수: 미설정 시 요청 초기에 오류로 차단(fail-fast). dev 에서는 생략 가능.
# ⚠️ 프로덕션에서는 wrangler secret put IDP_SIGNING_KEY_SECRET 사용
IDP_SIGNING_KEY_SECRET="your-very-long-random-secret-at-least-32-chars"

# OIDC/SAML issuer URL (배포 도메인과 일치시킬 것)
# 미설정 시 요청 origin으로 자동 대체 (로컬 개발 시 http://localhost:5173)
# ⚠️ 프로덕션 필수: 미설정 시 요청 초기에 503 으로 차단(fail-closed). Host 헤더 주입으로
# iss 클레임/SAML Issuer 가 오염되는 것을 막는다.
# dev 에서만 미설정 시 요청 origin 으로 자동 대체(로컬 http://localhost:5173).
IDP_ISSUER_URL="http://localhost:5173"

SMTP_HOSTNAME=""
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ jobs:
- name: Test
run: bun run test

- name: Migration drift check
run: |
bun run db:generate:all
if ! git diff --exit-code -- drizzle; then
echo "::error::스키마가 변경되었으나 마이그레이션이 생성되지 않았습니다. 'bun run db:generate:all' 실행 후 drizzle*/ 변경분을 커밋하세요." >&2
exit 1
fi

- name: Build
run: bun run build

Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Test coverage
/coverage

# ESLint cache
.eslintcache
# SQLite
*.db

Expand Down
9 changes: 9 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
# False positive: "pbkdf2/argon2id" 는 credentials.secret 컬럼이 어떤 해시 알고리즘 형식을 저장하는지 설명하는 문자열일 뿐, 실제 시크릿이 아님
02bc8b64f32a78d60cc39c46acfd6d49b384cbbd:src/lib/server/db/schema.ts:generic-api-key:62

# False positive: 테스트 시드용 고정 비밀번호("pw-strong-123") — 실제 시크릿 아님
57e8e95ccc4038c7a631a7c3318efcb446c15c8e:test/integration/p12-logic.test.ts:generic-api-key:45

# False positive: AES-GCM 도메인 분리 라벨 문자열("idp-signing-key-wrap-v1") — 실제 시크릿 아님
e83b290fba4a75c4c989434b024b589c1e256918:scripts/reencrypt-secrets.ts:generic-api-key:194

# False positive: 테스트 상수("wrap-secret-0123456789") — 실제 시크릿 아님
d48992c3dd491706b9795d1e2ae3de3d234eadcf:test/unit/crypto-keys.test.ts:generic-api-key:204
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,16 @@ wrangler secret put IDP_SIGNING_KEY_SECRET

## 환경변수

| 변수 | 필수 | 설명 |
| ----------------------------------- | ---- | ---------------------------------------------------------------------------------------- |
| `IDP_ISSUER_URL` | ✅ | OIDC/SAML 발급자 URL (배포 도메인과 일치) |
| `IDP_SIGNING_KEY_SECRET` | ✅ | 서명 키 암호화 KEK (프로덕션은 반드시 Secret) |
| `DISPATCHER_SERVICE_TOKEN` | 선택 | stardust dispatcher 가 `/api/totp/*` 호출 시 사용할 Bearer 토큰. 미설정이면 해당 API 503 |
| `IDP_DEFAULT_TENANT_NAME` | 선택 | 기본 테넌트 이름 (기본: `My Organization`) |
| `CLOUDFLARE_ACCOUNT_ID` | 선택 | Cloudflare 계정 ID (마이그레이션 스크립트에서 사용) |
| `CLOUDFLARE_D1_DATABASE_ID` | 선택 | D1 데이터베이스 ID (마이그레이션 스크립트에서 사용) |
| `CLOUDFLARE_D1_PREVIEW_DATABASE_ID` | 선택 | 프리뷰용 D1 데이터베이스 ID |
| `CLOUDFLARE_D1_TOKEN` | 선택 | D1 API 토큰 (`db:migrate` 스크립트에서 사용) |
| 변수 | 필수 | 설명 |
| ----------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `IDP_ISSUER_URL` | ✅ | OIDC/SAML 발급자 URL (배포 도메인과 일치). **프로덕션 필수** — 미설정 시 요청 초기 503(fail-closed). dev 에서만 요청 origin 자동 대체 |
| `IDP_SIGNING_KEY_SECRET` | ✅ | 서명 키 암호화 KEK (프로덕션은 반드시 Secret). **프로덕션 필수** — 미설정 시 요청 초기에 오류로 차단(fail-fast) |
| `DISPATCHER_SERVICE_TOKEN` | 선택 | stardust dispatcher 가 `/api/totp/*` 호출 시 사용할 Bearer 토큰. 미설정이면 해당 API 503 |
| `IDP_DEFAULT_TENANT_NAME` | 선택 | 기본 테넌트 이름 (기본: `My Organization`) |
| `CLOUDFLARE_ACCOUNT_ID` | 선택 | Cloudflare 계정 ID (마이그레이션 스크립트에서 사용) |
| `CLOUDFLARE_D1_DATABASE_ID` | 선택 | D1 데이터베이스 ID (마이그레이션 스크립트에서 사용) |
| `CLOUDFLARE_D1_PREVIEW_DATABASE_ID` | 선택 | 프리뷰용 D1 데이터베이스 ID |
| `CLOUDFLARE_D1_TOKEN` | 선택 | D1 API 토큰 (`db:migrate` 스크립트에서 사용) |

> **참고**: 초기 관리자 계정은 `bun run setup` 이 생성합니다. 수동/CI 시드가 필요하면 `IDP_BOOTSTRAP_ADMIN_USERNAME` / `IDP_BOOTSTRAP_ADMIN_EMAIL` / `IDP_BOOTSTRAP_ADMIN_PASSWORD` (+선택 `IDP_BOOTSTRAP_ADMIN_NAME`) 를 설정하고 `bun run db:seed`(방언별: `db:seed:pg` 등)를 실행하세요. 비대화 환경에서는 `SEED_RESET=0|1` 로 초기화 여부를 지정합니다.

Expand Down
39 changes: 38 additions & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading