Skip to content

Commit 978e927

Browse files
committed
first commit
0 parents  commit 978e927

14 files changed

+539
-0
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
*.png
2+
*.jpg
3+
*.jpeg
4+
*.gif
5+
*.sqlite
6+
*.json
7+
.DS_Store
8+
__pycache__
9+
sqlite3_db_for_window

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2024 Pupbani
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# 🔒 Auth Center
2+
3+
## Skills
4+
<img src="https://img.shields.io/badge/Python-3.11.0-FFD43B?style=for-the-badge&logo=python&logoColor=blue" />
5+
<img src="https://img.shields.io/badge/fastapi-109989?style=for-the-badge&logo=FASTAPI&logoColor=white" />
6+
<img src="https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=JSON%20web%20tokens&logoColor=white" />
7+
<img src=" https://img.shields.io/badge/Sqlite-003B57?style=for-the-badge&logo=sqlite&logoColor=white" />
8+
9+
## 기능
10+
1. 사용자 인증
11+
- 사용자의 계정 인증을 위한 DB
12+
13+
2. API Key 발급 및 인증
14+
- 사용자 계정별 API Key를 저장
15+
16+
## 실행 방법
17+
1. 의존성 설치
18+
```bash
19+
conda create --yes -n <your-env-name> python=3.11.0
20+
conda activate <your-env-name>
21+
pip install -r requirements.txt
22+
```
23+
2. 실행
24+
``
25+
python -m auth_server
26+
python3 -m auth_server
27+
``
28+
3. 테스트
29+
``
30+
python -m test
31+
python3 -m test
32+
``
33+
34+
# Endpoint
35+
36+
## /login
37+
- **URL**: `/login`
38+
- **Method**: `POST`
39+
- **요청 본문**:
40+
- `application/x-www-form-urlencoded`
41+
- `username`: 사용자 이름 (필수)
42+
- `password`: 비밀번호 (필수)
43+
- `grant_type`: (선택) "password" 또는 null
44+
- `scope`: (선택) 기본값은 빈 문자열
45+
- `client_id`: (선택) 문자열 또는 null
46+
- `client_secret`: (선택) 문자열 또는 null
47+
48+
- **응답**:
49+
- **200 OK**: 로그인 성공
50+
- 응답 본문:
51+
```json
52+
{
53+
"username": "사용자 이름",
54+
"token": "토큰" // null일 수 있음
55+
}
56+
```
57+
- **422 Unprocessable Entity**: 유효성 검사 오류
58+
- 응답 본문:
59+
```json
60+
{
61+
"detail": [
62+
{
63+
"loc": ["위치"],
64+
"msg": "메시지",
65+
"type": "오류 유형"
66+
}
67+
]
68+
}
69+
```
70+
71+
## /get-api-key
72+
- **URL**: `/get-api-key`
73+
- **Method**: `GET`
74+
- **보안**: OAuth2 인증 필요
75+
76+
- **응답**:
77+
- **200 OK**: API 키 가져오기 성공
78+
- 응답 본문:
79+
```json
80+
{
81+
"username": "사용자 이름",
82+
"apikey": "API 키" // null일 수 있음
83+
}
84+
```
85+
## /auth-api-key
86+
- **URL**: `/auth-api-key`
87+
- **Method**: `POST`
88+
- **요청 본문**:
89+
- `application/x-www-form-urlencoded`
90+
- `username`: 사용자 이름 (필수)
91+
- `api_key`: API 키 (필수)
92+
93+
- **응답**:
94+
- **200 OK**: API 키 인증 성공
95+
- 응답 본문:
96+
```json
97+
{
98+
"username": "사용자 이름",
99+
"apikey": "API 키",
100+
"success": true
101+
}
102+
```
103+
- **422 Unprocessable Entity**: 유효성 검사 오류
104+
- 응답 본문:
105+
```json
106+
{
107+
"detail": [
108+
{
109+
"loc": ["위치"],
110+
"msg": "메시지",
111+
"type": "오류 유형"
112+
}
113+
]
114+
}
115+
```
116+

auth_server.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# JWT 인증을 위한 서버
2+
from fastapi import FastAPI
3+
from modules.routers import auth,apikey
4+
from fastapi.middleware.cors import CORSMiddleware
5+
import uvicorn
6+
7+
app = FastAPI(title="🔒 Auth Center Server")
8+
# CORS 설정
9+
app.add_middleware(
10+
CORSMiddleware,
11+
allow_origins=["*"], # 모든 도메인 허용 (필요에 따라 수정)
12+
allow_credentials=True,
13+
allow_methods=["*"],
14+
allow_headers=["*"],
15+
)
16+
17+
app.include_router(auth.router,tags=['Auth'])
18+
app.include_router(apikey.router,tags=['APIKey'])
19+
20+
21+
if __name__ == "__main__":
22+
uvicorn.run(app=app,host="0.0.0.0",port=7777)

db/console.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from sqlalchemy import create_engine
2+
from db.models import User, APIKey
3+
from sqlalchemy.orm import sessionmaker
4+
import os
5+
6+
def getSession(URL:str="sqlite:///"):
7+
if "sqlite" in URL:
8+
temp = os.path.abspath("sqlite3_db_for_window/auth_center.sqlite")
9+
URL += temp
10+
engine = create_engine(URL)
11+
# Table Set
12+
User.metadata.create_all(engine)
13+
APIKey.metadata.create_all(engine)
14+
# # seesion
15+
Session = sessionmaker(bind=engine)
16+
session = Session()
17+
return session
18+
19+
if __name__ =="__main__":
20+
cur = getSession()

db/crud.py

+117
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from db.models import User,APIKey
2+
from sqlalchemy.orm import session
3+
import hashlib
4+
from typing import Callable
5+
from uuid import uuid4
6+
7+
def encryption(text:str,method:Callable=hashlib.sha256)->str:
8+
hashed_data = method(text.encode()).hexdigest()
9+
return str(hashed_data)[:16]
10+
11+
# Create User
12+
def add_user(session:session.Session,user:User)->bool:
13+
try:
14+
session.add(user)
15+
# Create API Key
16+
session.add(APIKey(
17+
username=user.username,
18+
))
19+
session.commit()
20+
return True
21+
except Exception as e:
22+
session.rollback()
23+
return False
24+
# User Read
25+
def auth_user(session:session.Session,user_id:str,password:str)->bool | str:
26+
try:
27+
# user_id 검사
28+
user_id_hash = encryption(user_id)
29+
q = session.query(User).filter_by(userid=user_id_hash).with_entities(User.password,User.username).one_or_none()
30+
if q is None:
31+
raise Exception("사용자 없음")
32+
# password 검사
33+
password_hash = encryption(password)
34+
if password_hash != q[0]:
35+
raise Exception("비밀번호 틀림")
36+
return q[1]
37+
38+
except Exception as e:
39+
session.rollback()
40+
return False
41+
42+
# API Key get
43+
def get_APIKey(session:session.Session,username:str)->str | bool:
44+
try:
45+
q = session.query(APIKey).filter_by(username=username).with_entities(APIKey.api_key).one_or_none()
46+
if q is None:
47+
raise Exception("APIKey 없음")
48+
return q[0]
49+
except Exception as e:
50+
session.rollback()
51+
return False
52+
53+
# API Key auth
54+
def auth_APIKey(session:session.Session,api_key:str,username:str)->bool:
55+
try:
56+
q = session.query(APIKey).filter_by(api_key=api_key).with_entities(APIKey.username).one_or_none()
57+
if q is None:
58+
raise Exception("올바르지 않은 APIKey")
59+
if q[0] != username:
60+
raise Exception("허락되지 않은 사용자")
61+
return True
62+
except Exception as e:
63+
session.rollback()
64+
return False
65+
66+
# Delete User and Key
67+
def delete_user(session:session.Session,username:str)->bool:
68+
try:
69+
q = session.query(User).filter_by(username=username).one_or_none()
70+
if q is None:
71+
raise Exception("사용자 없음")
72+
73+
api_key = session.query(APIKey).filter_by(username=q.username).one_or_none()
74+
75+
if api_key is None:
76+
raise Exception("APIKey 없음")
77+
78+
session.delete(q)
79+
session.delete(api_key)
80+
session.commit()
81+
return True
82+
83+
84+
except Exception as e:
85+
session.rollback()
86+
return False
87+
88+
def apiKey_update_all(session:session.Session)->bool:
89+
try:
90+
# 모든 APIKey를 업데이트
91+
q = session.query(APIKey).all()
92+
if q == []:
93+
raise Exception("APIKey 없음")
94+
for api_key in q:
95+
api_key.api_key = str(uuid4())
96+
97+
session.commit()
98+
return True
99+
100+
except Exception as e:
101+
session.rollback()
102+
return False
103+
104+
if __name__ == "__main__":
105+
pass
106+
from db.console import getSession
107+
108+
cur = getSession()
109+
# add_user(cur,User(
110+
# username="admin1",
111+
# userid=encryption("admin1",hashlib.sha256),
112+
# password=encryption("qwer1234!@",hashlib.sha256)
113+
# ))
114+
print(auth_user(cur,"admin1","qwer1234!@"))
115+
# auth_APIKey(cur,"34e53889-f1f7-4415-ab87-6fac408a00ae","admin1")
116+
# delete_user(cur,"admin1")
117+
# apiKey_update_all(cur)

db/models.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from sqlalchemy import Column,Integer, String, DateTime, ForeignKey
2+
from sqlalchemy.ext.declarative import declarative_base
3+
from sqlalchemy.orm import relationship
4+
from datetime import datetime
5+
from uuid import uuid4
6+
7+
Base = declarative_base()
8+
9+
class User(Base):
10+
__tablename__ = 'users'
11+
12+
username = Column(String, primary_key=True)
13+
userid = Column(String, unique=True, nullable=False)
14+
password = Column(String, nullable=False)
15+
created_at = Column(DateTime, default=datetime.now) # default를 datetime.utcnow로 수정
16+
17+
# User와 APIKey 간의 관계 설정
18+
api_keys = relationship("APIKey", back_populates="user")
19+
20+
class APIKey(Base):
21+
__tablename__ = "api_key"
22+
23+
id = Column(Integer, primary_key=True, autoincrement=True)
24+
username = Column(String, ForeignKey('users.username'), nullable=False)
25+
api_key = Column(String, unique=True, default=lambda: str(uuid4())) # default를 lambda로 수정
26+
created_at = Column(DateTime, default=datetime.now) # default를 datetime.utcnow로 수정
27+
28+
user = relationship("User", back_populates="api_keys")
29+

modules/exception_handler.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from fastapi import HTTPException,status
2+
3+
class AuthenticationFailed(HTTPException):
4+
def __init__(self, detail: str = "Authentication failed"):
5+
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)
6+
7+
class TokenCreationFailed(HTTPException):
8+
def __init__(self, detail: str = "Token creation failed"):
9+
super().__init__(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=detail)
10+
11+
class APIKeyNotFound(HTTPException):
12+
def __init__(self, detail: str = "API Key not found"):
13+
super().__init__(status_code=status.HTTP_404_NOT_FOUND, detail=detail)
14+
15+
class APIKeyAuthenticationFailed(HTTPException):
16+
def __init__(self, detail: str = "API Key Authentication Failed"):
17+
super().__init__(status_code=status.HTTP_401_UNAUTHORIZED, detail=detail)

0 commit comments

Comments
 (0)