diff --git a/app/main.py b/app/main.py index b5a1bc1..1102a0e 100644 --- a/app/main.py +++ b/app/main.py @@ -1,11 +1,46 @@ -from fastapi import FastAPI +import os +import json +import boto3 +from jose import jwt +from fastapi import FastAPI, Depends, HTTPException +from fastapi.security import OAuth2AuthorizationCodeBearer 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("/") async def root(): - return {"message": "Hello World"} + return {"message": "IPTV Updater API"} @app.get("/health") async def health(): - return {"status": "healthy"} \ No newline at end of file + return {"status": "healthy"} + +@app.get("/protected") +async def protected_route(user = Depends(get_current_user)): + return {"message": "This is a protected route", "user": user['Username']} \ No newline at end of file diff --git a/infrastructure/stack.py b/infrastructure/stack.py index cab843b..57cea57 100644 --- a/infrastructure/stack.py +++ b/infrastructure/stack.py @@ -3,6 +3,7 @@ from aws_cdk import ( Stack, aws_ec2 as ec2, aws_iam as iam, + aws_cognito as cognito, CfnOutput ) from constructs import Construct @@ -78,6 +79,10 @@ class IptvUpdaterStack(Stack): # 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 @@ -104,8 +109,45 @@ class IptvUpdaterStack(Stack): instance_id=instance.instance_id ) + # Add Cognito User Pool + user_pool = cognito.UserPool( + self, "IptvUpdaterUserPool", + user_pool_name="iptv-updater-users", + self_sign_up_enabled=False, # Only admins can create users + password_policy=cognito.PasswordPolicy( + min_length=8, + require_lowercase=True, + require_numbers=True, + require_symbols=True, + require_uppercase=True + ), + account_recovery=cognito.AccountRecovery.EMAIL_ONLY + ) + + # Add App Client + client = user_pool.add_client("IptvUpdaterClient", + o_auth=cognito.OAuthSettings( + flows=cognito.OAuthFlows( + authorization_code_grant=True + ), + scopes=[cognito.OAuthScope.OPENID], + callback_urls=[f"https://{instance.instance_public_dns_name}/auth/callback"] + ) + ) + + # Add Cognito permissions to instance role + role.add_managed_policy( + iam.ManagedPolicy.from_aws_managed_policy_name( + "AmazonCognitoReadOnly" + ) + ) + # Output the public DNS name CfnOutput( self, "InstancePublicDNS", - value=eip.attr_public_ip - ) \ No newline at end of file + value=instance.instance_public_dns_name, + ) + + # Output Cognito information + CfnOutput(self, "UserPoolId", value=user_pool.user_pool_id) + CfnOutput(self, "UserPoolClientId", value=client.user_pool_client_id) \ No newline at end of file diff --git a/infrastructure/userdata.sh b/infrastructure/userdata.sh index 92a26df..4d6e37b 100644 --- a/infrastructure/userdata.sh +++ b/infrastructure/userdata.sh @@ -25,6 +25,7 @@ Type=simple User=ec2-user WorkingDirectory=/home/ec2-user/iptv-updater-aws ExecStart=/usr/local/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 +EnvironmentFile=/etc/environment Restart=always [Install] diff --git a/requirements.txt b/requirements.txt index 482c9b8..dddbf35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,6 @@ constructs>=10.0.0 dotenv==0.9.9 python-dotenv==0.21.1 uvicorn==0.22.0 -requests==2.31.0 \ No newline at end of file +requests==2.31.0 +python-jose[cryptography]==3.3.0 +boto3==1.28.0 \ No newline at end of file