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:
Christian Krakau-Louis
2025-04-15 14:28:14 +02:00
parent 6306abf6d9
commit 6d796c95ff
8 changed files with 863 additions and 179 deletions
+74
View File
@@ -0,0 +1,74 @@
# LeagueLedger Demo Environment Configuration
# This file contains example values for development/demo purposes
# DO NOT use these values in production!
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=leagueledger_demo
DB_USER=leagueledger_user
DB_PASSWORD=demo_password_123
# Application Settings
SECRET_KEY=demo-secret-key-replace-in-production-e8a1b2c3d4e5f6
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1,demo.leagueledger.com
BASE_URL=http://localhost:8000
TIMEZONE=Europe/Dublin
# Email Configuration
EMAIL_BACKEND=smtp
EMAIL_HOST=smtp.mailgun.org
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=postmaster@demo.leagueledger.com
EMAIL_HOST_PASSWORD=demo_mail_password_123
DEFAULT_FROM_EMAIL=noreply@demo.leagueledger.com
# Security Settings
# These are set to False for local development
SESSION_COOKIE_SECURE=False
CSRF_COOKIE_SECURE=False
SECURE_BROWSER_XSS_FILTER=True
SECURE_CONTENT_TYPE_NOSNIFF=True
# OAuth Providers - Example values (these are not real credentials)
# Google
GOOGLE_CLIENT_ID=123456789012-abcdefghijklmnopqrstuvwxyz123456.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOCSPX-abcdefghijklmnopqrstuvwxyz12345
# Facebook
FACEBOOK_CLIENT_ID=1234567890123456
FACEBOOK_CLIENT_SECRET=abcdef0123456789abcdef0123456789
# GitHub
GITHUB_CLIENT_ID=Iv1.abcdef1234567890
GITHUB_CLIENT_SECRET=abcdef1234567890abcdef1234567890abcdef12
# LinkedIn
LINKEDIN_CLIENT_ID=abcdefghij1234567890
LINKEDIN_CLIENT_SECRET=ABCDEfghij1234567890abcdefghijklmno
# Microsoft
MICROSOFT_CLIENT_ID=12345678-1234-1234-1234-123456789012
MICROSOFT_CLIENT_SECRET=abc~DEF.ghiJKLmnoPQRstuvwxyz_12345678
MICROSOFT_TENANT=common
# Discord
DISCORD_CLIENT_ID=123456789012345678
DISCORD_CLIENT_SECRET=abcDEFghi1234567890JKLMNO_pqrSTUvwxyz
# Authentik
AUTHENTIK_CLIENT_ID=yourAuthentikClientID
AUTHENTIK_CLIENT_SECRET=yourAuthentikClientSecret
AUTHENTIK_CONFIG_URL=https://authentik.example.com/application/o/leagueledger/.well-known/openid-configuration
# NetID
NETID_CLIENT_ID=leagueledger-netid-client
NETID_CLIENT_SECRET=netid-client-secret-abcdef1234567890
# Feature Flags
ENABLE_REGISTRATION=True
ENABLE_PASSWORD_RESET=True
ENABLE_EMAIL_VERIFICATION=True
ENABLE_SOCIAL_LOGIN=True
+72
View File
@@ -0,0 +1,72 @@
# LeagueLedger Environment Configuration Template
# Copy this file to .env and fill in the values
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=leagueledger
DB_USER=postgres
DB_PASSWORD=
# Application Settings
SECRET_KEY=
DEBUG=False
ALLOWED_HOSTS=localhost,127.0.0.1
BASE_URL=http://localhost:8000
TIMEZONE=Europe/Dublin
# Email Configuration
EMAIL_BACKEND=smtp
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
DEFAULT_FROM_EMAIL=noreply@leagueledger.com
# Security Settings
SESSION_COOKIE_SECURE=True
CSRF_COOKIE_SECURE=True
SECURE_BROWSER_XSS_FILTER=True
SECURE_CONTENT_TYPE_NOSNIFF=True
# OAuth Providers
# Google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Facebook
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
# GitHub
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# LinkedIn
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
# Microsoft
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
MICROSOFT_TENANT=common
# Discord
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
# Authentik
AUTHENTIK_CLIENT_ID=
AUTHENTIK_CLIENT_SECRET=
AUTHENTIK_CONFIG_URL=
# NetID
NETID_CLIENT_ID=
NETID_CLIENT_SECRET=
# Feature Flags
ENABLE_REGISTRATION=True
ENABLE_PASSWORD_RESET=True
ENABLE_EMAIL_VERIFICATION=True
ENABLE_SOCIAL_LOGIN=True
+58
View File
@@ -0,0 +1,58 @@
# LeagueLedger <img src="app/static/images/logos/monogram.png" alt="LeagueLedger Logo" width="40" align="right">
A modern web application for managing sports league loyalty programs, events, and rewards through QR codes.
## Features
- **Team Management**: Create and manage teams with member profiles
- **QR Code Redemption**: Generate and scan QR codes for points and rewards
- **Leaderboards**: Track team and individual standings
- **OAuth Integration**: Multiple social login options including Google, GitHub, LinkedIn, Facebook, and netID
- **Internationalization**: Multi-language support with English and German locales
- **Responsive Design**: Mobile-friendly interface built with Tailwind CSS
## Quick Start
1. **Clone the repository**
```
git clone https://github.com/yourusername/LeagueLedger.git
cd LeagueLedger
```
2. **Set up environment variables**
```
cp .env.template .env
# Edit .env with your configuration
```
3. **Run with Docker**
```
docker-compose up -d
```
4. **Or run locally**
```
pip install -r requirements.txt
python app/main.py
```
5. **Access the application**
```
http://localhost:8000
```
## Documentation
Detailed documentation is available in the `/docs` directory. Generate the complete documentation site with:
```
mkdocs serve
```
## License
This project is licensed under the terms of the license file included in this repository.
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
+108
View File
@@ -818,6 +818,113 @@ class LinkedInOAuth(OAuthProvider):
"raw": user_info "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: class OAuthManager:
""" """
Manager class for handling multiple OAuth providers Manager class for handling multiple OAuth providers
@@ -836,6 +943,7 @@ class OAuthManager:
self.register_provider(MicrosoftOAuth()) self.register_provider(MicrosoftOAuth())
self.register_provider(DiscordOAuth()) self.register_provider(DiscordOAuth())
self.register_provider(LinkedInOAuth()) self.register_provider(LinkedInOAuth())
self.register_provider(NetIDOAuth()) # Register NetID provider
def register_provider(self, provider: OAuthProvider): def register_provider(self, provider: OAuthProvider):
"""Register a new provider""" """Register a new provider"""
+322 -1
View File
@@ -1,7 +1,7 @@
/* /*
* LeagueLedger Main Stylesheet * LeagueLedger Main Stylesheet
* Version: 1.0.0 * Version: 1.0.0
* Date: April 13, 2025 * Date: April 15, 2025
*/ */
:root { :root {
@@ -156,3 +156,324 @@ h1, h2, h3, h4, h5, h6 {
color: var(--irish-green); color: var(--irish-green);
} }
} }
/* Google Sign-In Button Styles */
.gsi-material-button {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
-webkit-appearance: none;
background-color: WHITE;
background-image: none;
border: 1px solid #747775;
-webkit-border-radius: 4px;
border-radius: 4px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #1f1f1f;
cursor: pointer;
font-family: 'Roboto', arial, sans-serif;
font-size: 14px;
height: 40px;
letter-spacing: 0.25px;
outline: none;
overflow: hidden;
padding: 0 12px;
position: relative;
text-align: center;
-webkit-transition: background-color .218s, border-color .218s, box-shadow .218s;
transition: background-color .218s, border-color .218s, box-shadow .218s;
vertical-align: middle;
white-space: nowrap;
width: auto;
max-width: 400px;
min-width: min-content;
}
.gsi-material-button .gsi-material-button-icon {
height: 20px;
margin-right: 12px;
min-width: 20px;
width: 20px;
}
.gsi-material-button .gsi-material-button-content-wrapper {
-webkit-align-items: center;
align-items: center;
display: flex;
-webkit-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: nowrap;
flex-wrap: nowrap;
height: 100%;
justify-content: space-between;
position: relative;
width: 100%;
}
.gsi-material-button .gsi-material-button-contents {
-webkit-flex-grow: 1;
flex-grow: 1;
font-family: 'Roboto', arial, sans-serif;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: top;
}
.gsi-material-button .gsi-material-button-state {
-webkit-transition: opacity .218s;
transition: opacity .218s;
bottom: 0;
left: 0;
opacity: 0;
position: absolute;
right: 0;
top: 0;
}
.gsi-material-button:disabled {
cursor: default;
background-color: #ffffff61;
border-color: #1f1f1f1f;
}
.gsi-material-button:disabled .gsi-material-button-contents {
opacity: 38%;
}
.gsi-material-button:disabled .gsi-material-button-icon {
opacity: 38%;
}
.gsi-material-button:not(:disabled):active .gsi-material-button-state,
.gsi-material-button:not(:disabled):focus .gsi-material-button-state {
background-color: #303030;
opacity: 12%;
}
.gsi-material-button:not(:disabled):hover {
-webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
}
.gsi-material-button:not(:disabled):hover .gsi-material-button-state {
background-color: #303030;
opacity: 8%;
}
/* OAuth Buttons Container */
.oauth-buttons-container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 280px;
margin: 0 auto;
}
/* LinkedIn Login Button */
.linkedin-login-button {
background-color: #0077B5;
border: none;
border-radius: 4px;
color: white;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
height: 40px;
letter-spacing: normal;
padding: 0;
position: relative;
text-align: center;
transition: background-color .218s;
width: 100%;
max-width: 280px;
margin: 0 auto 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.linkedin-login-button:hover {
background-color: #006097;
}
.linkedin-login-button .linkedin-icon {
display: inline-block;
margin-right: 8px;
height: 18px;
width: 18px;
}
.linkedin-login-button .linkedin-icon svg {
height: 18px;
width: 18px;
fill: white;
}
.linkedin-login-button .button-text {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
}
/* Facebook Login Button */
.fb-login-button {
background-color: #1877F2;
border: none;
border-radius: 4px;
color: white;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
height: 40px;
letter-spacing: normal;
padding: 0;
position: relative;
text-align: center;
transition: background-color .218s;
width: 100%;
max-width: 280px;
margin: 0 auto 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.fb-login-button:hover {
background-color: #166FE5;
}
.fb-login-button .fb-icon {
display: inline-block;
margin-right: 8px;
height: 18px;
width: 18px;
background-color: white;
border-radius: 2px;
}
.fb-login-button .fb-icon svg {
height: 18px;
width: 18px;
fill: #1877F2;
}
.fb-login-button .button-text {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
}
/* GitHub Login Button */
.github-login-button {
background-color: #24292e;
border: 1px solid rgba(27, 31, 35, 0.15);
border-radius: 6px;
color: white;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
height: 40px;
padding: 0;
position: relative;
text-align: center;
transition: background-color 0.2s;
width: 100%;
max-width: 280px;
margin: 0 auto 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.github-login-button:hover {
background-color: #2c3136;
}
.github-login-button .github-icon {
display: inline-block;
margin-right: 8px;
height: 20px;
width: 20px;
}
.github-login-button .github-icon svg {
height: 20px;
width: 20px;
fill: white;
}
.github-login-button .button-text {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
}
/* NetID Login Button */
.netid-login-button {
background-color: #76b82a;
border: none;
border-radius: 3px;
color: white;
font-family: 'IBM Plex Sans', Verdana, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
height: 40px;
padding: 0;
position: relative;
text-align: center;
transition: background-color 0.2s;
width: 100%;
max-width: 280px;
margin: 0 auto 8px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.netid-login-button:hover {
background-color: #5d9422;
}
.netid-login-button .netid-icon {
display: inline-block;
margin-right: 8px;
height: 20px;
width: 20px;
}
.netid-login-button .netid-icon svg {
height: 20px;
width: 20px;
fill: white;
}
.netid-login-button .button-text {
font-family: 'IBM Plex Sans', Verdana, Arial, sans-serif;
font-size: 14px;
font-weight: 600;
}
/* Other OAuth provider button customizations */
.oauth-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
transition: all 0.2s ease;
margin-bottom: 8px;
text-decoration: none;
}
+77 -16
View File
@@ -91,30 +91,91 @@
<div class="mt-6 pt-6 border-t border-gray-200"> <div class="mt-6 pt-6 border-t border-gray-200">
<p class="text-center text-gray-600 mb-4">Or sign in with</p> <p class="text-center text-gray-600 mb-4">Or sign in with</p>
<!-- For 2 or fewer providers, show them side by side --> <div class="oauth-buttons-container">
{% if oauth_providers|length <= 2 %}
<div class="flex justify-center space-x-4">
{% for provider in oauth_providers %} {% for provider in oauth_providers %}
<a href="/auth/oauth-login/{{ provider.id }}" {% if provider.id == 'google' %}
class="flex items-center justify-center w-full py-2 px-4 rounded-md transition" <!-- Custom Google Button -->
style="background-color: {{ provider.color }}; color: white;"> <a href="/auth/oauth-login/{{ provider.id }}" class="block w-full mb-3">
<i class="{{ provider.icon }} mr-2"></i> {{ provider.name }} <button class="gsi-material-button" type="button">
</a> <div class="gsi-material-button-state"></div>
{% endfor %} <div class="gsi-material-button-content-wrapper">
<div class="gsi-material-button-icon">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: block;">
<path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
<path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
<path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
<path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
<path fill="none" d="M0 0h48v48H0z"></path>
</svg>
</div> </div>
<span class="gsi-material-button-contents">Sign in with Google</span>
<!-- For more than 2 providers, stack them vertically --> <span style="display: none;">Sign in with Google</span>
</div>
</button>
</a>
{% elif provider.id == 'facebook' %}
<!-- Custom Facebook Button according to Meta's guidelines -->
<a href="/auth/oauth-login/{{ provider.id }}" class="block w-full mb-3">
<button class="fb-login-button" type="button">
<div class="fb-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M9.198 21.5h4v-8.01h3.604l.396-3.98h-4V7.5a1 1 0 0 1 1-1h3v-4h-3a5 5 0 0 0-5 5v2.01h-2l-.396 3.98h2.396v8.01Z" fill="#1877F2" />
</svg>
</div>
<span class="button-text">Continue with Facebook</span>
</button>
</a>
{% elif provider.id == 'linkedin' %}
<!-- Custom LinkedIn Button -->
<a href="/auth/oauth-login/{{ provider.id }}" class="block w-full mb-3">
<button class="linkedin-login-button" type="button">
<div class="linkedin-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1-2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z" fill="white" />
</svg>
</div>
<span class="button-text">Sign in with LinkedIn</span>
</button>
</a>
{% elif provider.id == 'github' %}
<!-- Custom GitHub Button -->
<a href="/auth/oauth-login/{{ provider.id }}" class="block w-full mb-3">
<button class="github-login-button" type="button">
<div class="github-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" fill="white" />
</svg>
</div>
<span class="button-text">Login with GitHub</span>
</button>
</a>
{% elif provider.id == 'netid' %}
<!-- NetID explanation text -->
<div class="text-center mb-2 text-xs text-gray-600">
<p>Login with GMX, WEB.DE, or other email providers via netID</p>
</div>
<!-- Custom NetID Button -->
<a href="/auth/oauth-login/{{ provider.id }}" class="block w-full mb-3">
<button class="netid-login-button" type="button">
<div class="netid-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40">
<path fill="#fff" d="M30.3 4.1a14.5 14.5 0 00-6-2.7C20.7 1 17 2 13.6 5.3L7 12.1l-6.9 7 19.8 19.8 8.8-8.9 5-4.9c3-3.1 4.2-6.7 3.8-10a12.9 12.9 0 00-1-3.8l-2 2a10 10 0 01.6 2c.3 2.7-.5 5.4-3.2 8l-4.9 5-16.3-16.4L15.5 7c3-3 5.7-3.6 8.3-3.1 1.6.2 3.2 1 4.7 2z"></path>
<path fill="#fff" d="M37.7 1.1l-12.9 13-4.6-4.7-2.3 2.3 6.9 7L40 3.5z"></path>
</svg>
</div>
<span class="button-text">Login mit netID</span>
</button>
</a>
{% else %} {% else %}
<div class="space-y-3"> <!-- Standard Button for other providers -->
{% for provider in oauth_providers %}
<a href="/auth/oauth-login/{{ provider.id }}" <a href="/auth/oauth-login/{{ provider.id }}"
class="flex items-center justify-center w-full py-2 px-4 rounded-md transition" class="oauth-button flex items-center justify-center w-full py-2 px-4 rounded-md transition mb-3"
style="background-color: {{ provider.color }}; color: white;"> style="background-color: {{ provider.color }}; color: white;">
<i class="{{ provider.icon }} mr-2"></i> {{ provider.name }} <i class="{{ provider.icon }} mr-2"></i> Sign in with {{ provider.name }}
</a> </a>
{% endif %}
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
+9 -5
View File
@@ -70,17 +70,21 @@ services:
FACEBOOK_CLIENT_SECRET: ${FACEBOOK_CLIENT_SECRET:-} FACEBOOK_CLIENT_SECRET: ${FACEBOOK_CLIENT_SECRET:-}
# Microsoft OAuth # Microsoft OAuth
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-} #MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-} #MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET:-}
MICROSOFT_TENANT: ${MICROSOFT_TENANT:-common} #MICROSOFT_TENANT: ${MICROSOFT_TENANT:-common}
# Discord OAuth # Discord OAuth
DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID:-} #DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID:-}
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:-} #DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET:-}
# LinkedIn OAuth # LinkedIn OAuth
LINKEDIN_CLIENT_ID: ${LINKEDIN_CLIENT_ID:-} LINKEDIN_CLIENT_ID: ${LINKEDIN_CLIENT_ID:-}
LINKEDIN_CLIENT_SECRET: ${LINKEDIN_CLIENT_SECRET:-} LINKEDIN_CLIENT_SECRET: ${LINKEDIN_CLIENT_SECRET:-}
NETID_CLIENT_ID: ${NETID_CLIENT_ID:-}
NETID_CLIENT_SECRET: ${NETID_CLIENT_SECRET:-}
command: uvicorn app.main:app --host 0.0.0.0 --reload command: uvicorn app.main:app --host 0.0.0.0 --reload
ports: ports:
- "8000:8000" - "8000:8000"
+134 -148
View File
@@ -1,54 +1,17 @@
# Setting Up Social Login in LeagueLedger # Social Login Setup
LeagueLedger supports multiple social login (OAuth) providers to give your users various options for authentication. This document explains how to set up each supported provider. This guide provides instructions for setting up various social login providers for LeagueLedger.
## Table of Contents ## Table of Contents
1. [General Setup](#general-setup) - [Google OAuth Setup](#google-oauth-setup)
2. [Callback URLs](#callback-urls) - [Facebook Login Setup](#facebook-login-setup)
3. [Provider-Specific Instructions](#provider-specific-instructions) - [GitHub OAuth Setup](#github-oauth-setup)
- [Google](#google) - [LinkedIn OAuth Setup](#linkedin-oauth-setup)
- [GitHub](#github) - [Microsoft OAuth Setup](#microsoft-oauth-setup)
- [Facebook](#facebook) - [Discord OAuth Setup](#discord-oauth-setup)
- [Microsoft](#microsoft) - [NetID OAuth Setup](#netid-oauth-setup)
- [Discord](#discord)
- [LinkedIn](#linkedin)
- [Authentik](#authentik)
4. [Troubleshooting](#troubleshooting)
## General Setup ## Google OAuth Setup
To enable social login in LeagueLedger, you need to:
1. Register your application with the desired OAuth provider(s)
2. Obtain client ID and client secret credentials
3. Add these credentials to your environment variables or `.env` file
4. Restart the application
Only providers with valid credentials will appear on the login page.
## Callback URLs
Each OAuth provider requires you to configure a **Redirect URI** (also known as a callback URL). This is where the provider redirects users after they authenticate.
For LeagueLedger, use the following pattern:
```
https://your-domain.com/auth/oauth-callback/{provider_id}
```
Replace:
- `your-domain.com` with your actual domain
- `{provider_id}` with one of: `google`, `github`, `facebook`, `microsoft`, `discord`, `linkedin`, or `authentik`
For local development, use:
```
http://localhost:8000/auth/oauth-callback/{provider_id}
```
**Important:** Most OAuth providers require exact URL matches, including protocol (http/https), domain, path, and any query parameters. Make sure to register the exact URL as shown above.
## Provider-Specific Instructions
### Google
1. Go to [Google Cloud Console](https://console.cloud.google.com/) 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project or select an existing one 2. Create a new project or select an existing one
@@ -68,26 +31,7 @@ http://localhost:8000/auth/oauth-callback/{provider_id}
GOOGLE_CLIENT_SECRET=your-client-secret GOOGLE_CLIENT_SECRET=your-client-secret
``` ```
### GitHub ## Facebook Login Setup
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
2. Click "New OAuth App"
3. Fill in your application details:
- Application name: "LeagueLedger"
- Homepage URL: Your app's URL or `http://localhost:8000`
- Authorization callback URL:
```
http://localhost:8000/auth/oauth-callback/github
```
4. Click "Register application"
5. Generate a new client secret
6. Add to your `.env` file:
```
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
```
### Facebook
1. Go to [Facebook Developers](https://developers.facebook.com/) 1. Go to [Facebook Developers](https://developers.facebook.com/)
2. Create a new app (choose "Consumer" or "Business" type) 2. Create a new app (choose "Consumer" or "Business" type)
@@ -103,48 +47,56 @@ http://localhost:8000/auth/oauth-callback/{provider_id}
FACEBOOK_CLIENT_SECRET=your-app-secret FACEBOOK_CLIENT_SECRET=your-app-secret
``` ```
### Microsoft ## GitHub OAuth Setup
1. Go to [Azure Portal](https://portal.azure.com/) ### 1. Create a GitHub OAuth App
2. Navigate to "App registrations"
3. Click "New registration"
4. Enter a name for your application
5. For "Supported account types," choose an option based on your needs
(typically "Accounts in any organizational directory and personal Microsoft accounts")
6. Add the following Redirect URI (type: Web):
```
http://localhost:8000/auth/oauth-callback/microsoft
```
7. Click "Register"
8. Note the Application (client) ID
9. Create a client secret: Navigate to "Certificates & secrets" > "New client secret"
10. Add to your `.env` file:
```
MICROSOFT_CLIENT_ID=your-client-id
MICROSOFT_CLIENT_SECRET=your-client-secret
MICROSOFT_TENANT=common
```
Note: Use `common` for multi-tenant apps, or your specific tenant ID
### Discord 1. Go to your GitHub account settings
2. Click on "Developer settings" in the left sidebar
3. Click on "OAuth Apps" and then "New OAuth App"
4. Fill out the form:
- **Application name**: LeagueLedger
- **Homepage URL**: Your site's URL (e.g. https://leagueledger.com)
- **Application description**: (Optional) A description of your app
- **Authorization callback URL**: Your callback URL (e.g. https://leagueledger.com/auth/oauth-callback/github)
5. Click "Register application"
6. You'll receive a Client ID
7. Click "Generate a new client secret" to create your Client Secret
8. Save both the Client ID and Client Secret safely
### 2. Configure Environment Variables
Add the following variables to your `.env` file:
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
2. Click "New Application"
3. Enter a name and click "Create"
4. Go to the "OAuth2" section in the left sidebar
5. Note the Client ID and generate a Client Secret
6. Add the following redirect URL:
``` ```
http://localhost:8000/auth/oauth-callback/discord GITHUB_CLIENT_ID=your_github_client_id
``` GITHUB_CLIENT_SECRET=your_github_client_secret
7. In the "OAuth2 URL Generator" section, select the "identify" and "email" scopes
8. Add to your `.env` file:
```
DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret
``` ```
### LinkedIn ### 3. Security Considerations
- **Never commit your Client Secret to version control**
- Store your Client Secret securely in environment variables or a secret management system
- In production, update the callback URL to use your production domain
- Consider implementing additional security measures like CSRF protection
### 4. Testing GitHub Login
After configuration:
1. Ensure the server is running with the environment variables loaded
2. Navigate to the login page
3. Click the "Login with GitHub" button
4. You should be redirected to GitHub's authorization page
5. After authorizing, you should be redirected back to your application and logged in
### 5. Troubleshooting GitHub OAuth
- **Invalid callback URL**: Ensure the callback URL registered in GitHub matches exactly what your application uses
- **Rate limiting**: GitHub has API rate limits that might affect your OAuth flow
- **Scope issues**: If you're not receiving email information, ensure you've requested the `user:email` scope
- **Token refresh**: If tokens expire, implement a refresh flow
## LinkedIn OAuth Setup
1. Go to the [LinkedIn Developer Portal](https://www.linkedin.com/developers/) 1. Go to the [LinkedIn Developer Portal](https://www.linkedin.com/developers/)
2. Click "Create app" 2. Click "Create app"
@@ -171,63 +123,97 @@ http://localhost:8000/auth/oauth-callback/{provider_id}
LINKEDIN_CLIENT_SECRET=your-client-secret LINKEDIN_CLIENT_SECRET=your-client-secret
``` ```
### Authentik ## Microsoft OAuth Setup
1. Access your Authentik admin interface 1. Go to [Azure Portal](https://portal.azure.com/)
2. Go to "Applications" > "Providers" > "Create" 2. Navigate to "App registrations"
3. Select "OAuth2/OIDC Provider" 3. Click "New registration"
4. Configure the provider: 4. Enter a name for your application
- Name: LeagueLedger 5. For "Supported account types," choose an option based on your needs
- Client Type: Confidential (typically "Accounts in any organizational directory and personal Microsoft accounts")
- Redirect URIs: 6. Add the following Redirect URI (type: Web):
``` ```
http://localhost:8000/auth/oauth-callback/authentik http://localhost:8000/auth/oauth-callback/microsoft
``` ```
- Signing Key: Select an appropriate key or create one 7. Click "Register"
5. Save the provider 8. Note the Application (client) ID
6. Create an application: 9. Create a client secret: Navigate to "Certificates & secrets" > "New client secret"
- Go to "Applications" > "Applications" > "Create" 10. Add to your `.env` file:
- Name: LeagueLedger
- Slug: leagueledger
- Provider: Select the provider you just created
7. Save the application
8. Note the Client ID and Client Secret
9. Add to your `.env` file:
``` ```
AUTHENTIK_CLIENT_ID=your-client-id MICROSOFT_CLIENT_ID=your-client-id
AUTHENTIK_CLIENT_SECRET=your-client-secret MICROSOFT_CLIENT_SECRET=your-client-secret
AUTHENTIK_CONFIG_URL=https://your-authentik-domain/application/o/leagueledger/.well-known/openid-configuration MICROSOFT_TENANT=common
```
Note: Use `common` for multi-tenant apps, or your specific tenant ID
## Discord OAuth Setup
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
2. Click "New Application"
3. Enter a name and click "Create"
4. Go to the "OAuth2" section in the left sidebar
5. Note the Client ID and generate a Client Secret
6. Add the following redirect URL:
```
http://localhost:8000/auth/oauth-callback/discord
```
7. In the "OAuth2 URL Generator" section, select the "identify" and "email" scopes
8. Add to your `.env` file:
```
DISCORD_CLIENT_ID=your-client-id
DISCORD_CLIENT_SECRET=your-client-secret
``` ```
## Troubleshooting ## NetID OAuth Setup
### Common Issues: ### 1. Create a NetID Service
1. **Provider not showing on login page** 1. Go to the [NetID Developer Zone](https://developer.netid.de/)
- Check that client ID and secret are correctly set in your environment/`.env` file 2. Create an account or log in with your existing credentials
- Verify that values are not empty strings 3. Go to "Services" in the menu and click "Add service"
- Check application logs for initialization errors 4. Fill in the required details:
- **Service domain**: Your site's domain (e.g., leagueledger.com)
- **URL privacy policy**: Link to your privacy policy
- **URL terms of usage**: Link to your terms of service
- Click "Add service"
2. **Authentication Error after provider login** ### 2. Create a NetID Client
- Verify that the redirect URI is exactly as registered with the provider
- Check for protocol mismatch (http vs https)
- Ensure all required scopes have been granted
3. **"Can't retrieve user email" errors** 1. In your service's detail view, click "Add client"
- Ensure you've requested the email scope from the provider 2. Select the application type:
- Some providers (like GitHub) require special permissions for email access - For web application: select "Website"
- For mobile apps: select "Native / Mobile App (PKCE)"
3. Fill out the required fields:
- **Name**: "LeagueLedger"
- **Callback URL**: Your callback URL (e.g., https://leagueledger.com/auth/oauth-callback/netid)
- **Token signing**: Select "RS256" (recommended)
4. Save the client configuration
5. Note the Client ID and Client Secret
### Checking Provider Status: ### 3. Configure Environment Variables
You can check which providers are correctly configured by examining the login page: Add the following variables to your `.env` file:
- Only providers with valid credentials will appear as login options
- Look at application logs during startup for provider initialization messages
### Provider-Specific Tips: ```
NETID_CLIENT_ID=your_netid_client_id
NETID_CLIENT_SECRET=your_netid_client_secret
```
- **Google**: Ensure the Google+ API is enabled in your Google Cloud project ### 4. Testing
- **GitHub**: For private email addresses, request the `user:email` scope
- **Discord**: Discord applications might need to be verified if you have a large user base
- **Microsoft**: Ensure the Microsoft Graph API permissions include User.Read
For more help, check the [official documentation](https://example.com/leagueledger/docs) or open an issue on the project repository. During development, you'll need to:
1. Add test users to your service in the NetID Developer Zone
2. Use these test users when testing the login functionality
3. Request production approval once your integration is ready
### 5. Requesting Production Approval
When ready for production:
1. Go to your service's details in the NetID Developer Zone
2. Click "Request service release"
3. NetID will review your integration and approve it for production use
### 6. Helpful Resources
- [NetID Technical Documentation](https://developer.netid.de/single-sign-on-integration/technical-details/)
- [NetID Styleguide](https://developer.netid.de/single-sign-on-integration/styleguide/) for button styling requirements