울면서개발하기
KARTORY 개발일지

마을 계정으로 바로 입장하기와 인증된 랭킹 기록

· 커밋 8개

같은 도메인에서 돌아가는 그래픽 채팅 마을 VUTOPIA 안에서 KARTORY 를 열 수 있게 연결한 날이다. 마을에서 게임으로 넘어올 때 닉네임을 다시 칠 필요 없이 마을 계정으로 바로 들어오고, 그렇게 들어온 사람의 기록은 랭킹에서 다른 사람이 같은 이름으로 덮어쓸 수 없게 했다.

한 일

마을 입장권으로 입장

  • 마을이 발급한 짧은 입장권을 링크로 받아, 게임 서버가 서명·대상·만료를 확인하고 한 번만 쓸 수 있게 처리한다. 확인에 실패하면 막지 않고 일반 닉네임 입장으로 넘어가며 이유를 알려 준다
  • 인증된 사람은 이름 옆에 ✔ 가 붙는다. 일반 입장한 사람이 이름에 ✔ 나 그와 닮은 글자를 넣으면 지운다
  • 랭킹에서 인증 기록은 이름이 아니라 계정으로 묶는다. 같은 계정이 닉네임을 바꾸면 기록의 이름도 지금 닉네임으로 따라간다
  • 입장권이 담긴 주소 파라미터는 읽자마자 주소창에서 지우고, 다른 곳으로 Referer 가 실리지 않게 했다
  • 방이 가득 찬 경우에는 입장권을 확인하기 전에 거절해서, 들어가지도 못했는데 입장권만 소모되는 일이 없게 했다

서버를 다시 켜도 이어지는 세션

처음에는 게임 세션 표를 서버 메모리에 들고 있었는데, 배포로 서버를 다시 띄우면 다들 다시 일반 입장이 됐다. 그래서 세션 표를 서명이 붙은 토큰으로 바꿨다. 서버는 저장소 없이 서명만 확인하면 되니 재시작과 무관하게 인증이 이어진다.

마을 화면 안에 넣기

마을의 패널 안에서 게임을 띄우려면 iframe 을 허용해야 한다. 아무 사이트나 KARTORY 를 감싸지 못하게 모든 응답에 Content-Security-Policy: frame-ancestors 를 붙이고, 허용 목록에 마을만 넣었다. 목록이 비면 자기 자신만 허용한다.

숫자 API

마을 전광판에서 쓸 수 있게 지금 접속자 수·방 수·상위 기록을 돌려주는 읽기 전용 API 를 만들었다.

기술 메모

서명 비교는 글자 그대로, 일정 시간으로. 서명은 받은 base64url 문자열을 다시 디코딩하지 않고 글자 그대로 비교한다(디코딩 단계에서 서로 다른 문자열이 같은 바이트로 풀리는 여지를 없앤다). 비교는 길이를 먼저 보고 crypto.timingSafeEqual 로 한다.

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(token: string, secret: string): boolean {
  const dot = token.lastIndexOf('.');
  if (dot < 0) return false;
  const body = token.slice(0, dot);
  const sig = token.slice(dot + 1);
  const expected = createHmac('sha256', secret).update(body).digest('base64url');
  const a = Buffer.from(sig);
  const b = Buffer.from(expected);
  return a.length === b.length && timingSafeEqual(a, b);
}

옛 기록과의 호환. 랭킹 파일에 계정 칸을 더하면서 옛 파일은 그대로 읽히게 했다. 대신 옛 일반 기록 이름에 남아 있던 ✔ 는 읽을 때 지우고, 지운 뒤 이름이 비면 버린다.