Added cognito authentication - Fix 1
All checks were successful
AWS Deploy on Push / build (push) Successful in 1m28s
All checks were successful
AWS Deploy on Push / build (push) Successful in 1m28s
This commit is contained in:
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -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
36
app/cabletv/utils/auth.py
Normal 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"},
|
||||||
|
)
|
||||||
47
app/main.py
47
app/main.py
@@ -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}
|
||||||
@@ -70,20 +70,13 @@ class IptvUpdaterStack(Stack):
|
|||||||
"AmazonSSMManagedInstanceCore"
|
"AmazonSSMManagedInstanceCore"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read the userdata script with proper path resolution
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
userdata_path = os.path.join(script_dir, "userdata.sh")
|
|
||||||
userdata_file = open(userdata_path, "rb").read()
|
|
||||||
|
|
||||||
# Creates a userdata object for Linux hosts
|
# Add Cognito permissions to instance role
|
||||||
userdata = ec2.UserData.for_linux()
|
role.add_managed_policy(
|
||||||
# Adds one or more commands to the userdata object.
|
iam.ManagedPolicy.from_aws_managed_policy_name(
|
||||||
userdata.add_commands(
|
"AmazonCognitoReadOnly"
|
||||||
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"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Read the userdata script with proper path resolution
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
userdata_path = os.path.join(script_dir, "userdata.sh")
|
||||||
|
userdata_file = open(userdata_path, "rb").read()
|
||||||
|
|
||||||
# Output the public DNS name
|
# Creates a userdata object for Linux hosts
|
||||||
CfnOutput(
|
userdata = ec2.UserData.for_linux()
|
||||||
self, "InstancePublicDNS",
|
# Adds one or more commands to the userdata object.
|
||||||
value=instance.instance_public_dns_name,
|
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'))
|
||||||
|
|
||||||
# Output Cognito information
|
# 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"
|
||||||
|
)
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user