diff --git a/cfg/sample/config.json b/cfg/sample/config.json index 49a1cca..bd11c42 100644 --- a/cfg/sample/config.json +++ b/cfg/sample/config.json @@ -11,5 +11,6 @@ }, "auth": { "enabled": false - } + }, + "veins": [] } diff --git a/soleprint/artery/veins/google/api/routes.py b/soleprint/artery/veins/google/api/routes.py index 4439669..f54446f 100644 --- a/soleprint/artery/veins/google/api/routes.py +++ b/soleprint/artery/veins/google/api/routes.py @@ -135,9 +135,25 @@ async def oauth_callback( if not code: raise HTTPException(400, "Missing authorization code") + # Extract redirect URL from state if present + # Format: "csrf_state|redirect_url" OR just "redirect_url" if no csrf state + redirect_url = None + if state: + if "|" in state: + parts = state.split("|", 1) + redirect_url = parts[1] if len(parts) > 1 else None + elif state.startswith("http"): + # No csrf state, just the redirect URL + redirect_url = state + try: tokens = oauth_client.exchange_code_for_tokens(code) token_storage.save_tokens(DEFAULT_USER_ID, tokens) + + # Redirect back to original page if specified + if redirect_url: + return RedirectResponse(url=redirect_url) + return { "status": "ok", "message": "Successfully authenticated with Google", @@ -164,6 +180,32 @@ async def get_userinfo( raise HTTPException(400, f"Failed to get user info: {e}") +@router.get("/oauth/status") +async def oauth_status(): + """Check if user is authenticated (for sidebar).""" + try: + tokens = token_storage.load_tokens(DEFAULT_USER_ID) + if not tokens: + return {"authenticated": False} + + # Check if expired + if token_storage.is_expired(tokens): + if "refresh_token" not in tokens: + return {"authenticated": False} + try: + new_tokens = oauth_client.refresh_access_token(tokens["refresh_token"]) + token_storage.save_tokens(DEFAULT_USER_ID, new_tokens) + except Exception: + return {"authenticated": False} + + return { + "authenticated": True, + "user": {"email": f"{DEFAULT_USER_ID}@authenticated"}, + } + except Exception: + return {"authenticated": False} + + @router.get("/oauth/logout") async def logout(): """Clear stored tokens.""" diff --git a/soleprint/index.html b/soleprint/index.html index 1ec3f92..1fffd99 100644 --- a/soleprint/index.html +++ b/soleprint/index.html @@ -215,56 +215,12 @@ color: #666; } + {% if managed %} + + + {% endif %} - {% if managed %} - - {% endif %}
diff --git a/soleprint/run.py b/soleprint/run.py index 1d26421..27f2b91 100644 --- a/soleprint/run.py +++ b/soleprint/run.py @@ -553,20 +553,16 @@ def sidebar_config(request: Request): host_no_port = host.split(":")[0] scheme = request.headers.get("x-forwarded-proto", "http") - # Soleprint tools are at /spr/* when accessed via .spr. - soleprint_base = "/spr" + # Soleprint tools are at root (no prefix needed) + soleprint_base = "" return { "room": managed.get("name", "standalone"), "soleprint_base": soleprint_base, "auth_enabled": auth.get("enabled", False), - "tools": { - "artery": f"{soleprint_base}/artery", - "atlas": f"{soleprint_base}/atlas", - "station": f"{soleprint_base}/station", - }, + "veins": config.get("veins", []), "auth": { - "login_url": f"{soleprint_base}/artery/google/oauth/login", + "login_url": f"{soleprint_base}/artery/google/oauth/start", "status_url": f"{soleprint_base}/artery/google/oauth/status", "logout_url": f"{soleprint_base}/artery/google/oauth/logout", }, diff --git a/soleprint/station/tools/sbwrapper/sidebar.css b/soleprint/station/tools/sbwrapper/sidebar.css index af0dfa1..87f6469 100755 --- a/soleprint/station/tools/sbwrapper/sidebar.css +++ b/soleprint/station/tools/sbwrapper/sidebar.css @@ -147,6 +147,63 @@ body.spr-sidebar-active.spr-sidebar-expanded { flex: 1; } +/* Vein items under artery icon */ +.spr-vein-item { + width: 36px; + height: 28px; + font-size: 0.7rem; + margin-left: 8px; + background: #252525; +} + +.spr-vein-item .label { + display: block !important; + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +#spr-sidebar.expanded .spr-vein-item { + margin-left: 20px; + width: calc(100% - 20px); +} + +/* System links (atlas, station when logged in) */ +.spr-system-link { + height: 28px; + font-size: 0.75rem; +} + +/* Locked state (not logged in) */ +.spr-sidebar-locked { + width: 44px; + display: flex; + align-items: center; + justify-content: center; + color: #555; + font-size: 0.5rem; + margin-bottom: 0.5rem; + text-align: center; +} + +.spr-sidebar-locked .lock-text { + writing-mode: vertical-rl; + text-orientation: mixed; + transform: rotate(180deg); + white-space: nowrap; +} + +#spr-sidebar.expanded .spr-sidebar-locked { + width: 100%; + justify-content: flex-start; + margin-left: 20px; +} + +#spr-sidebar.expanded .spr-sidebar-locked .lock-text { + writing-mode: horizontal-tb; + transform: none; +} + /* User/Auth Section */ .spr-sidebar-user { margin-top: auto; diff --git a/soleprint/station/tools/sbwrapper/sidebar.js b/soleprint/station/tools/sbwrapper/sidebar.js index 8bb7eb3..94482ba 100755 --- a/soleprint/station/tools/sbwrapper/sidebar.js +++ b/soleprint/station/tools/sbwrapper/sidebar.js @@ -4,7 +4,8 @@ (function () { "use strict"; - const SPR_BASE = "/spr"; + // Soleprint routes are at root (no prefix) + const SPR_BASE = ""; let config = null; let user = null; @@ -56,7 +57,7 @@ async function init() { try { - // Load config + // Load config using dynamic base URL const response = await fetch(`${SPR_BASE}/api/sidebar/config`); config = await response.json(); @@ -88,6 +89,50 @@ } } + function renderSystemSection(systemKey, title) { + const veins = config.veins || []; + + // System icon - ALWAYS shown as non-clickable separator + let html = ` +
+ ${icons[systemKey]} +
+ `; + + // Nested elements below icon - auth-gated + if (user) { + // Logged in + if (systemKey === "artery") { + // Show vein links under artery + for (const vein of veins) { + html += ` + + ${vein} + ${vein} + + `; + } + } else { + // Atlas/Station: clickable link to main page + html += ` + + ${title} + ${title} + + `; + } + } else if (config.auth_enabled) { + // Not logged in: show "login to access" below each system icon + html += ` +
+ login to access +
+ `; + } + + return html; + } + function createSidebar() { // Add body class document.body.classList.add("spr-sidebar-active"); @@ -122,17 +167,9 @@
-
- ${icons.artery} -
- -
- ${icons.atlas} -
- -
- ${icons.station} -
+ ${renderSystemSection("artery", "Artery")} + ${renderSystemSection("atlas", "Atlas")} + ${renderSystemSection("station", "Station")}
@@ -167,9 +204,12 @@ `; } else { + // Include current page as redirect target after login + const redirectUrl = encodeURIComponent(window.location.href); + const loginUrl = `${config.auth.login_url}?redirect=${redirectUrl}`; return `