forked from spacebar/SplatNet-Backend
implement autologin on login page
This commit is contained in:
parent
b32b40db49
commit
b9beb11268
5 changed files with 99 additions and 25 deletions
|
|
@ -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())
|
||||||
|
|
@ -1 +1,4 @@
|
||||||
APP_PORT=5000
|
FERNET_KEY=
|
||||||
|
DB_URL=
|
||||||
|
PORT=5000
|
||||||
|
FRONTEND_URL=
|
||||||
25
main.py
25
main.py
|
|
@ -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)
|
||||||
|
|
@ -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
33
routes/user.py
Normal 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
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue