Upravil jsem Google OAuth tak, že Google uživatel se hledá/vytváří přes email, ukládá auth_provider='google', provider_subject, display_name, avatar_url a po úspěšném loginu last_login_at. První Google uživatel v prázdné users tabulce dostane admin, další noví Google uživatelé viewer.
Přidal jsem AUTH_MODE=google|mixed|local: google: jen Google login, lokální/Gitea nejdou ani přes přímý endpoint mixed: Google + lokální/Gitea fallback local: jen lokální login Správa uživatelů zůstává admin-only, guard posledního admina platí pro odebrání role i deaktivaci. Přidal jsem do ní i přehled oprávnění rolí a zobrazení provider/avatar metadat. DB migrace teď přes PRAGMA table_info(users) doplňuje chybějící sloupce bez mazání/recreate existující tabulky; pokud users vůbec neexistuje, bezpečně ji vytvoří. Podporované sloupce zahrnují email, display_name, role, is_enabled, created_at, updated_at, last_login_at, auth_provider, provider_subject, plus kompatibilní sloupce pro lokální login.
This commit is contained in:
+69
-5
@@ -9,8 +9,9 @@ from urllib.request import urlopen
|
||||
from fastapi import APIRouter, Form, Request
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
from app.auth import authenticate_user, current_user, find_or_create_oauth_user, mark_last_login
|
||||
from app.auth import authenticate_user, current_user, find_or_create_google_user, find_or_create_oauth_user, mark_last_login
|
||||
from app.config import (
|
||||
get_auth_mode,
|
||||
get_gitea_public_url,
|
||||
get_gitea_redirect_uri,
|
||||
get_gitea_server_url,
|
||||
@@ -38,6 +39,9 @@ def login_form(request: Request):
|
||||
|
||||
@router.post("/login", response_class=HTMLResponse)
|
||||
def login(request: Request, username: str = Form(...), password: str = Form(...)):
|
||||
if get_auth_mode() == "google":
|
||||
return _render_login("Local login is disabled. Use Google sign in.")
|
||||
|
||||
user = authenticate_user(username, password)
|
||||
|
||||
if not user:
|
||||
@@ -60,6 +64,8 @@ def login(request: Request, username: str = Form(...), password: str = Form(...)
|
||||
def gitea_login(request: Request):
|
||||
if current_user(request):
|
||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
||||
if get_auth_mode() != "mixed":
|
||||
return _render_login("Gitea login is not enabled in this auth mode.")
|
||||
|
||||
gitea_url = get_gitea_public_url()
|
||||
client_id = read_env_value("GITEA_OAUTH_CLIENT_ID", "")
|
||||
@@ -92,6 +98,9 @@ def gitea_login(request: Request):
|
||||
|
||||
@router.get("/auth/gitea/callback", response_class=HTMLResponse)
|
||||
def gitea_callback(request: Request, code: str = "", state: str = "", error: str = ""):
|
||||
if get_auth_mode() != "mixed":
|
||||
return _render_login("Gitea login is not enabled in this auth mode.")
|
||||
|
||||
expected_state = request.session.pop("gitea_oauth_state", None)
|
||||
if error:
|
||||
_log_gitea_failure("provider_error", error=error)
|
||||
@@ -141,6 +150,8 @@ def gitea_callback(request: Request, code: str = "", state: str = "", error: str
|
||||
def google_login(request: Request):
|
||||
if current_user(request):
|
||||
return RedirectResponse(url="/portal/operations", status_code=303)
|
||||
if get_auth_mode() == "local":
|
||||
return _render_login("Google login is not enabled in local auth mode.")
|
||||
|
||||
if not is_google_oauth_button_enabled():
|
||||
_log_google_failure("missing_oauth_config")
|
||||
@@ -176,6 +187,9 @@ def google_login(request: Request):
|
||||
|
||||
@router.get("/auth/google/callback", response_class=HTMLResponse)
|
||||
def google_callback(request: Request, code: str = "", state: str = "", error: str = ""):
|
||||
if get_auth_mode() == "local":
|
||||
return _render_login("Google login is not enabled in local auth mode.")
|
||||
|
||||
expected_state = request.session.pop("google_oauth_state", None)
|
||||
if error:
|
||||
_log_google_failure("provider_error", error=error)
|
||||
@@ -204,7 +218,9 @@ def google_callback(request: Request, code: str = "", state: str = "", error: st
|
||||
|
||||
username = email
|
||||
display_name = (google_user.get("name") or email).strip()
|
||||
user = find_or_create_oauth_user(username, display_name, email)
|
||||
provider_subject = (google_user.get("sub") or "").strip()
|
||||
avatar_url = (google_user.get("picture") or "").strip()
|
||||
user, created = find_or_create_google_user(email, display_name, provider_subject, avatar_url)
|
||||
if not user.get("is_enabled", 1):
|
||||
_log_google_failure("disabled_user", username=username)
|
||||
return _render_login("User is disabled in the portal.")
|
||||
@@ -215,6 +231,19 @@ def google_callback(request: Request, code: str = "", state: str = "", error: st
|
||||
request.session.clear()
|
||||
request.session["user_id"] = user["id"]
|
||||
mark_last_login(int(user["id"]))
|
||||
if created:
|
||||
log_audit_event(
|
||||
user,
|
||||
action="auth.google.user.created",
|
||||
target_type="user",
|
||||
target_id=user.get("id"),
|
||||
metadata={
|
||||
"username": user.get("username"),
|
||||
"email": user.get("email"),
|
||||
"role": user.get("role"),
|
||||
"provider": "google",
|
||||
},
|
||||
)
|
||||
log_audit_event(
|
||||
user,
|
||||
action="auth.google.login.success",
|
||||
@@ -366,20 +395,55 @@ def _render_login(error: str | None = None) -> str:
|
||||
error_html = ""
|
||||
if error:
|
||||
error_html = f'<p class="alert alert-danger">{html.escape(error)}</p>'
|
||||
auth_mode = get_auth_mode()
|
||||
gitea_login_html = ""
|
||||
if is_gitea_oauth_button_enabled():
|
||||
if auth_mode == "mixed" and is_gitea_oauth_button_enabled():
|
||||
gitea_login_html = """
|
||||
<p>
|
||||
<a class="btn" href="/portal/auth/gitea/login">Sign in with Gitea</a>
|
||||
<a class="btn btn-secondary" href="/portal/auth/gitea/login">Sign in with Gitea</a>
|
||||
</p>
|
||||
"""
|
||||
google_login_html = ""
|
||||
if is_google_oauth_button_enabled():
|
||||
if auth_mode in {"google", "mixed"} and is_google_oauth_button_enabled():
|
||||
google_login_html = """
|
||||
<p>
|
||||
<a class="btn" href="/portal/auth/google/login">Sign in with Google</a>
|
||||
</p>
|
||||
"""
|
||||
elif auth_mode in {"google", "mixed"}:
|
||||
google_login_html = '<p class="alert alert-danger">Google login is not fully configured.</p>'
|
||||
|
||||
local_login_html = ""
|
||||
if auth_mode in {"mixed", "local"}:
|
||||
local_login_html = """
|
||||
<form method="post" action="/portal/login">
|
||||
<h3>Local login</h3>
|
||||
<p>
|
||||
<label>Username</label><br>
|
||||
<input name="username" autocomplete="username" required autofocus>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Password</label><br>
|
||||
<input type="password" name="password" autocomplete="current-password" required>
|
||||
</p>
|
||||
|
||||
<button type="submit" class="btn-secondary">Sign in locally</button>
|
||||
</form>
|
||||
"""
|
||||
|
||||
return page(
|
||||
"Sign in",
|
||||
f"""
|
||||
<div class="auth-card card">
|
||||
<h2>CSBot Services Portal</h2>
|
||||
{error_html}
|
||||
{google_login_html}
|
||||
{gitea_login_html}
|
||||
{local_login_html}
|
||||
</div>
|
||||
""",
|
||||
)
|
||||
|
||||
return page(
|
||||
"Přihlášení",
|
||||
|
||||
Reference in New Issue
Block a user