Skip to content

Latest commit

 

History

History
184 lines (132 loc) · 6.12 KB

File metadata and controls

184 lines (132 loc) · 6.12 KB

인증 구현 계약

상태: 구현 완료

이 문서는 인증 기능의 고정 계약이다. 범위는 가입, 로그인, Refresh Token Rotation, 현재 사용자 조회이며 이메일 인증과 비밀번호 재설정은 포함하지 않는다.

모든 JSON 응답은 api-response.md의 공통 envelope를 사용한다. 아래 성공 예시는 전체 응답의 data에 들어가는 payload만 표시한다.

사용자 모델

users 테이블은 다음 column만 가진다.

Column Type 제약
id UUID Primary key, 애플리케이션에서 UUID4 생성
name VARCHAR(50) 필수, 앞뒤 공백 제거 후 1~50자
email VARCHAR(254) 필수, 소문자 정규화, unique
student_number CHAR(5) 필수, 숫자 5자리, 중복 허용
password_hash TEXT 필수, Argon2 hash만 저장
created_at TIMESTAMPTZ 필수, DB의 현재 UTC 시각

학번은 프로필 정보일 뿐 로그인, 권한, 사용자 유일성 판단에 사용하지 않는다.

API

POST /auth/signup

요청:

{
  "name": "테스트학생",
  "email": "student@example.com",
  "studentNumber": "10311",
  "password": "example-password"
}

검증:

  • name: trim 후 1~50자
  • email: 유효한 이메일, trim·소문자 변환 후 최대 254자
  • studentNumber: ^[0-9]{5}$
  • password: 10~128자

성공: 201 Created

{
  "id": "5fbbb3f2-f55d-4652-b138-3595e527ba37",
  "name": "테스트학생",
  "email": "student@example.com",
  "studentNumber": "10311"
}

오류:

  • 409 EMAIL_ALREADY_EXISTS: 대소문자가 다른 동일 이메일 포함
  • 422 VALIDATION_ERROR: 필드 형식 또는 길이 오류

응답과 로그에 passwordpassword_hash를 포함하지 않는다.

POST /auth/login

요청:

{
  "email": "student@example.com",
  "password": "example-password"
}

성공: 200 OK

{
  "accessToken": "<signed-jwt>",
  "refreshToken": "<signed-jwt>",
  "tokenType": "bearer",
  "expiresIn": 1800,
  "refreshExpiresIn": 1209600
}

존재하지 않는 이메일과 잘못된 비밀번호는 모두 같은 401 INVALID_CREDENTIALS를 반환한다. 계정 존재 여부와 어느 필드가 틀렸는지 노출하지 않는다.

POST /auth/refresh

요청:

{ "refreshToken": "<refresh-jwt>" }

성공 시 새 access token과 새 refresh token을 반환한다. 사용한 refresh token은 즉시 폐기되며 같은 토큰을 다시 사용하면 401 INVALID_TOKEN을 반환한다.

GET /users/me

요청 header:

Authorization: Bearer <access-token>

성공 응답은 가입 성공 응답과 같다.

오류:

  • token 누락: 401 INVALID_TOKEN
  • token 위조·형식 오류·만료: 401 INVALID_TOKEN
  • token의 사용자가 DB에 없음: 401 INVALID_TOKEN

모든 401 응답은 WWW-Authenticate: Bearer header를 포함한다.

오류 형식

인증 오류는 api-response.md의 공통 오류 envelope를 사용한다. INVALID_CREDENTIALS, INVALID_TOKEN, EMAIL_ALREADY_EXISTS code와 기존 HTTP 상태는 그대로 유지하며 입력 검증 오류는 422 VALIDATION_ERROR로 변환한다.

비밀번호와 JWT

  • FastAPI 공식 권장 조합인 pwdlib[argon2]PyJWT를 사용한다.
  • 비밀번호 원문은 저장하거나 로그에 남기지 않는다.
  • JWT algorithm은 HS256이다.
  • JWT_SECRET_KEY는 최소 32 random bytes이며 .env와 운영 secret으로만 제공한다.
  • Access Token 만료는 30분이다.
  • payload에는 sub, jti, iat, exp, tokenType을 넣는다.
  • sub는 사용자 UUID 문자열이다. 이름, 이메일, 학번은 넣지 않는다.
  • refresh token의 jti는 DB에 저장하고 rotation 시 일회성으로 소비한다.

최소 파일 변경

인증 구현은 다음 파일만 추가·수정하는 것을 기본으로 한다.

  • app/models.py: User SQLAlchemy model
  • app/auth.py: schema, password·JWT 함수, 인증 dependency, router
  • app/config.py: JWT 설정
  • app/main.py: 인증 router 등록
  • alembic/versions/*_create_users.py: schema migration
  • tests/conftest.py: endpoint용 DB Session override
  • tests/test_auth.py: 인증 동작 테스트
  • dependency와 환경설정 파일

service·repository·JWT provider interface는 만들지 않는다.

테스트 시나리오

시나리오 기대 결과
정상 가입 201, 사용자 응답, DB에는 Argon2 hash 저장
같은 이메일 재가입 409 EMAIL_ALREADY_EXISTS
이메일 대소문자만 변경 동일한 중복으로 처리
학번 중복 가입 허용
잘못된 학번·짧은 비밀번호 422
정상 로그인 30분 Access Token 발급
refresh token rotation 새 토큰 발급, 기존 refresh token 재사용은 401
없는 이메일·틀린 비밀번호 동일한 401 응답
정상 token으로 /users/me 자신의 정보 반환
token 없음·위조·만료 401 INVALID_TOKEN
password 또는 hash 응답 노출 테스트 실패

구현·커밋 순서

각 단계에서 테스트를 먼저 실패시킨 뒤 최소 구현으로 통과시킨다. 커밋은 make check가 성공한 상태에서만 만든다.

  1. feat: add user persistence
    • model, migration, model 검증 테스트
  2. feat: implement user signup
    • schema, Argon2, 이메일 중복, 가입 endpoint
  3. feat: implement login and current user
    • JWT, 로그인, 인증 dependency, /users/me

완료 조건

  • 위 API와 오류 계약이 OpenAPI와 테스트에 반영된다.
  • 사용자 비밀번호 원문이 DB·응답·로그 어디에도 없다.
  • 중복 이메일이 DB unique constraint와 API 양쪽에서 차단된다.
  • 위조·만료 JWT가 사용자 조회에 사용되지 않는다.
  • make checkgit diff --check가 성공한다.

후속 작업

인증 완료 후 implementation-roadmap.md의 순서로 사용자별 과제 CRUD를 구현한다. 상세 계약은 assignments.md를 따르며 조회·수정·삭제는 반드시 과제 ID와 인증 사용자의 user_id를 같은 DB 조건에 사용한다.