You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
253 lines
7.8 KiB
Python
253 lines
7.8 KiB
Python
# app/auth/auth_bearer.py
|
|
|
|
import json
|
|
from typing import Annotated
|
|
|
|
import requests
|
|
from fastapi import Depends, HTTPException, Request
|
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
|
|
import src.config as config
|
|
|
|
from .model import UserBase
|
|
from .util import extract_template
|
|
|
|
class JWTBearer(HTTPBearer):
|
|
def __init__(self, auto_error: bool = True):
|
|
# Always pass auto_error=False so that HTTPBearer returns None (instead of
|
|
# raising HTTP 403) when the Authorization header is absent. We raise 401
|
|
# ourselves in the else-branch below, which is the correct status for a
|
|
# missing / unauthenticated credential.
|
|
super(JWTBearer, self).__init__(auto_error=False)
|
|
|
|
async def __call__(self, request: Request):
|
|
credentials: HTTPAuthorizationCredentials = await super(
|
|
JWTBearer, self
|
|
).__call__(request)
|
|
if credentials:
|
|
if not credentials.scheme == "Bearer":
|
|
raise HTTPException(
|
|
status_code=401, detail="Invalid authentication scheme."
|
|
)
|
|
method = request.method
|
|
|
|
if method == "OPTIONS":
|
|
return
|
|
|
|
path = extract_template(request.url.path, request.path_params)
|
|
|
|
endpoint = f"/optimumoh{path}"
|
|
|
|
user_info, message = self.verify_jwt(credentials.credentials, method, endpoint)
|
|
|
|
if not user_info:
|
|
if isinstance(message, dict):
|
|
message = message.get("message") or message.get("detail") or "Invalid token or expired token."
|
|
else:
|
|
message = str(message) if message else "Invalid token or expired token."
|
|
raise HTTPException(
|
|
status_code=401, detail=message
|
|
)
|
|
|
|
request.state.user = message
|
|
|
|
from src.context import set_user_id, set_username, set_role
|
|
if hasattr(message, "user_id"):
|
|
set_user_id(str(message.user_id))
|
|
username = getattr(message, "username", None) or getattr(message, "name", None)
|
|
if username:
|
|
set_username(username)
|
|
if hasattr(message, "role"):
|
|
set_role(message.role)
|
|
|
|
return message
|
|
else:
|
|
raise HTTPException(status_code=401, detail="Invalid authorization code.")
|
|
|
|
def verify_jwt(self, jwtoken: str, method: str, endpoint: str):
|
|
try:
|
|
url_to_verify = f"{config.AUTH_SERVICE_API}/verify-token"
|
|
response = requests.get(
|
|
url_to_verify,
|
|
headers={"Authorization": f"Bearer {jwtoken}"},
|
|
)
|
|
|
|
if not response.ok:
|
|
return False, response.json()
|
|
|
|
user_data = response.json()
|
|
return True, UserBase(**user_data["data"])
|
|
|
|
except Exception as e:
|
|
print(f"Token verification error: {str(e)}")
|
|
return False, str(e)
|
|
|
|
|
|
# Create dependency to get current user from request state
|
|
async def get_current_user(request: Request) -> UserBase:
|
|
return request.state.user
|
|
|
|
async def get_token(request: Request):
|
|
token = request.headers.get("Authorization")
|
|
if token:
|
|
return token.replace("Bearer ", "") # Menghapus prefix "Bearer "
|
|
else:
|
|
return request.cookies.get("access_token") # Fallback ke cookie
|
|
|
|
return "" # Mengembalikan token atau None jika tidak ada
|
|
|
|
|
|
async def internal_key(request: Request):
|
|
token = request.headers.get("Authorization")
|
|
|
|
if not token:
|
|
api_key = request.headers.get("X-Internal-Key")
|
|
|
|
if api_key != config.API_KEY:
|
|
raise HTTPException(
|
|
status_code=401, detail="Invalid Key."
|
|
)
|
|
|
|
try:
|
|
headers = {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
|
|
response = requests.post(
|
|
f"{config.AUTH_SERVICE_API}/sign-in",
|
|
headers=headers,
|
|
data=json.dumps({
|
|
"username": "ohuser",
|
|
"password": "123456789"
|
|
})
|
|
)
|
|
|
|
if not response.ok:
|
|
print(str(response.json()))
|
|
raise Exception("error auth")
|
|
|
|
user_data = response.json()
|
|
return user_data['data']['access_token']
|
|
|
|
except Exception as e:
|
|
raise Exception(str(e))
|
|
else:
|
|
try:
|
|
verify_url = f"{config.AUTH_SERVICE_API}/verify-token"
|
|
response = requests.get(
|
|
verify_url,
|
|
headers={"Authorization": f"{token}"},
|
|
)
|
|
|
|
if not response.ok:
|
|
raise HTTPException(
|
|
status_code=401, detail="Invalid token."
|
|
)
|
|
|
|
return token.split(" ")[1]
|
|
|
|
except Exception as e:
|
|
print(f"Token verification error: {str(e)}")
|
|
return False, str(e)
|
|
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Dict, Any
|
|
import src.config as config
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
AUTH_NOTIFY_ENDPOINT = f"{config.AUTH_SERVICE_API}/admin/notify-limit"
|
|
|
|
|
|
async def notify_admin_on_rate_limit(
|
|
endpoint_name: str,
|
|
ip_address: str,
|
|
request: Request,
|
|
method: str = "POST",
|
|
cooldown: int = 900,
|
|
timeout: int = 5
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Kirim notifikasi ke admin via be-auth service ketika rate limit terlampaui.
|
|
|
|
Async version - gunakan di async context.
|
|
"""
|
|
payload = {
|
|
"endpoint_name": f"oh/{endpoint_name}".replace("//", ""),
|
|
"ip_address": ip_address,
|
|
"method": method,
|
|
"cooldown": cooldown,
|
|
}
|
|
token = request.headers.get("Authorization")
|
|
headers = {"Authorization": token} if token else {}
|
|
|
|
try:
|
|
response = await asyncio.to_thread(
|
|
requests.post, AUTH_NOTIFY_ENDPOINT,
|
|
json=payload, headers=headers, timeout=timeout
|
|
)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
log.info(f"Notifikasi admin sent | Endpoint: {endpoint_name}")
|
|
return result
|
|
|
|
except Exception as e:
|
|
log.error(f"Error notifying admin: {str(e)}")
|
|
return {"status": False, "message": str(e), "data": payload}
|
|
|
|
|
|
|
|
|
|
def notify_admin_on_rate_limit_sync(
|
|
endpoint_name: str,
|
|
ip_address: str,
|
|
request: Request,
|
|
method: str = "POST",
|
|
cooldown: int = 900,
|
|
timeout: int = 5
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Kirim notifikasi ke admin via be-auth service.
|
|
|
|
Sync version - gunakan di exception handler atau sync context.
|
|
RECOMMENDED untuk use case ini.
|
|
"""
|
|
payload = {
|
|
"endpoint_name": f"oh/{endpoint_name}".replace("//", "/"),
|
|
"ip_address": ip_address,
|
|
"method": method,
|
|
"cooldown": cooldown,
|
|
}
|
|
token = request.headers.get("Authorization")
|
|
headers = {"Authorization": token} if token else {}
|
|
|
|
try:
|
|
response = requests.post(AUTH_NOTIFY_ENDPOINT, json=payload, headers=headers, timeout=timeout)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
log.info(f"Notifikasi admin sent | Endpoint: {endpoint_name}")
|
|
return result
|
|
|
|
except Exception as e:
|
|
log.error(f"Error notifying admin: {str(e)}")
|
|
return {"status": False, "message": str(e), "data": payload}
|
|
|
|
async def admin_required(request: Request):
|
|
user = await get_current_user(request)
|
|
if user.role != "Admin" or user.role != "Superadmin":
|
|
raise HTTPException(
|
|
status_code=403, detail="Invalid authorization code."
|
|
)
|
|
return user
|
|
|
|
Admin = Annotated[UserBase, Depends(admin_required)]
|
|
CurrentUser = Annotated[UserBase, Depends(get_current_user)]
|
|
Token = Annotated[str, Depends(get_token)]
|
|
InternalKey = Annotated[str, Depends(internal_key)] |