devsquad 3.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- devsquad-3.6.0.dist-info/METADATA +944 -0
- devsquad-3.6.0.dist-info/RECORD +95 -0
- devsquad-3.6.0.dist-info/WHEEL +5 -0
- devsquad-3.6.0.dist-info/entry_points.txt +2 -0
- devsquad-3.6.0.dist-info/licenses/LICENSE +21 -0
- devsquad-3.6.0.dist-info/top_level.txt +2 -0
- scripts/__init__.py +0 -0
- scripts/ai_semantic_matcher.py +512 -0
- scripts/alert_manager.py +505 -0
- scripts/api/__init__.py +43 -0
- scripts/api/models.py +386 -0
- scripts/api/routes/__init__.py +20 -0
- scripts/api/routes/dispatch.py +348 -0
- scripts/api/routes/lifecycle.py +330 -0
- scripts/api/routes/metrics_gates.py +347 -0
- scripts/api_server.py +318 -0
- scripts/auth.py +451 -0
- scripts/cli/__init__.py +1 -0
- scripts/cli/cli_visual.py +642 -0
- scripts/cli.py +1094 -0
- scripts/collaboration/__init__.py +212 -0
- scripts/collaboration/_version.py +1 -0
- scripts/collaboration/agent_briefing.py +656 -0
- scripts/collaboration/ai_semantic_matcher.py +260 -0
- scripts/collaboration/anchor_checker.py +281 -0
- scripts/collaboration/anti_rationalization.py +470 -0
- scripts/collaboration/async_integration_example.py +255 -0
- scripts/collaboration/batch_scheduler.py +149 -0
- scripts/collaboration/checkpoint_manager.py +561 -0
- scripts/collaboration/ci_feedback_adapter.py +351 -0
- scripts/collaboration/code_map_generator.py +247 -0
- scripts/collaboration/concern_pack_loader.py +352 -0
- scripts/collaboration/confidence_score.py +496 -0
- scripts/collaboration/config_loader.py +188 -0
- scripts/collaboration/consensus.py +244 -0
- scripts/collaboration/context_compressor.py +533 -0
- scripts/collaboration/coordinator.py +668 -0
- scripts/collaboration/dispatcher.py +1636 -0
- scripts/collaboration/dual_layer_context.py +128 -0
- scripts/collaboration/enhanced_worker.py +539 -0
- scripts/collaboration/feature_usage_tracker.py +206 -0
- scripts/collaboration/five_axis_consensus.py +334 -0
- scripts/collaboration/input_validator.py +401 -0
- scripts/collaboration/integration_example.py +287 -0
- scripts/collaboration/intent_workflow_mapper.py +350 -0
- scripts/collaboration/language_parsers.py +269 -0
- scripts/collaboration/lifecycle_protocol.py +1446 -0
- scripts/collaboration/llm_backend.py +453 -0
- scripts/collaboration/llm_cache.py +448 -0
- scripts/collaboration/llm_cache_async.py +347 -0
- scripts/collaboration/llm_retry.py +387 -0
- scripts/collaboration/llm_retry_async.py +389 -0
- scripts/collaboration/mce_adapter.py +597 -0
- scripts/collaboration/memory_bridge.py +1607 -0
- scripts/collaboration/models.py +537 -0
- scripts/collaboration/null_providers.py +297 -0
- scripts/collaboration/operation_classifier.py +289 -0
- scripts/collaboration/output_slicer.py +225 -0
- scripts/collaboration/performance_monitor.py +462 -0
- scripts/collaboration/permission_guard.py +865 -0
- scripts/collaboration/prompt_assembler.py +756 -0
- scripts/collaboration/prompt_variant_generator.py +483 -0
- scripts/collaboration/protocols.py +267 -0
- scripts/collaboration/report_formatter.py +352 -0
- scripts/collaboration/retrospective.py +279 -0
- scripts/collaboration/role_matcher.py +92 -0
- scripts/collaboration/role_template_market.py +352 -0
- scripts/collaboration/rule_collector.py +678 -0
- scripts/collaboration/scratchpad.py +346 -0
- scripts/collaboration/skill_registry.py +151 -0
- scripts/collaboration/skillifier.py +878 -0
- scripts/collaboration/standardized_role_template.py +317 -0
- scripts/collaboration/task_completion_checker.py +237 -0
- scripts/collaboration/test_quality_guard.py +695 -0
- scripts/collaboration/unified_gate_engine.py +598 -0
- scripts/collaboration/usage_tracker.py +309 -0
- scripts/collaboration/user_friendly_error.py +176 -0
- scripts/collaboration/verification_gate.py +312 -0
- scripts/collaboration/warmup_manager.py +635 -0
- scripts/collaboration/worker.py +513 -0
- scripts/collaboration/workflow_engine.py +684 -0
- scripts/dashboard.py +1088 -0
- scripts/generate_benchmark_report.py +786 -0
- scripts/history_manager.py +604 -0
- scripts/mcp_server.py +289 -0
- skills/__init__.py +32 -0
- skills/dispatch/handler.py +52 -0
- skills/intent/handler.py +59 -0
- skills/registry.py +67 -0
- skills/retrospective/__init__.py +0 -0
- skills/retrospective/handler.py +125 -0
- skills/review/handler.py +356 -0
- skills/security/handler.py +454 -0
- skills/test/__init__.py +0 -0
- skills/test/handler.py +78 -0
scripts/auth.py
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
DevSquad Authentication Module
|
|
5
|
+
|
|
6
|
+
Provides authentication and authorization for Streamlit Dashboard.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
- Basic authentication with configurable credentials
|
|
10
|
+
- Role-based access control (admin/operator/viewer)
|
|
11
|
+
- Session management
|
|
12
|
+
- OAuth2 support (optional)
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
from scripts.auth import AuthManager, require_auth, check_permission
|
|
16
|
+
|
|
17
|
+
auth = AuthManager(config_path="config/deployment.yaml")
|
|
18
|
+
auth.authenticate() # Call in Streamlit app
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import hashlib
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from datetime import datetime, timedelta
|
|
27
|
+
from enum import Enum
|
|
28
|
+
from functools import wraps
|
|
29
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
30
|
+
|
|
31
|
+
import yaml
|
|
32
|
+
|
|
33
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class UserRole(Enum):
|
|
39
|
+
"""User roles for access control."""
|
|
40
|
+
ADMIN = "admin"
|
|
41
|
+
OPERATOR = "operator"
|
|
42
|
+
VIEWER = "viewer"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class User:
|
|
47
|
+
"""Authenticated user information."""
|
|
48
|
+
username: str
|
|
49
|
+
email: str
|
|
50
|
+
name: str
|
|
51
|
+
role: UserRole
|
|
52
|
+
authenticated_at: datetime
|
|
53
|
+
session_id: str
|
|
54
|
+
|
|
55
|
+
def can_execute_phases(self) -> bool:
|
|
56
|
+
"""Check if user can execute lifecycle phases."""
|
|
57
|
+
return self.role in [UserRole.ADMIN, UserRole.OPERATOR]
|
|
58
|
+
|
|
59
|
+
def can_view_metrics(self) -> bool:
|
|
60
|
+
"""Check if user can view metrics."""
|
|
61
|
+
return True # All roles can view metrics
|
|
62
|
+
|
|
63
|
+
def can_modify_config(self) -> bool:
|
|
64
|
+
"""Check if user can modify configuration."""
|
|
65
|
+
return self.role == UserRole.ADMIN
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AuthManager:
|
|
69
|
+
"""
|
|
70
|
+
Authentication manager for DevSquad dashboard.
|
|
71
|
+
|
|
72
|
+
Handles user authentication, session management,
|
|
73
|
+
and role-based access control.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, config_path: Optional[str] = None):
|
|
77
|
+
"""
|
|
78
|
+
Initialize authentication manager.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
config_path: Path to deployment configuration file.
|
|
82
|
+
If None, uses default config/deployment.yaml
|
|
83
|
+
"""
|
|
84
|
+
self.config_path = config_path or os.path.join(
|
|
85
|
+
os.path.dirname(os.path.dirname(__file__)),
|
|
86
|
+
"config",
|
|
87
|
+
"deployment.yaml"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
self.config = self._load_config()
|
|
91
|
+
self.auth_enabled = self.config.get("authentication", {}).get("enabled", False)
|
|
92
|
+
self.credentials = self._get_credentials()
|
|
93
|
+
self.cookie_settings = self.config.get("authentication", {}).get("cookie", {})
|
|
94
|
+
|
|
95
|
+
# Validate configuration security
|
|
96
|
+
self._validate_config_security()
|
|
97
|
+
|
|
98
|
+
logger.info(f"AuthManager initialized (enabled={self.auth_enabled})")
|
|
99
|
+
|
|
100
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
101
|
+
"""Load deployment configuration from YAML file."""
|
|
102
|
+
try:
|
|
103
|
+
if os.path.exists(self.config_path):
|
|
104
|
+
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
105
|
+
return yaml.safe_load(f) or {}
|
|
106
|
+
else:
|
|
107
|
+
logger.warning(f"Config file not found: {self.config_path}")
|
|
108
|
+
return {}
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logger.error(f"Failed to load config: {e}")
|
|
111
|
+
return {}
|
|
112
|
+
|
|
113
|
+
def _get_credentials(self) -> Dict[str, Dict]:
|
|
114
|
+
"""Get credentials from configuration."""
|
|
115
|
+
auth_config = self.config.get("authentication", {})
|
|
116
|
+
return auth_config.get("credentials", {}).get("usernames", {})
|
|
117
|
+
|
|
118
|
+
def _validate_config_security(self):
|
|
119
|
+
"""
|
|
120
|
+
Validate configuration for security issues.
|
|
121
|
+
|
|
122
|
+
Warns about:
|
|
123
|
+
- Placeholder passwords
|
|
124
|
+
- Default session keys
|
|
125
|
+
- Insecure configurations
|
|
126
|
+
"""
|
|
127
|
+
if not self.auth_enabled:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
warnings = []
|
|
131
|
+
|
|
132
|
+
# Check for placeholder passwords
|
|
133
|
+
placeholder_patterns = [
|
|
134
|
+
"hashed_password_here",
|
|
135
|
+
"password",
|
|
136
|
+
"changeme",
|
|
137
|
+
"default",
|
|
138
|
+
"your_password",
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
for username, cred in self.credentials.items():
|
|
142
|
+
password = cred.get("password", "")
|
|
143
|
+
if any(pattern in password.lower() for pattern in placeholder_patterns):
|
|
144
|
+
warnings.append(
|
|
145
|
+
f"User '{username}' has placeholder password: {password[:20]}..."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Check for default session key
|
|
149
|
+
cookie_key = self.cookie_settings.get("key", "")
|
|
150
|
+
default_keys = [
|
|
151
|
+
"devsquad_session_key_change_in_production",
|
|
152
|
+
"change_this_key",
|
|
153
|
+
"default_secret_key",
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
if any(key == cookie_key for key in default_keys):
|
|
157
|
+
warnings.append(
|
|
158
|
+
f"Using default session key: {cookie_key}. "
|
|
159
|
+
"Generate a secure key with: python -c \"import secrets; print(secrets.token_hex(32))\""
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Log warnings
|
|
163
|
+
if warnings:
|
|
164
|
+
logger.warning("=" * 60)
|
|
165
|
+
logger.warning("SECURITY WARNINGS - Configuration Issues Detected:")
|
|
166
|
+
for warning in warnings:
|
|
167
|
+
logger.warning(f" ⚠️ {warning}")
|
|
168
|
+
logger.warning("=" * 60)
|
|
169
|
+
logger.warning(
|
|
170
|
+
"Please update config/deployment.yaml with secure values before production use."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def _hash_password(self, password: str) -> str:
|
|
174
|
+
"""Hash password using SHA-256."""
|
|
175
|
+
return hashlib.sha256(password.encode()).hexdigest()
|
|
176
|
+
|
|
177
|
+
def verify_credentials(self, username: str, password: str) -> Optional[User]:
|
|
178
|
+
"""
|
|
179
|
+
Verify user credentials.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
username: Username to verify
|
|
183
|
+
password: Plain text password
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
User object if authenticated, None otherwise
|
|
187
|
+
"""
|
|
188
|
+
if username not in self.credentials:
|
|
189
|
+
logger.warning(f"Login attempt for unknown user: {username}")
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
cred = self.credentials[username]
|
|
193
|
+
stored_password_hash = cred.get("password", "")
|
|
194
|
+
input_password_hash = self._hash_password(password)
|
|
195
|
+
|
|
196
|
+
if input_password_hash != stored_password_hash:
|
|
197
|
+
logger.warning(f"Failed login attempt for user: {username}")
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
# Create user object
|
|
201
|
+
try:
|
|
202
|
+
role = UserRole(cred.get("role", "viewer"))
|
|
203
|
+
except ValueError:
|
|
204
|
+
role = UserRole.VIEWER
|
|
205
|
+
|
|
206
|
+
user = User(
|
|
207
|
+
username=username,
|
|
208
|
+
email=cred.get("email", ""),
|
|
209
|
+
name=cred.get("name", username),
|
|
210
|
+
role=role,
|
|
211
|
+
authenticated_at=datetime.now(),
|
|
212
|
+
session_id=hashlib.md5(
|
|
213
|
+
f"{username}{datetime.now().isoformat()}".encode()
|
|
214
|
+
).hexdigest()[:16]
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
logger.info(f"User authenticated: {username} (role={role.value})")
|
|
218
|
+
return user
|
|
219
|
+
|
|
220
|
+
def authenticate_streamlit(self):
|
|
221
|
+
"""
|
|
222
|
+
Authenticate user in Streamlit application.
|
|
223
|
+
|
|
224
|
+
This method should be called at the beginning of a Streamlit app.
|
|
225
|
+
It handles the login form and session state management.
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
import streamlit as st
|
|
229
|
+
|
|
230
|
+
if not self.auth_enabled:
|
|
231
|
+
# Authentication disabled, set default admin user
|
|
232
|
+
if "user" not in st.session_state:
|
|
233
|
+
st.session_state.user = User(
|
|
234
|
+
username="admin",
|
|
235
|
+
email="admin@devsquad.local",
|
|
236
|
+
name="Administrator",
|
|
237
|
+
role=UserRole.ADMIN,
|
|
238
|
+
authenticated_at=datetime.now(),
|
|
239
|
+
session_id="dev_mode"
|
|
240
|
+
)
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
# Check if already authenticated
|
|
244
|
+
if "user" in st.session_state:
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# Show login form
|
|
248
|
+
st.title("🔐 DevSquad Login")
|
|
249
|
+
|
|
250
|
+
col1, col2 = st.columns([1, 2])
|
|
251
|
+
|
|
252
|
+
with col1:
|
|
253
|
+
st.image(
|
|
254
|
+
"https://streamlit.io/images/brand/streamlit-logo-primary-colormark-lighttext.png",
|
|
255
|
+
width=120
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
with col2:
|
|
259
|
+
st.markdown("""
|
|
260
|
+
### Welcome to DevSquad Dashboard
|
|
261
|
+
|
|
262
|
+
Please enter your credentials to access the lifecycle monitoring system.
|
|
263
|
+
""")
|
|
264
|
+
|
|
265
|
+
with st.form("login_form"):
|
|
266
|
+
username = st.text_input("Username")
|
|
267
|
+
password = st.text_input("Password", type="password")
|
|
268
|
+
submitted = st.form_submit_button("Login", type="primary")
|
|
269
|
+
|
|
270
|
+
if submitted:
|
|
271
|
+
user = self.verify_credentials(username, password)
|
|
272
|
+
if user:
|
|
273
|
+
st.session_state.user = user
|
|
274
|
+
st.success(f"✅ Welcome, {user.name}!")
|
|
275
|
+
st.rerun()
|
|
276
|
+
else:
|
|
277
|
+
st.error("❌ Invalid username or password")
|
|
278
|
+
|
|
279
|
+
# Stop execution if not authenticated
|
|
280
|
+
st.stop()
|
|
281
|
+
|
|
282
|
+
except ImportError:
|
|
283
|
+
logger.warning("Streamlit not available, skipping authentication UI")
|
|
284
|
+
|
|
285
|
+
def get_current_user(self) -> Optional[User]:
|
|
286
|
+
"""
|
|
287
|
+
Get currently authenticated user.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
User object if authenticated, None otherwise
|
|
291
|
+
"""
|
|
292
|
+
try:
|
|
293
|
+
import streamlit as st
|
|
294
|
+
return st.session_state.get("user")
|
|
295
|
+
except ImportError:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
def logout(self):
|
|
299
|
+
"""Logout current user."""
|
|
300
|
+
try:
|
|
301
|
+
import streamlit as st
|
|
302
|
+
if "user" in st.session_state:
|
|
303
|
+
del st.session_state.user
|
|
304
|
+
st.rerun()
|
|
305
|
+
except ImportError:
|
|
306
|
+
pass
|
|
307
|
+
|
|
308
|
+
def get_login_button(self):
|
|
309
|
+
"""
|
|
310
|
+
Generate logout button for Streamlit sidebar.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Streamlit button or None
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
import streamlit as st
|
|
317
|
+
|
|
318
|
+
user = self.get_current_user()
|
|
319
|
+
if user and self.auth_enabled:
|
|
320
|
+
if st.sidebar.button("🚪 Logout"):
|
|
321
|
+
self.logout()
|
|
322
|
+
|
|
323
|
+
st.sidebar.markdown(f"""
|
|
324
|
+
**Logged in as:** {user.name}
|
|
325
|
+
**Role:** `{user.role.value}`
|
|
326
|
+
**Session:** `{user.session_id[:8]}...`
|
|
327
|
+
""", unsafe_allow_html=True)
|
|
328
|
+
|
|
329
|
+
except ImportError:
|
|
330
|
+
pass
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def require_auth(func):
|
|
334
|
+
"""
|
|
335
|
+
Decorator to require authentication for API endpoints.
|
|
336
|
+
|
|
337
|
+
Usage:
|
|
338
|
+
@require_auth
|
|
339
|
+
def protected_endpoint():
|
|
340
|
+
return {"message": "This is protected"}
|
|
341
|
+
"""
|
|
342
|
+
@wraps(func)
|
|
343
|
+
def wrapper(*args, **kwargs):
|
|
344
|
+
_auth_instance = AuthManager.get_instance()
|
|
345
|
+
if _auth_instance and _auth_instance.enabled:
|
|
346
|
+
try:
|
|
347
|
+
import streamlit as st
|
|
348
|
+
user = st.session_state.get("user")
|
|
349
|
+
if not user:
|
|
350
|
+
logger.warning(f"Auth required but no session for {func.__name__}")
|
|
351
|
+
raise PermissionError("Authentication required")
|
|
352
|
+
except (ImportError, AttributeError):
|
|
353
|
+
pass
|
|
354
|
+
logger.debug(f"Auth check for {func.__name__}")
|
|
355
|
+
return func(*args, **kwargs)
|
|
356
|
+
return wrapper
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def check_permission(required_role: UserRole = UserRole.VIEWER):
|
|
360
|
+
"""
|
|
361
|
+
Decorator to check user permissions.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
required_role: Minimum role required to access the resource
|
|
365
|
+
"""
|
|
366
|
+
def decorator(func):
|
|
367
|
+
@wraps(func)
|
|
368
|
+
def wrapper(*args, **kwargs):
|
|
369
|
+
user = None
|
|
370
|
+
try:
|
|
371
|
+
import streamlit as st
|
|
372
|
+
user = st.session_state.get("user")
|
|
373
|
+
except (ImportError, AttributeError):
|
|
374
|
+
pass
|
|
375
|
+
|
|
376
|
+
if not user:
|
|
377
|
+
raise PermissionError("Not authenticated")
|
|
378
|
+
|
|
379
|
+
role_hierarchy = {
|
|
380
|
+
UserRole.VIEWER: 0,
|
|
381
|
+
UserRole.OPERATOR: 1,
|
|
382
|
+
UserRole.ADMIN: 2
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
if role_hierarchy.get(user.role, 0) < role_hierarchy.get(required_role, 0):
|
|
386
|
+
raise PermissionError(
|
|
387
|
+
f"Insufficient permissions. Required: {required_role.value}, "
|
|
388
|
+
f"Current: {user.role.value}"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
return func(*args, **kwargs)
|
|
392
|
+
return wrapper
|
|
393
|
+
return decorator
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def create_demo_credentials():
|
|
397
|
+
"""
|
|
398
|
+
Create demo credentials for testing purposes.
|
|
399
|
+
|
|
400
|
+
Generates hashed passwords for default users.
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
Dict with demo usernames and hashed passwords
|
|
404
|
+
"""
|
|
405
|
+
demo_users = {
|
|
406
|
+
"admin": {
|
|
407
|
+
"email": "admin@devsquad.local",
|
|
408
|
+
"name": "Administrator",
|
|
409
|
+
"password": "admin123",
|
|
410
|
+
"role": "admin"
|
|
411
|
+
},
|
|
412
|
+
"operator": {
|
|
413
|
+
"email": "operator@devsquad.local",
|
|
414
|
+
"name": "Operator",
|
|
415
|
+
"password": "operator123",
|
|
416
|
+
"role": "operator"
|
|
417
|
+
},
|
|
418
|
+
"viewer": {
|
|
419
|
+
"email": "viewer@devsquad.local",
|
|
420
|
+
"name": "Viewer",
|
|
421
|
+
"password": "viewer123",
|
|
422
|
+
"role": "viewer"
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
# Hash passwords
|
|
427
|
+
auth_manager = AuthManager.__new__(AuthManager)
|
|
428
|
+
for username, info in demo_users.items():
|
|
429
|
+
info["password"] = auth_manager._hash_password(info["password"])
|
|
430
|
+
|
|
431
|
+
print("\n📋 Demo Credentials Created:")
|
|
432
|
+
print("=" * 50)
|
|
433
|
+
for username, info in demo_users.items():
|
|
434
|
+
print(f"\nUsername: {username}")
|
|
435
|
+
print(f"Password: (see below)")
|
|
436
|
+
print(f"Role: {info['role']}")
|
|
437
|
+
print(f"Hashed Password: {info['password']}")
|
|
438
|
+
|
|
439
|
+
print("\n" + "=" * 50)
|
|
440
|
+
print("⚠️ Plain-text passwords for demo:")
|
|
441
|
+
print(" admin: admin123")
|
|
442
|
+
print(" operator: operator123")
|
|
443
|
+
print(" viewer: viewer123")
|
|
444
|
+
print("=" * 50 + "\n")
|
|
445
|
+
|
|
446
|
+
return demo_users
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
if __name__ == "__main__":
|
|
450
|
+
# Demo: Create test credentials
|
|
451
|
+
create_demo_credentials()
|
scripts/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# CLI Package
|