Merge branch 'main' of https://git.kellsupport.com/KellEngineering/complycore
This commit is contained in:
commit
49cd1ba5b8
|
|
@ -0,0 +1,40 @@
|
|||
# backend/auth/jwt.py
|
||||
|
||||
import requests
|
||||
from jose import jwt
|
||||
from jose.exceptions import JWTError
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from typing import Dict
|
||||
|
||||
SUPABASE_PROJECT_ID = "lcoretjgpauozmuoedus" # <-- Replace with your project ref
|
||||
SUPABASE_JWKS_URL = f"https://{SUPABASE_PROJECT_ID}.supabase.co/auth/v1/keys"
|
||||
|
||||
# Fetch JWKs from Supabase
|
||||
jwks = requests.get(SUPABASE_JWKS_URL).json()
|
||||
|
||||
# Define FastAPI's bearer auth scheme
|
||||
auth_scheme = HTTPBearer()
|
||||
|
||||
# Decode + verify JWT token
|
||||
def verify_jwt_token(token: str) -> Dict:
|
||||
try:
|
||||
header = jwt.get_unverified_header(token)
|
||||
kid = header["kid"]
|
||||
|
||||
key = next((k for k in jwks["keys"] if k["kid"] == kid), None)
|
||||
if key is None:
|
||||
raise HTTPException(status_code=403, detail="Invalid Supabase JWT: No matching key")
|
||||
|
||||
payload = jwt.decode(token, key, algorithms=["RS256"], options={"verify_aud": False})
|
||||
return payload
|
||||
|
||||
except JWTError as e:
|
||||
raise HTTPException(status_code=403, detail=f"Invalid Supabase JWT: {str(e)}")
|
||||
|
||||
# Dependency for protected endpoints
|
||||
def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials = Depends(auth_scheme)
|
||||
) -> Dict:
|
||||
token = credentials.credentials
|
||||
return verify_jwt_token(token)
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
# backend/db/session.py
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import NullPool
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Read .env settings
|
||||
USER = os.getenv("user")
|
||||
PASSWORD = os.getenv("password")
|
||||
HOST = os.getenv("host")
|
||||
PORT = os.getenv("port")
|
||||
DBNAME = os.getenv("dbname")
|
||||
|
||||
# Supabase Transaction Pooler (IPv4-safe) URI
|
||||
DATABASE_URL = f"postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?sslmode=require"
|
||||
|
||||
# SQLAlchemy engine with NullPool for Supabase
|
||||
engine = create_engine(DATABASE_URL, poolclass=NullPool)
|
||||
|
||||
# Session factory (used for queries)
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
# Base class for ORM models
|
||||
Base = declarative_base()
|
||||
|
|
@ -1,28 +1,33 @@
|
|||
from sqlalchemy import create_engine
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
# backend/main.py
|
||||
|
||||
# Load .env vars
|
||||
load_dotenv()
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
from sqlalchemy import text
|
||||
from db.session import engine
|
||||
from fastapi import Depends
|
||||
from auth.jwt import get_current_user
|
||||
|
||||
# Get env vars
|
||||
USER = os.getenv("user")
|
||||
PASSWORD = os.getenv("password")
|
||||
HOST = os.getenv("host")
|
||||
PORT = os.getenv("port")
|
||||
DBNAME = os.getenv("dbname")
|
||||
app = FastAPI()
|
||||
|
||||
# Full SQLAlchemy URI for Session Pooler
|
||||
DATABASE_URL = (
|
||||
f"postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?sslmode=require"
|
||||
)
|
||||
@app.get("/healthz")
|
||||
def health_check():
|
||||
return {"status": "ok"}
|
||||
|
||||
# Use NullPool to defer to Supabase's pooler
|
||||
from sqlalchemy.pool import NullPool
|
||||
engine = create_engine(DATABASE_URL, poolclass=NullPool)
|
||||
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
print("✅ Supabase Session Pooler connection successful.")
|
||||
except Exception as e:
|
||||
print(f"❌ Connection failed: {e}")
|
||||
@app.get("/supabase-check")
|
||||
def supabase_check():
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
result = conn.execute(text("SELECT current_database(), current_user;"))
|
||||
db, user = result.fetchone()
|
||||
return {"status": "connected", "db": db, "user": user}
|
||||
except Exception as e:
|
||||
return JSONResponse(status_code=500, content={"status": "error", "error": str(e)})
|
||||
|
||||
@app.get("/me")
|
||||
def me(user: dict = Depends(get_current_user)):
|
||||
return {
|
||||
"id": user.get("sub"),
|
||||
"email": user.get("email"),
|
||||
"role": user.get("role"),
|
||||
"tenant_id": user.get("tenant_id", "unknown")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,5 @@ uvicorn[standard]==0.27.1
|
|||
sqlalchemy==2.0.30
|
||||
psycopg2-binary==2.9.9
|
||||
python-dotenv==1.0.1
|
||||
python-jose
|
||||
requests
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
from sqlalchemy import create_engine
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Load .env vars
|
||||
load_dotenv()
|
||||
|
||||
# Get env vars
|
||||
USER = os.getenv("user")
|
||||
PASSWORD = os.getenv("password")
|
||||
HOST = os.getenv("host")
|
||||
PORT = os.getenv("port")
|
||||
DBNAME = os.getenv("dbname")
|
||||
|
||||
# Full SQLAlchemy URI for Session Pooler
|
||||
DATABASE_URL = (
|
||||
f"postgresql+psycopg2://{USER}:{PASSWORD}@{HOST}:{PORT}/{DBNAME}?sslmode=require"
|
||||
)
|
||||
|
||||
# Use NullPool to defer to Supabase's pooler
|
||||
from sqlalchemy.pool import NullPool
|
||||
engine = create_engine(DATABASE_URL, poolclass=NullPool)
|
||||
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
print("✅ Supabase Session Pooler connection successful.")
|
||||
except Exception as e:
|
||||
print(f"❌ Connection failed: {e}")
|
||||
|
|
@ -10,6 +10,8 @@ services:
|
|||
- internal_only
|
||||
expose:
|
||||
- "8000"
|
||||
ports:
|
||||
- "8000:8000"
|
||||
|
||||
|
||||
networks:
|
||||
|
|
|
|||
Loading…
Reference in New Issue