Skip to content

Commit

Permalink
[3.0.0-alpha.3] Resolving pr-suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
maaaNu committed Jul 2, 2024
1 parent 3c9fba9 commit 6351b62
Show file tree
Hide file tree
Showing 8 changed files with 758 additions and 1,260 deletions.
1 change: 1 addition & 0 deletions example/request.http
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
POST http://localhost:8080/api/authentication
3 changes: 0 additions & 3 deletions jest.integration.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,4 @@ module.exports = {
testPathIgnorePatterns: ['/node_modules/', '/dist/', '/config/'],
moduleDirectories: ['node_modules'],
testTimeout: 30000,
moduleNameMapper: {
axios: 'axios/dist/node/axios.cjs',
},
};
1,873 changes: 686 additions & 1,187 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@
"license": "MIT",
"devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.2.0",
"@types/jest": "^27.0.3",
"@types/jest": "^29.5.12",
"@types/node": "^20.8.10",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"axios": "^1.1.3",
"eslint": "^8.8.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.4.5",
"jest-mock-extended": "^3.0.1",
"jest": "^29.7.0",
"jest-mock-extended": "^3.0.7",
"prettier": "^2.5.1",
"ts-jest": "^27.1.2",
"ts-jest": "^29.1.5",
"typescript": "^4.5.4"
},
"dependencies": {
"@azure/functions": "^4.0.0",
"axios": "^1.1.3",
"jwt-decode": "^4.0.0"
},
"peerDependencies": {
Expand Down
16 changes: 7 additions & 9 deletions src/headerAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,23 @@ export default (opts?: Partial<HeaderAuthenticationOptions>): BeforeExecutionFun
const errorResponseBody = opts?.errorResponseBody ?? defaultErrorResponseBody;
const skipIfResultIsFaulty = opts?.skipIfResultIsFaulty ?? true;

return (req, context, result): Promise<void> => {
return (req, context, result) => {
if (skipIfResultIsFaulty && result.$failed) {
context.info('Skipping header-authentication because the result is faulty.');
return Promise.resolve();
return;
}

context.info('Executing header authentication.');
const validationResult = validateUsingHeaderFn(req.headers);
if (validationResult) {
context.info('Header authentication was successful.');
return Promise.resolve();
return;
} else {
context.info('Header authentication was NOT successful.');
return Promise.reject(
new ApplicationError(
'Authentication error',
403,
errorResponseBody ?? 'No sophisticated credentials provided',
),
throw new ApplicationError(
'Authentication error',
403,
errorResponseBody ?? 'No sophisticated credentials provided',
);
}
};
Expand Down
10 changes: 4 additions & 6 deletions src/jwtAuthorization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default <T>(
return (req, context, result) => {
if (skipIfResultIsFaulty && result.$failed) {
context.info('Skipping jwt-authorization because the result is faulty.');
return Promise.resolve();
return;
}

const authorizationHeader = req.headers.get('authorization');
Expand All @@ -43,15 +43,13 @@ export default <T>(
.map((ruleFunction) => evaluate(ruleFunction, parameters, jwt))
.reduce((previousValue, currentValue) => previousValue && currentValue);
if (!validationResult) {
return Promise.reject(
new ApplicationError('Authorization error', 401, errorResponseBody ?? 'Unauthorized'),
);
throw new ApplicationError('Authorization error', 401, errorResponseBody ?? 'Unauthorized');
} else {
context.extraInputs.set('jwt', jwt);
return Promise.resolve();
return;
}
}
}
return Promise.reject(new ApplicationError('Authorization error', 401, errorResponseBody ?? 'Unauthorized'));
throw new ApplicationError('Authorization error', 401, errorResponseBody ?? 'Unauthorized');
};
};
82 changes: 42 additions & 40 deletions src/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ import { requestValidation, responseValidation } from './validation';

describe('The requestValidation should', () => {
const exampleSchema = Joi.object({ example: Joi.string().required() });
const contextMock = mockDeep<InvocationContext>();
const requestMock = mockDeep<HttpRequest>();
const initialMiddlewareResult: MiddlewareResult<ReturnType<HttpHandler>> = { $failed: false, $result: undefined };
const createRequest = (jsonBody: unknown = { example: 'test-body' }) =>
new HttpRequest({
url: 'http://localhost:8080',
method: 'POST',
body: { string: JSON.stringify(jsonBody) },
});

test('successfully validate the passed object', async () => {
requestMock.clone.mockReturnValue(requestMock);
requestMock.json.mockResolvedValue({ example: 'test-body' });

const result = await requestValidation(exampleSchema)(requestMock, contextMock, initialMiddlewareResult);
const result = await requestValidation(exampleSchema)(
createRequest(),
new InvocationContext(),
initialMiddlewareResult,
);

expect(result).toBeUndefined();
});
Expand All @@ -26,42 +31,40 @@ describe('The requestValidation should', () => {
extractValidationContentFromRequest: () => ({
example: 'test-extracted-content',
}),
})(requestMock, contextMock, initialMiddlewareResult);
})(createRequest(), new InvocationContext(), initialMiddlewareResult);

expect(result).toBeUndefined();
});

test('fail when the validation was not successful', async () => {
requestMock.json.mockResolvedValue('test-body');

await expect(
requestValidation(exampleSchema)(requestMock, contextMock, initialMiddlewareResult),
requestValidation(exampleSchema)(
createRequest({ not: 'valid' }),
new InvocationContext(),
initialMiddlewareResult,
),
).rejects.toThrowError(new ApplicationError('Validation Error', 400));
});

test('do not throw an error, even when the validation was not successful, if throwing is disabled', async () => {
requestMock.json.mockResolvedValue({ example: 'test-body' });
const result = await requestValidation(exampleSchema, { shouldThrowOnValidationError: false })(
createRequest({ example: 'test-body' }),
new InvocationContext(),
initialMiddlewareResult,
);

await expect(
requestValidation(exampleSchema, { shouldThrowOnValidationError: false })(
requestMock,
contextMock,
initialMiddlewareResult,
),
).resolves.toEqual(undefined);
expect(result).toBeUndefined();
});

test('fail when the validation was not successful with transformed error message', async () => {
requestMock.json.mockResolvedValue({ example: { fail: 'test-body' } });

await expect(
requestValidation(exampleSchema, {
transformErrorMessage: (message) => ({
type: 'Validation Error',
message,
}),
})(requestMock, contextMock, initialMiddlewareResult),
).rejects.toThrowError(new ApplicationError('Validation Error', 400));
})(createRequest({ example: { fail: 'test-body' } }), new InvocationContext(), initialMiddlewareResult),
).rejects.toThrow(new ApplicationError('Validation Error', 400));
});
});

Expand All @@ -74,65 +77,64 @@ describe('The responseValidation should', () => {
example: Joi.string().required(),
}),
});
const contextMock = mockDeep<InvocationContext>();
const requestMock = mockDeep<HttpRequest>();

beforeEach(() => {
jest.restoreAllMocks();
});

test('do nothing, if the response is valid', async () => {
test('do nothing, if the response is valid', () => {
initialMiddlewareResult.$result = { status: 201, jsonBody: { example: 'test-body' } };

const result = await responseValidation(exampleSchema)(requestMock, contextMock, initialMiddlewareResult);
const result = responseValidation(exampleSchema)(requestMock, new InvocationContext(), initialMiddlewareResult);

expect(result).toBeUndefined();
});

test('do nothing, if the object, extracted through the passed function, is valid', async () => {
test('do nothing, if the object, extracted through the passed function, is valid', () => {
initialMiddlewareResult.$result = { jsonBody: { example: 'test-body' } };
const extractValidationContentFromRequest = () => ({
status: 201,
jsonBody: { example: 'not-test-body' },
});

const result = await responseValidation(exampleSchema, {
const result = responseValidation(exampleSchema, {
extractValidationContentFromRequest,
})(requestMock, contextMock, initialMiddlewareResult);
})(requestMock, new InvocationContext(), initialMiddlewareResult);

expect(result).toBeUndefined();
});

test('throw, if the response is invalid', async () => {
test('throw, if the response is invalid', () => {
initialMiddlewareResult.$result = { status: 201, jsonBody: { fail: 'this-fails' } };

await expect(
responseValidation(exampleSchema)(requestMock, contextMock, initialMiddlewareResult),
).rejects.toThrowError(new ApplicationError('Internal server error', 500));
expect(() =>
responseValidation(exampleSchema)(requestMock, new InvocationContext(), initialMiddlewareResult),
).toThrow(new ApplicationError('Internal server error', 500));
});

test('do not throw an error, even when the validation was not successful, if throwing is disabled', async () => {
test('do not throw an error, even when the validation was not successful, if throwing is disabled', () => {
initialMiddlewareResult.$result = { status: 201, jsonBody: { fail: 'this-fails' } };

await expect(
expect(() =>
responseValidation(exampleSchema, { shouldThrowOnValidationError: false })(
requestMock,
contextMock,
new InvocationContext(),
initialMiddlewareResult,
),
).resolves.toEqual(undefined);
).toEqual(undefined);
});

test('fail when the validation was not successful with transformed error message', async () => {
test('fail when the validation was not successful with transformed error message', () => {
initialMiddlewareResult.$result = { status: 201, jsonBody: { fail: 'this-fails' } };

await expect(
expect(() =>
responseValidation(exampleSchema, {
transformErrorMessage: (message: string) => ({
type: 'Validation Error',
message,
}),
})(requestMock, contextMock, initialMiddlewareResult),
).rejects.toThrowError(new ApplicationError('Internal server error', 500));
})(requestMock, new InvocationContext(), initialMiddlewareResult),
).toThrowError(new ApplicationError('Internal server error', 500));
});
});
23 changes: 13 additions & 10 deletions src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,33 @@ export function requestValidation(schema: AnySchema, opts?: ValidationOptions):
);

if (shouldThrowOnValidationError) {
return Promise.reject(
new ApplicationError(
'Validation Error',
400,
opts?.transformErrorMessage
? opts?.transformErrorMessage(validationResult.error.message)
: {
message: validationResult.error.message,
},
),
throw new ApplicationError(
'Validation Error',
400,
opts?.transformErrorMessage
? opts?.transformErrorMessage(validationResult.error.message)
: {
message: validationResult.error.message,
},
);
}
}

context.info('Finished validating the request.');
return;
} catch (error) {
if (error instanceof ApplicationError) {
throw error;
}

if (error instanceof SyntaxError) {
context.error(`The Json was probably ill-defined: ${error}`);
//see https://fetch.spec.whatwg.org/#dom-body-json
throw new ApplicationError('Validation Error', 400, {
message: error.message,
});
}

context.error(`Unexpected server error occurred: `, error);
throw new ApplicationError('Internal Server Error', 500, {
message: 'An internal Server Error occurred while validating the request.',
Expand Down

0 comments on commit 6351b62

Please sign in to comment.