diff --git a/cfg/sample/config.json b/cfg/sample/config.json
index bd11c42..2442333 100644
--- a/cfg/sample/config.json
+++ b/cfg/sample/config.json
@@ -1,16 +1,143 @@
{
- "managed": {
- "name": "sample",
- "repos": {
- "frontend": "/path/to/your/frontend/repo",
- "backend": "/path/to/your/backend/repo"
- }
- },
"framework": {
- "name": "soleprint"
+ "name": "soleprint",
+ "slug": "soleprint",
+ "version": "0.1.0",
+ "description": "Development workflow and documentation system",
+ "tagline": "Mapping development footprints",
+ "icon": "",
+ "hub_port": 12030
},
"auth": {
- "enabled": false
+ "enabled": true,
+ "provider": "google",
+ "allowed_domains": [],
+ "allowed_emails": [],
+ "session_secret": "ENV:AUTH_SESSION_SECRET"
},
- "veins": []
+ "veins": ["google"],
+ "managed": {
+ "name": "sample"
+ },
+ "systems": [
+ {
+ "key": "data_flow",
+ "name": "artery",
+ "slug": "artery",
+ "title": "Artery",
+ "tagline": "Todo lo vital",
+ "icon": ""
+ },
+ {
+ "key": "documentation",
+ "name": "atlas",
+ "slug": "atlas",
+ "title": "Atlas",
+ "tagline": "Documentacion accionable",
+ "icon": ""
+ },
+ {
+ "key": "execution",
+ "name": "station",
+ "slug": "station",
+ "title": "Station",
+ "tagline": "Monitores, Entornos y Herramientas",
+ "icon": ""
+ }
+ ],
+ "components": {
+ "shared": {
+ "config": {
+ "name": "room",
+ "title": "Room",
+ "description": "Runtime environment configuration",
+ "plural": "rooms"
+ },
+ "data": {
+ "name": "depot",
+ "title": "Depot",
+ "description": "Data storage / provisions",
+ "plural": "depots"
+ }
+ },
+ "data_flow": {
+ "connector": {
+ "name": "vein",
+ "title": "Vein",
+ "description": "Stateless API connector",
+ "plural": "veins"
+ },
+ "mock": {
+ "name": "shunt",
+ "title": "Shunt",
+ "description": "Fake connector for testing",
+ "plural": "shunts"
+ },
+ "composed": {
+ "name": "pulse",
+ "title": "Pulse",
+ "description": "Composed data flow",
+ "plural": "pulses",
+ "formula": "Vein + Room + Depot"
+ },
+ "app": {
+ "name": "plexus",
+ "title": "Plexus",
+ "description": "Full app with backend, frontend and DB",
+ "plural": "plexus"
+ }
+ },
+ "documentation": {
+ "pattern": {
+ "name": "template",
+ "title": "Template",
+ "description": "Documentation pattern",
+ "plural": "templates"
+ },
+ "library": {
+ "name": "book",
+ "title": "Book",
+ "description": "Documentation library"
+ },
+ "composed": {
+ "name": "book",
+ "title": "Book",
+ "description": "Composed documentation",
+ "plural": "books",
+ "formula": "Template + Depot"
+ }
+ },
+ "execution": {
+ "utility": {
+ "name": "tool",
+ "title": "Tool",
+ "description": "Execution utility",
+ "plural": "tools"
+ },
+ "watcher": {
+ "name": "monitor",
+ "title": "Monitor",
+ "description": "Service monitor",
+ "plural": "monitors"
+ },
+ "container": {
+ "name": "cabinet",
+ "title": "Cabinet",
+ "description": "Tool container",
+ "plural": "cabinets"
+ },
+ "workspace": {
+ "name": "desk",
+ "title": "Desk",
+ "description": "Execution workspace"
+ },
+ "composed": {
+ "name": "desk",
+ "title": "Desk",
+ "description": "Composed execution bundle",
+ "plural": "desks",
+ "formula": "Cabinet + Room + Depots"
+ }
+ }
+ }
}
diff --git a/cfg/sample/sample/index.html b/cfg/sample/sample/index.html
index 90bf11d..5531a5b 100644
--- a/cfg/sample/sample/index.html
+++ b/cfg/sample/sample/index.html
@@ -1,35 +1,102 @@
-
+
-
-
-
- Sample App
-
-
-
-
-
Sample App
-
This is a sample managed room for Soleprint.
-
Replace this with your actual frontend.
-
-
+
+
+
+ Sample - Public Demo
+
+
+
+
+
Sample
+
Public demo - open to any Gmail account
+
Public Demo
+
+
+
Soleprint Managed Room
+
+
+
+
diff --git a/cfg/sample/soleprint/.env b/cfg/sample/soleprint/.env
index 2e18aad..e9cbd4e 100644
--- a/cfg/sample/soleprint/.env
+++ b/cfg/sample/soleprint/.env
@@ -1,7 +1,6 @@
# =============================================================================
-# SOLEPRINT - Sample Room Configuration
+# Sample Soleprint Configuration
# =============================================================================
-# Copy this to cfg//soleprint/.env and customize
# =============================================================================
# DEPLOYMENT
@@ -9,32 +8,24 @@
DEPLOYMENT_NAME=sample_spr
# =============================================================================
-# NETWORK (unique per room to allow concurrent operation)
+# NETWORK
# =============================================================================
NETWORK_NAME=sample_network
# =============================================================================
-# PORTS (choose unique ports for each room)
+# PORTS (unique per room)
# =============================================================================
-SOLEPRINT_PORT=12020
+SOLEPRINT_PORT=12030
# =============================================================================
-# GOOGLE OAUTH (optional - for auth)
+# GOOGLE OAUTH
# =============================================================================
-GOOGLE_CLIENT_ID=
-GOOGLE_CLIENT_SECRET=
-GOOGLE_REDIRECT_URI=http://sample.spr.local.ar/spr/artery/google/oauth/callback
+GOOGLE_CLIENT_ID=1076380473867-k6gvdg8etujj2e51bqejve78ft99hnqd.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=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
+GOOGLE_REDIRECT_URI=http://sample.spr.local.ar/artery/google/oauth/callback
# =============================================================================
# AUTH
# =============================================================================
-AUTH_SESSION_SECRET=change-this-in-production
-
-# =============================================================================
-# DATABASE (optional - if your app uses a database)
-# =============================================================================
-DB_HOST=sample_db
-DB_PORT=5432
-DB_NAME=sampledb
-DB_USER=postgres
-DB_PASSWORD=localdev123
+AUTH_BYPASS=true
+AUTH_SESSION_SECRET=sample-dev-secret-change-in-production
diff --git a/soleprint/artery/veins/google/core/oauth.py b/soleprint/artery/veins/google/core/oauth.py
index e64d0cf..17ab99e 100644
--- a/soleprint/artery/veins/google/core/oauth.py
+++ b/soleprint/artery/veins/google/core/oauth.py
@@ -144,6 +144,9 @@ class GoogleOAuth:
"name": userinfo.get("name"),
"picture": userinfo.get("picture"),
"hd": userinfo.get("hd"), # Hosted domain (Google Workspace)
+ # Include tokens for storage
+ "access_token": credentials.token,
+ "refresh_token": credentials.refresh_token,
}
def refresh_access_token(self, refresh_token: str) -> dict:
diff --git a/soleprint/common/auth/config.py b/soleprint/common/auth/config.py
index 08f97ef..3a7863d 100644
--- a/soleprint/common/auth/config.py
+++ b/soleprint/common/auth/config.py
@@ -15,6 +15,7 @@ class AuthConfig(BaseModel):
enabled: bool = False
provider: str = "google" # Vein name to use for auth
allowed_domains: list[str] = [] # Empty = allow any domain
+ allowed_emails: list[str] = [] # Specific emails to allow
session_secret: str = "" # Required if enabled, can be "ENV:VAR_NAME"
session_timeout_hours: int = 24
login_redirect: str = "/"
diff --git a/soleprint/common/auth/middleware.py b/soleprint/common/auth/middleware.py
index 17a3fa1..094dc41 100644
--- a/soleprint/common/auth/middleware.py
+++ b/soleprint/common/auth/middleware.py
@@ -4,6 +4,7 @@ Authentication middleware for route protection.
Generic middleware, provider-agnostic.
"""
+import os
from datetime import datetime
from starlette.middleware.base import BaseHTTPMiddleware
@@ -11,6 +12,10 @@ from starlette.responses import JSONResponse, RedirectResponse
from .config import AuthConfig
+# Local dev bypass - set via environment variable only, can't be triggered remotely
+AUTH_BYPASS = os.environ.get("AUTH_BYPASS", "").lower() == "true"
+AUTH_BYPASS_USER = os.environ.get("AUTH_BYPASS_USER", "dev@local")
+
class AuthMiddleware(BaseHTTPMiddleware):
"""
@@ -31,6 +36,15 @@ class AuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
path = request.url.path
+ # Local dev bypass - auto-authenticate
+ if AUTH_BYPASS:
+ request.state.user = {
+ "email": AUTH_BYPASS_USER,
+ "name": "Dev User",
+ "domain": "local",
+ }
+ return await call_next(request)
+
# Check if route is public
if self._is_public(path):
return await call_next(request)
@@ -49,15 +63,20 @@ class AuthMiddleware(BaseHTTPMiddleware):
session.clear()
return self._unauthorized(request, "Session expired")
- # Check domain restriction
+ # Check domain/email restriction
user_domain = session.get("domain")
- if self.config.allowed_domains:
- if not user_domain or user_domain not in self.config.allowed_domains:
- session.clear()
- return self._unauthorized(
- request,
- f"Access restricted to: {', '.join(self.config.allowed_domains)}",
- )
+ email_allowed = user_email in self.config.allowed_emails
+ domain_allowed = user_domain and user_domain in self.config.allowed_domains
+ no_restrictions = (
+ not self.config.allowed_domains and not self.config.allowed_emails
+ )
+
+ if not (email_allowed or domain_allowed or no_restrictions):
+ session.clear()
+ return self._unauthorized(
+ request,
+ f"Access restricted",
+ )
# Attach user to request state for downstream use
request.state.user = {
diff --git a/soleprint/common/auth/routes.py b/soleprint/common/auth/routes.py
index 6ca9a87..7f50a1f 100644
--- a/soleprint/common/auth/routes.py
+++ b/soleprint/common/auth/routes.py
@@ -111,15 +111,18 @@ async def callback(
except httpx.RequestError as e:
raise HTTPException(500, f"Failed to contact provider: {e}")
- # Verify domain if restricted
+ # Verify domain/email restriction
+ user_email = user_info.get("email")
user_domain = user_info.get("hd")
- if auth_config.allowed_domains:
- if not user_domain or user_domain not in auth_config.allowed_domains:
- raise HTTPException(
- 403,
- f"Access restricted to: {', '.join(auth_config.allowed_domains)}. "
- f"Your account is from: {user_domain or 'personal Gmail'}",
- )
+ email_allowed = user_email in auth_config.allowed_emails
+ domain_allowed = user_domain and user_domain in auth_config.allowed_domains
+ no_restrictions = not auth_config.allowed_domains and not auth_config.allowed_emails
+
+ if not (email_allowed or domain_allowed or no_restrictions):
+ raise HTTPException(
+ 403,
+ f"Access restricted. Your account ({user_email}) is not authorized.",
+ )
# Create session
expires_at = datetime.now() + timedelta(hours=auth_config.session_timeout_hours)
diff --git a/soleprint/station/tools/sbwrapper/sidebar.js b/soleprint/station/tools/sbwrapper/sidebar.js
index 94482ba..2f7e64f 100755
--- a/soleprint/station/tools/sbwrapper/sidebar.js
+++ b/soleprint/station/tools/sbwrapper/sidebar.js
@@ -92,18 +92,19 @@
function renderSystemSection(systemKey, title) {
const veins = config.veins || [];
- // System icon - ALWAYS shown as non-clickable separator
+ // System link with icon and label (same as soleprint)
let html = `
-
+ ${title}
+ ${title}
+
`;
- // Nested elements below icon - auth-gated
+ // Nested elements below - auth-gated
if (user) {
- // Logged in
+ // Logged in - show vein links under artery
if (systemKey === "artery") {
- // Show vein links under artery
for (const vein of veins) {
html += `
`;
}
- } else {
- // Atlas/Station: clickable link to main page
- html += `
-
- `;
}
} else if (config.auth_enabled) {
// Not logged in: show "login to access" below each system icon
@@ -159,7 +152,7 @@
-