add limited db support in regards to the remember me button
This commit is contained in:
parent
feab2abe15
commit
b32b40db49
5 changed files with 119 additions and 30 deletions
13
config.py
Normal file
13
config.py
Normal 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
27
database.py
Normal 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
15
main.py
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
||||
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()
|
||||
|
||||
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("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)
|
||||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue