Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions backend/constants/common_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ class EntryStatus(str, Enum):
DELETED = 'DELETED'


class LogLevel(str, Enum):
CRITICAL = 'CRITICAL'
FATAL = 'FATAL'
ERROR = 'ERROR'
WARNING = 'WARNING'
WARN = 'WARN'
INFO = 'INFO'
DEBUG = 'DEBUG'
NOTSET = 'NOTSET'


class CommonConstants:
# DB Constants
CLS = 'cls'
Expand Down
13 changes: 6 additions & 7 deletions backend/controller/payment_controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from aws.cognito_settings import AccessUser, is_admin_user
from dependencies.verify_service_access import verify_service_access
from fastapi import APIRouter, Body, Depends, Path, Query
from fastapi.responses import JSONResponse
from model.common import Message
Expand All @@ -11,6 +11,7 @@
@payment_router.post(
'',
response_model=PaymentTransactionOut,
dependencies=[Depends(verify_service_access)],
responses={
400: {'model': Message, 'description': 'Bad request'},
500: {'model': Message, 'description': 'Internal server error'},
Expand All @@ -20,6 +21,7 @@
@payment_router.post(
'/',
response_model=PaymentTransactionOut,
dependencies=[Depends(verify_service_access)],
response_model_exclude_none=True,
response_model_exclude_unset=True,
include_in_schema=False,
Expand All @@ -43,26 +45,25 @@ def create_payment_transaction(
@payment_router.get(
'/pending',
response_model=list[PaymentTransactionOut],
dependencies=[Depends(verify_service_access)],
responses={
404: {'model': Message, 'description': 'Bad request'},
500: {'model': Message, 'description': 'Internal server error'},
},
summary='Get pending payment transactions',
)
def get_pending_payment_transactions(
current_user: AccessUser = Depends(is_admin_user),
):
def get_pending_payment_transactions():
"""
Get Payment Transaction with pending Status
"""
_ = current_user
payment_uc = PaymentUsecase()
return payment_uc.query_pending_payment_transactions()


@payment_router.put(
'/{paymentTransactionId}',
response_model=PaymentTransactionOut,
dependencies=[Depends(verify_service_access)],
responses={
400: {'model': Message, 'description': 'Bad request'},
500: {'model': Message, 'description': 'Internal server error'},
Expand All @@ -74,12 +75,10 @@ def update_payment_transaction(
..., description='The ID of the payment transaction', alias='paymentTransactionId'
),
payment_transaction: PaymentTransactionIn = Body(..., description='The payment transaction data'),
current_user: AccessUser = Depends(is_admin_user),
):
"""
Update payment transaction
"""
_ = current_user
payment_uc = PaymentUsecase()
return payment_uc.update_payment_transaction(payment_transaction_id, payment_transaction)

Expand Down
40 changes: 40 additions & 0 deletions backend/dependencies/verify_service_access.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import hmac
import os
from typing import Optional

from fastapi import HTTPException, Security, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from utils.logger import logger

bearer_scheme = HTTPBearer(auto_error=False)


def verify_service_access(
auth: Optional[HTTPAuthorizationCredentials] = Security(bearer_scheme),
) -> None:
"""Validate service-to-service access token using FastAPI's HTTPBearer.

Compares the Bearer token in the 'Authorization' header against
the 'SECRET_TOKEN' environment variable.

:param auth: Optional HTTPAuthorizationCredentials extracted by FastAPI's HTTPBearer.
:type auth: Optional[HTTPAuthorizationCredentials]

:raises HTTPException: 500 if SECRET_TOKEN is not configured on the server.
:raises HTTPException: 401 if Authorization Bearer token is missing or invalid.
"""
raw_secret = os.environ.get('SECRET_TOKEN')
secret_token = raw_secret.strip() if raw_secret else None
if not secret_token:
logger.error('SECRET_TOKEN environment variable is not configured')
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Server configuration error: SECRET_TOKEN is not configured',
)

if not auth or not auth.credentials or not hmac.compare_digest(auth.credentials.strip(), secret_token):
logger.warning('Invalid or missing Authorization header')
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail='Invalid or missing Authorization token',
)
3 changes: 2 additions & 1 deletion backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

STAGE = os.environ.get('STAGE')
root_path = f'/{STAGE}' if STAGE else '/'
CORS_ORIGIN = '*.durianpy.org' if STAGE == 'prod' else '*'

app = FastAPI(
root_path=root_path,
Expand Down Expand Up @@ -39,7 +40,7 @@ def welcome():
mangum_handler = Mangum(app, lifespan='off')


@cors_headers
@cors_headers(origin=CORS_ORIGIN)
@lambdawarmer.warmer
def handler(event, context):
return mangum_handler(event, context)
24 changes: 22 additions & 2 deletions backend/resources/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,31 @@ app:
- http:
path: /
method: get
cors: true
cors:
origin: ${self:custom.corsOriginValue}
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
- X-Amzn-Trace-Id
allowCredentials: true
- http:
path: /{proxy+}
method: any
cors: true
cors:
origin: ${self:custom.corsOriginValue}
headers:
- Content-Type
- X-Amz-Date
- Authorization
- X-Api-Key
- X-Amz-Security-Token
- X-Amz-User-Agent
- X-Amzn-Trace-Id
allowCredentials: true
- http:
path: /docs
method: get
Expand Down
55 changes: 46 additions & 9 deletions backend/resources/api_gateway.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
Resources:
GatewayResponse:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.WWW-Authenticate: "'Basic'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'
GatewayResponse:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.WWW-Authenticate: "'Basic'"
gatewayresponse.header.Access-Control-Allow-Origin: "'${self:custom.corsOriginValue}'"
gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'"
gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
ResponseType: UNAUTHORIZED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '401'

GatewayResponseDefault4XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'${self:custom.corsOriginValue}'"
gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'"
gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
ResponseType: DEFAULT_4XX
RestApiId:
Ref: 'ApiGatewayRestApi'

GatewayResponseDefault5XX:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'${self:custom.corsOriginValue}'"
gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'"
gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
ResponseType: DEFAULT_5XX
RestApiId:
Ref: 'ApiGatewayRestApi'

GatewayResponseThrottled:
Type: 'AWS::ApiGateway::GatewayResponse'
Properties:
ResponseParameters:
gatewayresponse.header.Access-Control-Allow-Origin: "'${self:custom.corsOriginValue}'"
gatewayresponse.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Amzn-Trace-Id'"
gatewayresponse.header.Access-Control-Allow-Methods: "'GET,POST,PUT,PATCH,DELETE,OPTIONS'"
ResponseType: THROTTLED
RestApiId:
Ref: 'ApiGatewayRestApi'
StatusCode: '429'
2 changes: 1 addition & 1 deletion backend/resources/s3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Resources:
CorsRules:
- AllowedHeaders: ["*"]
AllowedMethods: [GET, PUT, HEAD]
AllowedOrigins: ["*"]
AllowedOrigins: ${self:custom.s3CorsAllowedOrigins.${self:custom.stage}, self:custom.s3CorsAllowedOrigins.default}
Id: ${self:custom.bucket}-name
MaxAge: "3600"
PublicAccessBlockConfiguration:
Expand Down
18 changes: 18 additions & 0 deletions backend/serverless.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ custom:
projectName: ${self:custom.organization}-events
serviceName: events
stage: ${opt:stage, self:provider.stage}
corsOrigin:
prod: '*.durianpy.org'
default: '*'
corsOriginValue: ${self:custom.corsOrigin.${self:custom.stage}, self:custom.corsOrigin.default}
s3CorsAllowedOrigins:
prod:
- '*.durianpy.org'
- 'https://*.durianpy.org'
- 'https://durianpy.org'
default:
- '*'
apiGatewayRateLimit: 30
apiGatewayBurstLimit: 30
pitr:
prod: true
pitrEnabled: ${self:custom.pitr.${self:custom.stage}, false}
Expand Down Expand Up @@ -52,6 +65,10 @@ provider:
Action: execute-api:Invoke
Principal: "*"
Resource: execute-api:/*/*/*
usagePlan:
throttle:
rateLimit: ${self:custom.apiGatewayRateLimit}
burstLimit: ${self:custom.apiGatewayBurstLimit}
environment:
REGION: ${self:provider.region}
STAGE: ${self:custom.stage}
Expand All @@ -65,6 +82,7 @@ provider:
PAYMENT_QUEUE: ${self:custom.paymentQueue}
CERTIFICATE_QUEUE: ${self:custom.certificateQueue}
S3_BUCKET: ${self:custom.bucket}
SECRET_TOKEN: ${ssm:/techtix/events-api-secret-token-${self:custom.stage}}
# KONFHUB_API_KEY: ${self:custom.konfHubApiKey}
USER_POOL_ID: !ImportValue UserPoolId-${self:custom.stage}
USER_POOL_CLIENT_ID: !ImportValue AppClientId-${self:custom.stage}
Expand Down
2 changes: 2 additions & 0 deletions backend/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Unit tests package for TechTix backend."""

Loading
Loading