Files
gh-christianlouis-docuelevate/app/views/dropbox.py
T
copilot-swe-agent[bot] 23c8c76b39 feat(auth): default to system-wide app credentials in OAuth wizards for user mode
When system-wide Dropbox, Google Drive, or OneDrive app credentials are
configured by the admin, user-mode OAuth wizards now default to using
them. A toggle lets users switch to custom credentials if needed. This
removes the need for end users to register their own cloud provider apps.

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-03-20 13:17:40 +00:00

118 lines
4.9 KiB
Python

"""
Dropbox integration views for setup and OAuth callback.
"""
import json
from fastapi import Query, Request
from sqlalchemy.orm import Session
from app.models import UserIntegration
from app.utils.user_scope import get_current_owner_id
from app.views.base import APIRouter, Depends, get_db, require_login, settings, templates
router = APIRouter()
@router.get("/dropbox-setup")
@require_login
async def dropbox_setup_page(
request: Request,
integration_id: int | None = Query(None),
db: Session = Depends(get_db),
):
"""
Setup page for the Dropbox integration.
When ``integration_id`` is provided the page operates in **user mode**:
the OAuth wizard saves credentials to the named per-user integration
record rather than to the global application settings. Only the folder
path from the integration's existing config is pre-populated; global
admin credentials are never exposed in this mode.
"""
if integration_id is not None:
owner_id = get_current_owner_id(request)
integration = (
db.query(UserIntegration)
.filter(UserIntegration.id == integration_id, UserIntegration.owner_id == owner_id)
.first()
)
if integration:
cfg: dict = {}
if integration.config:
try:
cfg = json.loads(integration.config)
except (json.JSONDecodeError, TypeError):
cfg = {}
# Support both "folder" (DROPBOX destination) and "folder_path" (WATCH_FOLDER source)
folder_path = cfg.get("folder", cfg.get("folder_path", ""))
# Provide system-wide app credentials when available so users can
# authorize without creating their own Dropbox app.
has_system_credentials = bool(settings.dropbox_app_key and settings.dropbox_app_secret)
return templates.TemplateResponse(
"dropbox.html",
{
"request": request,
"user_mode": True,
"is_configured": bool(integration.credentials),
"integration_id": integration_id,
"integration_name": integration.name,
"integration_type": integration.integration_type,
"folder_path": folder_path,
"has_system_credentials": has_system_credentials,
"app_key_value": settings.dropbox_app_key or "" if has_system_credentials else "",
"app_secret_value": settings.dropbox_app_secret or "" if has_system_credentials else "",
"refresh_token_value": "",
},
)
# ── Admin / global mode ──────────────────────────────────────────────────
is_configured = bool(settings.dropbox_app_key and settings.dropbox_app_secret and settings.dropbox_refresh_token)
return templates.TemplateResponse(
"dropbox.html",
{
"request": request,
"user_mode": False,
"is_configured": is_configured,
"has_system_credentials": bool(settings.dropbox_app_key and settings.dropbox_app_secret),
"app_key_value": settings.dropbox_app_key or "",
"app_secret_value": settings.dropbox_app_secret if settings.dropbox_app_secret else "",
"refresh_token_value": settings.dropbox_refresh_token if settings.dropbox_refresh_token else "",
"folder_path": settings.dropbox_folder or "/Documents/Uploads",
"integration_id": integration_id,
"integration_name": None,
"integration_type": None,
},
)
@router.get("/dropbox-callback")
@require_login
async def dropbox_callback(request: Request, code: str = None, error: str = None):
"""
Callback endpoint for Dropbox OAuth flow.
Automatically exchanges the code for a token and saves it to the configuration.
"""
if error:
return templates.TemplateResponse("dropbox_callback_error.html", {"request": request, "error": error})
if not code:
return templates.TemplateResponse(
"dropbox_callback_error.html", {"request": request, "error": "No authorization code received from Dropbox"}
)
# Display the processing page with automatic token exchange
# Note: We provide empty strings for app_key_value and app_secret_value
# to prevent overriding what's in sessionStorage
return templates.TemplateResponse(
"dropbox_callback.html",
{
"request": request,
"code": code,
"app_key_value": "", # The callback will prioritize sessionStorage values
"app_secret_value": "", # The callback will prioritize sessionStorage values
"folder_path": "", # The callback will prioritize sessionStorage values
},
)