Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Error code responses #57

Merged
merged 11 commits into from
Mar 15, 2024
20 changes: 16 additions & 4 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ export async function handler(event) {

// This is repeated from authorizeRequest.js, should probably be refactored.
// Parse the path segments from the URL to check if this is a protected site.
const pathSegments = new URL(userRequest.url).pathname.split('/');
const parsedUrl = new URL(userRequest.url);
const pathSegments = parsedUrl.pathname.split('/');
// If the 'files' segment is the second segment, this is a root site.
const isRootSite = pathSegments.indexOf('files') === 1;
const sitePath = isRootSite ? domain : `${domain}/${pathSegments[1]}`;
Expand All @@ -82,8 +83,19 @@ export async function handler(event) {
// If the user is not authorized, return a 403 Forbidden response.
if (!authorized) {
// If the user is not authorized, return a 403 Access Denied response.
params.StatusCode = 403;
params.ErrorMessage = 'Access Denied';

// Check for a valid login; if there is one return a forbidden message,
// because they are logged in but not authorized.
if (userRequest.headers.Eppn) {
params.StatusCode = 403;
params.ErrorMessage = '403 Forbidden, you are logged in but not authorized to access this resource.';
await s3.writeGetObjectResponse(params);
return { statusCode: 200 };
}

// Otherwise, if there's not a login session, return a 401 Unauthorized response.
params.StatusCode = 401;
params.ErrorMessage = '401 Unauthorized, you have requested a restricted resource but are not logged in.';

await s3.writeGetObjectResponse(params);

Expand All @@ -96,7 +108,7 @@ export async function handler(event) {

// If the object is not found, return a 404 Not Found response.
if (response.Code === 'NoSuchKey') {
params.ErrorMessage = 'Not Found';
params.ErrorMessage = '404 Not Found';
params.StatusCode = 404;
} else {
// If the object is found, return its data with a 200 OK response.
Expand Down
63 changes: 56 additions & 7 deletions src/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const ssmMock = mockClient(SSMClient);

process.env.DYNAMODB_TABLE = 'test-table';
process.env.RANGES_SSM_PARAMETER_NAME = 'test-parameter';
process.env.ORIGINAL_BUCKET = 'test-bucket';

// Mock the ddb client.
ddbMock.on(GetCommand, {
Expand Down Expand Up @@ -47,12 +48,18 @@ ssmMock.on(GetParameterCommand).resolves({
},
});

// Mock the s3 client; this just returns a valid object for any get request.
// Mock the s3 client, by default just return a valid object for any get request.
const s3Mock = mockClient(S3Client);
s3Mock.on(GetObjectCommand).resolves({
Body: 'test',
});

// Add a more specific mocked S3 event for a non-existent object.
s3Mock.on(GetObjectCommand, {
Bucket: process.env.ORIGINAL_BUCKET,
Key: 'original_media/example.host.bu.edu/site/files/01/no-such-file.jpg',
}).rejects({ Code: 'NoSuchKey' });

const publicMediaEvent = {
userRequest: {
url: 'https://example-1111.s3-object-lambda.us-east-1.amazonaws.com/somesite/files/01/example.jpg',
Expand All @@ -71,6 +78,34 @@ const protectedSiteEvent = {
url: 'https://example-1111.s3-object-lambda.us-east-1.amazonaws.com/protected/files/01/example.jpg',
headers: {
'X-Forwarded-Host': 'example.host.bu.edu',
'Shib-Handler': 'https://example.host.bu.edu/saml/wp-app/shibboleth',
},
},
getObjectContext: {
outputRoute: 'test',
outputToken: 'test',
},
};

const nonExistentObjectEvent = {
userRequest: {
url: 'https://example-1111.s3-object-lambda.us-east-1.amazonaws.com/site/files/01/no-such-file.jpg',
headers: {
'X-Forwarded-Host': 'example.host.bu.edu',
},
},
getObjectContext: {
outputRoute: 'test',
outputToken: 'test',
},
};

const forbiddenEvent = {
userRequest: {
url: 'https://example-1111.s3-object-lambda.us-east-1.amazonaws.com/protected/files/01/example.jpg',
headers: {
Eppn: '[email protected]',
'X-Forwarded-Host': 'example.host.bu.edu',
},
},
getObjectContext: {
Expand All @@ -84,11 +119,25 @@ describe('handler', () => {
const result = await handler(publicMediaEvent);
expect(result.statusCode).toEqual(200);
});
});

// The Lambda returns a 200 response for everything as long as the Lambda itself doesn't crash.
// Which makes it a little hard to test, but this tests that protected media requests don't crash.
it('should return a 200 response for protected media request', async () => {
const result = await handler(protectedSiteEvent);
expect(result.statusCode).toEqual(200);
// The Lambda returns a 200 response for everything as long as the Lambda itself doesn't crash.
// Which makes it a little hard to test, but this tests that protected media requests don't crash.
it('should return a 200 response for protected media request', async () => {
const result = await handler(protectedSiteEvent);
expect(result.statusCode).toEqual(200);
});

// Non-existent objects return a 200 response.
// The status code is for the Lambda, not the user response.
it('should return a 200 response for non-existent object', async () => {
const result = await handler(nonExistentObjectEvent);
expect(result.statusCode).toEqual(200);
});

// Forbidden requests return a 200 response.
// The status code is for the Lambda, not the user response.
it('should return a 200 response for forbidden request', async () => {
const result = await handler(forbiddenEvent);
expect(result.statusCode).toEqual(200);
});
});
Loading