Skip to content

Commit

Permalink
feat: add pre-handlers package (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
alimd authored Oct 28, 2024
2 parents 852e035 + dc29d20 commit 056f5cd
Show file tree
Hide file tree
Showing 13 changed files with 935 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"jgclark.vscode-todo-highlight",
"Gruntfuggly.todo-tree",
"streetsidesoftware.code-spell-checker-persian",
"streetsidesoftware.code-spell-checker",
"arcanis.vscode-zipfs"
Expand Down
3 changes: 2 additions & 1 deletion packages/nanotron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"dependencies": {
"@alwatr/crypto": "workspace:^",
"@alwatr/nanolib": "^1.3.0",
"@alwatr/nanotron-api-server": "workspace:^"
"@alwatr/nanotron-api-server": "workspace:^",
"@alwatr/pre-handlers": "workspace:^"
},
"devDependencies": {
"@alwatr/nano-build": "^2.0.2",
Expand Down
1 change: 1 addition & 0 deletions packages/nanotron/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import {packageTracer} from '@alwatr/nanolib';

export * from '@alwatr/nanotron-api-server';
export * from '@alwatr/crypto';
export * from '@alwatr/pre-handlers';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);
2 changes: 1 addition & 1 deletion packages/nanotron/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"types": ["node", "@alwatr/nano-build", "@alwatr/type-helper"]
},
"include": ["src/**/*.ts"],
"references": [{"path": "../api-server"}, {"path": "../crypto"}],
"references": [{"path": "../api-server"}, {"path": "../pre-handlers"}],
}
661 changes: 661 additions & 0 deletions packages/pre-handlers/LICENSE

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions packages/pre-handlers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Pre Handlers

## Sponsors

The following companies, organizations, and individuals support Nanotron ongoing maintenance and development. Become a Sponsor to get your logo on our README and website.

[![Exir Studio](https://avatars.githubusercontent.com/u/181194967?s=200&v=4)](https://exirstudio.com)

### Contributing

Contributions are welcome! Please read our [contribution guidelines](https://github.com/Alwatr/.github/blob/next/CONTRIBUTING.md) before submitting a pull request.

### License

This project is licensed under the [AGPL-3.0 License](LICENSE).
75 changes: 75 additions & 0 deletions packages/pre-handlers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"name": "@alwatr/pre-handlers",
"version": "1.0.0-0",
"description": "Functions that will run before processing every defined route.",
"author": "S. Ali Mihandoost <[email protected]>",
"keywords": [
"server",
"nanoservice",
"api",
"typescript",
"esm",
"route-pre-handlers",
"handlers",
"middleware",
"alwatr"
],
"type": "module",
"main": "./dist/main.cjs",
"module": "./dist/main.mjs",
"types": "./dist/main.d.ts",
"exports": {
".": {
"types": "./dist/main.d.ts",
"import": "./dist/main.mjs",
"require": "./dist/main.cjs"
}
},
"license": "AGPL-3.0-only",
"files": [
"**/*.{js,mjs,cjs,map,d.ts,html,md}",
"!demo/**/*"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "https://github.com/Alwatr/pre-handlers",
"directory": "packages/pre-handlers"
},
"homepage": "https://github.com/Alwatr/pre-handlers#readme",
"bugs": {
"url": "https://github.com/Alwatr/pre-handlers/issues"
},
"prettier": "@alwatr/prettier-config",
"scripts": {
"b": "yarn run build",
"t": "yarn run test",
"w": "yarn run watch",
"c": "yarn run clean",
"cb": "yarn run clean && yarn run build",
"d": "yarn run build:es && yarn node --enable-source-maps --trace-warnings",
"build": "yarn run build:ts & yarn run build:es",
"build:es": "nano-build --preset=module",
"build:ts": "tsc --build",
"test": "NODE_OPTIONS=\"$NODE_OPTIONS --enable-source-maps --experimental-vm-modules\" jest",
"watch": "yarn run watch:ts & yarn run watch:es",
"watch:es": "yarn run build:es --watch",
"watch:ts": "yarn run build:ts --watch --preserveWatchOutput",
"clean": "rm -rfv dist *.tsbuildinfo"
},
"dependencies": {
"@alwatr/nanolib": "^1.3.0"
},
"devDependencies": {
"@alwatr/nano-build": "^2.0.2",
"@alwatr/nanotron-api-server": "workspace:^",
"@alwatr/prettier-config": "^1.0.6",
"@alwatr/tsconfig-base": "^1.3.2",
"@alwatr/type-helper": "^2.0.2",
"@types/node": "^22.7.5",
"jest": "^29.7.0",
"typescript": "^5.6.3"
}
}
54 changes: 54 additions & 0 deletions packages/pre-handlers/src/handler/parse-body-as-json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {HttpStatusCodes} from '@alwatr/nanotron-api-server';

import type {NanotronClientRequest} from '@alwatr/nanotron-api-server';

/**
* Middleware to parses the request body as JSON and assigns it to `this.sharedMeta.body`.
* If the body is empty or invalid, it sends an error response,
* which triggers `terminatedHandlers` and prevents further handlers from executing.
*
* @this {NanotronClientRequest<{body: DictionaryOpt}>}
* @returns {Promise<void>} A promise that resolves when the body is successfully parsed or an error response is sent.
*
* @example
* ```ts
* nanotronApiServer.defineRoute<{body: DictionaryOpt}>({
* preHandlers: [parseBodyAsJson],
* async handler() {
* const body = this.sharedMeta.body; // json object
* },
* });
* ```
*/
export async function parseBodyAsJson(
this: NanotronClientRequest<{body?: DictionaryOpt}>,
): Promise<void> {
this.logger_.logMethod?.('parseBodyAsJson');
const bodyBuffer = await this.getBodyRaw();

if (bodyBuffer.length === 0) {
this.logger_.error('parseBodyAsJson', 'body_required');
this.serverResponse.statusCode = HttpStatusCodes.Error_Client_422_Unprocessable_Entity;
this.serverResponse.replyErrorResponse({
ok: false,
errorCode: 'body_required',
errorMessage: 'Request body is required.',
});
return;
}

try {
this.sharedMeta.body = JSON.parse(bodyBuffer.toString()) as DictionaryOpt;
}
catch (error) {
this.logger_.error('parseBodyAsJson', 'invalid_body_json', error);
this.serverResponse.statusCode = HttpStatusCodes.Error_Client_422_Unprocessable_Entity;
this.serverResponse.replyErrorResponse({
ok: false,
errorCode: 'invalid_body_json',
errorMessage: 'Invalid JSON in request body.',
});
}

this.logger_.logProperty?.('body', this.sharedMeta.body);
}
53 changes: 53 additions & 0 deletions packages/pre-handlers/src/handler/require-access-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {HttpStatusCodes, type NanotronClientRequest} from '@alwatr/nanotron-api-server';

import {getAuthBearer} from '../lib/get-auth-bearer.js';

/**
* Middleware to require a valid access token for a Nanotron API request.
*
* This function checks the authorization header for a Bearer token and compares it to the provided access token.
* If the token is missing or invalid, it sends an appropriate error response and prevents further handlers from executing.
*
* @param {string} accessToken - The valid access token to compare against.
* @returns {Function} A middleware function for Nanotron API requests.
*
* @example
* ```ts
* nanotronApiServer.defineRoute({
* method: 'POST',
* url: 'secure-endpoint',
* preHandlers: [requireAccessToken('mySecretToken')],
* async handler() {
* this.serverResponse.replyJson({
* ok: true,
* message: 'Access granted!',
* });
* },
* });
* ```
*/
export const requireAccessToken = (accessToken: string) =>
async function requireAccessToken_(this: NanotronClientRequest): Promise<void> {
const userToken = getAuthBearer(this.headers.authorization);
this.logger_.logMethodArgs?.('requireAccessToken', {userToken});

if (userToken === null) {
this.serverResponse.statusCode = HttpStatusCodes.Error_Client_401_Unauthorized;
this.serverResponse.replyErrorResponse({
ok: false,
errorCode: 'authorization_required',
errorMessage: 'Authorization token required',
});
return;
}

if (userToken !== accessToken) {
this.serverResponse.statusCode = HttpStatusCodes.Error_Client_403_Forbidden;
this.serverResponse.replyErrorResponse({
ok: false,
errorCode: 'access_denied',
errorMessage: 'Access denied, token is invalid!',
});
}
};

26 changes: 26 additions & 0 deletions packages/pre-handlers/src/lib/get-auth-bearer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Extracts the Bearer token from the authorization header.
*
* This function takes an optional authorization header string, extracts the Bearer token, and returns it.
* If the header is invalid or the token is missing, it returns null.
*
* @param {string} [authorizationHeader] - The authorization header string.
* @returns {string | null} The extracted Bearer token or null if the header is invalid.
*
* @example
* ```ts
* console.log(this.headers.authorization); // 'Bearer myToken123'
* const token = getAuthBearer(this.headers.authorization);
* console.log(token); // 'myToken123'
*
* const invalidToken = getAuthBearer('Basic myToken123');
* console.log(invalidToken); // null
* ```
*/
export function getAuthBearer(authorizationHeader?: string): string | null {
const auth = authorizationHeader?.split(' ');
if (!auth || auth[0].toLowerCase() !== 'bearer' || !auth[1]) {
return null;
}
return auth[1];
}
7 changes: 7 additions & 0 deletions packages/pre-handlers/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {packageTracer} from '@alwatr/nanolib';

export * from './lib/get-auth-bearer.js';
export * from './handler/parse-body-as-json.js';
export * from './handler/require-access-token.js';

__dev_mode__: packageTracer.add(__package_name__, __package_version__);
12 changes: 12 additions & 0 deletions packages/pre-handlers/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "@alwatr/tsconfig-base/tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"emitDeclarationOnly": true,
"composite": true,
"types": ["node", "@alwatr/nano-build", "@alwatr/type-helper"]
},
"include": ["src/**/*.ts"],
"references": [{"path": "../api-server"}],
}
28 changes: 27 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ __metadata:
"@alwatr/nano-build": "npm:^2.0.2"
"@alwatr/nanolib": "npm:^1.3.0"
"@alwatr/nanotron-api-server": "workspace:^"
"@alwatr/pre-handlers": "workspace:^"
"@alwatr/prettier-config": "npm:^1.0.6"
"@alwatr/tsconfig-base": "npm:^1.3.2"
"@alwatr/type-helper": "npm:^2.0.2"
Expand Down Expand Up @@ -270,6 +271,22 @@ __metadata:
languageName: node
linkType: hard

"@alwatr/pre-handlers@workspace:^, @alwatr/pre-handlers@workspace:packages/pre-handlers":
version: 0.0.0-use.local
resolution: "@alwatr/pre-handlers@workspace:packages/pre-handlers"
dependencies:
"@alwatr/nano-build": "npm:^2.0.2"
"@alwatr/nanolib": "npm:^1.3.0"
"@alwatr/nanotron-api-server": "workspace:^"
"@alwatr/prettier-config": "npm:^1.0.6"
"@alwatr/tsconfig-base": "npm:^1.3.2"
"@alwatr/type-helper": "npm:^2.0.2"
"@types/node": "npm:^22.7.5"
jest: "npm:^29.7.0"
typescript: "npm:^5.6.3"
languageName: unknown
linkType: soft

"@alwatr/prettier-config@npm:^1.0.6":
version: 1.0.6
resolution: "@alwatr/prettier-config@npm:1.0.6"
Expand Down Expand Up @@ -2209,6 +2226,15 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:^22.7.5":
version: 22.8.1
resolution: "@types/node@npm:22.8.1"
dependencies:
undici-types: "npm:~6.19.8"
checksum: 10c0/83550fdf72a7db5b55eceac3f4fb038844eaee20202bdd2297a8248370cfa08317bda1605b781a8043eda4f173b75e73632e652fc85509eb14dfef78fa17337f
languageName: node
linkType: hard

"@types/node@npm:^22.7.9":
version: 22.7.9
resolution: "@types/node@npm:22.7.9"
Expand Down Expand Up @@ -7854,7 +7880,7 @@ __metadata:
languageName: node
linkType: hard

"undici-types@npm:~6.19.2":
"undici-types@npm:~6.19.2, undici-types@npm:~6.19.8":
version: 6.19.8
resolution: "undici-types@npm:6.19.8"
checksum: 10c0/078afa5990fba110f6824823ace86073b4638f1d5112ee26e790155f481f2a868cc3e0615505b6f4282bdf74a3d8caad715fd809e870c2bb0704e3ea6082f344
Expand Down

0 comments on commit 056f5cd

Please sign in to comment.