Files
autarch/core/user_profile.py

160 lines
5.0 KiB
Python
Raw Normal View History

"""
AUTARCH User Profile Manager
Handles user identity, preferences, and session management.
On first launch, creates a default profile in the user data directory.
Supports multiple users for shared deployments.
The profile stores non-sensitive user info (username, display name, role, etc.)
Sensitive data (API keys, passwords) goes in the keystore.
"""
import json
import logging
import os
import time
from pathlib import Path
from typing import Dict, Optional
_log = logging.getLogger('autarch.user_profile')
class UserProfile:
"""User profile and preferences."""
def __init__(self, profile_path: Path = None):
from core.paths import get_user_profile_path
self._path = profile_path or get_user_profile_path()
self._data: Dict = {}
self._load()
def _load(self):
"""Load profile from disk, or create default."""
if self._path.exists():
try:
with open(self._path) as f:
self._data = json.load(f)
_log.info(f'Loaded profile for {self._data.get("username", "unknown")}')
return
except Exception as e:
_log.error(f'Failed to load profile: {e}')
# Create default profile
self._data = self._create_default()
self._save()
_log.info('Created default user profile')
def _create_default(self) -> Dict:
"""Create the default user profile."""
username = os.getenv('USER', os.getenv('USERNAME', 'operator'))
return {
'username': username,
'display_name': username.capitalize(),
'role': 'admin',
'created': int(time.time()),
'last_login': int(time.time()),
'preferences': {
'theme': 'dark',
'llm_backend': 'local',
'hal_auto_analyze': True,
'hal_max_steps': 200,
'verbose': False,
'default_scan_target': 'local', # 'local' scans user device, 'server' scans server
'chat_memory_enabled': True,
},
'session': {
'last_module': '',
'last_target': '',
'last_scan_type': '',
},
}
def _save(self):
"""Save profile to disk."""
try:
self._path.parent.mkdir(parents=True, exist_ok=True)
with open(self._path, 'w') as f:
json.dump(self._data, f, indent=2)
except Exception as e:
_log.error(f'Failed to save profile: {e}')
@property
def username(self) -> str:
return self._data.get('username', 'default')
@property
def display_name(self) -> str:
return self._data.get('display_name', self.username)
@property
def role(self) -> str:
return self._data.get('role', 'user')
def get_preference(self, key: str, default=None):
"""Get a user preference value."""
return self._data.get('preferences', {}).get(key, default)
def set_preference(self, key: str, value):
"""Set a user preference and save."""
if 'preferences' not in self._data:
self._data['preferences'] = {}
self._data['preferences'][key] = value
self._save()
def get_session(self, key: str, default='') -> str:
"""Get a session value (non-persistent state hints)."""
return self._data.get('session', {}).get(key, default)
def set_session(self, key: str, value: str):
"""Set a session value."""
if 'session' not in self._data:
self._data['session'] = {}
self._data['session'][key] = value
self._save()
def update_last_login(self):
"""Record login timestamp."""
self._data['last_login'] = int(time.time())
self._save()
def to_dict(self) -> Dict:
"""Export profile as a dict (for API/display)."""
return {
'username': self.username,
'display_name': self.display_name,
'role': self.role,
'created': self._data.get('created', 0),
'last_login': self._data.get('last_login', 0),
'preferences': self._data.get('preferences', {}),
}
# Singleton
_profile_instance: Optional[UserProfile] = None
def get_profile() -> UserProfile:
"""Get the global user profile instance."""
global _profile_instance
if _profile_instance is None:
_profile_instance = UserProfile()
return _profile_instance
def init_profile_on_launch():
"""Called at app startup to ensure profile exists and update login time."""
profile = get_profile()
profile.update_last_login()
# Migrate old vault secrets to new keystore if needed
from core.paths import get_keystore_path
ks_path = get_keystore_path()
if not ks_path.exists():
from core.keystore import get_keystore
ks = get_keystore()
migrated = ks.migrate_from_vault()
if migrated:
_log.info(f'Migrated {migrated} secrets from vault to keystore on first run')
return profile