Merge pull request #5 from christianlouis/AuthenticationMiddleware
Authentication middleware
This commit is contained in:
@@ -0,0 +1,76 @@
|
|||||||
|
# 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
|
||||||
|
NETID_TOKEN_SIGNING_ALG=RS256
|
||||||
|
NETID_VALIDATE_TOKENS=true
|
||||||
|
|
||||||
|
# Feature Flags
|
||||||
|
ENABLE_REGISTRATION=True
|
||||||
|
ENABLE_PASSWORD_RESET=True
|
||||||
|
ENABLE_EMAIL_VERIFICATION=True
|
||||||
|
ENABLE_SOCIAL_LOGIN=True
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# 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=
|
||||||
|
NETID_TOKEN_SIGNING_ALG=RS256
|
||||||
|
NETID_VALIDATE_TOKENS=true
|
||||||
|
|
||||||
|
# Feature Flags
|
||||||
|
ENABLE_REGISTRATION=True
|
||||||
|
ENABLE_PASSWORD_RESET=True
|
||||||
|
ENABLE_EMAIL_VERIFICATION=True
|
||||||
|
ENABLE_SOCIAL_LOGIN=True
|
||||||
@@ -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.
|
||||||
@@ -818,6 +818,163 @@ 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"
|
||||||
|
JWKS_URL = "https://broker.netid.de/jwks" # JWKS endpoint for token validation
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client_id = os.getenv("NETID_CLIENT_ID", "")
|
||||||
|
self.client_secret = os.getenv("NETID_CLIENT_SECRET", "")
|
||||||
|
self.token_signing_alg = os.getenv("NETID_TOKEN_SIGNING_ALG", "RS256")
|
||||||
|
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:
|
||||||
|
# Add token signing algorithm parameter to the authorization request
|
||||||
|
extras_params = {
|
||||||
|
"response_type": "code",
|
||||||
|
"id_token_signed_response_alg": self.token_signing_alg
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization_url = await self.client.get_authorization_url(
|
||||||
|
redirect_uri=redirect_uri,
|
||||||
|
scope=["openid", "email", "profile"],
|
||||||
|
state=str(request.session.get("session_id", "")),
|
||||||
|
extras_params=extras_params
|
||||||
|
)
|
||||||
|
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_params = {
|
||||||
|
"id_token_signed_response_alg": self.token_signing_alg
|
||||||
|
}
|
||||||
|
|
||||||
|
token = await self.client.get_access_token(
|
||||||
|
code=code,
|
||||||
|
redirect_uri=redirect_uri,
|
||||||
|
extra_params=token_params
|
||||||
|
)
|
||||||
|
|
||||||
|
access_token = token.get("access_token")
|
||||||
|
id_token = token.get("id_token") # JWT containing identity information
|
||||||
|
|
||||||
|
if not access_token:
|
||||||
|
raise HTTPException(status_code=400, detail="Could not get NetID access token")
|
||||||
|
|
||||||
|
# If we have an ID token, validate its signature when configured to do so
|
||||||
|
if id_token and os.getenv("NETID_VALIDATE_TOKENS", "true").lower() == "true":
|
||||||
|
await self.validate_id_token(id_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)}")
|
||||||
|
|
||||||
|
async def validate_id_token(self, id_token: str) -> None:
|
||||||
|
"""Validate the NetID ID token signature using JWKS"""
|
||||||
|
try:
|
||||||
|
import jwt
|
||||||
|
from jwt.jwks_client import PyJWKClient
|
||||||
|
|
||||||
|
# Create a JWKS client to fetch the public keys from NetID
|
||||||
|
jwks_client = PyJWKClient(self.JWKS_URL)
|
||||||
|
|
||||||
|
# Get the signing key for this specific JWT
|
||||||
|
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
|
||||||
|
|
||||||
|
# Verify the JWT using the fetched public key
|
||||||
|
# This will raise exceptions if the token is invalid
|
||||||
|
jwt.decode(
|
||||||
|
id_token,
|
||||||
|
signing_key.key,
|
||||||
|
algorithms=[self.token_signing_alg],
|
||||||
|
audience=self.client_id,
|
||||||
|
options={"verify_exp": True}
|
||||||
|
)
|
||||||
|
|
||||||
|
# If we get here, the token is valid
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error validating NetID ID token: {str(e)}")
|
||||||
|
# In production, you might want to raise an exception here
|
||||||
|
# For now, we'll just log the error but not block the flow
|
||||||
|
return False
|
||||||
|
|
||||||
|
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 +993,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
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
+136
-150
@@ -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
|
||||||
|
|
||||||
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
|
### 2. Configure Environment Variables
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
### LinkedIn
|
Add the following variables to your `.env` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
GITHUB_CLIENT_ID=your_github_client_id
|
||||||
|
GITHUB_CLIENT_SECRET=your_github_client_secret
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
@@ -16,6 +16,7 @@ python-multipart>=0.0.6
|
|||||||
passlib>=1.7.4
|
passlib>=1.7.4
|
||||||
itsdangerous>=2.1.2
|
itsdangerous>=2.1.2
|
||||||
bcrypt>=4.0.1
|
bcrypt>=4.0.1
|
||||||
|
PyJWT>=2.6.0 # Added for NetID token validation
|
||||||
|
|
||||||
# OAuth client
|
# OAuth client
|
||||||
httpx-oauth>=0.10.0
|
httpx-oauth>=0.10.0
|
||||||
|
|||||||
Reference in New Issue
Block a user