diff --git a/src/app/admin/manage-products/manage-products.service.ts b/src/app/admin/manage-products/manage-products.service.ts index 07d0965a..c08582de 100644 --- a/src/app/admin/manage-products/manage-products.service.ts +++ b/src/app/admin/manage-products/manage-products.service.ts @@ -12,6 +12,7 @@ interface PresignedUrlResponse { @Injectable() export class ManageProductsService extends ApiService { + authorization_token = localStorage.getItem('authorization_token'); uploadProductsCSV(file: File): Observable { if (!this.endpointEnabled('import')) { console.warn( @@ -26,6 +27,7 @@ export class ManageProductsService extends ApiService { headers: { // eslint-disable-next-line @typescript-eslint/naming-convention 'Content-Type': 'text/csv', + Authorization: `Basic ${this.authorization_token}`, }, withCredentials: false, }); diff --git a/todo/.gitignore b/todo/.gitignore index f60797b6..39808c54 100644 --- a/todo/.gitignore +++ b/todo/.gitignore @@ -2,6 +2,7 @@ !jest.config.js *.d.ts node_modules +.env # CDK asset staging directory .cdk.staging diff --git a/todo/lib/task5/import-service-stack.ts b/todo/lib/task5/import-service-stack.ts index 4327d8b0..449ebfda 100644 --- a/todo/lib/task5/import-service-stack.ts +++ b/todo/lib/task5/import-service-stack.ts @@ -6,6 +6,9 @@ import { Construct } from 'constructs'; import * as path from 'path'; import * as apigw from 'aws-cdk-lib/aws-apigateway'; import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; export class ImportServiceStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { @@ -25,6 +28,11 @@ export class ImportServiceStack extends cdk.Stack { ], }); + // Create SQS queue for catalog items + const catalogItemsQueue = new sqs.Queue(this, 'CatalogItemsQueue', { + queueName: 'catalog-items-queue', + }); + const importProductsFile = new lambdaNodejs.NodejsFunction( this, 'importProductsFile', @@ -59,11 +67,13 @@ export class ImportServiceStack extends cdk.Stack { runtime: cdk.aws_lambda.Runtime.NODEJS_20_X, environment: { BUCKET_NAME: importBucket.bucketName, + SQS_URL: catalogItemsQueue.queueUrl, }, }, ); importBucket.grantRead(importFileParser); + catalogItemsQueue.grantSendMessages(importFileParser); // Configure S3 event trigger for 'uploaded/' folder importFileParser.addEventSource( @@ -81,8 +91,8 @@ export class ImportServiceStack extends cdk.Stack { }, }); - const immportProducts = api.root.addResource('import'); - immportProducts.addMethod( + const importProducts = api.root.addResource('import'); + importProducts.addMethod( 'GET', new apigw.LambdaIntegration(importProductsFile), ); @@ -92,6 +102,43 @@ export class ImportServiceStack extends cdk.Stack { sources: [s3deploy.Source.asset('assets/uploaded')], // empty file }); + // Create the basicAuthorizer Lambda + const basicAuthorizer = new lambda.Function(this, 'basicAuthorizer', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'basic-authorizer.handler', + code: lambda.Code.fromAsset( + path.join(__dirname, '../../authorization-service'), + ), + environment: { + [process.env.GITHUB_USER!]: 'TEST_PASSWORD', + }, + }); + + // Create API Gateway Lambda Authorizer + const authorizer = new apigateway.TokenAuthorizer( + this, + 'ImportAuthorizer', + { + handler: basicAuthorizer, + identitySource: 'method.request.header.Authorization', + }, + ); + + // Create /import resource + const importResource = api.root.addResource('import'); + + // Protect GET /import with authorizer + importResource.addMethod( + 'GET', + new apigateway.LambdaIntegration(importProductsFile), + { + authorizer: authorizer, + authorizationType: apigateway.AuthorizationType.CUSTOM, + }, + ); + + new cdk.CfnOutput(this, 'ImportApiUrl', { value: api.url }); + new cdk.CfnOutput(this, 'BucketName', { value: importBucket.bucketName, description: 'Import Service S3 Bucket Name', diff --git a/todo/lib/task7/authorization-service/authorization-service-stack.ts b/todo/lib/task7/authorization-service/authorization-service-stack.ts new file mode 100644 index 00000000..dafe84d1 --- /dev/null +++ b/todo/lib/task7/authorization-service/authorization-service-stack.ts @@ -0,0 +1,24 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as path from 'path'; +import { Construct } from 'constructs'; +import * as dotenv from 'dotenv'; +import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; + +dotenv.config(); // Load .env file + +export class AuthorizationServiceStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + new lambdaNodejs.NodejsFunction(this, 'basicAuthorizer', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'basicAuthorizer.handler', + code: lambda.Code.fromAsset(path.join(__dirname, './')), + environment: { + GITHUB_USER: process.env.GITHUB_USER!, + GITHUB_PASSWORD: process.env.GITHUB_PASSWORD!, + }, + }); + } +} diff --git a/todo/lib/task7/authorization-service/basicAuthorizer.ts b/todo/lib/task7/authorization-service/basicAuthorizer.ts new file mode 100644 index 00000000..9dc77106 --- /dev/null +++ b/todo/lib/task7/authorization-service/basicAuthorizer.ts @@ -0,0 +1,58 @@ +interface AuthorizerEvent { + authorizationToken?: string; + methodArn: string; + [key: string]: string | undefined; +} + +export const handler = async ( + event: AuthorizerEvent, +): Promise => { + console.log('Event:', event); + + if (!event.authorizationToken) { + throw new Error('Unauthorized'); // API Gateway converts to 401 + } + + const token = event.authorizationToken.replace('Basic ', ''); + const decoded = Buffer.from(token, 'base64').toString('utf-8'); + + const [username, password] = decoded.split(':'); + + const validPwd = process.env[username]; + + const effect = validPwd === password ? 'Allow' : 'Deny'; + + const response: AuthResponse = { + principalId: username, + policyDocument: { + Version: '2012-10-17', + Statement: [ + { + Action: 'execute-api:Invoke', + Effect: effect, + Resource: event.methodArn, + }, + ], + }, + context: { + statusCode: validPwd ? 200 : 403, // optional context + }, + }; + + return response; +}; + +interface AuthResponse { + principalId: string; + policyDocument: { + Version: string; + Statement: Array<{ + Action: string; + Effect: string; + Resource: string; + }>; + }; + context?: { + [key: string]: string | number | boolean; + }; +}