diff --git a/src/bonsaiviewer-autodesk/README.md b/src/bonsaiviewer-autodesk/README.md index f57475da56..bdd6ed51f1 100644 --- a/src/bonsaiviewer-autodesk/README.md +++ b/src/bonsaiviewer-autodesk/README.md @@ -28,7 +28,7 @@ bonsaiviewer-autodesk The connector launches without any configuration; on first run, invoke `open_settings` (or, equivalently, set the `APS_CLIENT_ID` env var) to -configure the Autodesk client id. +configure the Autodesk client id and OAuth callback port. Then send newline-delimited JSON-RPC 2.0 requests on `stdin`. Examples: @@ -51,6 +51,9 @@ The connector reads the Autodesk client id from two places, in order: overrides). 2. `/settings.json` (persisted via the settings dialog). +The OAuth callback host is always `localhost`. The callback port defaults to +`8080` and can be changed in the settings dialog. + The config directory is platform-specific: - Linux: `~/.config/bonsaiviewer-autodesk/` @@ -85,7 +88,7 @@ What is implemented: - Hub / project / folder browsing (Qt UI) - `pull_ifcfed_interactive`, `pull_ifcfed`, `pull_models`, `pull_models_interactive` - `push_ifcfed_interactive`, `push_ifcfed`, `push_model_interactive`, `push_model` -- `open_settings` — edit the client id, sign out +- `open_settings` — edit the client id and callback port, sign out - Connector-managed cache with sole-child invariant - Adjacent `.ifcfed.manifest` written/read alongside `.ifcfed` files diff --git a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py index 0f2b721fa9..4181ea8d1f 100644 --- a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/connector.py @@ -35,7 +35,6 @@ def _upload_callback(report: Report) -> ApsProgress: CONNECTOR_ID = "autodesk" KEYRING_SERVICE = "bonsaiviewer-autodesk" -DEFAULT_CALLBACK_URL = "http://localhost:8080/" DEFAULT_SCOPE = "data:read data:write data:create" @@ -53,9 +52,10 @@ class AutodeskConnector: self.aps = None return token_store = KeyringTokenStore(service_name=KEYRING_SERVICE, username=client_id) + callback_url = f"http://localhost:{settings.stored_callback_port()}/" self.auth = AuthSessionService( client_id=client_id, - callback_url=DEFAULT_CALLBACK_URL, + callback_url=callback_url, scope=DEFAULT_SCOPE, token_store=token_store, ) diff --git a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py index 3b4ebdf8d7..2e3b8b43f6 100644 --- a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/settings.py @@ -7,6 +7,9 @@ from pathlib import Path from typing import Any +DEFAULT_CALLBACK_PORT = 8080 + + def config_root() -> Path: system = platform.system() if system == "Windows": @@ -59,3 +62,20 @@ def save_client_id(client_id: str) -> None: data = _read() data["client_id"] = client_id.strip() _write(data) + + +def stored_callback_port() -> int: + value = _read().get("callback_port", DEFAULT_CALLBACK_PORT) + try: + port = int(value) + except (TypeError, ValueError): + return DEFAULT_CALLBACK_PORT + return port if 1 <= port <= 65535 else DEFAULT_CALLBACK_PORT + + +def save_callback_port(port: int) -> None: + if not 1 <= port <= 65535: + raise ValueError("Callback port must be between 1 and 65535.") + data = _read() + data["callback_port"] = port + _write(data) diff --git a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py index 9138648ce4..7fa1a02586 100644 --- a/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py +++ b/src/bonsaiviewer-autodesk/bonsaiviewer_autodesk/ui.py @@ -550,6 +550,7 @@ class SettingsDialog(_BaseDialog): env_override = settings.env_client_id() stored = settings.stored_client_id() effective = env_override or stored + callback_port = settings.stored_callback_port() body = ctk.CTkFrame(self, fg_color="transparent") body.pack(fill="both", expand=True, padx=24, pady=24) @@ -583,6 +584,11 @@ class SettingsDialog(_BaseDialog): text_color=("#b45309", "#f59e0b"), ).pack(fill="x", pady=(0, 8)) + ctk.CTkLabel(body, text="OAuth callback port", anchor="w").pack(fill="x") + self.callback_port_entry = ctk.CTkEntry(body, placeholder_text=str(settings.DEFAULT_CALLBACK_PORT)) + self.callback_port_entry.pack(fill="x", pady=(6, 8)) + self.callback_port_entry.insert(0, str(callback_port)) + self.status_label = ctk.CTkLabel( body, text=f"Signed in as {effective}" if effective else "No client id configured.", @@ -618,6 +624,12 @@ class SettingsDialog(_BaseDialog): def _save(self) -> None: new_id = self.client_id_entry.get().strip() + try: + callback_port = int(self.callback_port_entry.get().strip()) + settings.save_callback_port(callback_port) + except ValueError: + show_error(title="Invalid Callback Port", message="Callback port must be a number between 1 and 65535.") + return settings.save_client_id(new_id) try: self.connector.reload_credentials()