Added cognito authentication - Fix 1
All checks were successful
AWS Deploy on Push / build (push) Successful in 1m28s

This commit is contained in:
2025-05-15 15:33:12 -05:00
parent a07a28525f
commit 749e66e63f
5 changed files with 91 additions and 65 deletions

View File

@@ -2,6 +2,8 @@
"cSpell.words": [ "cSpell.words": [
"altinstall", "altinstall",
"awscliv", "awscliv",
"boto",
"cabletv",
"certbot", "certbot",
"certifi", "certifi",
"devel", "devel",
@@ -11,6 +13,8 @@
"gitea", "gitea",
"iptv", "iptv",
"nohup", "nohup",
"passlib",
"starlette",
"stefano", "stefano",
"uvicorn", "uvicorn",
"venv" "venv"

36
app/cabletv/utils/auth.py Normal file
View File

@@ -0,0 +1,36 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from fastapi.responses import RedirectResponse
from typing import Optional
import os
import boto3
REGION = "us-east-2"
USER_POOL_ID = os.getenv("COGNITO_USER_POOL_ID")
CLIENT_ID = os.getenv("COGNITO_CLIENT_ID")
DOMAIN = f"https://iptv-updater.auth.{REGION}.amazoncognito.com"
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl=f"{DOMAIN}/oauth2/authorize",
tokenUrl=f"{DOMAIN}/oauth2/token"
)
async def get_current_user(token: str = Depends(oauth2_scheme)):
if not token:
return RedirectResponse(
f"{DOMAIN}/login?client_id={CLIENT_ID}"
f"&response_type=code"
f"&scope=openid"
f"&redirect_uri=http://localhost:8000/auth/callback"
)
try:
cognito = boto3.client('cognito-idp', region_name=REGION)
response = cognito.get_user(AccessToken=token)
return response
except Exception as e:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)

View File

@@ -1,46 +1,21 @@
import os
import json
import boto3
from jose import jwt
from fastapi import FastAPI, Depends, HTTPException from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2AuthorizationCodeBearer from fastapi.responses import RedirectResponse
from app.cabletv.utils.auth import get_current_user, DOMAIN, CLIENT_ID
app = FastAPI() app = FastAPI()
# Get Cognito info from environment (set by userdata.sh)
REGION = "us-east-2"
USER_POOL_ID = os.getenv("COGNITO_USER_POOL_ID")
CLIENT_ID = os.getenv("COGNITO_CLIENT_ID")
# OAuth2 scheme for authorization code flow
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl=f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/oauth2/authorize",
tokenUrl=f"https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/oauth2/token"
)
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
# Verify the JWT token with Cognito
cognito_idp = boto3.client('cognito-idp', region_name=REGION)
response = cognito_idp.get_user(
AccessToken=token
)
return response
except Exception as e:
raise HTTPException(
status_code=401,
detail="Invalid authentication credentials",
headers={"WWW-Authenticate": "Bearer"},
)
@app.get("/") @app.get("/")
async def root(): async def root():
return {"message": "IPTV Updater API"} return {"message": "IPTV Updater API"}
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.get("/protected") @app.get("/protected")
async def protected_route(user = Depends(get_current_user)): async def protected_route(user = Depends(get_current_user)):
return {"message": "This is a protected route", "user": user['Username']} if isinstance(user, RedirectResponse):
return user
return {"message": "Protected content", "user": user['Username']}
@app.get("/auth/callback")
async def auth_callback(code: str):
# Here you would exchange the code for tokens
# For now, just redirect to protected route
return {"auth_code": code}

View File

@@ -71,19 +71,12 @@ class IptvUpdaterStack(Stack):
) )
) )
# Read the userdata script with proper path resolution # Add Cognito permissions to instance role
script_dir = os.path.dirname(os.path.abspath(__file__)) role.add_managed_policy(
userdata_path = os.path.join(script_dir, "userdata.sh") iam.ManagedPolicy.from_aws_managed_policy_name(
userdata_file = open(userdata_path, "rb").read() "AmazonCognitoReadOnly"
)
# Creates a userdata object for Linux hosts
userdata = ec2.UserData.for_linux()
# Adds one or more commands to the userdata object.
userdata.add_commands(
f'echo "COGNITO_USER_POOL_ID={user_pool.user_pool_id}" >> /etc/environment',
f'echo "COGNITO_CLIENT_ID={client.user_pool_client_id}" >> /etc/environment'
) )
userdata.add_commands(str(userdata_file, 'utf-8'))
# EC2 Instance # EC2 Instance
instance = ec2.Instance( instance = ec2.Instance(
@@ -98,8 +91,7 @@ class IptvUpdaterStack(Stack):
), ),
security_group=security_group, security_group=security_group,
key_pair=key_pair, key_pair=key_pair,
role=role, role=role
user_data=userdata,
) )
# Create Elastic IP # Create Elastic IP
@@ -117,37 +109,55 @@ class IptvUpdaterStack(Stack):
password_policy=cognito.PasswordPolicy( password_policy=cognito.PasswordPolicy(
min_length=8, min_length=8,
require_lowercase=True, require_lowercase=True,
require_numbers=True, require_digits=True,
require_symbols=True, require_symbols=True,
require_uppercase=True require_uppercase=True
), ),
account_recovery=cognito.AccountRecovery.EMAIL_ONLY account_recovery=cognito.AccountRecovery.EMAIL_ONLY
) )
# Add App Client # Add App Client with the correct callback URL
client = user_pool.add_client("IptvUpdaterClient", client = user_pool.add_client("IptvUpdaterClient",
o_auth=cognito.OAuthSettings( o_auth=cognito.OAuthSettings(
flows=cognito.OAuthFlows( flows=cognito.OAuthFlows(
authorization_code_grant=True authorization_code_grant=True
), ),
scopes=[cognito.OAuthScope.OPENID], scopes=[cognito.OAuthScope.OPENID],
callback_urls=[f"https://{instance.instance_public_dns_name}/auth/callback"] callback_urls=[
"http://localhost:8000/auth/callback", # For local testing
"https://*.amazonaws.com/auth/callback" # Will match EC2 public DNS
]
) )
) )
# Add Cognito permissions to instance role # Add domain for hosted UI
role.add_managed_policy( domain = user_pool.add_domain("IptvUpdaterDomain",
iam.ManagedPolicy.from_aws_managed_policy_name( cognito_domain=cognito.CognitoDomainOptions(
"AmazonCognitoReadOnly" domain_prefix="iptv-updater"
) )
) )
# Output the public DNS name # Read the userdata script with proper path resolution
CfnOutput( script_dir = os.path.dirname(os.path.abspath(__file__))
self, "InstancePublicDNS", userdata_path = os.path.join(script_dir, "userdata.sh")
value=instance.instance_public_dns_name, userdata_file = open(userdata_path, "rb").read()
)
# Output Cognito information # Creates a userdata object for Linux hosts
userdata = ec2.UserData.for_linux()
# Adds one or more commands to the userdata object.
userdata.add_commands(
f'echo "COGNITO_USER_POOL_ID={user_pool.user_pool_id}" >> /etc/environment',
f'echo "COGNITO_CLIENT_ID={client.user_pool_client_id}" >> /etc/environment'
)
userdata.add_commands(str(userdata_file, 'utf-8'))
# Update instance with userdata
instance.add_user_data(userdata.render())
# Outputs
CfnOutput(self, "InstancePublicIP", value=eip.attr_public_ip)
CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id) CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id)
CfnOutput(self, "UserPoolClientId", value=client.user_pool_client_id) CfnOutput(self, "UserPoolClientId", value=client.user_pool_client_id)
CfnOutput(self, "CognitoDomainUrl",
value=f"https://{domain.domain_name}.auth.{self.region}.amazoncognito.com"
)

View File

@@ -5,5 +5,6 @@ dotenv==0.9.9
python-dotenv==0.21.1 python-dotenv==0.21.1
uvicorn==0.22.0 uvicorn==0.22.0
requests==2.31.0 requests==2.31.0
python-jose[cryptography]==3.3.0 passlib[bcrypt]==1.7.4
boto3==1.28.0 boto3==1.28.0
starlette>=0.27.0