프라이빗 GitHub repo에 대한 접근 권한을 자동으로 판매하는 시스템을 구축한 방법
Source: Dev.to
(번역을 진행하려면 번역하고자 하는 본문 텍스트를 제공해 주세요.)
TL;DR
프라이빗 GitHub 레포에 대한 접근 권한을 판매해야 했습니다. ZIP 다운로드는 업데이트가 되지 않고, 라이선스 키는 과도하며, 여러 결제 제공업체가 제 비즈니스를 거부했습니다. 그래서 전체 흐름을 처리하는 완전 자동화 시스템을 직접 구축했습니다:
- Payment webhook fires → signature verified
- GitHub username captured via OAuth
- Collaborator invite sent in seconds
잘 작동하지만, Polar.sh에 이미 이 기능이 내장되어 있고 더 잘 구현돼 있다는 것을 알게 되었습니다. 제 버전을 사용하지는 않았지만, 구축하면서 많은 것을 배웠습니다.
문제
보일러플레이트를 만들었고 판매하고 싶었습니다. 제품은 완성됐지만 – 실제로 어떻게 전달할지가 가장 어려운 부분이었습니다.
시작 키트, 보일러플레이트, 템플릿 등 개발자들이 비용을 지불할 만한 무언가를 만들었습니다. 이제는? 결제 후 어떻게 전달할까요?
평가한 옵션
| 옵션 | 장점 | 단점 |
|---|---|---|
| Zip 파일 다운로드 | 간단하고 바로 사용 가능 | 업데이트 불가, git 히스토리 없음, pull/diff 불가 |
| License‑key 게이팅 | 접근을 잠글 수 있음 | 커스텀 CLI + 인프라 필요 – 과도함 |
| npm 프라이빗 패키지 | 개발자에게 친숙 | 레지스트리 인증 필요 → 마찰이 전환을 저해 |
| GitHub Sponsors (프라이빗 레포) | GitHub 내장 접근 | 구독 전용, 일회성 구매에 부적합 |
| 결제 플랫폼을 통한 수동 전달 | 소수 판매에선 가능 | 규모가 커지면 불가능 (예: 새벽 2시 3건 판매) |
제가 실제로 원했던 것은 아주 간단하게: 고객이 결제하면 → 고객이 자동으로, 즉시 프라이빗 레포에 접근할 수 있게.
결제 제공자 장애물
내가 등록한 사업체는 디자인 및 컨설팅 회사입니다. Lemon Squeezy와 몇몇 다른 플랫폼에 디지털 제품을 판매하려고 가입을 시도했지만 거절당했습니다.
- 나는 일회성 디지털 제품(보일러플레이트 코드, 스타터 킷)을 판매하고 있으며, 컨설팅 서비스는 제공하지 않는다고 설명했습니다.
- 거절은 계속되었고, 일부 제공자는 명백히 무례했습니다.
그때 나는 결제 시 웹훅을 호출할 수 있는 제공자를 찾아 나머지 작업을 직접 자동화해야 한다는 것만 알았습니다.
영감
- 결제 웹훅 → GitHub 협업자 초대
- 개인 저장소, 읽기 전용 접근 (복제 + 업데이트 풀, 전체 git 히스토리)
그들은 이에 대해 $30를 청구했습니다. 저는 직접 만들 수 있을 거라고 생각했어요 – 일석이조.
왜 가능했는가
- GitHub API를 사용하면 협업자를 프로그래밍 방식으로 추가할 수 있습니다.
- 웹훅을 발생시키는 모든 결제 제공업체가 흐름을 트리거할 수 있습니다.
- 권한을 pull (읽기 전용)으로 설정하면 고객이 클론은 할 수 있지만 푸시할 수 없습니다.
내 솔루션 아키텍처
A stand‑alone Next.js app that is payment‑provider agnostic.
User clicks "Buy"
|
GitHub OAuth (capture username)
|
Payment checkout
|
Webhook fires → verify signature → invite to repo → send email
|
Customer has access in seconds
주요 구성 요소
| Component | Description |
|---|---|
| GitHub OAuth | 결제 진행 전에 구매자의 GitHub 사용자 이름을 캡처합니다 |
| Webhook handler | 결제 확인을 수신하고, HMAC‑SHA256 서명을 검증합니다 |
| GitHub API client | 저장소 초대를 자동으로 보냅니다 |
| PostgreSQL | 각 고객을 추적합니다 (데이터 33개 필드) |
| Resend | 환영 이메일을 보냅니다 |
상세 흐름
1. 체크아웃 전 GitHub 사용자명 캡처
결제 제공자는 구매자의 GitHub 사용자명을 알지 못하므로 먼저 캡처해야 합니다.
// After OAuth callback, redirect to checkout with GitHub info
const checkoutUrl = new URL(CHECKOUT_URL);
checkoutUrl.searchParams.set('gh_username', githubUser.login);
checkoutUrl.searchParams.set('gh_user_id', String(githubUser.id));
결제 제공자는 이 필드들을 웹훅 페이로드에 포함시켜, 결제가 완료될 때 정확히 누구를 초대해야 하는지 알 수 있게 합니다.
2. 웹훅 서명 검증
누구든지 웹훅 엔드포인트에 POST 요청을 보낼 수 있습니다. 검증이 없으면 공격자가 스스로에게 무료 접근 권한을 부여할 수 있습니다.
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(payload: string, signature: string): boolean {
const expected = createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
주의사항
- 원시 요청 본문을 사용하고, 파싱된 JSON을 사용하지 마세요 – 재직렬화 시 공백이 바뀌어 서명이 깨집니다.
===대신timingSafeEqual을 사용하세요 – 단순 문자열 비교는 타이밍 정보를 누출합니다.
3. 고객 레코드 UPSERT
고객이 업그레이드나 두 번째 제품을 구매할 수 있습니다. UPSERT가 없으면 두 번째 웹훅이 이메일에 대한 고유 제약 위반으로 실패합니다.
INSERT INTO customers (email, name, amount_paid, ...)
VALUES (...)
ON CONFLICT (email) DO UPDATE SET
amount_paid = EXCLUDED.amount_paid,
order_id = EXCLUDED.order_id,
updated_at = NOW();
4. 보류 중인 초대 처리
고객이 초대를 수락하지 않으면 보류 중 상태가 됩니다. 보류 중인 초대가 50개가 되면 GitHub은 새 초대를 완전히 차단합니다.
- 초대 상태를 추적합니다.
- 보류 중인 초대를 모니터링하고 수동으로 재전송하거나 취소할 수 있는 관리자 패널을 구축했습니다.
5. GitHub API 실패 시 재시도
GitHub API가 다운되거나, 레이트 제한에 걸리거나, 네트워크 오류가 발생할 수 있습니다. 보일러플레이트에 대해 결제한 고객이 오류만 보고 지원팀에 연락하라는 메시지를 받으면 안 됩니다.
지수 백오프 전략
| 시도 | 지연 시간 |
|---|---|
| 1 | 1 초 |
| 2 | 2 초 |
| 3 | 4 초 |
| 4 | 8 초 |
| … | … |
| 10 | 1 시간 |
오류 분류
- 재시도 가능 – 네트워크 타임아웃, 레이트 제한 응답, 5xx 오류 → 백오프 루프를 통해 재시도합니다.
- 재시도 불가 – 4xx 클라이언트 오류(예: 잘못된 페이로드) → 로그를 남기고 알림을 보냅니다.
배운 점
- Payment providers can be finicky about the type of digital goods you sell. → 결제 제공업체는 판매하는 디지털 상품 유형에 대해 까다로울 수 있습니다.
- Signature verification must use the raw payload and a timing‑safe compare. → 서명 검증은 원시 페이로드와 타이밍‑안전 비교를 사용해야 합니다.
- UPSERT (or equivalent) prevents duplicate‑key errors when a buyer makes multiple purchases. → UPSERT(또는 동등한 방법)는 구매자가 여러 번 구매할 때 중복 키 오류를 방지합니다.
- Pending GitHub invites can silently block you – monitor and clean them up. → 보류 중인 GitHub 초대는 조용히 차단할 수 있으니 모니터링하고 정리하세요.
- Robust retry logic is essential when you depend on third‑party APIs. → 강력한 재시도 로직은 타사 API에 의존할 때 필수적입니다.
요약
저는 다음과 같은 완전 자동화된, 공급자에 구애받지 않는 시스템을 구축했습니다:
- OAuth를 통해 구매자의 GitHub 사용자 이름을 캡처합니다.
- 결제 웹훅을 수신하고, 이를 안전하게 검증합니다.
- 구매자를 읽기 전용 협업자로 프라이빗 레포지토리에 초대합니다.
- 레포지토리 링크가 포함된 환영 이메일을 보냅니다.
시스템은 정상적으로 동작했지만 Polar.sh가 이미 이 기능을 즉시 제공하고 더 우수하게 구현하고 있어 결국 그들의 서비스를 사용하게 되었습니다. 그럼에도 불구하고 이번 경험을 통해 OAuth, 웹훅 보안, GitHub API, 그리고 신뢰성 높은 자동화 패턴을 깊이 있게 학습할 수 있었습니다.
백오프가 있는 큐
- 영구적인 실패 (예: 사용자를 찾을 수 없음, 레포를 찾을 수 없음,
401/403) → 수동 검토를 위해 데드레터 큐로 보냅니다. - 10번의 실패 시도 후 해당 항목이 데드레터 큐로 이동하고 알림을 받습니다.
GitHub 토큰 교환 엔드포인트
올바른 엔드포인트는 https://github.com, **https://api.github.com**이 아닙니다.
# WRONG – returns an HTML login page
POST https://api.github.com/login/oauth/access_token
# CORRECT – returns the token
POST https://github.com/login/oauth/access_token
GitHub API를 사용하면서 익숙해진 근육 기억 때문에 API 서브도메인을 가리키고 있었지만, OAuth 흐름은 메인 사이트에서 동작합니다.
스택 개요
| Component | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| Database | PostgreSQL on Neon |
| GitHub API | Octokit |
| Resend | |
| Validation | Zod |
| Testing | Vitest (all passing) |
모든 호스팅 제공업체에서 실행될 수 있도록 구축되었습니다. 현재는 저용량 트래픽을 위해 Vercel 무료 티어에서 운영 중입니다.
이 접근 방식이 실제 장점을 갖는 이유
- 즉시 업데이트 –
main에 수정 사항을 푸시하면 모든 고객이git pull을 할 수 있어 zip 파일을 다시 다운로드할 필요가 없습니다. - 전체 git 히스토리 – 고객이 왜 그렇게 구현되었는지 확인할 수 있습니다.
- 기본적으로 읽기 전용 –
pull권한으로 클론은 가능하지만 레포에 푸시할 수 없습니다. - 추가 도구 불필요 –
git만 있으면 되며, 라이선스 키, 커스텀 CLI, 레지스트리 인증이 필요 없습니다. - 접근 권한 회수 가능 – 비용을 청구해야 할 경우, API 호출 하나로 협업자를 제거할 수 있습니다.
단점
- 보류 중인 초대 50개 제한 – GitHub은 미처리 초대 수를 제한합니다.
- GitHub 중심 – GitHub에 종속되어 있어 보편적인 솔루션이 아닙니다.
개발자에게 개발자 도구를 판매할 때는 이러한 단점이 보통 수용 가능합니다.
대신 사용하게 된 방법
도구를 만들면서 결제는 Polar.sh 로 전환했습니다. 그들은 제 비즈니스를 받아들였기 때문입니다 (Lemon Squeezy와는 달리). 그들의 플랫폼은 제가 다시 만들고 있던 모든 것을 이미 처리합니다:
- GitHub 사용자 이름 수집
- 레포지토리 초대
- 접근 관리
요약하면, Polar는 제가 만들려고 했던 정확한 흐름을 자동화합니다:
- 고객이 결제합니다.
- Polar가 그들의 GitHub 사용자 이름을 수집합니다.
- Polar가 레포지토리 초대를 보냅니다.
- 고객이 접근 권한을 얻습니다.
제가 필요했던 것은 승인이었으며, 그 후에 기능을 켤 수 있었습니다.
회고
내 도구는 작동합니다: 모든 테스트가 통과하고 파이프라인이 실행되지만, 배포할 필요는 없었습니다. 그래도 많은 것을 배웠습니다:
- 웹훅 서명 검증
- OAuth 흐름
- 지수 백오프를 이용한 재시도 시스템
- GitHub 협업자 API가 실제로 어떻게 작동하는지
이러한 세부 사항은 튜토리얼에서는 얻기 힘든 것들입니다.
열린 질문
비슷한 솔루션에 $30을 청구하는 사람을 보고 직접 만들 수 있을 것 같아 이 프로젝트를 시작했습니다.
- 누구든 실제로 이것을 유용하게 사용할까요?
- 보일러플레이트나 스타터 킷을 판매하고 Polar의 내장 솔루션에 얽매이고 싶지 않다면, 이런 독립형 도구가 가치가 있을까요?
- 오픈소스로 공개하는 것이 좋을까요?
댓글로 알려 주세요. 이걸 배포할지(오픈소스로 공개하거나 “커피 한 잔 사 주세요” 형태로 지원받을지) 아니면 학습 프로젝트로 유지할지 고민 중입니다. 판매하기엔 좀 무의미하게 느껴지지만, 만드는 과정은 즐거웠습니다.
TL;DR
Next.js, TypeScript와 웹훅 문서를 읽는 데 많은 시간을 투자하여 구축했습니다. 🚀