Skip to content

Commit ae58e47

Browse files
authored
Merge pull request #222 from Team-StackUp/fix/purge-legacy-withdrawn-tokens
이미 탈퇴한 사용자의 GitHub 토큰 백필 파기
2 parents 17b0737 + fc2f0fe commit ae58e47

3 files changed

Lines changed: 76 additions & 1 deletion

File tree

backend/src/main/java/com/stackup/stackup/github/application/InternalGithubTokenService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ public class InternalGithubTokenService {
1818
private final GithubTokenCipher tokenCipher;
1919

2020
public String fetchPlainAccessToken(Long userId) {
21-
User user = userRepository.findById(userId)
21+
// 탈퇴한 계정의 토큰은 위임하지 않는다. 탈퇴 시 토큰을 지우므로(User.withdraw) 보통은
22+
// hasGithubLink 에서 걸리지만, 그건 "값이 비어 있어서" 막히는 것이라 데이터 상태에
23+
// 기대는 방어다. 삭제 여부로 먼저 막아 상태와 무관하게 닫는다.
24+
User user = userRepository.findByIdAndDeletedFalse(userId)
2225
.orElseThrow(() -> new DomainException(ApiErrorCode.USER_NOT_FOUND));
2326
// Google 로 가입한 계정은 GitHub 토큰이 없다. 그대로 복호화로 넘기면 NPE 가 500 으로
2427
// 새어나가므로, 무엇이 부족한지 말해 주는 도메인 에러로 바꾼다.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- V28 은 탈퇴 시 GitHub 토큰을 비울 수 있도록 CHECK 제약을 완화하기만 했다. 그 시점에
2+
-- **이미 탈퇴해 있던** 사용자들의 토큰은 그대로 남아 있다 — #198 은 이후 탈퇴만 처리한다.
3+
--
4+
-- 그런데 "떠난 사용자의 repo 스코프 자격증명을 무기한 보관하지 않는다"는 목적에서 보면
5+
-- 그 사람들이 바로 그 대상이다. 이미 탈퇴했으니 앞으로 User.withdraw() 가 불릴 일도 없어
6+
-- 백필하지 않으면 영원히 남는다.
7+
UPDATE users
8+
SET encrypted_github_access_token = NULL
9+
WHERE is_deleted = TRUE
10+
AND encrypted_github_access_token IS NOT NULL;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.stackup.stackup.github.application;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
import static org.mockito.Mockito.when;
6+
7+
import com.stackup.stackup.common.exception.ApiErrorCode;
8+
import com.stackup.stackup.common.exception.DomainException;
9+
import com.stackup.stackup.github.infrastructure.GithubTokenCipher;
10+
import com.stackup.stackup.user.domain.User;
11+
import com.stackup.stackup.user.domain.UserRepository;
12+
import java.util.Optional;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.api.extension.ExtendWith;
15+
import org.mockito.InjectMocks;
16+
import org.mockito.Mock;
17+
import org.mockito.junit.jupiter.MockitoExtension;
18+
19+
/**
20+
* AI 서버가 레포 분석 시점에 위임받는 GitHub access token 은 `repo` 스코프다 —
21+
* 비공개 레포까지 읽을 수 있는 살아있는 자격증명이라 위임 조건이 좁아야 한다.
22+
*/
23+
@ExtendWith(MockitoExtension.class)
24+
class InternalGithubTokenServiceTest {
25+
26+
@Mock UserRepository userRepository;
27+
@Mock GithubTokenCipher tokenCipher;
28+
@InjectMocks InternalGithubTokenService service;
29+
30+
@Test
31+
void returnsDecryptedTokenForActiveUser() {
32+
User user = User.createGithubUser(1L, "u", null, null, "enc");
33+
when(userRepository.findByIdAndDeletedFalse(1L)).thenReturn(Optional.of(user));
34+
when(tokenCipher.decrypt("enc")).thenReturn("gho_plain");
35+
36+
assertThat(service.fetchPlainAccessToken(1L)).isEqualTo("gho_plain");
37+
}
38+
39+
// 탈퇴한 계정은 삭제 여부에서 먼저 막는다. 토큰이 비어 있어서 막히는 것에만 기대면
40+
// 백필 전 데이터·향후 실수에 그대로 뚫린다.
41+
@Test
42+
void refusesWithdrawnUserEvenIfTokenRowStillPresent() {
43+
when(userRepository.findByIdAndDeletedFalse(1L)).thenReturn(Optional.empty());
44+
45+
assertThatThrownBy(() -> service.fetchPlainAccessToken(1L))
46+
.isInstanceOf(DomainException.class)
47+
.extracting(e -> ((DomainException) e).getErrorCode())
48+
.isEqualTo(ApiErrorCode.USER_NOT_FOUND);
49+
}
50+
51+
// Google 로 가입한 계정은 GitHub 토큰이 없다 — NPE 가 500 으로 새지 않게 도메인 에러로.
52+
@Test
53+
void refusesGoogleOnlyAccountWithDomainError() {
54+
User google = User.createGoogleUser("g-1", "u", null, null);
55+
when(userRepository.findByIdAndDeletedFalse(1L)).thenReturn(Optional.of(google));
56+
57+
assertThatThrownBy(() -> service.fetchPlainAccessToken(1L))
58+
.isInstanceOf(DomainException.class)
59+
.extracting(e -> ((DomainException) e).getErrorCode())
60+
.isEqualTo(ApiErrorCode.AUTH_GITHUB_NOT_LINKED);
61+
}
62+
}

0 commit comments

Comments
 (0)