Files
thrillwiki_django_no_react/scripts/github-auth.py
pacnpal b5bae44cb8 Add Road Trip Planner template with interactive map and trip management features
- Implemented a new HTML template for the Road Trip Planner.
- Integrated Leaflet.js for interactive mapping and routing.
- Added functionality for searching and selecting parks to include in a trip.
- Enabled drag-and-drop reordering of selected parks.
- Included trip optimization and route calculation features.
- Created a summary display for trip statistics.
- Added functionality to save trips and manage saved trips.
- Enhanced UI with responsive design and dark mode support.
2025-08-15 20:53:00 -04:00

220 lines
6.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""
GitHub OAuth Device Flow Authentication for ThrillWiki CI/CD
This script implements GitHub's device flow to securely obtain access tokens.
"""
import os
import sys
import json
import time
import requests
import argparse
from pathlib import Path
from urllib.parse import urlencode
# GitHub OAuth App Configuration
CLIENT_ID = "Iv23liOX5Hp75AxhUvIe"
TOKEN_FILE = ".github-token"
def parse_response(response):
"""Parse HTTP response and handle errors."""
if response.status_code in [200, 201]:
return response.json()
elif response.status_code == 401:
print("You are not authorized. Run the `login` command.")
sys.exit(1)
else:
print(f"HTTP {response.status_code}: {response.text}")
sys.exit(1)
def request_device_code():
"""Request a device code from GitHub."""
url = "https://github.com/login/device/code"
data = {"client_id": CLIENT_ID}
headers = {"Accept": "application/json"}
response = requests.post(url, data=data, headers=headers)
return parse_response(response)
def request_token(device_code):
"""Request an access token using the device code."""
url = "https://github.com/login/oauth/access_token"
data = {
"client_id": CLIENT_ID,
"device_code": device_code,
"grant_type": "urn:ietf:params:oauth:grant-type:device_code"
}
headers = {"Accept": "application/json"}
response = requests.post(url, data=data, headers=headers)
return parse_response(response)
def poll_for_token(device_code, interval):
"""Poll GitHub for the access token after user authorization."""
print("Waiting for authorization...")
while True:
response = request_token(device_code)
error = response.get("error")
access_token = response.get("access_token")
if error:
if error == "authorization_pending":
# User hasn't entered the code yet
print(".", end="", flush=True)
time.sleep(interval)
continue
elif error == "slow_down":
# Polling too fast
time.sleep(interval + 5)
continue
elif error == "expired_token":
print("\nThe device code has expired. Please run `login` again.")
sys.exit(1)
elif error == "access_denied":
print("\nLogin cancelled by user.")
sys.exit(1)
else:
print(f"\nError: {response}")
sys.exit(1)
# Success! Save the token
token_path = Path(TOKEN_FILE)
token_path.write_text(access_token)
token_path.chmod(0o600) # Read/write for owner only
print(f"\nToken saved to {TOKEN_FILE}")
break
def login():
"""Initiate the GitHub OAuth device flow login process."""
print("Starting GitHub authentication...")
device_response = request_device_code()
verification_uri = device_response["verification_uri"]
user_code = device_response["user_code"]
device_code = device_response["device_code"]
interval = device_response["interval"]
print(f"\nPlease visit: {verification_uri}")
print(f"and enter code: {user_code}")
print("\nWaiting for you to complete authorization in your browser...")
poll_for_token(device_code, interval)
print("Successfully authenticated!")
return True
def whoami():
"""Display information about the authenticated user."""
token_path = Path(TOKEN_FILE)
if not token_path.exists():
print("You are not authorized. Run the `login` command.")
sys.exit(1)
try:
token = token_path.read_text().strip()
except Exception as e:
print(f"Error reading token: {e}")
print("You may need to run the `login` command again.")
sys.exit(1)
url = "https://api.github.com/user"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}"
}
response = requests.get(url, headers=headers)
user_data = parse_response(response)
print(f"You are authenticated as: {user_data['login']}")
print(f"Name: {user_data.get('name', 'Not set')}")
print(f"Email: {user_data.get('email', 'Not public')}")
return user_data
def get_token():
"""Get the current access token if available."""
token_path = Path(TOKEN_FILE)
if not token_path.exists():
return None
try:
return token_path.read_text().strip()
except Exception:
return None
def validate_token():
"""Validate that the current token is still valid."""
token = get_token()
if not token:
return False
url = "https://api.github.com/user"
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}"
}
try:
response = requests.get(url, headers=headers)
return response.status_code == 200
except Exception:
return False
def ensure_authenticated():
"""Ensure user is authenticated, prompting login if necessary."""
if validate_token():
return get_token()
print("GitHub authentication required.")
login()
return get_token()
def logout():
"""Remove the stored access token."""
token_path = Path(TOKEN_FILE)
if token_path.exists():
token_path.unlink()
print("Successfully logged out.")
else:
print("You are not currently logged in.")
def main():
"""Main CLI interface."""
parser = argparse.ArgumentParser(description="GitHub OAuth authentication for ThrillWiki CI/CD")
parser.add_argument("command", choices=["login", "logout", "whoami", "token", "validate"],
help="Command to execute")
if len(sys.argv) == 1:
parser.print_help()
sys.exit(1)
args = parser.parse_args()
if args.command == "login":
login()
elif args.command == "logout":
logout()
elif args.command == "whoami":
whoami()
elif args.command == "token":
token = get_token()
if token:
print(token)
else:
print("No token available. Run `login` first.")
sys.exit(1)
elif args.command == "validate":
if validate_token():
print("Token is valid.")
else:
print("Token is invalid or missing.")
sys.exit(1)
if __name__ == "__main__":
main()