mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 21:51:08 -05:00
Refactor test utilities and enhance ASGI settings
- Cleaned up and standardized assertions in ApiTestMixin for API response validation. - Updated ASGI settings to use os.environ for setting the DJANGO_SETTINGS_MODULE. - Removed unused imports and improved formatting in settings.py. - Refactored URL patterns in urls.py for better readability and organization. - Enhanced view functions in views.py for consistency and clarity. - Added .flake8 configuration for linting and style enforcement. - Introduced type stubs for django-environ to improve type checking with Pylance.
This commit is contained in:
@@ -4,19 +4,17 @@ 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]:
|
||||
@@ -28,37 +26,40 @@ def parse_response(response):
|
||||
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"
|
||||
"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
|
||||
@@ -78,124 +79,136 @@ def poll_for_token(device_code, interval):
|
||||
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}"
|
||||
"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}"
|
||||
"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")
|
||||
|
||||
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":
|
||||
@@ -216,5 +229,6 @@ def main():
|
||||
print("Token is invalid or missing.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user