Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
6
demo/tools/auth/__init__.py
Normal file
6
demo/tools/auth/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
Authentication Module
|
||||
|
||||
Atlassian-based authentication for DSS.
|
||||
Users authenticate with their Jira/Confluence credentials.
|
||||
"""
|
||||
246
demo/tools/auth/atlassian_auth.py
Normal file
246
demo/tools/auth/atlassian_auth.py
Normal file
@@ -0,0 +1,246 @@
|
||||
"""
|
||||
Atlassian-based Authentication
|
||||
|
||||
Validates users by verifying their Atlassian (Jira/Confluence) credentials.
|
||||
On successful login, creates a JWT token for subsequent requests.
|
||||
"""
|
||||
|
||||
import os
|
||||
import jwt
|
||||
import hashlib
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Dict, Any
|
||||
from atlassian import Jira, Confluence
|
||||
|
||||
from storage.database import get_connection
|
||||
|
||||
|
||||
class AtlassianAuth:
|
||||
"""
|
||||
Authentication using Atlassian API credentials.
|
||||
|
||||
Users provide:
|
||||
- Atlassian URL (Jira or Confluence)
|
||||
- Email
|
||||
- API Token
|
||||
|
||||
On successful validation, we:
|
||||
1. Verify credentials against Atlassian API
|
||||
2. Store user in database
|
||||
3. Generate JWT token
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.jwt_secret = os.getenv("JWT_SECRET", "change-me-in-production")
|
||||
self.jwt_algorithm = "HS256"
|
||||
self.jwt_expiry_hours = int(os.getenv("JWT_EXPIRY_HOURS", "24"))
|
||||
|
||||
async def verify_atlassian_credentials(
|
||||
self,
|
||||
url: str,
|
||||
email: str,
|
||||
api_token: str,
|
||||
service: str = "jira"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify Atlassian credentials by making a test API call.
|
||||
|
||||
Args:
|
||||
url: Atlassian URL (e.g., https://yourcompany.atlassian.net)
|
||||
email: User email
|
||||
api_token: Atlassian API token (use "1234" for mock mode)
|
||||
service: "jira" or "confluence"
|
||||
|
||||
Returns:
|
||||
User info dict if valid, raises exception if invalid
|
||||
"""
|
||||
# Mock mode for development/testing
|
||||
if api_token == "1234":
|
||||
return {
|
||||
"email": email,
|
||||
"display_name": email.split("@")[0].title().replace(".", " ") + " (Mock)",
|
||||
"account_id": "mock_" + hashlib.md5(email.encode()).hexdigest()[:8],
|
||||
"atlassian_url": url or "https://mock.atlassian.net",
|
||||
"service": service,
|
||||
"verified": True,
|
||||
"mock_mode": True
|
||||
}
|
||||
|
||||
try:
|
||||
if service == "jira":
|
||||
client = Jira(url=url, username=email, password=api_token)
|
||||
# Test API call - get current user
|
||||
user_info = client.myself()
|
||||
else: # confluence
|
||||
client = Confluence(url=url, username=email, password=api_token)
|
||||
# Test API call - get current user
|
||||
user_info = client.get_current_user()
|
||||
|
||||
return {
|
||||
"email": email,
|
||||
"display_name": user_info.get("displayName", email),
|
||||
"account_id": user_info.get("accountId"),
|
||||
"atlassian_url": url,
|
||||
"service": service,
|
||||
"verified": True,
|
||||
"mock_mode": False
|
||||
}
|
||||
except Exception as e:
|
||||
raise ValueError(f"Invalid Atlassian credentials: {str(e)}")
|
||||
|
||||
def hash_api_token(self, api_token: str) -> str:
|
||||
"""Hash API token for storage (we don't store plain tokens)"""
|
||||
return hashlib.sha256(api_token.encode()).hexdigest()
|
||||
|
||||
async def login(
|
||||
self,
|
||||
url: str,
|
||||
email: str,
|
||||
api_token: str,
|
||||
service: str = "jira"
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Authenticate user with Atlassian credentials.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"token": "jwt_token",
|
||||
"user": {...},
|
||||
"expires_at": "iso_timestamp"
|
||||
}
|
||||
"""
|
||||
# Verify credentials against Atlassian
|
||||
user_info = await self.verify_atlassian_credentials(
|
||||
url, email, api_token, service
|
||||
)
|
||||
|
||||
# Hash the API token
|
||||
token_hash = self.hash_api_token(api_token)
|
||||
|
||||
# Store or update user in database
|
||||
with get_connection() as conn:
|
||||
# Check if user exists
|
||||
existing = conn.execute(
|
||||
"SELECT id, email FROM users WHERE email = ?",
|
||||
(email,)
|
||||
).fetchone()
|
||||
|
||||
if existing:
|
||||
# Update existing user
|
||||
user_id = existing["id"]
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE users
|
||||
SET display_name = ?,
|
||||
atlassian_url = ?,
|
||||
atlassian_service = ?,
|
||||
api_token_hash = ?,
|
||||
last_login = ?
|
||||
WHERE id = ?
|
||||
""",
|
||||
(
|
||||
user_info["display_name"],
|
||||
url,
|
||||
service,
|
||||
token_hash,
|
||||
datetime.utcnow().isoformat(),
|
||||
user_id
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Create new user
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
INSERT INTO users (
|
||||
email, display_name, atlassian_url, atlassian_service,
|
||||
api_token_hash, created_at, last_login
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
email,
|
||||
user_info["display_name"],
|
||||
url,
|
||||
service,
|
||||
token_hash,
|
||||
datetime.utcnow().isoformat(),
|
||||
datetime.utcnow().isoformat()
|
||||
)
|
||||
)
|
||||
user_id = cursor.lastrowid
|
||||
|
||||
# Generate JWT token
|
||||
expires_at = datetime.utcnow() + timedelta(hours=self.jwt_expiry_hours)
|
||||
token_payload = {
|
||||
"user_id": user_id,
|
||||
"email": email,
|
||||
"display_name": user_info["display_name"],
|
||||
"exp": expires_at,
|
||||
"iat": datetime.utcnow()
|
||||
}
|
||||
|
||||
jwt_token = jwt.encode(
|
||||
token_payload,
|
||||
self.jwt_secret,
|
||||
algorithm=self.jwt_algorithm
|
||||
)
|
||||
|
||||
return {
|
||||
"token": jwt_token,
|
||||
"user": {
|
||||
"id": user_id,
|
||||
"email": email,
|
||||
"display_name": user_info["display_name"],
|
||||
"atlassian_url": url,
|
||||
"service": service
|
||||
},
|
||||
"expires_at": expires_at.isoformat()
|
||||
}
|
||||
|
||||
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Verify JWT token and return user info.
|
||||
|
||||
Returns:
|
||||
User dict if valid, None if invalid/expired
|
||||
"""
|
||||
try:
|
||||
payload = jwt.decode(
|
||||
token,
|
||||
self.jwt_secret,
|
||||
algorithms=[self.jwt_algorithm]
|
||||
)
|
||||
return payload
|
||||
except jwt.ExpiredSignatureError:
|
||||
return None
|
||||
except jwt.InvalidTokenError:
|
||||
return None
|
||||
|
||||
async def get_user_by_id(self, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
"""Get user information by ID"""
|
||||
with get_connection() as conn:
|
||||
user = conn.execute(
|
||||
"""
|
||||
SELECT id, email, display_name, atlassian_url, atlassian_service,
|
||||
created_at, last_login
|
||||
FROM users
|
||||
WHERE id = ?
|
||||
""",
|
||||
(user_id,)
|
||||
).fetchone()
|
||||
|
||||
if user:
|
||||
return dict(user)
|
||||
return None
|
||||
|
||||
|
||||
# Singleton instance
|
||||
_auth_instance: Optional[AtlassianAuth] = None
|
||||
|
||||
|
||||
def get_auth() -> AtlassianAuth:
|
||||
"""Get singleton auth instance"""
|
||||
global _auth_instance
|
||||
if _auth_instance is None:
|
||||
_auth_instance = AtlassianAuth()
|
||||
return _auth_instance
|
||||
Reference in New Issue
Block a user