Skip to content

Commit 91364a8

Browse files
authored
Merge pull request #200 from Pseudo-Lab/refactor/auth
refactor(getcloser): authentication and authorization using JWT
2 parents 27a82c6 + 6211fa4 commit 91364a8

12 files changed

Lines changed: 134 additions & 55 deletions

File tree

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,20 @@
1+
from api.v1.auth import auth
12
from api.v1.users import users
2-
from api.v1.challenge import assign_challenge
3+
from api.v1.challenges import challenges
34
from api.v1.teams import teams
4-
from fastapi import APIRouter
5+
from core.dependencies import get_current_user
6+
from fastapi import APIRouter, Depends
7+
8+
private_router = APIRouter(
9+
dependencies=[Depends(get_current_user)]
10+
)
11+
private_router.include_router(users.router, prefix="/users", tags=["users"])
12+
private_router.include_router(challenges.router, prefix="/challenges", tags=["challenges"])
13+
private_router.include_router(teams.router, prefix="/teams", tags=["teams"])
14+
15+
public_router = APIRouter()
16+
public_router.include_router(auth.router, prefix="/auth", tags=["auth"])
517

618
api_router = APIRouter()
7-
api_router.include_router(users.router, prefix="/users", tags=["users"])
8-
api_router.include_router(assign_challenge.router, prefix="/challenge", tags=["challenge"])
9-
api_router.include_router(teams.router, prefix="/teams", tags=["teams"])
19+
api_router.include_router(public_router)
20+
api_router.include_router(private_router)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from core.database import get_db
2+
from fastapi import APIRouter, Depends
3+
from models.users import User
4+
from services.user_service import auth_user
5+
from sqlalchemy.orm import Session
6+
from schemas.user_schema import UserAuth, UserResponse
7+
8+
9+
router = APIRouter()
10+
11+
@router.post('/', response_model=UserResponse)
12+
def auth(user_auth: UserAuth, db: Session = Depends(get_db)):
13+
token = auth_user(db, user_auth.email)
14+
return token

getcloser/backend/app/api/v1/challenge/assign_challenge.py

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from fastapi import APIRouter, Depends
1+
from fastapi import APIRouter, Depends, HTTPException
22
from sqlalchemy.orm import Session
33
from core.database import get_db
4-
from schemas.challenge_schema import ChallengeRetryRequest, ChallengeRetryResponse, GoodsRedeemRequest, GoodsRedeemResponse
5-
from services.challenge_service import redeem_goods, retry_challenge
4+
from schemas.challenge_schema import ChallengeRequest, ChallengeResponse, ChallengeRetryRequest, ChallengeRetryResponse, GoodsRedeemRequest, GoodsRedeemResponse
5+
from services.challenge_service import assign_challenges_logic, redeem_goods, retry_challenge
66

77
router = APIRouter()
88

@@ -13,3 +13,24 @@ def redeem_goods_controller(request: GoodsRedeemRequest, db: Session = Depends(g
1313
@router.post("/retry", response_model=ChallengeRetryResponse)
1414
def challenge_retry_controller(request: ChallengeRetryRequest, db: Session = Depends(get_db)):
1515
return retry_challenge(db, request.user_id)
16+
17+
@router.post("/assign", response_model=ChallengeResponse)
18+
def assign_challenges(request: ChallengeRequest, db: Session = Depends(get_db)):
19+
try:
20+
# my_id = parse_user_id(request.my_id).get("id")
21+
# if not my_id:
22+
# raise HTTPException(status_code=400, detail="올바른 형식의 유저 태그가 아닙니다. 예: 김민준#0001")
23+
24+
# members_ids = [request.my_id] # 팀원에 자기 자신 포함
25+
# for tag in request.members_ids:
26+
# parsed = parse_user_id(tag)
27+
# if "error" in parsed:
28+
# raise HTTPException(status_code=400, detail=parsed["error"])
29+
# members_ids.append(parsed["id"])
30+
31+
members_ids = [request.my_id] + request.members_ids
32+
assigned = assign_challenges_logic(request.my_id, members_ids, db)
33+
return ChallengeResponse(team_id=request.team_id, my_assigned=assigned)
34+
35+
except ValueError as e:
36+
raise HTTPException(status_code=400, detail=str(e))
Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
from core.database import get_db
2+
from core.dependencies import get_current_user
23
from fastapi import APIRouter, Depends
3-
from services.user_service import auth_user, get_user, parse_user_id
4+
from models.users import User
5+
from services.user_service import get_user, parse_user_id
46
from sqlalchemy.orm import Session
5-
from schemas.user_schema import UserAuth, UserResponse
67

78
router = APIRouter()
89

9-
@router.post('/auth', response_model=UserResponse)
10-
def auth(user_auth: UserAuth, db: Session = Depends(get_db)):
11-
user = auth_user(db, user_auth.email)
12-
return user
13-
10+
@router.get("/me")
11+
def get_my_info(
12+
current_user: User = Depends(get_current_user),
13+
):
14+
return current_user
15+
1416

1517
@router.get("/{id}")
16-
def get_user_name(id: int, db: Session = Depends(get_db)):
18+
def get_user_name(
19+
id: int,
20+
db: Session = Depends(get_db)
21+
):
1722
user = get_user(db, id)
1823
return {"data": f"{user.name}#{str(user.id).zfill(4)}"}
1924

@@ -24,4 +29,4 @@ def get_user_id(tag: str):
2429
'이름#0001' 형식의 문자열을 받아서 ID(int)로 변환합니다.
2530
"""
2631
response = parse_user_id(tag)
27-
return response
32+
return response

getcloser/backend/app/core/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ class Settings(BaseSettings):
66
"""
77
JWT 안쓸 것 같아 일단 주석 처리하고 추후 확정 시 삭제
88
"""
9-
# SECRET_KEY: str = os.getenv("SECRET_KEY", "change-me-in-prod")
10-
# ALGORITHM: str = "HS256"
11-
# ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
9+
SECRET_KEY: str = os.getenv("SECRET_KEY", "change-me-in-prod")
10+
ALGORITHM: str = "HS256"
11+
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
1212

1313
settings = Settings()
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from core.database import get_db
2+
from fastapi import Depends, HTTPException, status
3+
from fastapi.security import OAuth2PasswordBearer
4+
from jose import jwt, JWTError
5+
from sqlalchemy.orm import Session
6+
7+
from core.config import settings
8+
from models.users import User
9+
10+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth")
11+
12+
def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(get_db)):
13+
credentials_exception = HTTPException(
14+
status_code=status.HTTP_401_UNAUTHORIZED,
15+
detail="Invalid authentication credentials",
16+
headers={"WWW-Authenticate": "Bearer"},
17+
)
18+
19+
try:
20+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
21+
user_id: str = payload.get("sub")
22+
if user_id is None:
23+
raise credentials_exception
24+
except JWTError:
25+
raise credentials_exception
26+
27+
# user = db.query(User).filter(User.id == user_id).first()
28+
# if user is None:
29+
# raise credentials_exception
30+
31+
return payload
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from datetime import datetime, timedelta
2+
from jose import JWTError, jwt
3+
from core.config import settings
4+
5+
6+
def create_access_token(data: dict, expires_delta: timedelta | None = None):
7+
to_encode = data.copy()
8+
expire = datetime.utcnow() + (
9+
expires_delta or timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
10+
)
11+
to_encode.update({"exp": expire})
12+
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
13+
return encoded_jwt
14+
15+
def verify_access_token(token: str):
16+
try:
17+
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
18+
return payload
19+
except JWTError:
20+
return None

getcloser/backend/app/schemas/user_schema.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@ class UserAuth(BaseModel):
44
email: EmailStr
55

66
class UserResponse(BaseModel):
7-
id: int
8-
email: str
9-
name: str
7+
accessToken: str
108

119
class Config:
1210
orm_mode = True

getcloser/backend/app/services/user_service.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from core.security import create_access_token
12
from exceptions.user_exceptions import UserNotFoundException
23
from models.users import User
34
from sqlalchemy.orm import Session
@@ -7,7 +8,13 @@ def auth_user(db: Session, email: str):
78
user = db.query(User).filter(User.email == email).first()
89
if not user:
910
raise UserNotFoundException(email)
10-
return user
11+
12+
token = create_access_token({
13+
"sub": str(user.id),
14+
"email": user.email,
15+
"name": user.name
16+
})
17+
return {"accessToken": token}
1118

1219

1320
def get_user(db: Session, id: int):

0 commit comments

Comments
 (0)