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

[2.2.0] #356

Merged
merged 3 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.2.0 (31.10.2023)
+ Introduced responseValidation functionality in the middleware. This new feature enhances the robustness of your applications by enabling schema validation for handlers' responses.
+ Implemented the capability to dynamically enable or disable middleware functions within the execution flow. This addition brings conditional processing to your middleware stack, allowing greater control based on runtime conditions or application logic. Functions can now be seamlessly included or excluded from the execution process by resolving to true or false through a new integration pattern. This feature ensures that your application maintains high efficiency and adaptability in handling requests and processing logic.

## 2.1.0 (19.09.2023)
+ Post-Execution-Functions will now be executed, even if the handler failed
- The middlewareWithErrorHandling was removed. To regain the same functionality you can now pass an option-flag, called disableErrorHandling
Expand Down
91 changes: 65 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# Azure Function Middleware

Azure Function Middleware provides a simple way to use the middleware pattern for Azure Functions with NodeJS in order to define cross-cutting functionality such as schema validation, authorization and error handling.
Introduction

The Azure Function Middleware introduces a middleware pattern for Azure Functions in Node.js, enhancing the development
experience by simplifying the integration of cross-cutting concerns such as schema validation, authorization, and error handling.

## Installation

Before you integrate this middleware into your project, ensure you have Node.js installed, and you're familiar with Azure Functions. Follow these steps to set up:


```bash
npm install @senacor/azure-function-middleware
```

## Usage
The interface is simple to use and could be easily expanded:
The middleware interface is intuitive, designed for expansion, and integrates seamlessly with Azure Functions. Here's a quick example to get you started:

```typescript
const schema = Joi.object().keys({
Expand All @@ -14,14 +26,15 @@ const functionHandler = async (context: Context, req: HttpRequest): Promise<void
context.res = { status: 201 };
};

export default middleware(functionHandler, [validation(schema)]);
export default middleware([validation(schema), functionHandler, []]);
```

The goal is to provide a minimal feature set with generic functionality and an easy interface to create other middleware functions, which could be used for an Azure function.
This pattern aims to deliver a core set of features and a simplified interface for creating additional middleware functions tailored for Azure Functions.

## Error Handling

The middleware provides a central way to handle errors occurring in the control flow of the function. Every error thrown within the function gets caught by the middleware and processed. To define the correct response to a specific error the following structure can be thrown:
Centralized error management is a key feature, ensuring all errors within the function's flow are intercepted and appropriately handled.
Specific error responses can be defined by throwing errors in the following format:

```typescript
export class ApplicationError<T> extends Error {
Expand All @@ -34,54 +47,76 @@ Any error thrown in the function with this signature is getting returned to the

## Generic Functions

For additional generic functionality like request validation or authorization functions could be defined. The functions need to have the following structure:
The middleware supports the integration of generic functions like request validation or authorization.
These functions must comply with the 'AzureFunction' type from the '@azure/functions' package.
They are crucial for extending the middleware's capabilities while adhering to Azure's function signature requirements.

```typescript
export type MiddlewareFunction = (context: Context, request: HttpRequest) => Promise<void>;
```
import { AzureFunction } from '@azure/functions';

The function receives the Azure Function context and request to operate. The function needs to be passed when the middleware is configured.
// 'AzureFunction' type signature
export type AzureFunction = (context: Context, ...args: any[]) => Promise<any> | void;

```typescript
export default middleware(functionHandler, [validation(schema)]);
// Configuring middleware with generic functions
export default middleware([validation(schema)], functionHandler, []);
```

In the above example a `validation` function is passed with a schema. All passed functions are called before the in the defined order before the handler function containing the logic for the request is called.
Such generic functions are executed in sequence before the main handler function.
If a post-execution function is necessary, it can be included in the postExecution array, the third argument in the middleware function.
maaaNu marked this conversation as resolved.
Show resolved Hide resolved

### Validation

The function to validate requests is based on [Joi](https://www.npmjs.com/package/joi). The usage is fairly simply:

```typescript
export default middleware(functionHandler, [validation(schema)]);
export default middleware([requestValidation(schema)], functionHandler, []);
```

The passed schema is a Joi ObjectSchema to check the passed request against. When the request is valid against the schema, the next middleware function gets called. In case the check of the request against the schema is invalid, the middleware function throws an error, canceling the request and returning aan `400 - Bad Request` with the Joi error message.
The passed schema is a Joi ObjectSchema to check the passed request against. When the request is valid against the schema, the next middleware function gets called. In case the check of the request against the schema is invalid, the middleware function throws an error, canceling the request and returning an `400 - Bad Request` with the Joi error message.

The body of the response could be customized by adding a transformer like in the following example. The passed message is the Joi error message.

```typescript
export default middleware(handler, [
validation(schema, (message) => ({
type: 'Invalid request object',
detail: message,
})),
requestValidation(schema, {
transformErrorMessage: (message) => ({
type: 'Invalid request object',
detail: message,
})
}),
])
```

By default, the request body is getting validated. To validate other parts of the request or context the `extractValidationContentFromRequest` function could be used, when initializing the middleware.

```typescript
export default middleware(handler, [
validation(schema, undefined, (req, context) => req.query.name)),
])
export default middleware([
requestValidation(schema, {extractValidationContentFromRequest: (req, context) => req.query.name})],
handler,
[]
)
```

In this example the `name` contained in the query is getting validated against the passed request.

You can also ensure the integrity of your handler's responses by utilizing the responseValidation function.
However, you might prefer to avoid interruptions in the application flow caused by thrown errors, opting instead for error logging.
This can be achieved with the following configuration:

```typescript
export default middleware([
responseValidation(schema, {shouldThrowOnValidationError: false})],
handler,
[]
)
```

This approach validates the response against the provided schema but, instead of halting execution
when encountering validation errors, it logs the issues for review without throwing an exception.

### Authorization

To authorize a request the middleware function `authorization` could be used. The function is verifying a request parameter against a JWT Bearer Token. The information get extracted using two functions for the correct parameter and token counterpart.
The authorization function verifies request parameters against JWT Bearer Tokens, employing customizable extraction functions for flexible security checks.

```typescript
export default middleware(functionHandler, [authorization([])]);
Expand Down Expand Up @@ -112,15 +147,15 @@ This could be done in the following manner `headerAuthentication((context, reque

### Post function execution

To execute a function after the handler is called, a post function execution could be defined. The post function could be used to close for example a database connection or something similar.
Post-execution functions, ideal for tasks like closing database connections, can be defined to run after the main handler execution.

```typescript
const afterFunction = (context: Context, request: HttpRequest): Promise<void> => {
const postFunction = (context: Context, request: HttpRequest): Promise<void> => {
context.log("Called after function")
return;
}

export default middleware(functionHandler, [], [afterFunction]);
export default middleware(functionHandler, [], [postFunction]);
```

### Logging and Tracing with appInsights
Expand All @@ -135,4 +170,8 @@ import {AppInsightForHttpTrigger} from "./appInsightsWrapper";
export default middleware([AppInsightForHttpTrigger.setup], handler, [AppInsightForHttpTrigger.finalizeAppInsight])
```

and the `AppInsightForNonHttpTrigger` for functions with different kinds of trigger (e.g. `activityTrigger` or `timerTrigger`).
and the `AppInsightForNonHttpTrigger` for functions with different kinds of trigger (e.g. `activityTrigger` or `timerTrigger`).

## Support and Contact

If you encounter any issues or have questions about using this middleware, please file an issue in this repository or contact the maintainers at <[email protected]> or <[email protected]>.
14 changes: 7 additions & 7 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@
"dependencies": {
"@azure/functions": "^3.0.0",
"@types/node": "^18.11.18",
"joi": "^17.8.3"
"joi": "^17.9.1"
}
}
6 changes: 3 additions & 3 deletions example/test-validation-function/test-validation-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as Joi from 'joi';
import { ObjectSchema } from 'joi';

import { middleware } from '../../src';
import validation from '../../src/validation';
import { requestValidation as validation } from '../../src/validation';

const schema: ObjectSchema = Joi.object({
name: Joi.string().min(3).max(30).required(),
Expand All @@ -14,9 +14,9 @@ const functionHandler = async (context: Context, req: HttpRequest): Promise<void
context.res = { status: 200, body: { text: `Hallo ${req.body.name}` } };
};

const afterFunction = (context: Context): Promise<void> => {
const postFunction = (context: Context): Promise<void> => {
context.log('Called after function');
return;
};

export default middleware([validation(schema)], functionHandler, [afterFunction]);
export default middleware([validation(schema)], functionHandler, [postFunction]);
13 changes: 7 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@senacor/azure-function-middleware",
"version": "2.1.0",
"version": "2.2.0",
"description": "Middleware for azure functions to handle authentication, authorization, error handling and logging",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -30,7 +30,7 @@
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"eslint": "^8.8.0",
"eslint-config-prettier": "^8.3.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.4.5",
"jest-mock-extended": "^3.0.1",
Expand All @@ -46,6 +46,6 @@
},
"peerDependencies": {
"applicationinsights": "^2.5.0",
"joi": "^17.8.3"
"joi": "^17.9.1"
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export { default as headerAuthentication } from './headerAuthentication';
export * from './middleware';
export * from './error';
export { Rule, default as jwtAuthorization } from './jwtAuthorization';
export { default as validation } from './validation';
export * from './validation';
export { AppInsightForHttpTrigger, AppInsightForNoNHttpTrigger } from './appInsights/appInsightsWrapper';
Loading