Skip to content

Commit f8a293b

Browse files
Pandakingxbcleoxyang
andauthored
Improve Notion OAuth HTTPS error guidance (#26)
Co-authored-by: leoxyang <leoxyang@tencent.com>
1 parent 9d22b5c commit f8a293b

7 files changed

Lines changed: 43 additions & 0 deletions

File tree

dashboard/src/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"CHANNEL_NAME_TAKEN": "This channel name is already in use. Please choose another.",
7575
"CONNECTOR_NOT_FOUND": "Connector not found.",
7676
"CONNECTOR_INVALID_CREDENTIALS": "Invalid connector credentials.",
77+
"CONNECTOR_OAUTH_HTTPS_REQUIRED": "Octop is currently being accessed over public HTTP, but Notion OAuth callbacks require HTTPS. Use HTTPS, or access Octop locally through localhost or 127.0.0.1.",
7778
"CONNECTOR_KIND_UNSUPPORTED": "Unsupported connector type.",
7879
"CONNECTOR_NOT_BOUND": "Connector is not bound to this agent.",
7980
"CONNECTOR_ALREADY_BOUND": "Connector is already bound.",

dashboard/src/locales/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"CHANNEL_NAME_TAKEN": "该通道名称已被使用,请换一个名称。",
7575
"CONNECTOR_NOT_FOUND": "未找到连接器。",
7676
"CONNECTOR_INVALID_CREDENTIALS": "连接器凭证无效。",
77+
"CONNECTOR_OAUTH_HTTPS_REQUIRED": "当前通过公网 HTTP 访问 Octop,Notion 授权回调地址需要 HTTPS。请改用 HTTPS,或通过 localhost、127.0.0.1 在本机访问 Octop。",
7778
"CONNECTOR_KIND_UNSUPPORTED": "不支持的连接器类型。",
7879
"CONNECTOR_NOT_BOUND": "连接器未绑定到此 Agent。",
7980
"CONNECTOR_ALREADY_BOUND": "连接器已绑定。",

src/octop/api/routers/connectors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import logging
88
import secrets
99
from typing import Any
10+
from urllib.parse import urlparse
1011

1112
from fastapi import APIRouter, Depends, Query, Request
1213
from fastapi.responses import HTMLResponse
@@ -226,6 +227,12 @@ async def _run() -> None:
226227
asyncio.create_task(_run())
227228

228229

230+
def _is_public_http_uri(uri: str) -> bool:
231+
parsed = urlparse(uri)
232+
host = (parsed.hostname or "").lower()
233+
return parsed.scheme == "http" and host not in {"localhost", "127.0.0.1", "::1"}
234+
235+
229236
@router.get("/connectors/catalog", summary="Connector catalog")
230237
async def get_catalog(
231238
user: Any = Depends(current_user),
@@ -695,6 +702,11 @@ async def oauth_start(
695702
state_id = new_ulid()
696703
base = str(request.base_url).rstrip("/")
697704
redirect_uri = f"{base}/api/connectors/oauth/callback"
705+
if kind == "notion" and _is_public_http_uri(redirect_uri):
706+
raise OctopError(
707+
ErrorCode.CONNECTOR_OAUTH_HTTPS_REQUIRED,
708+
"Notion OAuth callbacks require HTTPS for non-loopback addresses",
709+
)
698710

699711
try:
700712
authorize_url, verifier, ctx = await start_oauth(

src/octop/i18n/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@
296296
"CHANNEL_NAME_TAKEN": "This channel name is already in use. Please choose another.",
297297
"CONNECTOR_NOT_FOUND": "Connector not found.",
298298
"CONNECTOR_INVALID_CREDENTIALS": "Invalid connector credentials.",
299+
"CONNECTOR_OAUTH_HTTPS_REQUIRED": "Octop is currently being accessed over public HTTP, but Notion OAuth callbacks require HTTPS. Use HTTPS, or access Octop locally through localhost or 127.0.0.1.",
299300
"CONNECTOR_KIND_UNSUPPORTED": "Unsupported connector type.",
300301
"CONNECTOR_NOT_BOUND": "Connector is not bound to this agent.",
301302
"CONNECTOR_ALREADY_BOUND": "Connector is already bound.",

src/octop/i18n/zh.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@
296296
"CHANNEL_NAME_TAKEN": "该通道名称已被使用,请换一个名称。",
297297
"CONNECTOR_NOT_FOUND": "未找到连接器。",
298298
"CONNECTOR_INVALID_CREDENTIALS": "连接器凭证无效。",
299+
"CONNECTOR_OAUTH_HTTPS_REQUIRED": "当前通过公网 HTTP 访问 Octop,Notion 授权回调地址需要 HTTPS。请改用 HTTPS,或通过 localhost、127.0.0.1 在本机访问 Octop。",
299300
"CONNECTOR_KIND_UNSUPPORTED": "不支持的连接器类型。",
300301
"CONNECTOR_NOT_BOUND": "连接器未绑定到此 Agent。",
301302
"CONNECTOR_ALREADY_BOUND": "连接器已绑定。",

src/octop/infra/errors.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class ErrorCode(StrEnum):
3333
CHANNEL_NAME_TAKEN = "CHANNEL_NAME_TAKEN"
3434
CONNECTOR_NOT_FOUND = "CONNECTOR_NOT_FOUND"
3535
CONNECTOR_INVALID_CREDENTIALS = "CONNECTOR_INVALID_CREDENTIALS"
36+
CONNECTOR_OAUTH_HTTPS_REQUIRED = "CONNECTOR_OAUTH_HTTPS_REQUIRED"
3637
CONNECTOR_KIND_UNSUPPORTED = "CONNECTOR_KIND_UNSUPPORTED"
3738
CONNECTOR_NOT_BOUND = "CONNECTOR_NOT_BOUND"
3839
CONNECTOR_ALREADY_BOUND = "CONNECTOR_ALREADY_BOUND"
@@ -80,6 +81,7 @@ class ErrorCode(StrEnum):
8081
ErrorCode.CHANNEL_NAME_TAKEN: 409,
8182
ErrorCode.CONNECTOR_NOT_FOUND: 404,
8283
ErrorCode.CONNECTOR_INVALID_CREDENTIALS: 400,
84+
ErrorCode.CONNECTOR_OAUTH_HTTPS_REQUIRED: 400,
8385
ErrorCode.CONNECTOR_KIND_UNSUPPORTED: 400,
8486
ErrorCode.CONNECTOR_NOT_BOUND: 400,
8587
ErrorCode.CONNECTOR_ALREADY_BOUND: 409,

tests/integration/test_connectors_api.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
from __future__ import annotations
44

5+
from pathlib import Path
6+
from unittest.mock import AsyncMock, patch
7+
58
import pytest
69

10+
from tests.support.app import octop_client, write_octop_config
11+
from tests.support.auth import auth_header, bootstrap_admin
712
from tests.support.http import ws_chat_turn
813

914

@@ -165,6 +170,26 @@ async def test_auth_info(env):
165170
assert data["auth_hint"]
166171

167172

173+
async def test_oauth_start_public_http_notion_error_is_actionable(tmp_octop_home: Path):
174+
write_octop_config(tmp_octop_home)
175+
async with octop_client(tmp_octop_home) as (c, _srv):
176+
await bootstrap_admin(c, tmp_octop_home)
177+
auth = await auth_header(c)
178+
mocked_start = AsyncMock()
179+
with patch("octop.api.routers.connectors.start_oauth", mocked_start):
180+
r = await c.post(
181+
"/api/connectors/oauth/notion/start",
182+
headers={**auth, "host": "58.87.70.170"},
183+
json={"redirect_after": "/connectors"},
184+
)
185+
assert r.status_code == 400
186+
body = r.json()
187+
assert body["error"]["code"] == "CONNECTOR_OAUTH_HTTPS_REQUIRED"
188+
assert "Notion" in body["error"]["message"]
189+
assert "HTTPS" in body["error"]["message"]
190+
mocked_start.assert_not_awaited()
191+
192+
168193
async def test_patch_instance_status(env):
169194
c, _, auth, _ = env
170195
r = await c.post(

0 commit comments

Comments
 (0)