feat: multi-provider OAuth callback + GitHub config + login buttons
- Callback now supports any provider (Gitea/GitHub) via providers.get_provider() - OAuth tokens stored in user_oauth_tokens table per user + provider - Login page shows both Gitea and GitHub OAuth buttons - config.py: github_client_id + github_client_secret - Auth flow no longer depends on gitea_oauth import — fully abstracted - Fallback admin user now sets is_active=1 and admin@localhost email
This commit is contained in:
@@ -17,6 +17,10 @@ class Settings(BaseSettings):
|
||||
gitea_oauth_client_secret: str = ""
|
||||
gitea_webhook_secret: str = ""
|
||||
|
||||
# GitHub
|
||||
github_client_id: str = ""
|
||||
github_client_secret: str = ""
|
||||
|
||||
# OAuth2
|
||||
oauth_redirect_uri: str = "http://localhost:8080/auth/callback"
|
||||
|
||||
|
||||
+38
-19
@@ -8,7 +8,6 @@ from fastapi import APIRouter, Request, Query
|
||||
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||
|
||||
from app.auth.session import SessionManager
|
||||
from app.auth.oauth import gitea_oauth
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -62,7 +61,8 @@ body{background:#191919;color:#fff;font-family:-apple-system,BlinkMacSystemFont,
|
||||
<button type="submit" class="btn btn-primary" id="submit-btn">Login</button>
|
||||
</form>
|
||||
<div class="oauth-section">
|
||||
<button class="oauth-btn" onclick="window.location='/auth/login?provider=gitea'">🔗 Login with Gitea</button>
|
||||
<button class="oauth-btn" onclick="window.location='/auth/login?provider=gitea'" style="margin-bottom:8px">🔗 Login with Gitea</button>
|
||||
<button class="oauth-btn" onclick="window.location='/auth/login?provider=github'">🐙 Login with GitHub</button>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
@@ -82,14 +82,19 @@ async def login(request: Request, provider: str = Query("gitea")):
|
||||
from fastapi.responses import HTMLResponse
|
||||
return HTMLResponse(LOCAL_LOGIN_HTML, status_code=200)
|
||||
|
||||
# OAuth flow
|
||||
if not gitea_oauth.enabled:
|
||||
# Fallback: use global token, create a fake session
|
||||
# OAuth flow — check if provider is configured
|
||||
from app.auth.providers import get_provider
|
||||
oauth_provider = get_provider(provider)
|
||||
if not oauth_provider:
|
||||
# Fallback: no OAuth configured, create admin session
|
||||
from app.db import get_conn
|
||||
with get_conn() as conn:
|
||||
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
||||
if not user:
|
||||
conn.execute("INSERT INTO users (login, full_name, email, avatar_url) VALUES ('admin', 'Admin', '', '')")
|
||||
conn.execute(
|
||||
"INSERT INTO users (login, full_name, email, avatar_url, is_active) "
|
||||
"VALUES ('admin', 'Admin', 'admin@localhost', '', 1)"
|
||||
)
|
||||
conn.commit()
|
||||
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
||||
user_data = dict(user)
|
||||
@@ -100,7 +105,8 @@ async def login(request: Request, provider: str = Query("gitea")):
|
||||
|
||||
state = secrets.token_hex(32)
|
||||
request.session["oauth_state"] = state
|
||||
auth_url = gitea_oauth.get_authorize_url(state)
|
||||
request.session["oauth_provider"] = provider
|
||||
auth_url = oauth_provider.get_authorize_url(state)
|
||||
return RedirectResponse(url=auth_url, status_code=302)
|
||||
|
||||
|
||||
@@ -207,11 +213,17 @@ async def callback(
|
||||
"""Handle OAuth2 callback from Gitea."""
|
||||
# Validate state
|
||||
expected_state = request.session.get("oauth_state", "")
|
||||
provider_name = request.session.get("oauth_provider", "gitea")
|
||||
if not expected_state or state != expected_state:
|
||||
return HTMLResponse("<h1>Invalid state</h1>", status_code=400)
|
||||
|
||||
from app.auth.providers import get_provider
|
||||
oauth_provider = get_provider(provider_name)
|
||||
if not oauth_provider:
|
||||
return HTMLResponse(f"<h1>Unknown provider: {provider_name}</h1>", status_code=400)
|
||||
|
||||
# Exchange code for token
|
||||
token_data = await gitea_oauth.exchange_code(code)
|
||||
token_data = await oauth_provider.exchange_code(code)
|
||||
if not token_data:
|
||||
return HTMLResponse("<h1>Token exchange failed</h1>", status_code=400)
|
||||
|
||||
@@ -220,29 +232,36 @@ async def callback(
|
||||
return HTMLResponse("<h1>No access token</h1>", status_code=400)
|
||||
|
||||
# Get user info
|
||||
user = await gitea_oauth.get_user(access_token)
|
||||
if not user:
|
||||
oauth_user = await oauth_provider.get_user(access_token)
|
||||
if not oauth_user:
|
||||
return HTMLResponse("<h1>Failed to get user</h1>", status_code=400)
|
||||
|
||||
# Store user in DB
|
||||
from app.db import get_conn
|
||||
login_id = f"{provider_name}_{oauth_user['login']}"
|
||||
with get_conn() as conn:
|
||||
conn.execute(
|
||||
"""INSERT INTO users (login, full_name, email, avatar_url)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(login)
|
||||
DO UPDATE SET full_name=excluded.full_name, email=excluded.email, avatar_url=excluded.avatar_url""",
|
||||
(user["login"], user.get("full_name", ""), user.get("email", ""), user.get("avatar_url", "")),
|
||||
(login_id, oauth_user.get("full_name", ""), oauth_user.get("email", ""), oauth_user.get("avatar_url", "")),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
# Store token
|
||||
SessionManager.store_token(user["id"], access_token)
|
||||
|
||||
# Also store user in local DB
|
||||
with get_conn() as conn:
|
||||
db_user = conn.execute("SELECT * FROM users WHERE login=?", (user["login"],)).fetchone()
|
||||
user_data = dict(db_user) if db_user else user
|
||||
# Store OAuth token
|
||||
uid = conn.execute("SELECT id FROM users WHERE login=?", (login_id,)).fetchone()
|
||||
if uid:
|
||||
conn.execute(
|
||||
"""INSERT OR REPLACE INTO user_oauth_tokens
|
||||
(user_id, provider, access_token, refresh_token, expires_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)""",
|
||||
(uid["id"], provider_name, access_token, token_data.get("refresh_token"), token_data.get("expires_at")),
|
||||
)
|
||||
conn.commit()
|
||||
user = conn.execute("SELECT * FROM users WHERE id=?", (uid["id"],)).fetchone()
|
||||
else:
|
||||
user = conn.execute("SELECT * FROM users WHERE login=?", (login_id,)).fetchone()
|
||||
user_data = dict(user) if user else oauth_user
|
||||
|
||||
# Create session
|
||||
session = SessionManager.create_session(user_data)
|
||||
|
||||
Reference in New Issue
Block a user