20206205tech-python-auth 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 20206205tech_python_auth-0.0.0.dist-info/METADATA +9 -0
- 20206205tech_python_auth-0.0.0.dist-info/RECORD +8 -0
- 20206205tech_python_auth-0.0.0.dist-info/WHEEL +4 -0
- tech_auth/__init__.py +13 -0
- tech_auth/dependencies.py +35 -0
- tech_auth/enums.py +6 -0
- tech_auth/schemas.py +9 -0
- tech_auth/security.py +50 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: 20206205tech-python-auth
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Requires-Dist: fastapi>=0.115.8
|
|
7
|
+
Requires-Dist: loguru>=0.7.3
|
|
8
|
+
Requires-Dist: pydantic>=2.10.6
|
|
9
|
+
Requires-Dist: pyjwt[crypto]>=2.10.1
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
tech_auth/__init__.py,sha256=WyaazASkfcfR-LToY17PEZC9R9W_xlAhiIQ_1MUXrRY,317
|
|
2
|
+
tech_auth/dependencies.py,sha256=KTrnvnmSyExrya6d0bi46lvmLgGj7zdQYX94IXPvqe0,1022
|
|
3
|
+
tech_auth/enums.py,sha256=8LtdrkdtGS5964uhkUfjvNHf5dD6t2Fp7qKOvuWS5JE,98
|
|
4
|
+
tech_auth/schemas.py,sha256=LXVcwIN3osOG8FszyIGuqVyyAjryiMs4LUL2HBQN2f8,155
|
|
5
|
+
tech_auth/security.py,sha256=Vda7pD38yRPetCC649Q5BGI5rltMQ_DzI-FOKJESEYk,1575
|
|
6
|
+
20206205tech_python_auth-0.0.0.dist-info/METADATA,sha256=-NjHyQYc3wTo1woOHnxufOAbk-ZdNGRKGbX8NesW6P4,257
|
|
7
|
+
20206205tech_python_auth-0.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
20206205tech_python_auth-0.0.0.dist-info/RECORD,,
|
tech_auth/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .dependencies import get_current_user, get_current_user_id, require_admin
|
|
2
|
+
from .enums import UserRole
|
|
3
|
+
from .schemas import CurrentUser
|
|
4
|
+
from .security import verify_token
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"get_current_user",
|
|
8
|
+
"get_current_user_id",
|
|
9
|
+
"require_admin",
|
|
10
|
+
"UserRole",
|
|
11
|
+
"CurrentUser",
|
|
12
|
+
"verify_token",
|
|
13
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from fastapi import Depends, HTTPException, status
|
|
2
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
3
|
+
|
|
4
|
+
from .enums import UserRole
|
|
5
|
+
from .schemas import CurrentUser
|
|
6
|
+
from .security import verify_token
|
|
7
|
+
|
|
8
|
+
security = HTTPBearer()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_current_user(
|
|
12
|
+
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
13
|
+
) -> CurrentUser:
|
|
14
|
+
token = credentials.credentials
|
|
15
|
+
payload = verify_token(token)
|
|
16
|
+
|
|
17
|
+
app_metadata = payload.get("app_metadata", {})
|
|
18
|
+
role = app_metadata.get("role") or payload.get("role")
|
|
19
|
+
|
|
20
|
+
return CurrentUser(
|
|
21
|
+
user_id=payload.get("sub"), email=payload.get("email"), role=role
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_current_user_id(user: CurrentUser = Depends(get_current_user)) -> str:
|
|
26
|
+
return user.user_id
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def require_admin(user: CurrentUser = Depends(get_current_user)) -> CurrentUser:
|
|
30
|
+
if user.role != UserRole.ADMIN:
|
|
31
|
+
raise HTTPException(
|
|
32
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
33
|
+
detail="Forbidden - Requires Admin role",
|
|
34
|
+
)
|
|
35
|
+
return user
|
tech_auth/enums.py
ADDED
tech_auth/schemas.py
ADDED
tech_auth/security.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import jwt
|
|
4
|
+
from fastapi import HTTPException, status
|
|
5
|
+
from jwt import PyJWKClient
|
|
6
|
+
|
|
7
|
+
# Configuration from environment variables
|
|
8
|
+
JWKS_URL = os.getenv("SUPABASE_JWKS_URL")
|
|
9
|
+
AUDIENCE = os.getenv("SUPABASE_AUDIENCE", "authenticated")
|
|
10
|
+
ISSUER = os.getenv("SUPABASE_ISSUER")
|
|
11
|
+
|
|
12
|
+
# Fallback for Supabase projects
|
|
13
|
+
if not JWKS_URL or not ISSUER:
|
|
14
|
+
PROJECT_ID = os.getenv("SUPABASE_PROJECT_ID")
|
|
15
|
+
if PROJECT_ID:
|
|
16
|
+
if not JWKS_URL:
|
|
17
|
+
JWKS_URL = f"https://{PROJECT_ID}.supabase.co/auth/v1/.well-known/jwks.json"
|
|
18
|
+
if not ISSUER:
|
|
19
|
+
ISSUER = f"https://{PROJECT_ID}.supabase.co/auth/v1"
|
|
20
|
+
|
|
21
|
+
if not JWKS_URL:
|
|
22
|
+
raise RuntimeError("SUPABASE_JWKS_URL or SUPABASE_PROJECT_ID must be set")
|
|
23
|
+
|
|
24
|
+
jwks_client = PyJWKClient(JWKS_URL, cache_keys=True)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def verify_token(token: str) -> dict:
|
|
28
|
+
try:
|
|
29
|
+
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
30
|
+
payload = jwt.decode(
|
|
31
|
+
token,
|
|
32
|
+
signing_key.key,
|
|
33
|
+
algorithms=["RS256", "ES256"],
|
|
34
|
+
audience=AUDIENCE,
|
|
35
|
+
issuer=ISSUER,
|
|
36
|
+
)
|
|
37
|
+
return payload
|
|
38
|
+
except jwt.PyJWKClientError:
|
|
39
|
+
raise HTTPException(
|
|
40
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
41
|
+
detail="Unable to fetch JWKS",
|
|
42
|
+
)
|
|
43
|
+
except jwt.ExpiredSignatureError:
|
|
44
|
+
raise HTTPException(
|
|
45
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired"
|
|
46
|
+
)
|
|
47
|
+
except jwt.InvalidTokenError:
|
|
48
|
+
raise HTTPException(
|
|
49
|
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token"
|
|
50
|
+
)
|