Skip to content

Commit

Permalink
Feat: handle Auth logic in the handler instead of globally (#41)
Browse files Browse the repository at this point in the history
* fix: handle auth per handler

* fix: conditions order in token validation

* fix: code cleaness and methods improvements

* fix: new api response and add missed token condition
  • Loading branch information
jesus4497 authored Nov 29, 2023
1 parent 616e513 commit 617ea6a
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 48 deletions.
22 changes: 2 additions & 20 deletions serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,7 @@ provider:
CONTENTFUL_DELIVERY_API_TOKEN: ${env:CONTENTFUL_DELIVERY_API_TOKEN}
httpApi:
cors: true
authorizers:
simpleAuthorizerFunc:
type: request
identitySource:
- $request.header.Authorization
functionName: customAuthorizer

iam:
role:
statements:
Expand All @@ -55,10 +50,6 @@ provider:

# The `functions` block defines what code to deploy
functions:
# auth functions & APIs
customAuthorizer:
handler: src/handlers/auth/authorizer.handler

getValidToken:
handler: src/handlers/auth/getValidToken.handler
events:
Expand Down Expand Up @@ -94,41 +85,32 @@ functions:
- httpApi:
path: /genres/movies
method: get
authorizer:
name: simpleAuthorizerFunc

movies:
handler: src/handlers/movies/getMovies.handler
events:
- httpApi:
path: /movies
method: get
authorizer:
name: simpleAuthorizerFunc

movieById:
handler: src/handlers/movies/movieById.handler
events:
- httpApi:
path: /movies/{id}
method: get
authorizer:
name: simpleAuthorizerFunc

movieTitles:
handler: src/handlers/movies/getMovieTitles.handler
events:
- httpApi:
path: /movies/titles
method: get
authorizer:
name: simpleAuthorizerFunc

genreById:
handler: src/handlers/genres/genreById.handler
events:
- httpApi:
path: /movies/genres/{id}
method: get
authorizer:
name: simpleAuthorizerFunc

16 changes: 0 additions & 16 deletions src/handlers/auth/authorizer.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/handlers/genres/genreById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {
notFoundResponse,
serverErrorResponse,
} from '@utils/api/apiResponses';
import { withAuthorization } from '@utils/api/withAuthorization';

export const handler: APIGatewayProxyHandler = async (event) => {
export const handler: APIGatewayProxyHandler = withAuthorization(async (event) => {
const genreId = event?.pathParameters?.id;

if (!genreId) {
Expand All @@ -33,4 +34,4 @@ export const handler: APIGatewayProxyHandler = async (event) => {
} catch (err) {
return serverErrorResponse;
}
};
});
5 changes: 3 additions & 2 deletions src/handlers/genres/moviesByGenre.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { APIGatewayProxyHandler } from 'aws-lambda';
import getAllGenre from '@models/Genre/getAll';
import { mountSuccessResponse, serverErrorResponse } from '@utils/api/apiResponses';
import { DEFAULT_CONTENTFUL_LIMIT } from '@utils/contentful';
import { withAuthorization } from '@utils/api/withAuthorization';

export const handler: APIGatewayProxyHandler = async (event) => {
export const handler: APIGatewayProxyHandler = withAuthorization(async (event) => {
const { page: queryStringPage = '', limit: queryStringLimit = '' } =
event?.queryStringParameters || {};

Expand All @@ -25,4 +26,4 @@ export const handler: APIGatewayProxyHandler = async (event) => {
} catch (err) {
return serverErrorResponse;
}
};
});
5 changes: 3 additions & 2 deletions src/handlers/movies/getMovieTitles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { APIGatewayProxyHandler } from 'aws-lambda';
import getAllMovies from '@models/Movie/getAll';
import { mountSuccessResponse, serverErrorResponse } from '@utils/api/apiResponses';
import { DEFAULT_CONTENTFUL_LIMIT } from '@utils/contentful';
import { withAuthorization } from '@utils/api/withAuthorization';

export const handler: APIGatewayProxyHandler = async (event) => {
export const handler: APIGatewayProxyHandler = withAuthorization(async (event) => {
const { page: queryStringPage = '', limit: queryStringLimit = '' } =
event?.queryStringParameters || {};

Expand All @@ -25,4 +26,4 @@ export const handler: APIGatewayProxyHandler = async (event) => {
} catch (err) {
return serverErrorResponse;
}
};
});
5 changes: 3 additions & 2 deletions src/handlers/movies/getMovies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
notFoundResponse,
serverErrorResponse,
} from '@utils/api/apiResponses';
import { withAuthorization } from '@utils/api/withAuthorization';

type SearchFilters = {
page?: number;
Expand All @@ -19,7 +20,7 @@ type SearchFilters = {
include?: ContentfulIncludeOptions;
};

export const handler: APIGatewayProxyHandler = async (event) => {
export const handler: APIGatewayProxyHandler = withAuthorization(async (event) => {
try {
const searchFilters: {
page?: number;
Expand Down Expand Up @@ -72,7 +73,7 @@ export const handler: APIGatewayProxyHandler = async (event) => {
console.error('Error fetching movies:', error);
return serverErrorResponse;
}
};
});

export async function fetchMoviesByGenre(searchFilters: SearchFilters) {
if (!searchFilters.genre) {
Expand Down
5 changes: 3 additions & 2 deletions src/handlers/movies/movieById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import {
import getMovieById from '@models/Movie/getById';
import { CONTENTFUL_INCLUDE } from '@customTypes/contentful';
import { isCustomContentfulError } from '@utils/api/utils';
import { withAuthorization } from '@utils/api/withAuthorization';

export const handler: APIGatewayProxyHandler = async (event) => {
export const handler: APIGatewayProxyHandler = withAuthorization(async (event) => {
const movieId = event?.pathParameters?.id;

if (!movieId) {
Expand Down Expand Up @@ -38,4 +39,4 @@ export const handler: APIGatewayProxyHandler = async (event) => {

return serverErrorResponse;
}
};
});
7 changes: 5 additions & 2 deletions src/utils/api/apiAuth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Genereated from https://jwt.io/

export const authorizedJWTs = [
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJvcGVuSnd0MCIsIm5hbWUiOiJPcGVuSldUWzBdIn0.49JQF4ICJeqxpiIZ9x748VVOHj6FElyRm1tNpFGqaUY',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJvcGVuSnd0MSIsIm5hbWUiOiJPcGVuSldUWzFdIn0.n8x8GHYe8RQYKkAoMVMlw9-FMZ57bs0HrwxBeJn3hQM',
Expand All @@ -8,10 +9,12 @@ export const authorizedJWTs = [
];

export function isTokenValid(token: string) {
if (!token) {
if (!token || !token.startsWith('Bearer ')) {
return false;
}
return authorizedJWTs.includes(token);
const tokenValue = token.substring(7);

return authorizedJWTs.includes(tokenValue);
}

export function getRandomValidToken(): string {
Expand Down
15 changes: 15 additions & 0 deletions src/utils/api/apiResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,18 @@ export const mountSuccessResponse = (bodyObject: object): APIGatewayProxyResult
body: JSON.stringify(bodyObject),
};
};

export const unauthorizedResponse: APIGatewayProxyResult = {
statusCode: 401,
body: JSON.stringify({ message: 'No auth token provided.' }),
};

export const forbiddenResponse: APIGatewayProxyResult = {
statusCode: 403,
body: JSON.stringify({ message: 'You do not have permission to access this resource' }),
};

export const noContentResponse: APIGatewayProxyResult = {
statusCode: 204,
body: JSON.stringify({ message: 'No content' }),
};
40 changes: 40 additions & 0 deletions src/utils/api/withAuthorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
APIGatewayProxyEvent,
APIGatewayProxyHandler,
APIGatewayProxyResult,
Callback,
Context,
} from 'aws-lambda';
import { isTokenValid } from '@utils/api/apiAuth';
import {
forbiddenResponse,
noContentResponse,
unauthorizedResponse,
} from '@utils/api/apiResponses';

export function withAuthorization(handler: APIGatewayProxyHandler): APIGatewayProxyHandler {
return async (
event: APIGatewayProxyEvent,
context: Context,
callback: Callback<APIGatewayProxyResult>
): Promise<APIGatewayProxyResult> => {
const authorization = event.headers?.['authorization'] || '';

if (!authorization) {
return unauthorizedResponse;
}

if (!isTokenValid(authorization)) {
return forbiddenResponse;
}

const result = await handler(event, context, callback);

// Provide a default response if the handler returns void
if (result === undefined) {
return noContentResponse;
}

return result;
};
}

0 comments on commit 617ea6a

Please sign in to comment.