Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/app/admin/manage-products/manage-products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface PresignedUrlResponse {

@Injectable()
export class ManageProductsService extends ApiService {
authorization_token = localStorage.getItem('authorization_token');
uploadProductsCSV(file: File): Observable<unknown> {
if (!this.endpointEnabled('import')) {
console.warn(
Expand All @@ -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,
});
Expand Down
1 change: 1 addition & 0 deletions todo/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
!jest.config.js
*.d.ts
node_modules
.env

# CDK asset staging directory
.cdk.staging
Expand Down
51 changes: 49 additions & 2 deletions todo/lib/task5/import-service-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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',
Expand Down Expand Up @@ -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(
Expand All @@ -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),
);
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
@@ -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!,
},
});
}
}
58 changes: 58 additions & 0 deletions todo/lib/task7/authorization-service/basicAuthorizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
interface AuthorizerEvent {
authorizationToken?: string;
methodArn: string;
[key: string]: string | undefined;
}

export const handler = async (
event: AuthorizerEvent,
): Promise<AuthResponse> => {
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;
};
}