Files
Bennys-Board/core/auth.py
2025-11-27 00:00:50 +00:00

128 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# /var/www/bennysboard/core/auth.py
import os
import requests
from functools import wraps
from flask import Blueprint, render_template, request, redirect, url_for, session, flash
from .models import db, User
auth_bp = Blueprint("auth", __name__, template_folder="templates")
# ---------- session helpers ----------
def current_user():
uid = session.get("uid")
return User.query.get(uid) if uid else None
def login_user(user: User):
session["uid"] = user.id
session.permanent = True
def logout_user():
session.pop("uid", None)
# ---------- decorators ----------
def require_login(view):
@wraps(view)
def _wrap(*a, **k):
if not current_user():
return redirect(url_for("auth.login", next=request.path))
return view(*a, **k)
return _wrap
def require_perms(*perms):
def deco(view):
@wraps(view)
def _wrap(*a, **k):
u = current_user()
if not u:
return redirect(url_for("auth.login", next=request.path))
if not any(u.has_perm(p) for p in perms):
flash("You dont have permission to view that.", "error")
return redirect(url_for("core.home"))
return view(*a, **k)
return _wrap
return deco
# ---------- local login ----------
@auth_bp.get("/login")
def login():
return render_template("core/login.html", next=request.args.get("next", "/"))
@auth_bp.post("/login")
def login_post():
username = request.form.get("username", "")
password = request.form.get("password", "")
nxt = request.form.get("next") or url_for("core.home")
u = User.query.filter((User.email == username) | (User.username == username)).first()
if not u or not u.check_password(password):
flash("Invalid credentials", "error")
return redirect(url_for("auth.login", next=nxt))
login_user(u)
return redirect(nxt)
@auth_bp.post("/logout")
@require_login
def logout():
logout_user()
return redirect(url_for("auth.login"))
# ---------- Discord OAuth (optional) ----------
@auth_bp.get("/discord")
def discord_start():
cid = os.getenv("DISCORD_CLIENT_ID", "")
redir = os.getenv("DISCORD_REDIRECT_URI", "http://localhost:5000/auth/discord/callback")
scope = "identify"
return redirect(
"https://discord.com/oauth2/authorize"
f"?client_id={cid}&response_type=code&redirect_uri={requests.utils.quote(redir)}&scope={scope}&prompt=none"
)
@auth_bp.get("/discord/callback")
def discord_cb():
code = request.args.get("code")
if not code:
flash("Discord login failed.", "error")
return redirect(url_for("auth.login"))
data = {
"client_id": os.getenv("DISCORD_CLIENT_ID"),
"client_secret": os.getenv("DISCORD_CLIENT_SECRET"),
"grant_type": "authorization_code",
"code": code,
"redirect_uri": os.getenv("DISCORD_REDIRECT_URI"),
}
tok = requests.post(
"https://discord.com/api/v10/oauth2/token",
data=data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=10,
)
if tok.status_code != 200:
flash("Discord login failed.", "error")
return redirect(url_for("auth.login"))
access_token = tok.json().get("access_token")
me = requests.get(
"https://discord.com/api/v10/users/@me",
headers={"Authorization": f"Bearer {access_token}"},
timeout=10,
)
if me.status_code != 200:
flash("Discord login failed.", "error")
return redirect(url_for("auth.login"))
d = me.json()
discord_id = d["id"]
uname = d.get("global_name") or d.get("username") or f"user{discord_id[-4:]}"
u = User.query.filter_by(discord_id=discord_id).first()
if not u:
u = User(username=uname, discord_id=discord_id)
db.session.add(u)
db.session.commit()
login_user(u)
return redirect(url_for("core.home"))