Backend/NestJS

TypeORM - findOne with undefined

hou27 2022. 11. 7. 19:01

이슈의 발현

프로젝트를 진행하던 중,

갑자기 다른 사용자의 계정으로 로그인된다는 이슈가 제보됐다.

 

그럴 수도 없고, 그래서도 안되고, 믿기지 않았지만

제보해주신 분이 보내온 캡처를 확인하니 분명 내 계정으로 로그인된 모습이었다.

 

다행히도 많은 도움을 주신 덕에 금방 원인을 파악할 수 있었다.

 

원인

현재 필자의 프로젝트는 카카오 로그인을 지원하고 있다.

아직 비즈 앱으로 등록하지 않아서

EMAIL을 필수 동의 항목으로 설정할 수 없어서 선택사항으로 두었었는데,

제보해주신 분이 최초로 카카오 로그인을 시도할 때

이메일 제공에 동의하지 않았고,

 

그 탓에 이메일로 사용자 식별을 하고 있는 우리 서버가

undefined 값을 기준으로 사용자를 찾았더니

내 계정이 찾아진 것이었다.

 

..?

여기까지 읽고 무슨 소린지 모르겠다면 정상이다.

카카오 메일을 제공받지 못해 undefined 값이 서버로 들어온 건 알겠는데,

카카오 로그인이 진행되는 과정에서 왜 내 계정으로 로그인되는 건지 도무지 이해를 할 수가 없었다.

 

 

아래는 카카오 로그인을 담당하는 메서드의 코드 일부이다.

const email = userInfo.kakao_account.email;

// check user exist with email
const userInDb = await this.users.findOne({
	where: { email },
	select: { id: true, email: true, password: true },
});

유저가 제공한 정보 중 email을 통해 DB에 유저가 존재하는지, 신규 유저인지 판단하게 되는데,

이 과정에서 where 절에 undefined가 지정된 상황이다.

 

 

TypeORM findOne issue

https://github.com/typeorm/typeorm/issues/2500

 

.findOne(undefined) returns first item in the database instead of undefined · Issue #2500 · typeorm/typeorm

Issue type: [ ] question [x] bug report [ ] feature request [ ] documentation issue Database system/driver: [ ] cordova [ ] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [x ] postgres [ ] sqlite...

github.com

알고 보니 TypeORM의 findOne 메서드는

undefined 값이 넘어올 시 DB의 첫 번째 단일 레코드를 반환하도록 설계되어있었다.

(심지어 의도한 설계인 듯하다.)

 

때문에 DB에 가장 상단에 위치해있던 나의 계정이 findOne의 결과로 반환되었고,

자연스레 제보하신 분의 PC에서 내 계정으로 로그인이 진행되었던 것이다..........

 

유저들의 이슈 제보에 달린 답 중 하나를 가져왔다.

Should it mean that if the field condition is undefined, no conditional statement for that field should be added/checked in the query (the former and current behavior)? Or does it mean the user's intention was to compare to a null value (the latter)?

With the former, non-existent (undefined) clauses are ignored and no conditionals are added to the query, hence a clause where all fields are undefined would equate a query with no conditionals. It is why in those cases a single record is returned. In the latter, an undefined on either of the input variables would effectively add a null comparison conditional in a field that has an undefined conditional expression. Furthermore, if we make one-off rules to "make undefined behave like null when only one field is used" or hacks that break usage consistency only make learning and using the API harder and more error-prone.

 

필드 조건이 정의되지 않은 경우 해당 필드에 대한 조건문을 쿼리에서 추가/체크하지 않아야 한다는 의미인가(이전 및 현재 동작)? 아니면 사용자의 의도가 null 값(후자)과 비교하고자 한다는 의미인가?

 

전자의 경우 존재하지 않는(undefined) 절은 무시되며 조회에 조건이 추가되지 않으므로 모든 필드가 정의되지 않은 절은 조회가 조건 없는 것과 같다. 그것이 바로 그러한 경우에 단일 레코드가 반환되는 이유이다. 후자의 경우, 입력 변수 중 하나에 정의되지 않은 것은 정의되지 않은 조건식을 갖는 필드에 null 비교 조건을 효과적으로 추가할 수 있다. 또한, "한 필드만 사용될 때 undefined이 null처럼 행동하도록 한다"와 같은 일회성 규칙을 만들거나 사용 일관성을 깨는 해킹은 API를 학습하고 사용하는 것을 더 어렵게 만들고 오류를 일으키기 쉽다.

 


개인적인 생각

null을 지정할 수 있는 방법이 있는데 undefined가 null 비교의 기본값이 되어서는 안 된다고 생각하는 것에는 동의한다.

 

그러나 undefined에 대해 확인/검증하는 것은 애플리케이션 로직에 달려 있으며 기본 동작은 가장 일관되고 일반적인 사용 사례를 허용한다고 하는 부분은 분명히 맞는 말이지만 undefined라는 값이 넘어왔을 때 조건이 추가되지 않은 것으로 처리되어 전체 레코드 중 첫 번째 값을 반환하는 것이 아닌 에러를 반환해달라는 요구사항을 왜 아직도 반영해주지 않는지 나의 머리론 아직까지 이해하지 못했다.

 

유저들은 의도치 않은 문제를 야기할 수 있는 심각한 버그라며 하루빨리 고쳐지길 바란다고 하고 있지만 아직까지 수정된 건 없는 것으로 확인된다. - 2022.11.07.

 


참고

 

https://github.com/typeorm/typeorm/issues/9316

https://github.com/typeorm/typeorm/issues/2500