This commit is contained in:
1 parent
a603566235
commit
7280c5dd07
5 files changed
+66
-10
No files matched your search
@@ -0,0 +1,24 @@
|
|||||||
|
.git/
|
||||||
|
.gitea/
|
||||||
|
.gitignore
|
||||||
|
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
.ruff_cache/
|
||||||
|
.uv/
|
||||||
|
|
||||||
|
*.py[cod]
|
||||||
|
*.pyo
|
||||||
|
*.log
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
.secrets.toml
|
||||||
|
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
Dockerfile
|
||||||
|
docker-compose*.yml
|
||||||
|
README.md
|
||||||
@@ -34,9 +34,9 @@ class JWTAuthBackend(AuthenticationBackend):
|
|||||||
if scheme.lower() == "bearer" and credentials:
|
if scheme.lower() == "bearer" and credentials:
|
||||||
return credentials
|
return credentials
|
||||||
|
|
||||||
# Browser WebSocket APIs cannot set Authorization headers.
|
if conn.scope["type"] == "websocket":
|
||||||
token = conn.query_params.get("token")
|
return conn.query_params.get("token") or None
|
||||||
return token or None
|
return None
|
||||||
|
|
||||||
async def authenticate(
|
async def authenticate(
|
||||||
self, conn: HTTPConnection
|
self, conn: HTTPConnection
|
||||||
@@ -54,6 +54,13 @@ class JWTAuthBackend(AuthenticationBackend):
|
|||||||
if not isinstance(user_id, int):
|
if not isinstance(user_id, int):
|
||||||
raise AuthenticationError("Invalid or expired token")
|
raise AuthenticationError("Invalid or expired token")
|
||||||
|
|
||||||
|
typ = payload.get("typ")
|
||||||
|
if conn.scope["type"] == "websocket":
|
||||||
|
if typ != "ws":
|
||||||
|
raise AuthenticationError("Invalid or expired token")
|
||||||
|
elif typ != "web":
|
||||||
|
raise AuthenticationError("Invalid or expired token")
|
||||||
|
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
user = session.exec(select(User).where(User.id == user_id)).first()
|
user = session.exec(select(User).where(User.id == user_id)).first()
|
||||||
|
|
||||||
|
|||||||
+15
-3
@@ -2,10 +2,11 @@ from fastapi import APIRouter, HTTPException, Request, status
|
|||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from loon.web import limiter
|
from loon.web import limiter
|
||||||
|
from loon.web.auth.middleware import authenticated
|
||||||
from loon.web.auth.schema import JWTResponse, JWTRequest
|
from loon.web.auth.schema import JWTResponse, JWTRequest
|
||||||
from loon.web.db import engine
|
from loon.web.db import engine
|
||||||
from loon.web.users.models import User
|
from loon.web.users.models import User
|
||||||
from loon.web.utils.jwt import generate_token
|
from loon.web.utils.jwt import generate_token, generate_ws_token
|
||||||
from loon.web.utils.password import verify_password
|
from loon.web.utils.password import verify_password
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||||
@@ -18,11 +19,22 @@ async def get_jwt(request: Request, body: JWTRequest) -> JWTResponse:
|
|||||||
statement = select(User).where(User.internal_username == body.username)
|
statement = select(User).where(User.internal_username == body.username)
|
||||||
user = session.exec(statement).first()
|
user = session.exec(statement).first()
|
||||||
|
|
||||||
if user is None or not verify_password(body.password, user.password):
|
if user is None or user.id is None or not verify_password(body.password, user.password):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail="Invalid username or password",
|
detail="Invalid username or password",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert user.id is not None
|
|
||||||
return JWTResponse(access_token=generate_token(user.id))
|
return JWTResponse(access_token=generate_token(user.id))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/ws-token", response_model=JWTResponse)
|
||||||
|
@authenticated
|
||||||
|
async def get_ws_token(request: Request) -> JWTResponse:
|
||||||
|
user_id = request.user.user.id
|
||||||
|
if user_id is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Invalid or expired token",
|
||||||
|
)
|
||||||
|
return JWTResponse(access_token=generate_ws_token(user_id))
|
||||||
+14
-3
@@ -1,4 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import jwt
|
import jwt
|
||||||
|
|
||||||
@@ -8,13 +9,23 @@ JWT_SECRET = settings.jwt.secret
|
|||||||
JWT_ALGORITHM = "HS256"
|
JWT_ALGORITHM = "HS256"
|
||||||
|
|
||||||
|
|
||||||
def generate_token(user_id: int) -> str:
|
def _encode_jwt(user_id: int, ttl: int, typ: Literal["ws", "web"]) -> str:
|
||||||
payload = {
|
payload = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"exp": int(time.time()) + 60 * 60 * 24,
|
"exp": int(time.time()) + ttl,
|
||||||
|
"typ": typ,
|
||||||
}
|
}
|
||||||
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
|
return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_token(user_id: int) -> str:
|
||||||
|
return _encode_jwt(user_id, settings.jwt.access_ttl, "web")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_ws_token(user_id: int) -> str:
|
||||||
|
return _encode_jwt(user_id, settings.jwt.ws_ttl, "ws")
|
||||||
|
|
||||||
|
|
||||||
def decode_token(token: str) -> dict:
|
def decode_token(token: str) -> dict:
|
||||||
payload = jwt.decode(token, JWT_SECRET)
|
payload = jwt.decode(token, JWT_SECRET)
|
||||||
return payload
|
return payload
|
||||||
+3
-1
@@ -23,4 +23,6 @@ password = 'pwd'
|
|||||||
log_data = false
|
log_data = false
|
||||||
|
|
||||||
[jwt]
|
[jwt]
|
||||||
secret = 'secret'
|
secret = 'secret'
|
||||||
|
access_ttl = 86400 # 24h
|
||||||
|
ws_ttl = 45
|
||||||
Reference in new issue
Block a user