Commit 2b07302d authored by Your Name's avatar Your Name

Providers for users ok

parent d39326be
...@@ -101,26 +101,31 @@ class ClaudeAuth: ...@@ -101,26 +101,31 @@ class ClaudeAuth:
REDIRECT_URI = DEFAULT_REDIRECT_URI REDIRECT_URI = DEFAULT_REDIRECT_URI
CLI_USER_AGENT = CLI_USER_AGENT CLI_USER_AGENT = CLI_USER_AGENT
def __init__(self, credentials_file: Optional[str] = None, redirect_uri: Optional[str] = None): def __init__(self, credentials_file: Optional[str] = None, redirect_uri: Optional[str] = None, skip_initial_load: bool = False, save_callback: Optional[callable] = None):
""" """
Initialize Claude authentication. Initialize Claude authentication.
Args: Args:
credentials_file: Path to credentials file (default: ~/.aisbf/claude_credentials.json) credentials_file: Path to credentials file (default: ~/.aisbf/claude_credentials.json)
skip_initial_load: If True, do not load credentials from file on initialization
save_callback: Optional callback to save credentials instead of writing to file
""" """
if credentials_file: if credentials_file:
self.credentials_file = Path(credentials_file).expanduser() self.credentials_file = Path(credentials_file).expanduser()
else: else:
# Store credentials in ~/.aisbf/ directory (AISBF config directory) # Store credentials in ~/.aisbf/ directory (AISBF config directory)
self.credentials_file = Path.home() / ".aisbf" / "claude_credentials.json" self.credentials_file = Path.home() / ".aisbf" / "claude_credentials.json"
# Allow overriding redirect URI for reverse proxy deployments # Allow overriding redirect URI for reverse proxy deployments
self.redirect_uri = redirect_uri if redirect_uri is not None else DEFAULT_REDIRECT_URI self.redirect_uri = redirect_uri if redirect_uri is not None else DEFAULT_REDIRECT_URI
self.tokens = self._load_credentials() self.tokens = None
self._save_callback = save_callback
if not skip_initial_load:
self.tokens = self._load_credentials()
self._oauth_state = None # Store state for OAuth flow self._oauth_state = None # Store state for OAuth flow
self._code_verifier = None # Store verifier for OAuth flow self._code_verifier = None # Store verifier for OAuth flow
# Log TLS fingerprinting capability # Log TLS fingerprinting capability
if HAS_CURL_CFFI: if HAS_CURL_CFFI:
logger.info(f"ClaudeAuth initialized with TLS fingerprinting (curl_cffi) - credentials: {self.credentials_file}") logger.info(f"ClaudeAuth initialized with TLS fingerprinting (curl_cffi) - credentials: {self.credentials_file}")
...@@ -142,7 +147,25 @@ class ClaudeAuth: ...@@ -142,7 +147,25 @@ class ClaudeAuth:
return None return None
def _save_credentials(self, data: Dict): def _save_credentials(self, data: Dict):
"""Save credentials to file with file locking to prevent race conditions.""" """
Save credentials:
- If save_callback is provided, use it (database save for user providers)
- Otherwise, save to file with file locking to prevent race conditions
"""
self.tokens = data
if self._save_callback:
# User provider: ONLY use callback, NO file fallback EVER
try:
self._save_callback({'tokens': data})
logger.info("ClaudeAuth: Saved credentials via callback")
return
except Exception as e:
logger.error(f"ClaudeAuth: Failed to save credentials to database: {e}")
# DO NOT FALLBACK TO FILE SAVE FOR REGULAR USERS
raise
# Admin/global provider ONLY: save to file
try: try:
self.tokens = data self.tokens = data
# Store id_token if received (contains account info) # Store id_token if received (contains account info)
......
...@@ -54,13 +54,15 @@ class CodexOAuth2: ...@@ -54,13 +54,15 @@ class CodexOAuth2:
Supports authentication with OpenAI's Codex OAuth2 endpoints. Supports authentication with OpenAI's Codex OAuth2 endpoints.
""" """
def __init__(self, credentials_file: Optional[str] = None, issuer: Optional[str] = None): def __init__(self, credentials_file: Optional[str] = None, issuer: Optional[str] = None, skip_initial_load: bool = False, save_callback: Optional[callable] = None):
""" """
Initialize Codex OAuth2 client. Initialize Codex OAuth2 client.
Args: Args:
credentials_file: Path to credentials JSON file (default: ~/.aisbf/codex_credentials.json) credentials_file: Path to credentials JSON file (default: ~/.aisbf/codex_credentials.json)
issuer: OAuth2 issuer URL (default: https://auth.openai.com) issuer: OAuth2 issuer URL (default: https://auth.openai.com)
skip_initial_load: If True, do not load credentials from file on initialization
save_callback: Optional callback to save credentials instead of writing to file
""" """
# Expand and resolve path immediately to absolute path # Expand and resolve path immediately to absolute path
default_path = os.path.expanduser("~/.aisbf/codex_credentials.json") default_path = os.path.expanduser("~/.aisbf/codex_credentials.json")
...@@ -71,10 +73,12 @@ class CodexOAuth2: ...@@ -71,10 +73,12 @@ class CodexOAuth2:
self.credentials_file = os.path.abspath(expanded) self.credentials_file = os.path.abspath(expanded)
else: else:
self.credentials_file = default_path self.credentials_file = default_path
self.issuer = (issuer or DEFAULT_ISSUER).rstrip("/") self.issuer = (issuer or DEFAULT_ISSUER).rstrip("/")
self.credentials = None self.credentials = None
self._load_credentials() self._save_callback = save_callback
if not skip_initial_load:
self._load_credentials()
def _load_credentials(self) -> None: def _load_credentials(self) -> None:
"""Load credentials from file if it exists.""" """Load credentials from file if it exists."""
...@@ -89,11 +93,27 @@ class CodexOAuth2: ...@@ -89,11 +93,27 @@ class CodexOAuth2:
def _save_credentials(self, credentials: Dict[str, Any]) -> None: def _save_credentials(self, credentials: Dict[str, Any]) -> None:
""" """
Save credentials to file with secure permissions. Save credentials:
- If save_callback is provided, use it (database save for user providers)
- Otherwise, save to file with secure permissions (admin/global providers)
Args: Args:
credentials: Credentials dict to save credentials: Credentials dict to save
""" """
self.credentials = credentials
if self._save_callback:
# User provider: ONLY use callback, NO file fallback EVER
try:
self._save_callback(credentials)
logger.info(f"CodexOAuth2: Saved credentials via callback")
return
except Exception as e:
logger.error(f"CodexOAuth2: Failed to save credentials to database: {e}")
# DO NOT FALLBACK TO FILE SAVE FOR REGULAR USERS
raise
# Admin/global provider ONLY: save to file
try: try:
# Path is already expanded and absolute from __init__ # Path is already expanded and absolute from __init__
resolved_path = self.credentials_file resolved_path = self.credentials_file
......
...@@ -40,18 +40,21 @@ class KiloOAuth2: ...@@ -40,18 +40,21 @@ class KiloOAuth2:
Supports authentication with Kilo Gateway at https://api.kilo.ai. Supports authentication with Kilo Gateway at https://api.kilo.ai.
""" """
def __init__(self, credentials_file: Optional[str] = None, api_base: Optional[str] = None): def __init__(self, credentials_file: Optional[str] = None, api_base: Optional[str] = None, skip_initial_load: bool = False, save_callback: Optional[callable] = None):
""" """
Initialize Kilo OAuth2 client. Initialize Kilo OAuth2 client.
Args: Args:
credentials_file: Path to credentials JSON file (default: ~/.kilo_credentials.json) credentials_file: Path to credentials JSON file (default: ~/.kilo_credentials.json)
api_base: Base URL for Kilo API (default: https://api.kilo.ai) api_base: Base URL for Kilo API (default: https://api.kilo.ai)
skip_initial_load: If True, do not load credentials from file on initialization
""" """
self.credentials_file = os.path.expanduser(credentials_file) if credentials_file else os.path.expanduser("~/.kilo_credentials.json") self.credentials_file = os.path.expanduser(credentials_file) if credentials_file else os.path.expanduser("~/.kilo_credentials.json")
self.api_base = api_base or os.environ.get("KILO_API_URL", "https://api.kilo.ai") self.api_base = api_base or os.environ.get("KILO_API_URL", "https://api.kilo.ai")
self.credentials = None self.credentials = None
self._load_credentials() self._save_callback = save_callback
if not skip_initial_load:
self._load_credentials()
def _load_credentials(self) -> None: def _load_credentials(self) -> None:
"""Load credentials from file if it exists.""" """Load credentials from file if it exists."""
...@@ -66,17 +69,33 @@ class KiloOAuth2: ...@@ -66,17 +69,33 @@ class KiloOAuth2:
def _save_credentials(self, credentials: Dict[str, Any]) -> None: def _save_credentials(self, credentials: Dict[str, Any]) -> None:
""" """
Save credentials to file with secure permissions. Save credentials:
- If save_callback is provided, use it (database save for user providers)
- Otherwise, save to file with secure permissions (admin/global providers)
Args: Args:
credentials: Credentials dict to save credentials: Credentials dict to save
""" """
self.credentials = credentials
if self._save_callback:
# User provider: ONLY use callback, NO file fallback EVER
try:
self._save_callback(credentials)
logger.info(f"KiloOAuth2: Saved credentials via callback")
return
except Exception as e:
logger.error(f"KiloOAuth2: Failed to save credentials to database: {e}")
# DO NOT FALLBACK TO FILE SAVE FOR REGULAR USERS
raise
# Admin/global provider ONLY: save to file
try: try:
# Ensure directory exists # Ensure directory exists
cred_dir = os.path.dirname(self.credentials_file) cred_dir = os.path.dirname(self.credentials_file)
if cred_dir: # Only create if there's a directory component if cred_dir: # Only create if there's a directory component
os.makedirs(cred_dir, exist_ok=True) os.makedirs(cred_dir, exist_ok=True)
# Write credentials # Write credentials
with open(self.credentials_file, 'w') as f: with open(self.credentials_file, 'w') as f:
json.dump(credentials, f, indent=2) json.dump(credentials, f, indent=2)
......
...@@ -96,15 +96,17 @@ class QwenOAuth2: ...@@ -96,15 +96,17 @@ class QwenOAuth2:
Supports authentication with Qwen's OAuth2 endpoints and automatic token refresh. Supports authentication with Qwen's OAuth2 endpoints and automatic token refresh.
""" """
def __init__(self, credentials_file: Optional[str] = None): def __init__(self, credentials_file: Optional[str] = None, skip_initial_load: bool = False, save_callback: Optional[callable] = None):
""" """
Initialize Qwen OAuth2 client. Initialize Qwen OAuth2 client.
⚠️ WARNING: OAuth2 authentication for Qwen has been discontinued. ⚠️ WARNING: OAuth2 authentication for Qwen has been discontinued.
This client will not work with DashScope API. Use API key authentication instead. This client will not work with DashScope API. Use API key authentication instead.
Args: Args:
credentials_file: Path to credentials JSON file (default: ~/.aisbf/qwen_credentials.json) credentials_file: Path to credentials JSON file (default: ~/.aisbf/qwen_credentials.json)
skip_initial_load: If True, do not load credentials from file on initialization
save_callback: Optional callback to save credentials instead of writing to file
""" """
logger.warning( logger.warning(
"⚠️ Qwen OAuth2 service has been discontinued by Qwen. " "⚠️ Qwen OAuth2 service has been discontinued by Qwen. "
...@@ -116,7 +118,9 @@ class QwenOAuth2: ...@@ -116,7 +118,9 @@ class QwenOAuth2:
self.credentials = None self.credentials = None
self._file_mod_time = 0 self._file_mod_time = 0
self._last_check = 0 self._last_check = 0
self._load_credentials() self._save_callback = save_callback
if not skip_initial_load:
self._load_credentials()
def _load_credentials(self) -> None: def _load_credentials(self) -> None:
"""Load credentials from file if it exists.""" """Load credentials from file if it exists."""
...@@ -136,11 +140,27 @@ class QwenOAuth2: ...@@ -136,11 +140,27 @@ class QwenOAuth2:
def _save_credentials(self, credentials: Dict[str, Any]) -> None: def _save_credentials(self, credentials: Dict[str, Any]) -> None:
""" """
Save credentials to file with secure permissions and file locking. Save credentials:
- If save_callback is provided, use it (database save for user providers)
- Otherwise, save to file with secure permissions and file locking (admin/global providers)
Args: Args:
credentials: Credentials dict to save credentials: Credentials dict to save
""" """
self.credentials = credentials
if self._save_callback:
# User provider: ONLY use callback, NO file fallback EVER
try:
self._save_callback(credentials)
logger.info(f"QwenOAuth2: Saved credentials via callback")
return
except Exception as e:
logger.error(f"QwenOAuth2: Failed to save credentials to database: {e}")
# DO NOT FALLBACK TO FILE SAVE FOR REGULAR USERS
raise
# Admin/global provider ONLY: save to file
try: try:
# Ensure directory exists # Ensure directory exists
os.makedirs(os.path.dirname(self.credentials_file), exist_ok=True) os.makedirs(os.path.dirname(self.credentials_file), exist_ok=True)
......
...@@ -102,12 +102,21 @@ class ClaudeProviderHandler(BaseProviderHandler): ...@@ -102,12 +102,21 @@ class ClaudeProviderHandler(BaseProviderHandler):
def _load_auth_from_db(self, provider_id: str, credentials_file: str): def _load_auth_from_db(self, provider_id: str, credentials_file: str):
""" """
Load OAuth2 credentials from database for non-admin users. Load OAuth2 credentials:
Falls back to file-based credentials if not found in database. - Admin users (user_id=None): ONLY load from file
- Regular users: ONLY load from database, NO file fallback
""" """
from ..auth.claude import ClaudeAuth
import logging
if self.user_id is None:
# Admin user: ONLY use file-based credentials
logging.getLogger(__name__).info(f"ClaudeProviderHandler: Admin user, loading credentials from file: {credentials_file}")
return ClaudeAuth(credentials_file=credentials_file)
# Regular user: ONLY use database credentials, NO file fallback
try: try:
from ..database import get_database from ..database import get_database
from ..auth.claude import ClaudeAuth
db = get_database() db = get_database()
if db: if db:
db_creds = db.get_user_oauth2_credentials( db_creds = db.get_user_oauth2_credentials(
...@@ -116,22 +125,24 @@ class ClaudeProviderHandler(BaseProviderHandler): ...@@ -116,22 +125,24 @@ class ClaudeProviderHandler(BaseProviderHandler):
auth_type='claude_oauth2' auth_type='claude_oauth2'
) )
if db_creds and db_creds.get('credentials'): if db_creds and db_creds.get('credentials'):
# Create auth instance with database credentials # Create auth instance with skip_initial_load=True to avoid file read
auth = ClaudeAuth(credentials_file=credentials_file) # Pass save callback to save credentials back to database
# Override the loaded credentials with database credentials auth = ClaudeAuth(
credentials_file=credentials_file,
skip_initial_load=True,
save_callback=lambda creds: self._save_auth_to_db(creds)
)
# Set tokens directly from database
auth.tokens = db_creds['credentials'].get('tokens', {}) auth.tokens = db_creds['credentials'].get('tokens', {})
import logging import logging
logging.getLogger(__name__).info(f"ClaudeProviderHandler: Loaded credentials from database for user {self.user_id}") logging.getLogger(__name__).info(f"ClaudeProviderHandler: Loaded credentials from database for user {self.user_id}")
return auth return auth
except Exception as e: except Exception as e:
import logging
logging.getLogger(__name__).warning(f"ClaudeProviderHandler: Failed to load credentials from database: {e}") logging.getLogger(__name__).warning(f"ClaudeProviderHandler: Failed to load credentials from database: {e}")
# Fall back to file-based credentials # For regular users, NO file fallback - return empty auth instance
from ..auth.claude import ClaudeAuth logging.getLogger(__name__).info(f"ClaudeProviderHandler: No database credentials found for user {self.user_id}, returning unauthenticated instance")
import logging return ClaudeAuth(credentials_file=credentials_file, skip_initial_load=True)
logging.getLogger(__name__).info(f"ClaudeProviderHandler: Falling back to file-based credentials for user {self.user_id}")
return ClaudeAuth(credentials_file=credentials_file)
def _init_session_identifiers(self): def _init_session_identifiers(self):
"""Initialize persistent session identifiers (device_id, account_uuid, session_id).""" """Initialize persistent session identifiers (device_id, account_uuid, session_id)."""
......
...@@ -112,9 +112,22 @@ class CodexProviderHandler(BaseProviderHandler): ...@@ -112,9 +112,22 @@ class CodexProviderHandler(BaseProviderHandler):
def _load_oauth2_from_db(self, provider_id: str, credentials_file: str, issuer: str) -> CodexOAuth2: def _load_oauth2_from_db(self, provider_id: str, credentials_file: str, issuer: str) -> CodexOAuth2:
""" """
Load OAuth2 credentials from database for non-admin users. Load OAuth2 credentials:
Falls back to file-based credentials if not found in database. - Admin users (user_id=None): ONLY load from file
- Regular users: ONLY load from database, NO file fallback
""" """
from ..auth.codex import CodexOAuth2
import logging
if self.user_id is None:
# Admin user: ONLY use file-based credentials
logging.getLogger(__name__).info(f"CodexProviderHandler: Admin user, loading credentials from file: {credentials_file}")
return CodexOAuth2(
credentials_file=credentials_file,
issuer=issuer,
)
# Regular user: ONLY use database credentials, NO file fallback
try: try:
from ..database import get_database from ..database import get_database
db = get_database() db = get_database()
...@@ -125,23 +138,28 @@ class CodexProviderHandler(BaseProviderHandler): ...@@ -125,23 +138,28 @@ class CodexProviderHandler(BaseProviderHandler):
auth_type='codex_oauth2' auth_type='codex_oauth2'
) )
if db_creds and db_creds.get('credentials'): if db_creds and db_creds.get('credentials'):
# Create OAuth2 instance with database credentials # Create OAuth2 instance with skip_initial_load=True to avoid file read
# Pass save callback to save credentials back to database
oauth2 = CodexOAuth2( oauth2 = CodexOAuth2(
credentials_file=credentials_file, credentials_file=credentials_file,
issuer=issuer, issuer=issuer,
skip_initial_load=True,
save_callback=lambda creds: self._save_oauth2_to_db(creds)
) )
# Override the loaded credentials with database credentials # Set credentials directly from database
oauth2.credentials = db_creds['credentials'] oauth2.credentials = db_creds['credentials']
logger.info(f"CodexProviderHandler: Loaded credentials from database for user {self.user_id}") logging.getLogger(__name__).info(f"CodexProviderHandler: Loaded credentials from database for user {self.user_id}")
return oauth2 return oauth2
except Exception as e: except Exception as e:
logger.warning(f"CodexProviderHandler: Failed to load credentials from database: {e}") logging.getLogger(__name__).warning(f"CodexProviderHandler: Failed to load credentials from database: {e}")
# Fall back to file-based credentials # For regular users, NO file fallback - return empty auth instance
logger.info(f"CodexProviderHandler: Falling back to file-based credentials for user {self.user_id}") logging.getLogger(__name__).info(f"CodexProviderHandler: No database credentials found for user {self.user_id}, returning unauthenticated instance")
return CodexOAuth2( return CodexOAuth2(
credentials_file=credentials_file, credentials_file=credentials_file,
issuer=issuer, issuer=issuer,
skip_initial_load=True,
save_callback=lambda creds: self._save_oauth2_to_db(creds)
) )
async def _get_valid_api_key(self) -> str: async def _get_valid_api_key(self) -> str:
......
...@@ -110,12 +110,21 @@ class KiloProviderHandler(BaseProviderHandler): ...@@ -110,12 +110,21 @@ class KiloProviderHandler(BaseProviderHandler):
def _load_oauth2_from_db(self, provider_id: str, credentials_file: str, api_base: str): def _load_oauth2_from_db(self, provider_id: str, credentials_file: str, api_base: str):
""" """
Load OAuth2 credentials from database for non-admin users. Load OAuth2 credentials:
Falls back to file-based credentials if not found in database. - Admin users (user_id=None): ONLY load from file
- Regular users: ONLY load from database, NO file fallback
""" """
from ..auth.kilo import KiloOAuth2
import logging
if self.user_id is None:
# Admin user: ONLY use file-based credentials
logging.getLogger(__name__).info(f"KiloProviderHandler: Admin user, loading credentials from file: {credentials_file}")
return KiloOAuth2(credentials_file=credentials_file, api_base=api_base)
# Regular user: ONLY use database credentials, NO file fallback
try: try:
from ..database import get_database from ..database import get_database
from ..auth.kilo import KiloOAuth2
db = get_database() db = get_database()
if db: if db:
db_creds = db.get_user_oauth2_credentials( db_creds = db.get_user_oauth2_credentials(
...@@ -124,22 +133,29 @@ class KiloProviderHandler(BaseProviderHandler): ...@@ -124,22 +133,29 @@ class KiloProviderHandler(BaseProviderHandler):
auth_type='kilo_oauth2' auth_type='kilo_oauth2'
) )
if db_creds and db_creds.get('credentials'): if db_creds and db_creds.get('credentials'):
# Create OAuth2 instance with database credentials # Create OAuth2 instance with skip_initial_load=True to avoid file read
oauth2 = KiloOAuth2(credentials_file=credentials_file, api_base=api_base) # Pass save callback to save credentials back to database
# Override the loaded credentials with database credentials oauth2 = KiloOAuth2(
credentials_file=credentials_file,
api_base=api_base,
skip_initial_load=True,
save_callback=lambda creds: self._save_oauth2_to_db(creds)
)
# Set credentials directly from database
oauth2.credentials = db_creds['credentials'] oauth2.credentials = db_creds['credentials']
import logging
logging.getLogger(__name__).info(f"KiloProviderHandler: Loaded credentials from database for user {self.user_id}") logging.getLogger(__name__).info(f"KiloProviderHandler: Loaded credentials from database for user {self.user_id}")
return oauth2 return oauth2
except Exception as e: except Exception as e:
import logging
logging.getLogger(__name__).warning(f"KiloProviderHandler: Failed to load credentials from database: {e}") logging.getLogger(__name__).warning(f"KiloProviderHandler: Failed to load credentials from database: {e}")
# Fall back to file-based credentials # For regular users, NO file fallback - return empty auth instance
from ..auth.kilo import KiloOAuth2 logging.getLogger(__name__).info(f"KiloProviderHandler: No database credentials found for user {self.user_id}, returning unauthenticated instance")
import logging return KiloOAuth2(
logging.getLogger(__name__).info(f"KiloProviderHandler: Falling back to file-based credentials for user {self.user_id}") credentials_file=credentials_file,
return KiloOAuth2(credentials_file=credentials_file, api_base=api_base) api_base=api_base,
skip_initial_load=True,
save_callback=lambda creds: self._save_oauth2_to_db(creds)
)
def _save_oauth2_to_db(self, credentials: Dict) -> None: def _save_oauth2_to_db(self, credentials: Dict) -> None:
""" """
......
...@@ -98,12 +98,21 @@ class QwenProviderHandler(BaseProviderHandler): ...@@ -98,12 +98,21 @@ class QwenProviderHandler(BaseProviderHandler):
def _load_auth_from_db(self, provider_id: str, credentials_file: str): def _load_auth_from_db(self, provider_id: str, credentials_file: str):
""" """
Load OAuth2 credentials from database for non-admin users. Load OAuth2 credentials:
Falls back to file-based credentials if not found in database. - Admin users (user_id=None): ONLY load from file
- Regular users: ONLY load from database, NO file fallback
""" """
from ..auth.qwen import QwenOAuth2
import logging
if self.user_id is None:
# Admin user: ONLY use file-based credentials
logging.getLogger(__name__).info(f"QwenProviderHandler: Admin user, loading credentials from file: {credentials_file}")
return QwenOAuth2(credentials_file=credentials_file)
# Regular user: ONLY use database credentials, NO file fallback
try: try:
from ..database import get_database from ..database import get_database
from ..auth.qwen import QwenOAuth2
db = get_database() db = get_database()
if db: if db:
db_creds = db.get_user_oauth2_credentials( db_creds = db.get_user_oauth2_credentials(
...@@ -112,22 +121,52 @@ class QwenProviderHandler(BaseProviderHandler): ...@@ -112,22 +121,52 @@ class QwenProviderHandler(BaseProviderHandler):
auth_type='qwen_oauth2' auth_type='qwen_oauth2'
) )
if db_creds and db_creds.get('credentials'): if db_creds and db_creds.get('credentials'):
# Create auth instance with database credentials # Create auth instance with skip_initial_load=True to avoid file read
auth = QwenOAuth2(credentials_file=credentials_file) # Pass save callback to save credentials back to database
# Override the loaded credentials with database credentials auth = QwenOAuth2(
credentials_file=credentials_file,
skip_initial_load=True,
save_callback=lambda creds: self._save_auth_to_db(creds)
)
# Set credentials directly from database
auth.credentials = db_creds['credentials'] auth.credentials = db_creds['credentials']
import logging
logging.getLogger(__name__).info(f"QwenProviderHandler: Loaded credentials from database for user {self.user_id}") logging.getLogger(__name__).info(f"QwenProviderHandler: Loaded credentials from database for user {self.user_id}")
return auth return auth
except Exception as e: except Exception as e:
import logging
logging.getLogger(__name__).warning(f"QwenProviderHandler: Failed to load credentials from database: {e}") logging.getLogger(__name__).warning(f"QwenProviderHandler: Failed to load credentials from database: {e}")
# Fall back to file-based credentials # For regular users, NO file fallback - return empty auth instance
from ..auth.qwen import QwenOAuth2 logging.getLogger(__name__).info(f"QwenProviderHandler: No database credentials found for user {self.user_id}, returning unauthenticated instance")
import logging return QwenOAuth2(
logging.getLogger(__name__).info(f"QwenProviderHandler: Falling back to file-based credentials for user {self.user_id}") credentials_file=credentials_file,
return QwenOAuth2(credentials_file=credentials_file) skip_initial_load=True,
save_callback=lambda creds: self._save_auth_to_db(creds)
)
def _save_auth_to_db(self, credentials: Dict) -> None:
"""
Save OAuth2 credentials to database for non-admin users.
This is called after successful device flow authentication.
"""
if self.user_id is None:
# Admin user uses file-based credentials, nothing to save to DB
return
try:
from ..database import get_database
db = get_database()
if db:
db.save_user_oauth2_credentials(
user_id=self.user_id,
provider_id=self.provider_id,
auth_type='qwen_oauth2',
credentials=credentials
)
import logging
logging.getLogger(__name__).info(f"QwenProviderHandler: Saved credentials to database for user {self.user_id}")
except Exception as e:
import logging
logging.getLogger(__name__).warning(f"QwenProviderHandler: Failed to save credentials to database: {e}")
async def _get_sdk_client(self): async def _get_sdk_client(self):
"""Get or create an OpenAI SDK client configured with authentication (OAuth2 or API key).""" """Get or create an OpenAI SDK client configured with authentication (OAuth2 or API key)."""
......
This diff is collapsed.
...@@ -134,6 +134,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. ...@@ -134,6 +134,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% block extra_js %} {% block extra_js %}
<script> <script>
let providersData = {}; let providersData = {};
let expandedProviders = new Set();
let rawProviders = {{ user_providers_json | safe }}; let rawProviders = {{ user_providers_json | safe }};
// Convert user providers format to the format expected by the UI // Convert user providers format to the format expected by the UI
...@@ -141,8 +142,6 @@ rawProviders.forEach(provider => { ...@@ -141,8 +142,6 @@ rawProviders.forEach(provider => {
providersData[provider.provider_id] = provider.config; providersData[provider.provider_id] = provider.config;
}); });
let expandedProviders = new Set();
// Chunk size: 512KB chunks for maximum compatibility with restrictive proxies // Chunk size: 512KB chunks for maximum compatibility with restrictive proxies
const CHUNK_SIZE = 512 * 1024; const CHUNK_SIZE = 512 * 1024;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment