Files
joke-app/docs/05-accounts-auth.md
EJClaw 43156a9f63 방: 나가기 버튼을 막대로 빼고 ← 홈도 나가기로, 이름 눌러 기록 카드, 게스트 경고, 관리자 계정별 기록
- 방 막대에 [🚪 나가기]를 두고 메뉴에서는 뺀다. [← 홈]도 같은 동작. 한 번 누르면 바로 나가고,
  게임 중에 자리에 앉아 있을 때만 기권 확인을 묻는다. 휴대폰은 방 링크 복사를 메뉴로 옮겨 한 줄에 맞춘다.
- 자리·관전자·결과 화면의 이름을 누르면 기록 카드(이 게임 판·승·패·무·승률, 이 방 전적, 전체 합계).
  GET /api/users/:id/stats (로그인 필요).
- 게스트 경고: 이름 정하기, 로그인 페이지, /me, 게스트가 있는 대기실.
- 관리자 사용자 상세: 전체 기록, 게임별 기록 표, 최근 게임(GET /api/admin/users/:id/games). 목록에 전적 열(PC).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:34:08 +09:00

6.3 KiB

05. 계정과 로그인

1. 두 가지 방식

게스트 디스코드 로그인
시작 닉네임 한 칸 입력 → "시작하기" "디스코드로 로그인" → 디스코드 승인 → 돌아옴
닉네임 입력한 값 처음엔 디스코드 표시 이름(global_name, 없으면 username). 계정 페이지에서 언제든 변경
프로필 사진 닉네임 첫 글자 + 자동 색상 동그라미 디스코드 아바타(끌 수 있음)
유지 이 브라우저 쿠키(1년). 쿠키를 지우면 사라짐 어느 기기에서든 로그인하면 같은 계정
전적 이 브라우저에만 계정에 저장
전환 게스트로 쓰다가 "디스코드 연결"하면 전적·닉네임을 디스코드 계정으로 합침 —

어디서든 로그인이 필요해지면(예: 링크로 방에 들어옴) 같은 화면에서 두 방식을 나란히 보여 주고, 게스트 쪽을 기본으로 강조한다(가장 빠름).

2. 닉네임 규칙

  • 2~12자, 한글/영문/숫자/공백(앞뒤 공백 제거, 연속 공백 1개로), 이모지·특수문자 불가(_, -, .만 허용).
  • 중복 허용(사람 구분은 내부 ID로). 같은 방에 같은 닉네임이 있으면 화면에서 뒤에 (2)를 붙여 구분.
  • 욕설 필터 통과 필수. 실패 시 "다른 닉네임을 써 주세요".
  • 변경은 언제나 가능, 단 게임 진행 중에는 그 게임이 끝난 뒤 반영(진행 중 이름이 바뀌면 혼란). 하루 변경 횟수 제한 10회.
  • 처음 들어오면 닉네임 칸에 무작위 추천 이름(예: "용감한 호랑이")을 미리 채워 두어 그냥 눌러도 시작되게 한다.

3. 디스코드 OAuth2 흐름

  • 디스코드 개발자 포털에서 앱 생성 → client_id, client_secret, Redirect URI https://<도메인>/api/auth/discord/callback 등록(사용자 작업).
  • 범위(scope): identify만(이메일·서버 목록 안 받음).
  • 흐름(Authorization Code + state, 서버가 client secret을 보관하는 기밀 클라이언트):
    1. GET /api/auth/discord/start?next=/r/123456 → 서버가 state(32바이트 무작위)를 만들어 next와 함께 짧은 수명(10분) 서명 쿠키에 넣고 디스코드 승인 페이지로 리다이렉트.
    2. 디스코드가 /api/auth/discord/callback?code&state로 돌려보냄 → state 비교 → 토큰 교환 → GET /users/@me.
    3. oauth_accounts(provider='discord', provider_user_id)로 사용자 찾기/만들기. 현재 게스트 세션이 있으면 그 게스트를 이 계정에 합침(아래 4절).
    4. 새 세션 발급 → next로 리다이렉트(next는 우리 사이트 내부 경로만 허용 — 오픈 리다이렉트 방지).
  • 디스코드 액세스 토큰은 사용자 정보를 받은 뒤 저장하지 않는다(필요 없음).
  • 아바타 URL: https://cdn.discordapp.com/avatars/<id>/<avatar>.png?size=128 (로그인할 때마다 갱신).

4. 게스트 → 디스코드 합치기

  • 게스트 사용자 G로 로그인된 상태에서 디스코드 계정 D로 로그인하면:
    • D가 처음이면: G에 디스코드 연결을 추가하고 G를 일반 계정으로 바꾼다(닉네임 유지).
    • D가 이미 있으면: G의 전적을 D로 옮기고 G를 삭제. 닉네임은 D 것을 유지. 진행 중인 방의 자리도 D로 바꾼다.
  • 합치기 결과를 토스트로 알림: "디스코드 계정과 연결했어요. 기록이 합쳐졌어요."

5. 세션

  • 로그인/게스트 생성 시 32바이트 무작위 토큰을 만들어 쿠키 sid로 주고, DB에는 SHA-256 해시를 저장.
  • 수명: 게스트 365일, 디스코드 90일(사용할 때마다 연장, 하루 1번만 DB 갱신).
  • 로그아웃: 세션 삭제 + 쿠키 삭제. 게스트가 로그아웃하면 게스트 계정을 지운다(방 자리, 전적, 세션 포함, 지난 대국 기록에는 '알 수 없음'으로 남음). 확인 창에서 지워진다고 알린다.
  • 게스트가 사이트를 닫고 하루(24시간) 동안 오지 않으면 같은 방식으로 지운다. 서버가 1분마다 users.last_seen_at(요청·소켓 종료 때 갱신)을 보고 정리하며, 소켓이 열려 있거나 방 자리를 갖고 있으면 건너뛴다.
  • 계정 페이지의 "모든 기기에서 로그아웃"은 그 사용자의 모든 세션 삭제.
  • 게스트로 시작하는 곳(로그인 페이지, 이름 정하기 칸), 게스트의 /me, 게스트가 있는 대기실에 "⚠️ 게스트는 기록이 오래 저장되지 않아요. 사이트에 들어오지 않은 채 하루가 지나면 계정과 기록이 모두 지워져요." 경고를 보인다.

6. HTTP API

메서드 경로 설명
POST /api/auth/guest {nickname} → 게스트 생성 + 세션
GET /api/auth/discord/start 디스코드 로그인 시작
GET /api/auth/discord/callback 디스코드 콜백
POST /api/auth/logout 로그아웃
POST /api/auth/logout-all 모든 기기 로그아웃
GET /api/me 내 정보(id, 닉네임, 종류, 아바타, 설정)
PATCH /api/me {nickname?, useDiscordAvatar?, settings?}
GET /api/me/stats 게임별 전적
GET /api/users/:id/stats 다른 사람의 공개 정보 + 게임별 전적(방의 기록 카드, 로그인 필요)
DELETE /api/me 계정 삭제(확인 문구 입력 필요). 전적 익명화

7. 계정 페이지(/me)

  • 큰 프로필 사진 + 닉네임 + "닉네임 바꾸기" 버튼(누르면 그 자리에서 편집, 저장/취소).
  • 로그인 방식 표시: "게스트" 또는 "디스코드(이름)". 게스트면 "디스코드 연결하기(기록을 안전하게 보관)" 버튼.
  • 화면 설정: 글자 크기(보통/크게/아주 크게), 다크 모드(자동/밝게/어둡게), 소리 켜기/끄기, 진동 켜기/끄기, 색약 모드.
  • 내 전적: 게임별 판 수/승 수.
  • 로그아웃, 모든 기기 로그아웃, 계정 삭제(맨 아래, 작게).

8. 권한(역할)

  • 일반 / 어드민 / 슈퍼어드민. 자세한 내용은 14-admin.md.
  • /api/me는 role(user·admin·superadmin)을 함께 돌려주고, 웹은 어드민 이상에게만 계정 페이지에 [관리자 페이지] 버튼을 보여 준다.
  • 이용 제한된 계정은 세션이 모두 지워지고 디스코드로 다시 로그인할 수 없다(/?login=banned).