add limited db support in regards to the remember me button

This commit is contained in:
kittentm 2026-02-06 04:00:43 +01:00
commit b32b40db49
5 changed files with 119 additions and 30 deletions

13
config.py Normal file
View file

@ -0,0 +1,13 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
from cryptography.fernet import Fernet
class Settings(BaseSettings):
port: int = 5000
db_url: str
fernet_key: str
cookie_httponly: bool = True
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
settings = Settings()
cipher = Fernet(settings.fernet_key.encode())

27
database.py Normal file
View file

@ -0,0 +1,27 @@
from sqlalchemy import create_engine, Column, String, Integer, Boolean, DateTime, ForeignKey, Text
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from config import settings
import uuid
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(16), unique=True, index=True, nullable=False)
local_hash = Column(Text, nullable=False)
spfn_pass_enc = Column(Text, nullable=False)
class Session(Base):
__tablename__ = "sessions"
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
username = Column(String(16), ForeignKey("users.username"), nullable=False)
expires_at = Column(DateTime, nullable=True)
remember_me = Column(Boolean, default=False)
engine = create_engine(settings.db_url)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def init_db():
Base.metadata.create_all(bind=engine)

15
main.py
View file

@ -1,17 +1,14 @@
from fastapi import FastAPI
from pydantic_settings import BaseSettings, SettingsConfigDict
from routes import sso
from database import init_db
from config import settings
import uvicorn
class Settings(BaseSettings):
port: int = 5000
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
settings = Settings()
app = FastAPI()
init_db()
app.include_router(sso.router, prefix="/api/v2/sso")
if __name__ == '__main__':
uvicorn.run(app, host="0.0.0.0", port=settings.port)
uvicorn.run("main:app", host="0.0.0.0", port=settings.port, reload=True)

View file

@ -1,36 +1,77 @@
from fastapi import APIRouter, Request, Form, Response
from fastapi import APIRouter, Request, Form, Response, Depends
from fastapi.responses import RedirectResponse
from typing import Optional
from sqlalchemy.orm import Session as DBSession
from database import SessionLocal, User, Session
from config import settings, cipher
from services import auth
import uuid
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.post('/spfn/generate_token')
async def login(
request: Request,
username: Optional[str] = Form(None),
password: Optional[str] = Form(None),
frontend_origin: Optional[str] = Form(None)
username: str = Form(...),
password: str = Form(...),
frontend_origin: str = Form(None),
rememberMe: bool = Form(False),
db: DBSession = Depends(get_db)
):
host = (frontend_origin or request.headers.get("referer") or "/").rstrip('/')
sep = "&" if "?" in host else "?"
if not username or not password:
return RedirectResponse(f"{host}/users/auth/splatfestival/{sep}error=auth&username={username or ''}", 303)
try:
token = auth.get_token(username, password)
if not token:
return RedirectResponse(f"{host}/users/auth/splatfestival/{sep}error=auth&username={username}", 303)
print("fetching spfn token")
data = auth.get_token(username, password)
if not data or "token" not in data:
print("auth failed")
return RedirectResponse(f"{host}/login?error=auth", 303)
# Logging profile for debugging
profile = auth.get_profile(token)
if profile:
print(f"=== PROFILE ===\n{profile}\n===============")
print("encrypting password")
enc_pass = cipher.encrypt(password.encode()).decode()
redirect_arg = request.query_params.get('redirect')
target = redirect_arg if redirect_arg else f"{host}/friend_list/"
return RedirectResponse(url=target, status_code=303)
print(f"checking user {username}")
user = db.query(User).filter(User.username == username).first()
if not user:
print("creating new user")
user = User(
username=username,
local_hash=auth.hash_password(password),
spfn_pass_enc=enc_pass
)
db.add(user)
else:
print("updating user")
user.spfn_pass_enc = enc_pass
db.flush()
print("creating session")
new_session = Session(username=username, remember_me=rememberMe)
db.add(new_session)
db.commit()
print("setting cookie and redirecting")
response = RedirectResponse(url=f"{host}/friend_list/", status_code=303)
cookie_age = 2592000 if rememberMe else None
response.set_cookie(
key="session_id",
value=new_session.id,
httponly=settings.cookie_httponly,
secure=True,
samesite="lax",
max_age=cookie_age
)
return response
except Exception as e:
return Response(content=f"Proxy Error: {str(e)}", status_code=500)
print(f"error: {str(e)}")
db.rollback()
return Response(content=f"internal error: {str(e)}", status_code=500)

View file

@ -1,7 +1,18 @@
import requests
import base64
from argon2 import PasswordHasher
API_URL = "https://account.spfn.net/api/v2"
ph = PasswordHasher()
def hash_password(password: str):
return ph.hash(password)
def verify_password(hashed: str, password: str):
try:
return ph.verify(hashed, password)
except Exception:
return False
def get_token(username, password):
creds = f"{username} {password}"
@ -13,7 +24,7 @@ def get_token(username, password):
}
response = requests.get(f"{API_URL}/oauth2/generate_token", headers=headers, timeout=10)
return response.json().get("token") if response.ok else None
return response.json() if response.ok else None
def get_profile(token):
headers = {