fix: clickup redirect
This commit is contained in:
parent
0f8e128eaa
commit
c097b28b6c
5 changed files with 38 additions and 8 deletions
|
|
@ -48,10 +48,10 @@ Service_GOOGLE_DATA_CLIENT_ID = 813678306829-3f23dnf1cs4aaftubjfickt46tlmkgjm.ap
|
|||
Service_GOOGLE_DATA_CLIENT_SECRET = INT_ENC:Z0FBQUFBQnFBa1kyUTUwNXNGaHRNaGxxbF9sdWJ3Q0xLYU5yOHB4Yk8zMDZvQ29yaEhWOE5JMENXRk5jb2ZBdzRKQ2ZTTld6ZlIxemhOYzN1VE10TjBDRWZEMXlLVWRNYjZ0VG5RZ3I3NWt0SEJzMzdsUmRzcVNmbktRNHZqTUF6a2EyUkVUSFJnZFE=
|
||||
Service_GOOGLE_DATA_REDIRECT_URI = https://api-int.poweron.swiss/api/google/auth/connect/callback
|
||||
|
||||
# ClickUp OAuth (Verbindungen / automation). Create an app in ClickUp: Settings → Apps → API; set redirect URL to Service_CLICKUP_OAUTH_REDIRECT_URI exactly.
|
||||
# ClickUp OAuth — same app as gateway-int; add https://api-int.poweron.swiss as second redirect in ClickUp (root URL, no path).
|
||||
Service_CLICKUP_CLIENT_ID = O3FX3H602A30MQN4I4SBNGJLIDBD5SL4
|
||||
Service_CLICKUP_CLIENT_SECRET = CZECD706WLSX6UV13YI4ACNW50ADZHHXDAJALHE0YE030QFSI6Y9HP4Y61JT7CF0
|
||||
Service_CLICKUP_OAUTH_REDIRECT_URI = https://api-int.poweron.swiss/api/clickup/auth/connect/callback
|
||||
Service_CLICKUP_OAUTH_REDIRECT_URI = https://api-int.poweron.swiss
|
||||
|
||||
# Infomaniak: no OAuth client. Users paste a Personal Access Token (kdrive + mail) per UI.
|
||||
|
||||
|
|
|
|||
|
|
@ -48,10 +48,11 @@ Service_GOOGLE_DATA_CLIENT_ID = 813678306829-3f23dnf1cs4aaftubjfickt46tlmkgjm.ap
|
|||
Service_GOOGLE_DATA_CLIENT_SECRET = INT_ENC:Z0FBQUFBQnFBa1kyV1FRVjF0c0d3d0dyWU1TdW9HdXVkdHdsVWZKYTJjbGZPRDhMRjA2M0FkaUZIVmhIUmFKNjg2ekFodHd6NG80VTI3TC1icW1LZ01jWVZuQ1pKRm5nMW5UREJEaGp2Wl9oRDRCSmZVT0JpTnkwXzgwY0pkV29yczQ5akF2d1ZGcVY=
|
||||
Service_GOOGLE_DATA_REDIRECT_URI = https://gateway-int.poweron.swiss/api/google/auth/connect/callback
|
||||
|
||||
# ClickUp OAuth (Verbindungen / automation). Create an app in ClickUp: Settings → Apps → API; set redirect URL to Service_CLICKUP_OAUTH_REDIRECT_URI exactly.
|
||||
# ClickUp OAuth — redirect URL must match ClickUp app exactly (often API root only).
|
||||
# OAuth lands on /?code=&state=; gateway forwards to /api/clickup/auth/connect/callback (routeAdmin root).
|
||||
Service_CLICKUP_CLIENT_ID = O3FX3H602A30MQN4I4SBNGJLIDBD5SL4
|
||||
Service_CLICKUP_CLIENT_SECRET = INT_ENC:Z0FBQUFBQnB5dkd5SE1uVURMNVE3NkM4cHBKa2R2TjBnLWdpSXI5dHpKWGExZVFiUF95TFNnZ1NwLWFLdmh6eWFZTHVHYTBzU2FGRUpLYkVyM1NvZjZkWDZHN21qUER5ZVNOaGpCc3NrUGd3VnFTclF3OW1nUlVuWXQ1UVhDLVpyb1BwRExOeFpDeVhtbEhDVnd4TVdpbzNBNk5QQWFPdjdza0xBWGxFY1E3WFpCSUlNa1l4RDlBPQ==
|
||||
Service_CLICKUP_OAUTH_REDIRECT_URI = https://gateway-int.poweron.swiss/api/clickup/auth/connect/callback
|
||||
Service_CLICKUP_OAUTH_REDIRECT_URI = https://gateway-int.poweron.swiss
|
||||
|
||||
# Infomaniak: no OAuth client. Users paste a Personal Access Token (kdrive + mail) per UI.
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,25 @@ _msg = apiRouteContext("oauthConnectTicket")
|
|||
|
||||
_CONNECT_TICKET_TTL_SEC = 600
|
||||
|
||||
# OAuth providers sometimes redirect to the API root if the app redirect URL omits the path.
|
||||
OAUTH_FLOW_CALLBACK_PATHS: Dict[str, str] = {
|
||||
"clickup_connect": "/api/clickup/auth/connect/callback",
|
||||
"msft_connect": "/api/msft/auth/connect/callback",
|
||||
"google_connect": "/api/google/auth/connect/callback",
|
||||
}
|
||||
|
||||
|
||||
def oauth_callback_redirect_path(state: str) -> str | None:
|
||||
"""Map connect-ticket JWT (ClickUp ``state`` param) to the correct callback route."""
|
||||
try:
|
||||
data = jose_jwt.decode(state, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
except JWTError:
|
||||
return None
|
||||
flow = data.get("flow")
|
||||
if not isinstance(flow, str):
|
||||
return None
|
||||
return OAUTH_FLOW_CALLBACK_PATHS.get(flow)
|
||||
|
||||
|
||||
def issue_connect_ticket(flow: str, connection_id: str, user_id: str) -> str:
|
||||
"""Issue a short-lived JWT for starting a data-connection OAuth popup."""
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2025 Patrick Motsch
|
||||
# All rights reserved.
|
||||
from fastapi import APIRouter, Response, Depends, Request, Body
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi.responses import FileResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import os
|
||||
import logging
|
||||
|
|
@ -11,6 +11,7 @@ from fastapi import HTTPException, status
|
|||
|
||||
from modules.shared.configuration import APP_CONFIG
|
||||
from modules.auth import limiter, getCurrentUser
|
||||
from modules.auth.oauthConnectTicket import oauth_callback_redirect_path
|
||||
from modules.datamodels.datamodelUam import User
|
||||
from modules.interfaces.interfaceDbApp import getRootInterface
|
||||
from modules.shared.i18nRegistry import apiRouteContext
|
||||
|
|
@ -35,8 +36,15 @@ router.mount(
|
|||
|
||||
@router.get("/")
|
||||
@limiter.limit("30/minute")
|
||||
def root(request: Request) -> Dict[str, str]:
|
||||
"""API status endpoint"""
|
||||
def root(request: Request):
|
||||
"""API status endpoint; forwards OAuth callbacks that land on ``/`` by mistake."""
|
||||
code = request.query_params.get("code")
|
||||
state = request.query_params.get("state")
|
||||
if code and state:
|
||||
callback_path = oauth_callback_redirect_path(state)
|
||||
if callback_path:
|
||||
return RedirectResponse(url=f"{callback_path}?{request.url.query}", status_code=302)
|
||||
|
||||
# Validate required configuration values
|
||||
allowedOrigins = APP_CONFIG.get("APP_ALLOWED_ORIGINS")
|
||||
if not allowedOrigins:
|
||||
|
|
|
|||
|
|
@ -257,6 +257,8 @@ async def auth_connect_callback(
|
|||
except Exception as _cbErr:
|
||||
logger.warning("connection.established callback failed for %s: %s", connection.id, _cbErr)
|
||||
|
||||
allowed = (APP_CONFIG.get("APP_ALLOWED_ORIGINS") or "").split(",")[0].strip()
|
||||
post_target = allowed if allowed else "*"
|
||||
return HTMLResponse(
|
||||
content=f"""
|
||||
<html>
|
||||
|
|
@ -273,7 +275,7 @@ async def auth_connect_callback(
|
|||
lastChecked: {getUtcTimestamp()},
|
||||
expiresAt: {expires_at}
|
||||
}}
|
||||
}}, '*');
|
||||
}}, {json.dumps(post_target)});
|
||||
setTimeout(() => window.close(), 1000);
|
||||
}} else {{
|
||||
window.close();
|
||||
|
|
|
|||
Loading…
Reference in a new issue