"""CLI for searching Auth0 users by e-mail address across multiple tenants.""" import json import logging from typing import Annotated import typer from auth0_client.auth0_client import Auth0Client from auth0_client.secrets_manager import SecretsManagerClient from config import Auth0Config LOGGER = logging.getLogger('auth0_client.cli') app: typer.Typer = typer.Typer(help='Auth0 CLI tool.') @app.callback() def callback( debug: Annotated[ bool, typer.Option('--debug/--no-debug', help='Show debug logs.') ] = False, ) -> None: """Configure logging for the Auth0 CLI.""" level = logging.DEBUG if debug else logging.INFO logging.basicConfig( level=level, format='%(asctime)s %(name)s %(levelname)s: %(message)s' ) @app.command() def search_user_by_email( email: Annotated[ str, typer.Option('-e', '--email', help='E-mail address to search.') ], ) -> None: """Search for an Auth0 user by e-mail across all configured tenants.""" try: cfg = Auth0Config() except Exception as exc: LOGGER.error('Configuration error: %s', exc) raise typer.Exit(code=1) secrets = SecretsManagerClient() results: dict[str, list] = {} for tenant in cfg.tenant_list: try: creds = secrets.get_tenant_credentials(tenant) except Exception as exc: LOGGER.error("Failed to fetch credentials for tenant '%s': %s", tenant, exc) raise typer.Exit(code=1) client = Auth0Client(creds.domain, creds.client_id, creds.client_secret) try: users = client.search_user_by_email(email) except Exception as exc: LOGGER.error( "Failed to search for '%s' in tenant '%s': %s", email, tenant, exc, ) raise typer.Exit(code=1) if users: results[tenant] = users LOGGER.info("Found %d user(s) in tenant '%s'.", len(users), tenant) else: LOGGER.info("No user found in tenant '%s' with e-mail: %s", tenant, email) print(json.dumps(results, indent=2)) @app.command() def delete_user_by_id( user_id: Annotated[ str, typer.Option('-i', '--user-id', help='Auth0 user ID to delete.') ], tenant: Annotated[ str, typer.Option('--tenant', help='Auth0 tenant name to delete the user from.'), ], dry_run: Annotated[ bool, typer.Option( '--dry-run/--no-dry-run', help='Perform a dry run without deleting.', ), ] = False, ) -> None: """Delete an Auth0 user by their user ID from a specific tenant.""" if dry_run: LOGGER.info( "[Dry run] Would delete user '%s' from tenant '%s'.", user_id, tenant, ) return secrets = SecretsManagerClient() try: creds = secrets.get_tenant_credentials(tenant) except Exception as exc: LOGGER.error("Failed to fetch credentials for tenant '%s': %s", tenant, exc) raise typer.Exit(code=1) client = Auth0Client(creds.domain, creds.client_id, creds.client_secret) try: client.delete_user_by_id(user_id) LOGGER.info("Deleted user '%s' from tenant '%s'.", user_id, tenant) except Exception as exc: LOGGER.error( "Failed to delete user '%s' from tenant '%s': %s", user_id, tenant, exc, ) raise typer.Exit(code=1) @app.command() def suspend_user_by_id( user_id: Annotated[ str, typer.Option('-i', '--user-id', help='Auth0 user ID to suspend.') ], tenant: Annotated[ str, typer.Option('--tenant', help='Auth0 tenant name to suspend the user in.'), ], dry_run: Annotated[ bool, typer.Option( '--dry-run/--no-dry-run', help='Perform a dry run without suspending.', ), ] = False, ) -> None: """Suspend (block) an Auth0 user by their user ID in a specific tenant.""" if dry_run: LOGGER.info( "[Dry run] Would suspend user '%s' in tenant '%s'.", user_id, tenant, ) return secrets = SecretsManagerClient() try: creds = secrets.get_tenant_credentials(tenant) except Exception as exc: LOGGER.error("Failed to fetch credentials for tenant '%s': %s", tenant, exc) raise typer.Exit(code=1) client = Auth0Client(creds.domain, creds.client_id, creds.client_secret) try: client.block_user_by_id(user_id) LOGGER.info("Suspended user '%s' in tenant '%s'.", user_id, tenant) except Exception as exc: LOGGER.error( "Failed to suspend user '%s' in tenant '%s': %s", user_id, tenant, exc, ) raise typer.Exit(code=1) if __name__ == '__main__': app()