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:
JiriUhlir
2026-06-10 08:37:42 +02:00
parent 9cec3d001f
commit 272b1eb883
7 changed files with 295 additions and 16 deletions
+46 -7
View File
@@ -7,18 +7,57 @@ def _table_columns(con, table_name: str) -> set[str]:
def ensure_user_columns(con) -> None:
con.execute(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
display_name TEXT,
email TEXT,
password_hash TEXT NOT NULL DEFAULT '',
role TEXT NOT NULL DEFAULT 'viewer',
is_active INTEGER NOT NULL DEFAULT 1,
is_enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_at TEXT,
auth_provider TEXT,
provider_subject TEXT,
avatar_url TEXT
)
"""
)
columns = _table_columns(con, "users")
if not columns:
return
if "is_enabled" not in columns:
default_expression = "COALESCE(is_active, 1)" if "is_active" in columns else "1"
con.execute("ALTER TABLE users ADD COLUMN is_enabled INTEGER NOT NULL DEFAULT 1")
con.execute(f"UPDATE users SET is_enabled = {default_expression} WHERE is_enabled IS NULL OR is_enabled = 1")
defaults = {
"username": "TEXT",
"display_name": "TEXT",
"email": "TEXT",
"password_hash": "TEXT NOT NULL DEFAULT ''",
"role": "TEXT NOT NULL DEFAULT 'viewer'",
"is_active": "INTEGER NOT NULL DEFAULT 1",
"is_enabled": "INTEGER NOT NULL DEFAULT 1",
"created_at": "TEXT",
"updated_at": "TEXT",
"last_login_at": "TEXT",
"auth_provider": "TEXT",
"provider_subject": "TEXT",
"avatar_url": "TEXT",
}
for column, definition in defaults.items():
if column not in columns:
con.execute(f"ALTER TABLE users ADD COLUMN {column} {definition}")
if "updated_at" not in columns:
con.execute("ALTER TABLE users ADD COLUMN updated_at TEXT")
con.execute("UPDATE users SET updated_at = COALESCE(created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL")
columns = _table_columns(con, "users")
if "is_active" in columns and "is_enabled" in columns:
con.execute("UPDATE users SET is_enabled = COALESCE(is_active, 1) WHERE is_enabled IS NULL")
con.execute("UPDATE users SET role = 'viewer' WHERE role IS NULL OR role = ''")
con.execute("UPDATE users SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL OR created_at = ''")
con.execute("UPDATE users SET updated_at = COALESCE(created_at, CURRENT_TIMESTAMP) WHERE updated_at IS NULL OR updated_at = ''")
con.execute("CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)")
con.execute("CREATE INDEX IF NOT EXISTS idx_users_provider_subject ON users(auth_provider, provider_subject)")
def run_migrations():
+15 -2
View File
@@ -16,6 +16,9 @@ def list_users() -> list[dict[str, Any]]:
con = get_connection()
columns = _table_columns(con, "users")
last_login_select = "last_login_at" if "last_login_at" in columns else "NULL AS last_login_at"
auth_provider_select = "auth_provider" if "auth_provider" in columns else "NULL AS auth_provider"
provider_subject_select = "provider_subject" if "provider_subject" in columns else "NULL AS provider_subject"
avatar_url_select = "avatar_url" if "avatar_url" in columns else "NULL AS avatar_url"
rows = con.execute(
f"""
@@ -28,7 +31,10 @@ def list_users() -> list[dict[str, Any]]:
COALESCE(is_enabled, 1) AS is_enabled,
created_at,
updated_at,
{last_login_select}
{last_login_select},
{auth_provider_select},
{provider_subject_select},
{avatar_url_select}
FROM users
ORDER BY id
"""
@@ -43,6 +49,9 @@ def get_user(user_id: int) -> dict[str, Any] | None:
con = get_connection()
columns = _table_columns(con, "users")
last_login_select = "last_login_at" if "last_login_at" in columns else "NULL AS last_login_at"
auth_provider_select = "auth_provider" if "auth_provider" in columns else "NULL AS auth_provider"
provider_subject_select = "provider_subject" if "provider_subject" in columns else "NULL AS provider_subject"
avatar_url_select = "avatar_url" if "avatar_url" in columns else "NULL AS avatar_url"
row = con.execute(
f"""
@@ -55,7 +64,10 @@ def get_user(user_id: int) -> dict[str, Any] | None:
COALESCE(is_enabled, 1) AS is_enabled,
created_at,
updated_at,
{last_login_select}
{last_login_select},
{auth_provider_select},
{provider_subject_select},
{avatar_url_select}
FROM users
WHERE id = ?
""",
@@ -81,6 +93,7 @@ def count_enabled_admins(excluding_user_id: int | None = None) -> int:
FROM users
WHERE LOWER(COALESCE(role, '')) = 'admin'
AND COALESCE(is_enabled, 1) = 1
AND COALESCE(is_active, 1) = 1
{exclusion}
""",
params,