|
| 1 | +# 공유 링크 코어 시스템 (Shared Link) |
| 2 | + |
| 3 | +## 1. 개요 |
| 4 | + |
| 5 | +모든 모듈이 공통으로 사용할 수 있는 **공개 링크 발행 인프라**입니다. |
| 6 | +모듈은 이 코어를 호출하기만 하면 어떤 데이터든 외부 공개 링크를 발행할 수 있습니다. |
| 7 | + |
| 8 | +``` |
| 9 | +[모듈] → POST /core/share → 토큰 발행 |
| 10 | +[외부] → GET /s/:token → 토큰 검증 → 모듈 데이터 렌더링 |
| 11 | +``` |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## 2. 모듈 연동 방식 |
| 16 | + |
| 17 | +각 모듈은 공유할 데이터의 **리소스 타입과 ID**만 코어에 전달합니다. |
| 18 | +링크 관리(발행, 만료, 접근 제어)는 전부 코어가 처리합니다. |
| 19 | + |
| 20 | +```ts |
| 21 | +// 모듈에서 링크 발행 요청 |
| 22 | +POST /core/share |
| 23 | +{ |
| 24 | + resourceType: 'invoice', // 모듈이 정의한 리소스 타입 |
| 25 | + resourceId: 'inv_abc123', // 해당 리소스 ID |
| 26 | + expiresAt: '2026-05-01', // 만료일 (optional) |
| 27 | + password: 'secret', // 비밀번호 보호 (optional) |
| 28 | + maxAccessCount: 10, // 최대 접근 횟수 (optional) |
| 29 | +} |
| 30 | + |
| 31 | +// 응답 |
| 32 | +{ |
| 33 | + token: 'a1b2c3d4e5f6', |
| 34 | + url: 'https://your-server.com/s/a1b2c3d4e5f6' |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## 3. DB 스키마 |
| 41 | + |
| 42 | +```sql |
| 43 | +CREATE TABLE shared_links ( |
| 44 | + id TEXT PRIMARY KEY, -- UUID |
| 45 | + token TEXT UNIQUE NOT NULL, -- 공개 URL 토큰 (랜덤 12자 이상) |
| 46 | + resource_type TEXT NOT NULL, -- 모듈이 정의한 리소스 타입 |
| 47 | + resource_id TEXT NOT NULL, -- 해당 리소스 ID |
| 48 | + password_hash TEXT, -- 비밀번호 해시 (없으면 NULL) |
| 49 | + max_access INTEGER, -- 최대 접근 횟수 (없으면 무제한) |
| 50 | + access_count INTEGER DEFAULT 0, -- 현재 접근 횟수 |
| 51 | + expires_at DATETIME, -- 만료일시 (없으면 영구) |
| 52 | + created_by TEXT NOT NULL, -- 발행한 사용자 ID |
| 53 | + created_at DATETIME DEFAULT NOW(), |
| 54 | + revoked_at DATETIME -- 수동 무효화 시각 (NULL이면 유효) |
| 55 | +); |
| 56 | + |
| 57 | +CREATE TABLE shared_link_logs ( |
| 58 | + id TEXT PRIMARY KEY, |
| 59 | + token TEXT NOT NULL, |
| 60 | + accessed_at DATETIME DEFAULT NOW(), |
| 61 | + ip_address TEXT, |
| 62 | + user_agent TEXT |
| 63 | +); |
| 64 | +``` |
| 65 | + |
| 66 | +--- |
| 67 | + |
| 68 | +## 4. API 명세 |
| 69 | + |
| 70 | +### 링크 발행 |
| 71 | +``` |
| 72 | +POST /core/share |
| 73 | +Authorization: Bearer <jwt> ← 인증된 사용자만 발행 가능 |
| 74 | +
|
| 75 | +Body: { |
| 76 | + resourceType: string |
| 77 | + resourceId: string |
| 78 | + expiresAt?: string // ISO 8601 |
| 79 | + password?: string |
| 80 | + maxAccessCount?: number |
| 81 | +} |
| 82 | +
|
| 83 | +Response: { token: string, url: string } |
| 84 | +``` |
| 85 | + |
| 86 | +### 링크 접근 (공개, 비인증) |
| 87 | +``` |
| 88 | +GET /s/:token |
| 89 | +
|
| 90 | +Query: ?password=secret ← 비밀번호 보호 링크의 경우 |
| 91 | +
|
| 92 | +Response: |
| 93 | + 200 → { resourceType, resourceId, data: <모듈이 제공하는 렌더 데이터> } |
| 94 | + 401 → 비밀번호 필요 또는 불일치 |
| 95 | + 404 → 토큰 없음 |
| 96 | + 410 → 만료 또는 무효화됨 |
| 97 | + 429 → 접근 횟수 초과 |
| 98 | +``` |
| 99 | + |
| 100 | +### 링크 무효화 |
| 101 | +``` |
| 102 | +DELETE /core/share/:token |
| 103 | +Authorization: Bearer <jwt> ← 발행자 또는 관리자만 가능 |
| 104 | +``` |
| 105 | + |
| 106 | +### 내가 발행한 링크 목록 |
| 107 | +``` |
| 108 | +GET /core/share |
| 109 | +Authorization: Bearer <jwt> |
| 110 | +``` |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## 5. 모듈 측 구현 가이드 |
| 115 | + |
| 116 | +링크 접근 시 코어는 `resourceType`과 `resourceId`를 해당 모듈에 위임합니다. |
| 117 | +각 모듈은 자신의 리소스 타입에 대한 **렌더 핸들러**를 등록해야 합니다. |
| 118 | + |
| 119 | +```ts |
| 120 | +// 모듈에서 렌더 핸들러 등록 (module.json 또는 모듈 초기화 시) |
| 121 | +registerSharedLinkRenderer('invoice', async (resourceId, ctx) => { |
| 122 | + const invoice = await getInvoice(resourceId); |
| 123 | + return { ...invoice }; // 외부에 노출할 데이터만 반환 |
| 124 | +}); |
| 125 | +``` |
| 126 | + |
| 127 | +코어가 `/s/:token` 요청을 받으면: |
| 128 | +1. 토큰 유효성 검증 (만료, 비밀번호, 횟수) |
| 129 | +2. `resourceType`에 등록된 핸들러 호출 |
| 130 | +3. 핸들러가 반환한 데이터를 응답 |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## 6. 보안 고려사항 |
| 135 | + |
| 136 | +- 토큰은 최소 **16자 이상의 랜덤 문자열** (crypto.randomBytes 기반) |
| 137 | +- 비밀번호는 평문 저장 금지 — bcrypt 해시로 저장 |
| 138 | +- 접근 로그는 IP/User-Agent 기록 (텔레메트리 동의 여부와 무관하게 내부 로그로 유지) |
| 139 | +- 공개 링크 응답에는 **내부 ID, 사용자 정보 등 민감 데이터 포함 금지** — 모듈 핸들러가 필터링 책임 |
| 140 | +- 무효화된 링크는 삭제하지 않고 `revoked_at`만 기록 (접근 시도 감사 추적 가능) |
| 141 | + |
| 142 | +--- |
| 143 | + |
| 144 | +## 7. 활용 예시 |
| 145 | + |
| 146 | +| 모듈 | 리소스 타입 | 외부 접속자가 보는 것 | |
| 147 | +|------|------------|----------------------| |
| 148 | +| 청구서 | `invoice` | 청구서 내용, 결제 링크 | |
| 149 | +| 폼 빌더 | `form` | 응답 입력 폼 | |
| 150 | +| 프로젝트 | `project-status` | 진행 현황 (읽기 전용) | |
| 151 | +| 출퇴근 | `schedule` | 근무 일정 확인 | |
0 commit comments