google login works

This commit is contained in:
2026-01-21 03:09:46 +10:30
parent 4cf4a27c9a
commit ae2421763a
8 changed files with 2744 additions and 173 deletions
+340
View File
@@ -0,0 +1,340 @@
# Gatehouse Scripts
This directory contains utility scripts for managing and configuring Gatehouse.
## OAuth Provider Configuration Script
The [`configure_oauth_provider.py`](configure_oauth_provider.py:1) script allows administrators to easily configure OAuth providers at the application level.
### Overview
This script manages application-wide OAuth provider configurations using the new [`ApplicationProviderConfig`](../gatehouse_app/models/authentication_method.py:99) architecture. Unlike the deprecated organization-specific configuration, this allows users to authenticate with OAuth providers without needing to specify an organization first.
### Prerequisites
- Python 3.8+
- Virtual environment with dependencies installed
- Flask app must be properly configured (`.env` or environment variables)
### Quick Start
```bash
# Activate virtual environment
cd gatehouse-api
source .venv/bin/activate
# Create Google OAuth configuration
python scripts/configure_oauth_provider.py create google \
--client-id "YOUR_CLIENT_ID" \
--client-secret "YOUR_CLIENT_SECRET" \
--redirect-url "http://localhost:5173/auth/callback"
# List all configured providers
python scripts/configure_oauth_provider.py list
# Show provider details
python scripts/configure_oauth_provider.py show google
```
### Commands
#### `create` - Create a New Provider
Create a new OAuth provider configuration at the application level.
```bash
python scripts/configure_oauth_provider.py create PROVIDER [OPTIONS]
```
**Arguments:**
- `PROVIDER`: Provider type (google, github, microsoft)
**Options:**
- `--client-id TEXT`: OAuth client ID (required, or via environment)
- `--client-secret TEXT`: OAuth client secret (required, or via environment)
- `--redirect-url TEXT`: Default redirect URL for callbacks
- `--disabled`: Create provider in disabled state
- `--settings KEY=VALUE`: Custom settings (can be specified multiple times)
**Examples:**
```bash
# Basic Google configuration
python scripts/configure_oauth_provider.py create google \
--client-id "xxx.apps.googleusercontent.com" \
--client-secret "GOCSPX-xxx"
# With redirect URL
python scripts/configure_oauth_provider.py create google \
--client-id "xxx" \
--client-secret "yyy" \
--redirect-url "https://app.example.com/auth/callback"
# Create disabled initially
python scripts/configure_oauth_provider.py create github \
--client-id "xxx" \
--client-secret "yyy" \
--disabled
# With custom settings
python scripts/configure_oauth_provider.py create google \
--client-id "xxx" \
--client-secret "yyy" \
--settings "hosted_domain=example.com" \
--settings "prompt=consent"
```
#### `update` - Update Existing Provider
Update an existing OAuth provider configuration.
```bash
python scripts/configure_oauth_provider.py update PROVIDER [OPTIONS]
```
**Arguments:**
- `PROVIDER`: Provider type to update
**Options:**
- `--client-id TEXT`: New OAuth client ID
- `--client-secret TEXT`: New OAuth client secret
- `--redirect-url TEXT`: New default redirect URL
- `--enabled true|false`: Enable or disable the provider
- `--settings KEY=VALUE`: Custom settings to update
**Examples:**
```bash
# Update client credentials
python scripts/configure_oauth_provider.py update google \
--client-id "new-client-id" \
--client-secret "new-secret"
# Enable/disable provider
python scripts/configure_oauth_provider.py update google --enabled false
python scripts/configure_oauth_provider.py update google --enabled true
# Update redirect URL
python scripts/configure_oauth_provider.py update google \
--redirect-url "https://new-domain.com/auth/callback"
```
#### `list` - List All Providers
List all configured OAuth providers with their status.
```bash
python scripts/configure_oauth_provider.py list
```
**Example Output:**
```
Configured OAuth Providers
google - enabled
Client ID: 972920496362-xxx.apps.googleusercontent.com
Redirect URL: https://app.example.com/auth/callback
Created: 2026-01-20T13:00:00
Auth URL: https://accounts.google.com/o/oauth2/v2/auth
Scopes: openid, profile, email
github - disabled
Client ID: Iv1.xxx
Created: 2026-01-19T10:00:00
Auth URL: https://github.com/login/oauth/authorize
Scopes: read:user, user:email
```
#### `show` - Show Provider Details
Display detailed information about a specific OAuth provider.
```bash
python scripts/configure_oauth_provider.py show PROVIDER
```
**Arguments:**
- `PROVIDER`: Provider type to display
**Example:**
```bash
python scripts/configure_oauth_provider.py show google
```
**Example Output:**
```
Google OAuth Provider Details
Basic Information:
Provider Type: google
Provider ID: 123e4567-e89b-12d3-a456-426614174000
Client ID: 972920496362-xxx.apps.googleusercontent.com
Status: enabled
Default Redirect URL: https://app.example.com/auth/callback
Timestamps:
Created: 2026-01-20T13:00:00
Updated: 2026-01-20T14:30:00
OAuth Configuration:
Authorization URL: https://accounts.google.com/o/oauth2/v2/auth
Token URL: https://oauth2.googleapis.com/token
User Info URL: https://openidconnect.googleapis.com/v1/userinfo
JWKS URL: https://www.googleapis.com/oauth2/v3/certs
Scopes: openid, profile, email
```
#### `delete` - Delete Provider Configuration
Remove an OAuth provider configuration.
```bash
python scripts/configure_oauth_provider.py delete PROVIDER [OPTIONS]
```
**Arguments:**
- `PROVIDER`: Provider type to delete
**Options:**
- `--yes`, `-y`: Skip confirmation prompt
**Examples:**
```bash
# Delete with confirmation prompt
python scripts/configure_oauth_provider.py delete google
# Delete without confirmation
python scripts/configure_oauth_provider.py delete google --yes
```
### Environment Variables
The script supports loading OAuth credentials from environment variables, which is useful for automation and CI/CD pipelines.
**Supported Variables:**
- `{PROVIDER}_CLIENT_ID`: OAuth client ID
- `{PROVIDER}_CLIENT_SECRET`: OAuth client secret
- `{PROVIDER}_REDIRECT_URL`: Default redirect URL
**Example:**
```bash
# Export environment variables
export GOOGLE_CLIENT_ID="xxx.apps.googleusercontent.com"
export GOOGLE_CLIENT_SECRET="GOCSPX-xxx"
export GOOGLE_REDIRECT_URL="https://app.example.com/auth/callback"
# Create provider using environment variables
python scripts/configure_oauth_provider.py create google
# You can still override with command-line arguments
python scripts/configure_oauth_provider.py create google \
--redirect-url "https://different.com/callback"
```
### Supported Providers
The script comes with pre-configured endpoint information for:
- **Google** (`google`)
- Authorization: `https://accounts.google.com/o/oauth2/v2/auth`
- Token: `https://oauth2.googleapis.com/token`
- User Info: `https://openidconnect.googleapis.com/v1/userinfo`
- Default Scopes: `openid, profile, email`
- **GitHub** (`github`)
- Authorization: `https://github.com/login/oauth/authorize`
- Token: `https://github.com/login/oauth/access_token`
- User Info: `https://api.github.com/user`
- Default Scopes: `read:user, user:email`
- **Microsoft** (`microsoft`)
- Authorization: `https://login.microsoftonline.com/common/oauth2/v2.0/authorize`
- Token: `https://login.microsoftonline.com/common/oauth2/v2.0/token`
- User Info: `https://graph.microsoft.com/oidc/userinfo`
- Default Scopes: `openid, profile, email`
### Error Handling
The script provides clear error messages and appropriate exit codes:
- **Exit Code 0**: Success
- **Exit Code 1**: Error occurred
**Common Errors:**
1. **Provider Already Exists**
```
✗ Failed to create provider: Provider google already exists
Use 'update' command to modify existing provider configuration.
```
2. **Provider Not Found**
```
✗ Failed to update provider: Provider google not found
Use 'create' command to add a new provider configuration.
```
3. **Missing Credentials**
```
✗ Client ID is required. Provide via --client-id or GOOGLE_CLIENT_ID environment variable.
```
### Integration with Shell Scripts
The [`configure-google-auth.sh`](../../docs/configure-google-auth.sh:1) script demonstrates how to integrate the Python script into a shell script for easier deployment:
```bash
#!/bin/bash
# Set credentials
GOOGLE_CLIENT_ID="xxx"
GOOGLE_CLIENT_SECRET="yyy"
REDIRECT_URL="https://app.example.com/callback"
# Call Python script
cd gatehouse-api
python3 scripts/configure_oauth_provider.py create google \
--client-id "$GOOGLE_CLIENT_ID" \
--client-secret "$GOOGLE_CLIENT_SECRET" \
--redirect-url "$REDIRECT_URL"
```
### API Service Methods
The script uses the following [`ExternalAuthService`](../gatehouse_app/services/external_auth_service.py:1) methods:
- [`create_app_provider_config()`](../gatehouse_app/services/external_auth_service.py:308) - Create provider configuration
- [`update_app_provider_config()`](../gatehouse_app/services/external_auth_service.py:369) - Update provider configuration
- [`get_app_provider_config()`](../gatehouse_app/services/external_auth_service.py:427) - Get single provider
- [`list_app_provider_configs()`](../gatehouse_app/services/external_auth_service.py:454) - List all providers
- [`delete_app_provider_config()`](../gatehouse_app/services/external_auth_service.py:465) - Delete provider configuration
### Security Considerations
1. **Client Secret Storage**: Client secrets are encrypted using the application's encryption key before storage in the database
2. **Environment Variables**: Be cautious when using environment variables in shared environments
3. **Secret Exposure**: The `show` command never displays the client secret (it's always excluded)
4. **Confirmation Prompts**: The `delete` command requires confirmation unless `--yes` flag is used
### Troubleshooting
**Database Connection Issues:**
- Ensure PostgreSQL is running and accessible
- Check `.env` file for correct `DATABASE_URL`
- Verify virtual environment is activated
**Import Errors:**
- Activate the virtual environment: `source .venv/bin/activate`
- Install dependencies: `pip install -r requirements.txt`
**Permission Issues:**
- Ensure script is executable: `chmod +x scripts/configure_oauth_provider.py`
### Related Documentation
- [External Auth Architecture](../../docs/external-auth-architecture.md)
- [Application-Wide OAuth Design](../../docs/external-auth-application-wide-design.md)
- [OAuth API Changes](../../docs/oauth-api-changes.md)
+484
View File
@@ -0,0 +1,484 @@
#!/usr/bin/env python3
"""
OAuth Provider Configuration Script for Gatehouse
This script allows administrators to configure OAuth providers at the application level
using the new ApplicationProviderConfig architecture.
Usage:
# Create a new provider configuration
python scripts/configure_oauth_provider.py create google \\
--client-id "YOUR_CLIENT_ID" \\
--client-secret "YOUR_CLIENT_SECRET" \\
--redirect-url "http://localhost:5173/auth/callback"
# List all configured providers
python scripts/configure_oauth_provider.py list
# Show details of a specific provider
python scripts/configure_oauth_provider.py show google
# Update a provider configuration
python scripts/configure_oauth_provider.py update google --enabled false
# Delete a provider configuration
python scripts/configure_oauth_provider.py delete google
# Use environment variables
GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy \\
python scripts/configure_oauth_provider.py create google
"""
import os
import sys
import argparse
from typing import Optional, Dict, Any
# Add the parent directory to the path for imports
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Load environment variables from .env file before any other imports
# This ensures database and other configurations are available
from dotenv import load_dotenv
script_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
env_file = os.path.join(script_dir, '.env')
if os.path.exists(env_file):
load_dotenv(env_file)
# Import after path setup
from gatehouse_app import create_app
from gatehouse_app.services.external_auth_service import ExternalAuthService, ExternalAuthError
# Provider endpoint configurations
PROVIDER_DEFAULTS = {
"google": {
"auth_url": "https://accounts.google.com/o/oauth2/v2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"userinfo_url": "https://openidconnect.googleapis.com/v1/userinfo",
"jwks_url": "https://www.googleapis.com/oauth2/v3/certs",
"scopes": ["openid", "profile", "email"],
},
"github": {
"auth_url": "https://github.com/login/oauth/authorize",
"token_url": "https://github.com/login/oauth/access_token",
"userinfo_url": "https://api.github.com/user",
"scopes": ["read:user", "user:email"],
},
"microsoft": {
"auth_url": "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
"token_url": "https://login.microsoftonline.com/common/oauth2/v2.0/token",
"userinfo_url": "https://graph.microsoft.com/oidc/userinfo",
"jwks_url": "https://login.microsoftonline.com/common/discovery/v2.0/keys",
"scopes": ["openid", "profile", "email"],
},
}
class Colors:
"""ANSI color codes for terminal output."""
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
def print_success(message: str):
"""Print success message in green."""
print(f"{Colors.OKGREEN}{message}{Colors.ENDC}")
def print_error(message: str):
"""Print error message in red."""
print(f"{Colors.FAIL}{message}{Colors.ENDC}", file=sys.stderr)
def print_warning(message: str):
"""Print warning message in yellow."""
print(f"{Colors.WARNING}{message}{Colors.ENDC}")
def print_info(message: str):
"""Print info message in blue."""
print(f"{Colors.OKBLUE} {message}{Colors.ENDC}")
def print_header(message: str):
"""Print header message."""
print(f"\n{Colors.BOLD}{Colors.HEADER}{message}{Colors.ENDC}")
def get_env_credentials(provider_type: str) -> Dict[str, Optional[str]]:
"""
Get OAuth credentials from environment variables.
Supports the following patterns:
- {PROVIDER}_CLIENT_ID
- {PROVIDER}_CLIENT_SECRET
- {PROVIDER}_REDIRECT_URL
Args:
provider_type: Provider type (google, github, microsoft)
Returns:
Dictionary with client_id, client_secret, and redirect_url if found
"""
provider_upper = provider_type.upper()
return {
"client_id": os.environ.get(f"{provider_upper}_CLIENT_ID"),
"client_secret": os.environ.get(f"{provider_upper}_CLIENT_SECRET"),
"redirect_url": os.environ.get(f"{provider_upper}_REDIRECT_URL"),
}
def create_provider(args):
"""Create a new OAuth provider configuration."""
provider_type = args.provider.lower()
print_header(f"Creating {provider_type.title()} OAuth Provider Configuration")
# Get credentials from args or environment
env_creds = get_env_credentials(provider_type)
client_id = args.client_id or env_creds.get("client_id")
client_secret = args.client_secret or env_creds.get("client_secret")
redirect_url = args.redirect_url or env_creds.get("redirect_url")
# Validation
if not client_id:
print_error(f"Client ID is required. Provide via --client-id or {provider_type.upper()}_CLIENT_ID environment variable.")
return 1
if not client_secret:
print_error(f"Client secret is required. Provide via --client-secret or {provider_type.upper()}_CLIENT_SECRET environment variable.")
return 1
# Get provider defaults
if provider_type not in PROVIDER_DEFAULTS:
print_error(f"Unknown provider: {provider_type}. Supported providers: {', '.join(PROVIDER_DEFAULTS.keys())}")
return 1
defaults = PROVIDER_DEFAULTS[provider_type]
# Build configuration
config_data = {
"client_id": client_id,
"client_secret": client_secret,
"default_redirect_url": redirect_url,
"is_enabled": not args.disabled,
**defaults,
}
# Add custom settings if provided
if args.settings:
settings = {}
for setting in args.settings:
try:
key, value = setting.split("=", 1)
settings[key] = value
except ValueError:
print_warning(f"Skipping invalid setting format: {setting}")
config_data["settings"] = settings
try:
# Create the provider configuration
config = ExternalAuthService.create_app_provider_config(
provider_type=provider_type,
**config_data
)
print_success(f"{provider_type.title()} provider created successfully!")
print_info(f"Provider ID: {config.id}")
print_info(f"Client ID: {config.client_id}")
if redirect_url:
print_info(f"Default Redirect URL: {redirect_url}")
print_info(f"Enabled: {config.is_enabled}")
return 0
except ExternalAuthError as e:
print_error(f"Failed to create provider: {e.message}")
if e.error_type == "PROVIDER_EXISTS":
print_info("Use 'update' command to modify existing provider configuration.")
return 1
except Exception as e:
print_error(f"Unexpected error: {str(e)}")
return 1
def update_provider(args):
"""Update an existing OAuth provider configuration."""
provider_type = args.provider.lower()
print_header(f"Updating {provider_type.title()} OAuth Provider Configuration")
# Build updates dictionary
updates = {}
if args.client_id:
updates["client_id"] = args.client_id
if args.client_secret:
updates["client_secret"] = args.client_secret
if args.redirect_url:
updates["default_redirect_url"] = args.redirect_url
if args.enabled is not None:
updates["is_enabled"] = args.enabled
if args.settings:
settings = {}
for setting in args.settings:
try:
key, value = setting.split("=", 1)
settings[key] = value
except ValueError:
print_warning(f"Skipping invalid setting format: {setting}")
updates["settings"] = settings
if not updates:
print_warning("No updates specified. Use --help to see available options.")
return 1
try:
config = ExternalAuthService.update_app_provider_config(
provider_type=provider_type,
**updates
)
print_success(f"{provider_type.title()} provider updated successfully!")
print_info(f"Provider ID: {config.id}")
print_info(f"Client ID: {config.client_id}")
if config.default_redirect_url:
print_info(f"Default Redirect URL: {config.default_redirect_url}")
print_info(f"Enabled: {config.is_enabled}")
return 0
except ExternalAuthError as e:
print_error(f"Failed to update provider: {e.message}")
if e.error_type == "PROVIDER_NOT_FOUND":
print_info("Use 'create' command to add a new provider configuration.")
return 1
except Exception as e:
print_error(f"Unexpected error: {str(e)}")
return 1
def list_providers(args):
"""List all configured OAuth providers."""
print_header("Configured OAuth Providers")
try:
configs = ExternalAuthService.list_app_provider_configs()
if not configs:
print_info("No OAuth providers configured yet.")
print_info("Use 'create' command to add a provider.")
return 0
print()
for config in configs:
status = f"{Colors.OKGREEN}enabled{Colors.ENDC}" if config.get("is_enabled") else f"{Colors.WARNING}disabled{Colors.ENDC}"
print(f" {Colors.BOLD}{config['provider_type']}{Colors.ENDC} - {status}")
print(f" Client ID: {config['client_id']}")
if config.get('default_redirect_url'):
print(f" Redirect URL: {config['default_redirect_url']}")
print(f" Created: {config.get('created_at', 'N/A')}")
# Show endpoint info if available
additional_config = config.get('additional_config', {})
if additional_config:
if additional_config.get('auth_url'):
print(f" Auth URL: {additional_config['auth_url']}")
if additional_config.get('scopes'):
scopes = ', '.join(additional_config['scopes'])
print(f" Scopes: {scopes}")
print()
return 0
except Exception as e:
print_error(f"Failed to list providers: {str(e)}")
return 1
def show_provider(args):
"""Show details of a specific OAuth provider."""
provider_type = args.provider.lower()
print_header(f"{provider_type.title()} OAuth Provider Details")
try:
config = ExternalAuthService.get_app_provider_config(provider_type)
config_dict = config.to_dict()
print()
print(f"{Colors.BOLD}Basic Information:{Colors.ENDC}")
print(f" Provider Type: {config_dict['provider_type']}")
print(f" Provider ID: {config_dict['id']}")
print(f" Client ID: {config_dict['client_id']}")
status = f"{Colors.OKGREEN}enabled{Colors.ENDC}" if config_dict['is_enabled'] else f"{Colors.WARNING}disabled{Colors.ENDC}"
print(f" Status: {status}")
if config_dict.get('default_redirect_url'):
print(f" Default Redirect URL: {config_dict['default_redirect_url']}")
print()
print(f"{Colors.BOLD}Timestamps:{Colors.ENDC}")
print(f" Created: {config_dict.get('created_at', 'N/A')}")
print(f" Updated: {config_dict.get('updated_at', 'N/A')}")
# Show additional configuration
additional_config = config_dict.get('additional_config', {})
if additional_config:
print()
print(f"{Colors.BOLD}OAuth Configuration:{Colors.ENDC}")
if additional_config.get('auth_url'):
print(f" Authorization URL: {additional_config['auth_url']}")
if additional_config.get('token_url'):
print(f" Token URL: {additional_config['token_url']}")
if additional_config.get('userinfo_url'):
print(f" User Info URL: {additional_config['userinfo_url']}")
if additional_config.get('jwks_url'):
print(f" JWKS URL: {additional_config['jwks_url']}")
if additional_config.get('scopes'):
scopes = ', '.join(additional_config['scopes'])
print(f" Scopes: {scopes}")
# Show any custom settings
custom_settings = {k: v for k, v in additional_config.items()
if k not in ['auth_url', 'token_url', 'userinfo_url', 'jwks_url', 'scopes']}
if custom_settings:
print()
print(f"{Colors.BOLD}Custom Settings:{Colors.ENDC}")
for key, value in custom_settings.items():
print(f" {key}: {value}")
print()
return 0
except ExternalAuthError as e:
print_error(f"Failed to get provider: {e.message}")
return 1
except Exception as e:
print_error(f"Unexpected error: {str(e)}")
return 1
def delete_provider(args):
"""Delete an OAuth provider configuration."""
provider_type = args.provider.lower()
print_header(f"Deleting {provider_type.title()} OAuth Provider Configuration")
# Confirm deletion unless --yes flag is provided
if not args.yes:
print_warning("This will permanently delete the provider configuration.")
response = input(f"Are you sure you want to delete {provider_type}? (yes/no): ")
if response.lower() not in ['yes', 'y']:
print_info("Deletion cancelled.")
return 0
try:
ExternalAuthService.delete_app_provider_config(provider_type)
print_success(f"{provider_type.title()} provider deleted successfully!")
return 0
except ExternalAuthError as e:
print_error(f"Failed to delete provider: {e.message}")
return 1
except Exception as e:
print_error(f"Unexpected error: {str(e)}")
return 1
def main():
"""Main entry point for the script."""
parser = argparse.ArgumentParser(
description="Configure OAuth providers for Gatehouse authentication",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Create Google OAuth configuration
%(prog)s create google --client-id "CLIENT_ID" --client-secret "SECRET"
# Create with environment variables
GOOGLE_CLIENT_ID=xxx GOOGLE_CLIENT_SECRET=yyy %(prog)s create google
# List all providers
%(prog)s list
# Show provider details
%(prog)s show google
# Update provider
%(prog)s update google --enabled true
# Delete provider
%(prog)s delete google --yes
Supported Providers:
- google
- github
- microsoft
"""
)
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
subparsers.required = True
# Create command
create_parser = subparsers.add_parser("create", help="Create a new OAuth provider configuration")
create_parser.add_argument("provider", help="Provider type (google, github, microsoft)")
create_parser.add_argument("--client-id", help="OAuth client ID")
create_parser.add_argument("--client-secret", help="OAuth client secret")
create_parser.add_argument("--redirect-url", help="Default redirect URL for OAuth callbacks")
create_parser.add_argument("--disabled", action="store_true", help="Create provider in disabled state")
create_parser.add_argument("--settings", action="append", help="Custom settings (key=value format)")
create_parser.set_defaults(func=create_provider)
# Update command
update_parser = subparsers.add_parser("update", help="Update an existing OAuth provider configuration")
update_parser.add_argument("provider", help="Provider type to update")
update_parser.add_argument("--client-id", help="New OAuth client ID")
update_parser.add_argument("--client-secret", help="New OAuth client secret")
update_parser.add_argument("--redirect-url", help="New default redirect URL")
update_parser.add_argument("--enabled", type=lambda x: x.lower() in ['true', '1', 'yes'],
help="Enable or disable the provider (true/false)")
update_parser.add_argument("--settings", action="append", help="Custom settings to update (key=value format)")
update_parser.set_defaults(func=update_provider)
# List command
list_parser = subparsers.add_parser("list", help="List all configured OAuth providers")
list_parser.set_defaults(func=list_providers)
# Show command
show_parser = subparsers.add_parser("show", help="Show details of a specific OAuth provider")
show_parser.add_argument("provider", help="Provider type to show")
show_parser.set_defaults(func=show_provider)
# Delete command
delete_parser = subparsers.add_parser("delete", help="Delete an OAuth provider configuration")
delete_parser.add_argument("provider", help="Provider type to delete")
delete_parser.add_argument("--yes", "-y", action="store_true", help="Skip confirmation prompt")
delete_parser.set_defaults(func=delete_provider)
args = parser.parse_args()
# Create Flask app context
app = create_app()
with app.app_context():
return args.func(args)
if __name__ == "__main__":
sys.exit(main())