diff --git a/invokeai/app/util/user_management.py b/invokeai/app/util/user_management.py new file mode 100644 index 00000000000..8f8a4d01d8c --- /dev/null +++ b/invokeai/app/util/user_management.py @@ -0,0 +1,556 @@ +"""User management command entry points for InvokeAI. + +These functions are registered as console scripts in pyproject.toml and can be +called from the command line after installing the package: + + invoke-useradd -- add a user + invoke-userdel -- delete a user + invoke-userlist -- list users + invoke-usermod -- modify a user +""" + +import argparse +import getpass +import json +import sys + +# --------------------------------------------------------------------------- +# useradd +# --------------------------------------------------------------------------- + + +def _add_user_interactive() -> bool: + """Add a user interactively by prompting for details.""" + from invokeai.app.services.auth.password_utils import validate_password_strength + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_common import UserCreateRequest + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + print("=== Add InvokeAI User ===\n") + + email = input("Email address: ").strip() + if not email: + print("Error: Email is required") + return False + + display_name = input("Display name (optional): ").strip() or None + + while True: + password = getpass.getpass("Password: ") + password_confirm = getpass.getpass("Confirm password: ") + + if password != password_confirm: + print("Error: Passwords do not match. Please try again.\n") + continue + + is_valid, error_msg = validate_password_strength(password) + if not is_valid: + print(f"Error: {error_msg}\n") + continue + + break + + is_admin_input = input("Make this user an administrator? (y/N): ").strip().lower() + is_admin = is_admin_input in ("y", "yes") + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin) + user = user_service.create(user_data) + + print("\n✅ User created successfully!") + print(f" User ID: {user.user_id}") + print(f" Email: {user.email}") + print(f" Display Name: {user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if user.is_admin else 'No'}") + print(f" Active: {'Yes' if user.is_active else 'No'}") + return True + + except ValueError as e: + print(f"\n❌ Error: {e}") + return False + except Exception as e: + print(f"\n❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def _add_user_cli(email: str, password: str, display_name: str | None = None, is_admin: bool = False) -> bool: + """Add a user via CLI arguments.""" + from invokeai.app.services.auth.password_utils import validate_password_strength + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_common import UserCreateRequest + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + is_valid, error_msg = validate_password_strength(password) + if not is_valid: + print(f"❌ Password validation failed: {error_msg}") + return False + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin) + user = user_service.create(user_data) + + print("✅ User created successfully!") + print(f" User ID: {user.user_id}") + print(f" Email: {user.email}") + print(f" Display Name: {user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if user.is_admin else 'No'}") + print(f" Active: {'Yes' if user.is_active else 'No'}") + return True + + except ValueError as e: + print(f"❌ Error: {e}") + return False + except Exception as e: + print(f"❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def useradd() -> None: + """Entry point for ``invoke-useradd``.""" + parser = argparse.ArgumentParser( + description="Add a user to the InvokeAI database", + epilog="If no arguments are provided, the script will run in interactive mode.", + ) + parser.add_argument("--email", "-e", help="User email address") + parser.add_argument("--password", "-p", help="User password") + parser.add_argument("--name", "-n", help="User display name (optional)") + parser.add_argument("--admin", "-a", action="store_true", help="Make user an administrator") + + args = parser.parse_args() + + if args.email or args.password: + if not args.email or not args.password: + print("❌ Error: Both --email and --password are required when using CLI mode") + print(" Run without arguments for interactive mode") + sys.exit(1) + success = _add_user_cli(args.email, args.password, args.name, args.admin) + else: + success = _add_user_interactive() + + sys.exit(0 if success else 1) + + +# --------------------------------------------------------------------------- +# userdel +# --------------------------------------------------------------------------- + + +def _delete_user_interactive() -> bool: + """Delete a user interactively by prompting for email.""" + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + print("=== Delete InvokeAI User ===\n") + + email = input("Email address of user to delete: ").strip() + if not email: + print("Error: Email is required") + return False + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user = user_service.get_by_email(email) + if not user: + print(f"\n❌ Error: No user found with email '{email}'") + return False + + print("\nUser to delete:") + print(f" User ID: {user.user_id}") + print(f" Email: {user.email}") + print(f" Display Name: {user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if user.is_admin else 'No'}") + print(f" Active: {'Yes' if user.is_active else 'No'}") + + confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("Deletion cancelled.") + return False + + user_service.delete(user.user_id) + print("\n✅ User deleted successfully!") + return True + + except ValueError as e: + print(f"\n❌ Error: {e}") + return False + except Exception as e: + print(f"\n❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def _delete_user_cli(email: str, force: bool = False) -> bool: + """Delete a user via CLI arguments.""" + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user = user_service.get_by_email(email) + if not user: + print(f"❌ Error: No user found with email '{email}'") + return False + + if not force: + print("User to delete:") + print(f" User ID: {user.user_id}") + print(f" Email: {user.email}") + print(f" Display Name: {user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if user.is_admin else 'No'}") + print(f" Active: {'Yes' if user.is_active else 'No'}") + + confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower() + if confirm not in ("yes", "y"): + print("Deletion cancelled.") + return False + + user_service.delete(user.user_id) + print("✅ User deleted successfully!") + return True + + except ValueError as e: + print(f"❌ Error: {e}") + return False + except Exception as e: + print(f"❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def userdel() -> None: + """Entry point for ``invoke-userdel``.""" + parser = argparse.ArgumentParser( + description="Delete a user from the InvokeAI database", + epilog="If no arguments are provided, the script will run in interactive mode.", + ) + parser.add_argument("--email", "-e", help="User email address") + parser.add_argument("--force", "-f", action="store_true", help="Delete without confirmation prompt") + + args = parser.parse_args() + + if args.email: + success = _delete_user_cli(args.email, args.force) + else: + success = _delete_user_interactive() + + sys.exit(0 if success else 1) + + +# --------------------------------------------------------------------------- +# userlist +# --------------------------------------------------------------------------- + + +def _list_users_table() -> bool: + """List all users in a formatted table.""" + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + config = get_config() + logger = InvokeAILogger.get_logger(config=config) + db = SqliteDatabase(config.db_path, logger) + user_service = UserService(db) + + try: + users = user_service.list_users() + + if not users: + print("No users found in database.") + return True + + print("\n=== InvokeAI Users ===\n") + print(f"{'User ID':<36} {'Email':<30} {'Display Name':<20} {'Admin':<8} {'Active':<8}") + print("-" * 108) + + for user in users: + user_id = user.user_id + email = user.email[:29] if len(user.email) > 29 else user.email + raw_name = user.display_name or "" + name = raw_name[:19] if len(raw_name) > 19 else raw_name + is_admin = "Yes" if user.is_admin else "No" + is_active = "Yes" if user.is_active else "No" + print(f"{user_id:<36} {email:<30} {name:<20} {is_admin:<8} {is_active:<8}") + + print(f"\nTotal users: {len(users)}") + return True + + except Exception as e: + print(f"Error listing users: {e}") + return False + + +def _list_users_json() -> bool: + """List all users in JSON format.""" + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + config = get_config() + logger = InvokeAILogger.get_logger(config=config) + db = SqliteDatabase(config.db_path, logger) + user_service = UserService(db) + + try: + users = user_service.list_users() + + users_data = [ + { + "id": user.user_id, + "email": user.email, + "name": user.display_name, + "is_admin": user.is_admin, + "is_active": user.is_active, + } + for user in users + ] + + print(json.dumps(users_data, indent=2)) + return True + + except Exception as e: + print(f'{{"error": "{e}"}}', file=sys.stderr) + return False + + +def userlist() -> None: + """Entry point for ``invoke-userlist``.""" + parser = argparse.ArgumentParser( + description="List users from the InvokeAI database", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + invoke-userlist + invoke-userlist --json + """, + ) + parser.add_argument( + "--json", + action="store_true", + help="Output users in JSON format instead of table", + ) + + args = parser.parse_args() + + success = _list_users_json() if args.json else _list_users_table() + sys.exit(0 if success else 1) + + +# --------------------------------------------------------------------------- +# usermod +# --------------------------------------------------------------------------- + + +def _modify_user_interactive() -> bool: + """Modify a user interactively by prompting for details.""" + from invokeai.app.services.auth.password_utils import validate_password_strength + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_common import UserUpdateRequest + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + print("=== Modify InvokeAI User ===\n") + + email = input("Email address of user to modify: ").strip() + if not email: + print("Error: Email is required") + return False + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user = user_service.get_by_email(email) + if not user: + print(f"\n❌ Error: No user found with email '{email}'") + return False + + print("\nCurrent user details:") + print(f" User ID: {user.user_id}") + print(f" Email: {user.email}") + print(f" Display Name: {user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if user.is_admin else 'No'}") + print(f" Active: {'Yes' if user.is_active else 'No'}") + + print("\n--- What would you like to change? (leave blank to keep current value) ---\n") + + new_name = input(f"New display name [{user.display_name or '(not set)'}]: ").strip() + display_name = new_name if new_name else None + + change_password = input("Change password? (y/N): ").strip().lower() + password = None + if change_password in ("y", "yes"): + while True: + password = getpass.getpass("New password: ") + if not password: + print("Keeping existing password.") + password = None + break + + password_confirm = getpass.getpass("Confirm new password: ") + + if password != password_confirm: + print("Error: Passwords do not match. Please try again.\n") + continue + + is_valid, error_msg = validate_password_strength(password) + if not is_valid: + print(f"Error: {error_msg}\n") + continue + + break + + change_admin = input("Change admin status? (y/N): ").strip().lower() + is_admin = None + if change_admin in ("y", "yes"): + is_admin_input = ( + input(f"Make administrator? [current: {'Yes' if user.is_admin else 'No'}] (y/N): ").strip().lower() + ) + is_admin = is_admin_input in ("y", "yes") + + if display_name is None and password is None and is_admin is None: + print("\nNo changes requested. User not modified.") + return True + + changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin) + updated_user = user_service.update(user.user_id, changes) + + print("\n✅ User updated successfully!") + print(f" User ID: {updated_user.user_id}") + print(f" Email: {updated_user.email}") + print(f" Display Name: {updated_user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}") + print(f" Active: {'Yes' if updated_user.is_active else 'No'}") + return True + + except ValueError as e: + print(f"\n❌ Error: {e}") + return False + except Exception as e: + print(f"\n❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def _modify_user_cli( + email: str, + display_name: str | None = None, + password: str | None = None, + is_admin: bool | None = None, +) -> bool: + """Modify a user via CLI arguments.""" + from invokeai.app.services.auth.password_utils import validate_password_strength + from invokeai.app.services.config import get_config + from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase + from invokeai.app.services.users.users_common import UserUpdateRequest + from invokeai.app.services.users.users_default import UserService + from invokeai.backend.util.logging import InvokeAILogger + + if password is not None: + is_valid, error_msg = validate_password_strength(password) + if not is_valid: + print(f"❌ Password validation failed: {error_msg}") + return False + + try: + config = get_config() + db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) + user_service = UserService(db) + + user = user_service.get_by_email(email) + if not user: + print(f"❌ Error: No user found with email '{email}'") + return False + + if display_name is None and password is None and is_admin is None: + print("❌ Error: No changes specified. Use --name, --password, --admin, or --no-admin") + return False + + changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin) + updated_user = user_service.update(user.user_id, changes) + + print("✅ User updated successfully!") + print(f" User ID: {updated_user.user_id}") + print(f" Email: {updated_user.email}") + print(f" Display Name: {updated_user.display_name or '(not set)'}") + print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}") + print(f" Active: {'Yes' if updated_user.is_active else 'No'}") + return True + + except ValueError as e: + print(f"❌ Error: {e}") + return False + except Exception as e: + print(f"❌ Unexpected error: {e}") + import traceback + + traceback.print_exc() + return False + + +def usermod() -> None: + """Entry point for ``invoke-usermod``.""" + parser = argparse.ArgumentParser( + description="Modify a user in the InvokeAI database", + epilog="If no arguments are provided, the script will run in interactive mode.", + ) + parser.add_argument("--email", "-e", help="User email address") + parser.add_argument("--name", "-n", help="New display name") + parser.add_argument("--password", "-p", help="New password") + + admin_group = parser.add_mutually_exclusive_group() + admin_group.add_argument("--admin", "-a", action="store_true", help="Grant administrator privileges") + admin_group.add_argument("--no-admin", dest="no_admin", action="store_true", help="Remove administrator privileges") + + args = parser.parse_args() + + is_admin = None + if args.admin: + is_admin = True + elif args.no_admin: + is_admin = False + + if args.email: + success = _modify_user_cli(args.email, args.name, args.password, is_admin) + else: + success = _modify_user_interactive() + + sys.exit(0 if success else 1) diff --git a/pyproject.toml b/pyproject.toml index c83480202a9..018cf1970de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -163,6 +163,10 @@ explicit = true [project.scripts] "invokeai-web" = "invokeai.app.run_app:run_app" +"invoke-useradd" = "invokeai.app.util.user_management:useradd" +"invoke-userdel" = "invokeai.app.util.user_management:userdel" +"invoke-userlist" = "invokeai.app.util.user_management:userlist" +"invoke-usermod" = "invokeai.app.util.user_management:usermod" [project.urls] "Homepage" = "https://invoke-ai.github.io/InvokeAI/" diff --git a/scripts/useradd.py b/scripts/useradd.py deleted file mode 100755 index a3006d940d9..00000000000 --- a/scripts/useradd.py +++ /dev/null @@ -1,179 +0,0 @@ -#!/usr/bin/env python3 -"""Script to add a user to the InvokeAI database. - -This script provides a convenient way to add users (admin or regular) to the InvokeAI -database for testing and administration purposes. It can be run from the command line -or imported and used programmatically. - -Usage: - # Interactive mode (prompts for all details) - python scripts/add_user.py - - # Command line mode - python scripts/add_user.py --email user@example.com --password securepass123 --name "Test User" - - # Add admin user - python scripts/add_user.py --email admin@example.com --password adminpass123 --admin - -Examples: - # Add a regular user - python scripts/add_user.py --email alice@test.local --password Password123 --name "Alice Smith" - - # Add an admin user - python scripts/add_user.py --email admin@test.local --password AdminPass123 --name "Admin User" --admin -""" - -import argparse -import getpass -import sys -from pathlib import Path - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -def add_user_interactive(): - """Add a user interactively by prompting for details.""" - from invokeai.app.services.auth.password_utils import validate_password_strength - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_common import UserCreateRequest - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - print("=== Add InvokeAI User ===\n") - - # Get user details - email = input("Email address: ").strip() - if not email: - print("Error: Email is required") - return False - - display_name = input("Display name (optional): ").strip() or None - - # Get password with confirmation - while True: - password = getpass.getpass("Password: ") - password_confirm = getpass.getpass("Confirm password: ") - - if password != password_confirm: - print("Error: Passwords do not match. Please try again.\n") - continue - - # Validate password strength - is_valid, error_msg = validate_password_strength(password) - if not is_valid: - print(f"Error: {error_msg}\n") - continue - - break - - # Ask if user should be admin - is_admin_input = input("Make this user an administrator? (y/N): ").strip().lower() - is_admin = is_admin_input in ("y", "yes") - - # Create user - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin) - - user = user_service.create(user_data) - - print("\n✅ User created successfully!") - print(f" User ID: {user.user_id}") - print(f" Email: {user.email}") - print(f" Display Name: {user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if user.is_admin else 'No'}") - print(f" Active: {'Yes' if user.is_active else 'No'}") - - return True - - except ValueError as e: - print(f"\n❌ Error: {e}") - return False - except Exception as e: - print(f"\n❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def add_user_cli(email: str, password: str, display_name: str | None = None, is_admin: bool = False): - """Add a user via CLI arguments.""" - from invokeai.app.services.auth.password_utils import validate_password_strength - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_common import UserCreateRequest - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - # Validate password - is_valid, error_msg = validate_password_strength(password) - if not is_valid: - print(f"❌ Password validation failed: {error_msg}") - return False - - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - user_data = UserCreateRequest(email=email, display_name=display_name, password=password, is_admin=is_admin) - - user = user_service.create(user_data) - - print("✅ User created successfully!") - print(f" User ID: {user.user_id}") - print(f" Email: {user.email}") - print(f" Display Name: {user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if user.is_admin else 'No'}") - print(f" Active: {'Yes' if user.is_active else 'No'}") - - return True - - except ValueError as e: - print(f"❌ Error: {e}") - return False - except Exception as e: - print(f"❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def main(): - """Main entry point.""" - parser = argparse.ArgumentParser( - description="Add a user to the InvokeAI database", - epilog="If no arguments are provided, the script will run in interactive mode.", - ) - parser.add_argument("--email", "-e", help="User email address") - parser.add_argument("--password", "-p", help="User password") - parser.add_argument("--name", "-n", help="User display name (optional)") - parser.add_argument("--admin", "-a", action="store_true", help="Make user an administrator") - - args = parser.parse_args() - - # Check if any arguments were provided - if args.email or args.password: - # CLI mode - require both email and password - if not args.email or not args.password: - print("❌ Error: Both --email and --password are required when using CLI mode") - print(" Run without arguments for interactive mode") - sys.exit(1) - - success = add_user_cli(args.email, args.password, args.name, args.admin) - else: - # Interactive mode - success = add_user_interactive() - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/scripts/userdel.py b/scripts/userdel.py deleted file mode 100755 index d0bb4649e82..00000000000 --- a/scripts/userdel.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python3 -"""Script to delete a user from the InvokeAI database. - -This script provides a convenient way to delete users from the InvokeAI database -for administration purposes. It can be run from the command line or imported and -used programmatically. - -Usage: - # Interactive mode (prompts for email) - python scripts/userdel.py - - # Command line mode - python scripts/userdel.py --email user@example.com - - # Force delete without confirmation - python scripts/userdel.py --email user@example.com --force - -Examples: - # Delete a user with confirmation - python scripts/userdel.py --email alice@test.local - - # Delete a user without confirmation prompt - python scripts/userdel.py --email alice@test.local --force -""" - -import argparse -import sys -from pathlib import Path - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -def delete_user_interactive(): - """Delete a user interactively by prompting for email.""" - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - print("=== Delete InvokeAI User ===\n") - - # Get user email - email = input("Email address of user to delete: ").strip() - if not email: - print("Error: Email is required") - return False - - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - # Get user to show details before deletion - user = user_service.get_by_email(email) - if not user: - print(f"\n❌ Error: No user found with email '{email}'") - return False - - print("\nUser to delete:") - print(f" User ID: {user.user_id}") - print(f" Email: {user.email}") - print(f" Display Name: {user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if user.is_admin else 'No'}") - print(f" Active: {'Yes' if user.is_active else 'No'}") - - # Confirm deletion - confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower() - if confirm not in ("yes", "y"): - print("Deletion cancelled.") - return False - - user_service.delete(user.user_id) - - print("\n✅ User deleted successfully!") - return True - - except ValueError as e: - print(f"\n❌ Error: {e}") - return False - except Exception as e: - print(f"\n❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def delete_user_cli(email: str, force: bool = False): - """Delete a user via CLI arguments.""" - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - # Get user to show details before deletion - user = user_service.get_by_email(email) - if not user: - print(f"❌ Error: No user found with email '{email}'") - return False - - if not force: - print("User to delete:") - print(f" User ID: {user.user_id}") - print(f" Email: {user.email}") - print(f" Display Name: {user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if user.is_admin else 'No'}") - print(f" Active: {'Yes' if user.is_active else 'No'}") - - confirm = input("\n⚠️ Are you sure you want to delete this user? (yes/no): ").strip().lower() - if confirm not in ("yes", "y"): - print("Deletion cancelled.") - return False - - user_service.delete(user.user_id) - - print("✅ User deleted successfully!") - return True - - except ValueError as e: - print(f"❌ Error: {e}") - return False - except Exception as e: - print(f"❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def main(): - """Main entry point.""" - parser = argparse.ArgumentParser( - description="Delete a user from the InvokeAI database", - epilog="If no arguments are provided, the script will run in interactive mode.", - ) - parser.add_argument("--email", "-e", help="User email address") - parser.add_argument("--force", "-f", action="store_true", help="Delete without confirmation prompt") - - args = parser.parse_args() - - # Check if email was provided - if args.email: - # CLI mode - success = delete_user_cli(args.email, args.force) - else: - # Interactive mode - success = delete_user_interactive() - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/scripts/userlist.py b/scripts/userlist.py deleted file mode 100755 index 54559512444..00000000000 --- a/scripts/userlist.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -"""Script to list users from the InvokeAI database. - -This script provides a convenient way to view all users in the InvokeAI database -with their details. It can output in table format (default) or JSON format. - -Usage: - # Display users as a table - python scripts/userlist.py - - # Display users as JSON - python scripts/userlist.py --json - -Examples: - # View all users in table format - python scripts/userlist.py - - # View all users in JSON format for scripting - python scripts/userlist.py --json -""" - -import argparse -import json -import sys -from pathlib import Path - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -def list_users_table(): - """List all users in a formatted table.""" - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - config = get_config() - logger = InvokeAILogger.get_logger(config=config) - db = SqliteDatabase(config.db_path, logger) - user_service = UserService(db) - - try: - # Get all users - users = user_service.list_users() - - if not users: - print("No users found in database.") - return True - - # Print header - print("\n=== InvokeAI Users ===\n") - print(f"{'User ID':<36} {'Email':<30} {'Display Name':<20} {'Admin':<8} {'Active':<8}") - print("-" * 108) - - # Print each user - for user in users: - user_id = user.user_id - email = user.email[:29] if len(user.email) > 29 else user.email - name = user.display_name[:19] if len(user.display_name) > 19 else user.display_name - is_admin = "Yes" if user.is_admin else "No" - is_active = "Yes" if user.is_active else "No" - - print(f"{user_id:<36} {email:<30} {name:<20} {is_admin:<8} {is_active:<8}") - - print(f"\nTotal users: {len(users)}") - return True - - except Exception as e: - print(f"Error listing users: {e}") - return False - - -def list_users_json(): - """List all users in JSON format.""" - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - config = get_config() - logger = InvokeAILogger.get_logger(config=config) - db = SqliteDatabase(config.db_path, logger) - user_service = UserService(db) - - try: - # Get all users - users = user_service.list_users() - - # Convert to JSON-serializable format - users_data = [ - { - "id": user.user_id, - "email": user.email, - "name": user.display_name, - "is_admin": user.is_admin, - "is_active": user.is_active, - } - for user in users - ] - - # Print JSON - print(json.dumps(users_data, indent=2)) - return True - - except Exception as e: - print(f'{{"error": "{e}"}}', file=sys.stderr) - return False - - -def main(): - """Main entry point for the script.""" - parser = argparse.ArgumentParser( - description="List users from the InvokeAI database", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - # View all users in table format - python scripts/userlist.py - - # View all users in JSON format for scripting - python scripts/userlist.py --json - """, - ) - - parser.add_argument( - "--json", - action="store_true", - help="Output users in JSON format instead of table", - ) - - args = parser.parse_args() - - # List users in requested format - if args.json: - success = list_users_json() - else: - success = list_users_table() - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/scripts/usermod.py b/scripts/usermod.py deleted file mode 100755 index c64e5a22526..00000000000 --- a/scripts/usermod.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -"""Script to modify a user in the InvokeAI database. - -This script provides a convenient way to modify user details (name, password, admin status) -in the InvokeAI database for administration purposes. It can be run from the command line -or imported and used programmatically. - -Usage: - # Interactive mode (prompts for details) - python scripts/usermod.py - - # Command line mode - python scripts/usermod.py --email user@example.com --name "New Name" - python scripts/usermod.py --email user@example.com --password newpass123 - python scripts/usermod.py --email user@example.com --admin - python scripts/usermod.py --email user@example.com --no-admin - -Examples: - # Change user's display name - python scripts/usermod.py --email alice@test.local --name "Alice Johnson" - - # Change user's password - python scripts/usermod.py --email alice@test.local --password NewPassword123 - - # Make user an admin - python scripts/usermod.py --email alice@test.local --admin - - # Remove admin privileges - python scripts/usermod.py --email alice@test.local --no-admin - - # Change multiple properties at once - python scripts/usermod.py --email alice@test.local --name "Alice Admin" --password Secret123 --admin -""" - -import argparse -import getpass -import sys -from pathlib import Path - -# Add parent directory to path for imports -sys.path.insert(0, str(Path(__file__).parent.parent)) - - -def modify_user_interactive(): - """Modify a user interactively by prompting for details.""" - from invokeai.app.services.auth.password_utils import validate_password_strength - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_common import UserUpdateRequest - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - print("=== Modify InvokeAI User ===\n") - - # Get user email - email = input("Email address of user to modify: ").strip() - if not email: - print("Error: Email is required") - return False - - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - # Get user to show current details - user = user_service.get_by_email(email) - if not user: - print(f"\n❌ Error: No user found with email '{email}'") - return False - - print("\nCurrent user details:") - print(f" User ID: {user.user_id}") - print(f" Email: {user.email}") - print(f" Display Name: {user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if user.is_admin else 'No'}") - print(f" Active: {'Yes' if user.is_active else 'No'}") - - print("\n--- What would you like to change? (leave blank to keep current value) ---\n") - - # Get new display name - new_name = input(f"New display name [{user.display_name or '(not set)'}]: ").strip() - display_name = new_name if new_name else None - - # Get new password - change_password = input("Change password? (y/N): ").strip().lower() - password = None - if change_password in ("y", "yes"): - while True: - password = getpass.getpass("New password: ") - if not password: - print("Keeping existing password.") - password = None - break - - password_confirm = getpass.getpass("Confirm new password: ") - - if password != password_confirm: - print("Error: Passwords do not match. Please try again.\n") - continue - - # Validate password strength - is_valid, error_msg = validate_password_strength(password) - if not is_valid: - print(f"Error: {error_msg}\n") - continue - - break - - # Get new admin status - change_admin = input("Change admin status? (y/N): ").strip().lower() - is_admin = None - if change_admin in ("y", "yes"): - is_admin_input = ( - input(f"Make administrator? [current: {'Yes' if user.is_admin else 'No'}] (y/N): ").strip().lower() - ) - is_admin = is_admin_input in ("y", "yes") - - # Check if any changes were made - if display_name is None and password is None and is_admin is None: - print("\nNo changes requested. User not modified.") - return True - - # Update user - changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin) - updated_user = user_service.update(user.user_id, changes) - - print("\n✅ User updated successfully!") - print(f" User ID: {updated_user.user_id}") - print(f" Email: {updated_user.email}") - print(f" Display Name: {updated_user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}") - print(f" Active: {'Yes' if updated_user.is_active else 'No'}") - - return True - - except ValueError as e: - print(f"\n❌ Error: {e}") - return False - except Exception as e: - print(f"\n❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def modify_user_cli( - email: str, - display_name: str | None = None, - password: str | None = None, - is_admin: bool | None = None, -): - """Modify a user via CLI arguments.""" - from invokeai.app.services.auth.password_utils import validate_password_strength - from invokeai.app.services.config import get_config - from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase - from invokeai.app.services.users.users_common import UserUpdateRequest - from invokeai.app.services.users.users_default import UserService - from invokeai.backend.util.logging import InvokeAILogger - - # Validate password if provided - if password is not None: - is_valid, error_msg = validate_password_strength(password) - if not is_valid: - print(f"❌ Password validation failed: {error_msg}") - return False - - try: - config = get_config() - db = SqliteDatabase(config.db_path, InvokeAILogger.get_logger()) - user_service = UserService(db) - - # Get user to verify existence - user = user_service.get_by_email(email) - if not user: - print(f"❌ Error: No user found with email '{email}'") - return False - - # Check if any changes were requested - if display_name is None and password is None and is_admin is None: - print("❌ Error: No changes specified. Use --name, --password, --admin, or --no-admin") - return False - - # Update user - changes = UserUpdateRequest(display_name=display_name, password=password, is_admin=is_admin) - updated_user = user_service.update(user.user_id, changes) - - print("✅ User updated successfully!") - print(f" User ID: {updated_user.user_id}") - print(f" Email: {updated_user.email}") - print(f" Display Name: {updated_user.display_name or '(not set)'}") - print(f" Admin: {'Yes' if updated_user.is_admin else 'No'}") - print(f" Active: {'Yes' if updated_user.is_active else 'No'}") - - return True - - except ValueError as e: - print(f"❌ Error: {e}") - return False - except Exception as e: - print(f"❌ Unexpected error: {e}") - import traceback - - traceback.print_exc() - return False - - -def main(): - """Main entry point.""" - parser = argparse.ArgumentParser( - description="Modify a user in the InvokeAI database", - epilog="If no arguments are provided, the script will run in interactive mode.", - ) - parser.add_argument("--email", "-e", help="User email address") - parser.add_argument("--name", "-n", help="New display name") - parser.add_argument("--password", "-p", help="New password") - - admin_group = parser.add_mutually_exclusive_group() - admin_group.add_argument("--admin", "-a", action="store_true", help="Grant administrator privileges") - admin_group.add_argument("--no-admin", dest="no_admin", action="store_true", help="Remove administrator privileges") - - args = parser.parse_args() - - # Determine admin status change - is_admin = None - if args.admin: - is_admin = True - elif args.no_admin: - is_admin = False - - # Check if email was provided - if args.email: - # CLI mode - success = modify_user_cli(args.email, args.name, args.password, is_admin) - else: - # Interactive mode - success = modify_user_interactive() - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main()