implement autologin on login page

This commit is contained in:
kittentm 2026-02-06 23:06:27 +01:00
commit b9beb11268
5 changed files with 99 additions and 25 deletions

View file

@ -5,9 +5,10 @@ class Settings(BaseSettings):
port: int = 5000 port: int = 5000
db_url: str db_url: str
fernet_key: str fernet_key: str
cookie_httponly: bool = True cookie_httponly: bool = False
frontend_url: str
model_config = SettingsConfigDict(env_file=".env", extra="ignore") model_config = SettingsConfigDict(env_file=".env", extra="ignore")
cookie_secure: bool = False
settings = Settings() settings = Settings()
cipher = Fernet(settings.fernet_key.encode()) cipher = Fernet(settings.fernet_key.encode())

View file

@ -1 +1,4 @@
APP_PORT=5000 FERNET_KEY=
DB_URL=
PORT=5000
FRONTEND_URL=

25
main.py
View file

@ -1,14 +1,31 @@
from fastapi import FastAPI from fastapi import FastAPI, Request
from routes import sso from routes import sso, user
from database import init_db
from config import settings from config import settings
import uvicorn import uvicorn
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI() app = FastAPI()
init_db() app.add_middleware(
CORSMiddleware,
allow_origins=[settings.frontend_url],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["Set-Cookie"],
)
@app.middleware("http")
async def force_cors_on_errors(request: Request, call_next):
response = await call_next(request)
origin = request.headers.get("origin")
if origin == settings.frontend_url:
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
return response
app.include_router(sso.router, prefix="/api/v2/sso") app.include_router(sso.router, prefix="/api/v2/sso")
app.include_router(user.router, prefix="/api/v1/users")
if __name__ == '__main__': if __name__ == '__main__':
uvicorn.run("main:app", host="0.0.0.0", port=settings.port, reload=True) uvicorn.run("main:app", host="0.0.0.0", port=settings.port, reload=True)

View file

@ -4,6 +4,7 @@ from sqlalchemy.orm import Session as DBSession
from database import SessionLocal, User, Session from database import SessionLocal, User, Session
from config import settings, cipher from config import settings, cipher
from services import auth from services import auth
from typing import Optional
import uuid import uuid
router = APIRouter() router = APIRouter()
@ -18,56 +19,75 @@ def get_db():
@router.post('/spfn/generate_token') @router.post('/spfn/generate_token')
async def login( async def login(
request: Request, request: Request,
username: str = Form(...), username: Optional[str] = Form(None),
password: str = Form(...), password: Optional[str] = Form(None),
frontend_origin: str = Form(None), frontend_origin: Optional[str] = Form(None),
rememberMe: bool = Form(False), rememberMe: bool = Form(False),
db: DBSession = Depends(get_db) db: DBSession = Depends(get_db)
): ):
host = (frontend_origin or request.headers.get("referer") or "/").rstrip('/') host = (frontend_origin or request.headers.get("referer") or "/").rstrip('/')
sep = "&" if "?" in host else "?"
auth_path = f"{host}/users/auth/splatfestival/"
print(f"host: {host}")
if not username or not password:
print("blank fields detected")
return RedirectResponse(f"{auth_path}{sep}error=auth&username={username or ''}", 303)
try: try:
print("fetching spfn token") print("fetching spfn token")
data = auth.get_token(username, password) data = auth.get_token(username, password)
if not data or "token" not in data: if not data or "token" not in data:
print("auth failed") print("auth failed")
return RedirectResponse(f"{host}/login?error=auth", 303) return RedirectResponse(f"{auth_path}{sep}error=auth&username={username}", 303)
print("encrypting password")
enc_pass = cipher.encrypt(password.encode()).decode()
print(f"checking user {username}") print(f"checking user {username}")
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == username).first()
if not user: if not user:
print("creating new user") print("creating new user with argon2id")
enc_pass = cipher.encrypt(password.encode()).decode() if rememberMe else ""
user = User( user = User(
username=username, username=username,
local_hash=auth.hash_password(password), local_hash=auth.hash_password(password),
spfn_pass_enc=enc_pass spfn_pass_enc=enc_pass
) )
db.add(user) db.add(user)
elif rememberMe:
print("updating encrypted pass for client")
user.spfn_pass_enc = cipher.encrypt(password.encode()).decode()
else: else:
print("updating user") print("client requested no password storage, skipping update")
user.spfn_pass_enc = enc_pass
db.flush() db.flush()
print("checking for existing session")
active_session = db.query(Session).filter(Session.username == username).first()
print("creating session") if active_session:
new_session = Session(username=username, remember_me=rememberMe) print(f"refreshing session for {username}")
db.add(new_session) active_session.remember_me = rememberMe
else:
print(f"creating new session for {username}")
active_session = Session(username=username, remember_me=rememberMe)
db.add(active_session)
db.commit() db.commit()
print("setting cookie and redirecting") print("setting client-based cookie")
response = RedirectResponse(url=f"{host}/friend_list/", status_code=303) response = RedirectResponse(url=f"{host}/friend_list/", status_code=303)
cookie_age = 2592000 if rememberMe else None cookie_age = 2592000 if rememberMe else None
#you are a pain in the ass respectfully
response.set_cookie( response.set_cookie(
key="session_id", key="session_id",
value=new_session.id, value=active_session.id,
httponly=settings.cookie_httponly, httponly=settings.cookie_httponly,
secure=True, secure=False,
samesite="lax", samesite="lax",
max_age=cookie_age path="/",
max_age=cookie_age,
domain=None
) )
return response return response

33
routes/user.py Normal file
View file

@ -0,0 +1,33 @@
from fastapi import APIRouter, Request, Depends, HTTPException
from sqlalchemy.orm import Session as DBSession
from database import SessionLocal, User, Session
from typing import Dict
router = APIRouter()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
@router.get('/me')
async def get_current_user(request: Request, db: DBSession = Depends(get_db)):
session_id = request.cookies.get("session_id")
if not session_id:
raise HTTPException(status_code=401, detail="No session cookie")
active_session = db.query(Session).filter(Session.id == session_id).first()
if not active_session:
print(f"invalid or expired session: {session_id}")
raise HTTPException(status_code=401, detail="invalid session")
user = db.query(User).filter(User.username == active_session.username).first()
if not user:
print(f"session linked to non-existent user: {active_session.username}")
raise HTTPException(status_code=401, detail="user not found")
print(f"auto-login success for {user.username}")
return {
"status": "ok",
"username": user.username
}