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_oauth_client_secret: str = ""
|
||||||
gitea_webhook_secret: str = ""
|
gitea_webhook_secret: str = ""
|
||||||
|
|
||||||
|
# GitHub
|
||||||
|
github_client_id: str = ""
|
||||||
|
github_client_secret: str = ""
|
||||||
|
|
||||||
# OAuth2
|
# OAuth2
|
||||||
oauth_redirect_uri: str = "http://localhost:8080/auth/callback"
|
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 fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
|
|
||||||
from app.auth.session import SessionManager
|
from app.auth.session import SessionManager
|
||||||
from app.auth.oauth import gitea_oauth
|
|
||||||
from app.config import settings
|
from app.config import settings
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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>
|
<button type="submit" class="btn btn-primary" id="submit-btn">Login</button>
|
||||||
</form>
|
</form>
|
||||||
<div class="oauth-section">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
@@ -82,14 +82,19 @@ async def login(request: Request, provider: str = Query("gitea")):
|
|||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
return HTMLResponse(LOCAL_LOGIN_HTML, status_code=200)
|
return HTMLResponse(LOCAL_LOGIN_HTML, status_code=200)
|
||||||
|
|
||||||
# OAuth flow
|
# OAuth flow — check if provider is configured
|
||||||
if not gitea_oauth.enabled:
|
from app.auth.providers import get_provider
|
||||||
# Fallback: use global token, create a fake session
|
oauth_provider = get_provider(provider)
|
||||||
|
if not oauth_provider:
|
||||||
|
# Fallback: no OAuth configured, create admin session
|
||||||
from app.db import get_conn
|
from app.db import get_conn
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
||||||
if not user:
|
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()
|
conn.commit()
|
||||||
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
user = conn.execute("SELECT * FROM users WHERE login='admin'").fetchone()
|
||||||
user_data = dict(user)
|
user_data = dict(user)
|
||||||
@@ -100,7 +105,8 @@ async def login(request: Request, provider: str = Query("gitea")):
|
|||||||
|
|
||||||
state = secrets.token_hex(32)
|
state = secrets.token_hex(32)
|
||||||
request.session["oauth_state"] = state
|
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)
|
return RedirectResponse(url=auth_url, status_code=302)
|
||||||
|
|
||||||
|
|
||||||
@@ -207,11 +213,17 @@ async def callback(
|
|||||||
"""Handle OAuth2 callback from Gitea."""
|
"""Handle OAuth2 callback from Gitea."""
|
||||||
# Validate state
|
# Validate state
|
||||||
expected_state = request.session.get("oauth_state", "")
|
expected_state = request.session.get("oauth_state", "")
|
||||||
|
provider_name = request.session.get("oauth_provider", "gitea")
|
||||||
if not expected_state or state != expected_state:
|
if not expected_state or state != expected_state:
|
||||||
return HTMLResponse("<h1>Invalid state</h1>", status_code=400)
|
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
|
# Exchange code for token
|
||||||
token_data = await gitea_oauth.exchange_code(code)
|
token_data = await oauth_provider.exchange_code(code)
|
||||||
if not token_data:
|
if not token_data:
|
||||||
return HTMLResponse("<h1>Token exchange failed</h1>", status_code=400)
|
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)
|
return HTMLResponse("<h1>No access token</h1>", status_code=400)
|
||||||
|
|
||||||
# Get user info
|
# Get user info
|
||||||
user = await gitea_oauth.get_user(access_token)
|
oauth_user = await oauth_provider.get_user(access_token)
|
||||||
if not user:
|
if not oauth_user:
|
||||||
return HTMLResponse("<h1>Failed to get user</h1>", status_code=400)
|
return HTMLResponse("<h1>Failed to get user</h1>", status_code=400)
|
||||||
|
|
||||||
# Store user in DB
|
# Store user in DB
|
||||||
from app.db import get_conn
|
from app.db import get_conn
|
||||||
|
login_id = f"{provider_name}_{oauth_user['login']}"
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"""INSERT INTO users (login, full_name, email, avatar_url)
|
"""INSERT INTO users (login, full_name, email, avatar_url)
|
||||||
VALUES (?, ?, ?, ?)
|
VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(login)
|
ON CONFLICT(login)
|
||||||
DO UPDATE SET full_name=excluded.full_name, email=excluded.email, avatar_url=excluded.avatar_url""",
|
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()
|
conn.commit()
|
||||||
|
# Store OAuth token
|
||||||
# Store token
|
uid = conn.execute("SELECT id FROM users WHERE login=?", (login_id,)).fetchone()
|
||||||
SessionManager.store_token(user["id"], access_token)
|
if uid:
|
||||||
|
conn.execute(
|
||||||
# Also store user in local DB
|
"""INSERT OR REPLACE INTO user_oauth_tokens
|
||||||
with get_conn() as conn:
|
(user_id, provider, access_token, refresh_token, expires_at, updated_at)
|
||||||
db_user = conn.execute("SELECT * FROM users WHERE login=?", (user["login"],)).fetchone()
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)""",
|
||||||
user_data = dict(db_user) if db_user else user
|
(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
|
# Create session
|
||||||
session = SessionManager.create_session(user_data)
|
session = SessionManager.create_session(user_data)
|
||||||
|
|||||||
Reference in New Issue
Block a user