feat: Add NetID OAuth provider integration and update login UI
- Implemented NetID OAuth provider in app/auth/oauth.py - Added styles for NetID login button in app/static/css/styles.css - Updated login.html to include NetID login option with custom button - Modified docker-compose.yml to include NetID client credentials - Enhanced social login setup documentation to include NetID instructions - Created demo environment configuration file (.env.demo) for easier setup - Updated .env.template to include NetID configuration options - Added comprehensive README.md with project features and setup instructions
This commit is contained in:
@@ -818,6 +818,113 @@ class LinkedInOAuth(OAuthProvider):
|
||||
"raw": user_info
|
||||
}
|
||||
|
||||
class NetIDOAuth(OAuthProvider):
|
||||
"""NetID OAuth provider implementation"""
|
||||
|
||||
provider_id = "netid"
|
||||
display_name = "netID"
|
||||
icon_class = "fas fa-check" # Could be replaced with a custom netID icon class if available
|
||||
button_color = "#76b82a"
|
||||
|
||||
# NetID OIDC endpoints
|
||||
AUTHORIZATION_URL = "https://broker.netid.de/authorize"
|
||||
TOKEN_URL = "https://broker.netid.de/token"
|
||||
USERINFO_URL = "https://broker.netid.de/userinfo"
|
||||
|
||||
def __init__(self):
|
||||
self.client_id = os.getenv("NETID_CLIENT_ID", "")
|
||||
self.client_secret = os.getenv("NETID_CLIENT_SECRET", "")
|
||||
super().__init__()
|
||||
|
||||
def initialize_client(self):
|
||||
if not self.client_id or not self.client_secret:
|
||||
self.client = None
|
||||
return
|
||||
|
||||
try:
|
||||
# Create a custom OAuth2 client for NetID OpenID Connect
|
||||
from httpx_oauth.oauth2 import OAuth2
|
||||
self.client = OAuth2(
|
||||
client_id=self.client_id,
|
||||
client_secret=self.client_secret,
|
||||
authorize_endpoint=self.AUTHORIZATION_URL,
|
||||
access_token_endpoint=self.TOKEN_URL,
|
||||
refresh_token_endpoint=self.TOKEN_URL,
|
||||
base_scopes=["openid", "email", "profile"]
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error initializing NetID OAuth client: {str(e)}")
|
||||
self.client = None
|
||||
|
||||
async def get_login_url(self, request: Request, redirect_uri: str) -> str:
|
||||
if not self.client:
|
||||
self.initialize_client()
|
||||
|
||||
if not self.client:
|
||||
raise HTTPException(status_code=500, detail="NetID OAuth client could not be initialized")
|
||||
|
||||
try:
|
||||
authorization_url = await self.client.get_authorization_url(
|
||||
redirect_uri=redirect_uri,
|
||||
scope=["openid", "email", "profile"],
|
||||
state=str(request.session.get("session_id", ""))
|
||||
)
|
||||
return authorization_url
|
||||
except Exception as e:
|
||||
print(f"Error getting NetID authorization URL: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"OAuth error: {str(e)}")
|
||||
|
||||
async def get_user_info(self, request: Request, redirect_uri: str, code: str) -> Dict[str, Any]:
|
||||
if not self.client:
|
||||
self.initialize_client()
|
||||
|
||||
if not self.client:
|
||||
raise HTTPException(status_code=500, detail="NetID OAuth client could not be initialized")
|
||||
|
||||
try:
|
||||
# Exchange code for token
|
||||
token = await self.client.get_access_token(
|
||||
code=code,
|
||||
redirect_uri=redirect_uri
|
||||
)
|
||||
|
||||
access_token = token.get("access_token")
|
||||
|
||||
if not access_token:
|
||||
raise HTTPException(status_code=400, detail="Could not get NetID access token")
|
||||
|
||||
# Get user info from NetID UserInfo endpoint
|
||||
async with httpx.AsyncClient() as client:
|
||||
headers = {"Authorization": f"Bearer {access_token}"}
|
||||
response = await client.get(
|
||||
self.USERINFO_URL,
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
raise HTTPException(status_code=500, detail=f"Error fetching NetID user info: {response.text}")
|
||||
|
||||
return response.json()
|
||||
|
||||
except GetAccessTokenError as e:
|
||||
error_description = e.args[0]
|
||||
raise HTTPException(status_code=400, detail=f"NetID OAuth error: {error_description}")
|
||||
except Exception as e:
|
||||
print(f"Error getting NetID user info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"OAuth error: {str(e)}")
|
||||
|
||||
def get_normalized_user_data(self, user_info: Dict[str, Any]) -> Dict[str, Any]:
|
||||
# NetID OpenID Connect response normalization
|
||||
return {
|
||||
"id": user_info.get("sub", ""), # 'sub' is the standard OIDC subject identifier
|
||||
"email": user_info.get("email"),
|
||||
"name": f"{user_info.get('given_name', '')} {user_info.get('family_name', '')}".strip(),
|
||||
"first_name": user_info.get("given_name"),
|
||||
"last_name": user_info.get("family_name"),
|
||||
"picture": None, # NetID might not provide profile picture
|
||||
"raw": user_info
|
||||
}
|
||||
|
||||
class OAuthManager:
|
||||
"""
|
||||
Manager class for handling multiple OAuth providers
|
||||
@@ -836,6 +943,7 @@ class OAuthManager:
|
||||
self.register_provider(MicrosoftOAuth())
|
||||
self.register_provider(DiscordOAuth())
|
||||
self.register_provider(LinkedInOAuth())
|
||||
self.register_provider(NetIDOAuth()) # Register NetID provider
|
||||
|
||||
def register_provider(self, provider: OAuthProvider):
|
||||
"""Register a new provider"""
|
||||
|
||||
Reference in New Issue
Block a user