Skip to content

Commit

Permalink
Merge pull request #43 from AikidoSec/patch-helpers
Browse files Browse the repository at this point in the history
Rename helpers
  • Loading branch information
hansott authored Feb 27, 2024
2 parents d309203 + c0ff64a commit 9de3ded
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 186 deletions.
20 changes: 5 additions & 15 deletions library/src/agent/Context.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
import { AsyncLocalStorage } from "node:async_hooks";
import type { ParsedQs } from "qs";

/**
* @prop url This is the URL where the (express) request came in
* @prop method This is the HTTP Method used for the (express) request
* @prop query These are the URL Query parameters (e.g. example.com?param1=value1)
* @prop headers The HTTP headers accompanying the request
* @prop remoteAddress The remote address of the end-user
* @prop body This is the (form) body that possible accompanies the request
* @prop cookies These are the cookies accompanying the request
* @interface
*/
export type Context = {
url: string | undefined;
method: string;
Expand All @@ -24,18 +14,18 @@ export type Context = {
const requestContext = new AsyncLocalStorage<Context>();

/**
* This function gives you the {@link Context} stored in asynchronous local storage
* @returns the stored context in asynchronous local storage
* Get the current request context that is being handled
*/
export function getContext() {
return requestContext.getStore();
}

/**
* Executes a function with a given request context
*
* The code executed inside the function will have access to the context using {@link getContext}
*
* @param context The context you want to set ({@link Context})
* @param fn
* @returns
* This is needed because Node.js is single-threaded, so we can't use a global variable to store the context.
*/
export function runWithContext<T>(context: Context, fn: () => T) {
return requestContext.run(context, fn);
Expand Down
27 changes: 0 additions & 27 deletions library/src/helpers/extractStringsFromContext.test.ts

This file was deleted.

22 changes: 0 additions & 22 deletions library/src/helpers/extractStringsFromContext.ts

This file was deleted.

75 changes: 0 additions & 75 deletions library/src/helpers/extractStringsFromObjects.test.ts

This file was deleted.

32 changes: 0 additions & 32 deletions library/src/helpers/extractStringsFromObjects.ts

This file was deleted.

88 changes: 88 additions & 0 deletions library/src/helpers/extractStringsFromUserInput.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/* eslint-disable camelcase */
import * as t from "tap";
import { extractStringsFromUserInput } from "./extractStringsFromUserInput";

t.test("empty object returns empty array", async () => {
t.same(extractStringsFromUserInput({}), []);
});

t.test("it can extract query objects", async () => {
t.same(extractStringsFromUserInput({ age: { $gt: "21" } }), [
"age",
"$gt",
"21",
]);
t.same(extractStringsFromUserInput({ title: { $ne: "null" } }), [
"title",
"$ne",
"null",
]);
t.same(
extractStringsFromUserInput({
age: "whaat",
user_input: ["whaat", "dangerous"],
}),
["user_input", "age", "whaat", "dangerous"]
);
});

t.test("it can extract cookie objects", async () => {
t.same(extractStringsFromUserInput({ session: "ABC", session2: "DEF" }), [
"session2",
"session",
"ABC",
"DEF",
]);
t.same(extractStringsFromUserInput({ session: "ABC", session2: 1234 }), [
"session2",
"session",
"ABC",
]);
});

t.test("it can extract header objects", async () => {
t.same(
extractStringsFromUserInput({
"Content-Type": "application/json",
}),
["Content-Type", "application/json"]
);
t.same(
extractStringsFromUserInput({
"Content-Type": 54321,
}),
["Content-Type"]
);
t.notSame(
extractStringsFromUserInput({
"Content-Type": "application/json",
ExtraHeader: "value",
}),
["Content-Type", "application/json"]
);
});

t.test("it can extract body objects", async () => {
t.same(extractStringsFromUserInput({ nested: { nested: { $ne: null } } }), [
"nested",
"$ne",
]);

t.same(extractStringsFromUserInput({ age: { $gt: "21", $lt: "100" } }), [
"age",
"$lt",
"$gt",
"21",
"100",
]);
});

t.test("it decodes JWTs", async () => {
t.same(
extractStringsFromUserInput({
token:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidXNlcm5hbWUiOnsiJG5lIjpudWxsfSwiaWF0IjoxNTE2MjM5MDIyfQ._jhGJw9WzB6gHKPSozTFHDo9NOHs3CNOlvJ8rWy6VrQ",
}),
["token", "iat", "username", "sub", "1234567890", "$ne"]
);
});
39 changes: 39 additions & 0 deletions library/src/helpers/extractStringsFromUserInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { isPlainObject } from "./isPlainObject";
import { tryDecodeAsJWT } from "./tryDecodeAsJWT";

/**
* Extract all strings from an object, see unit tests for examples
*/
export function extractStringsFromUserInput(obj: unknown): string[] {
let results: Set<string> = new Set();

if (isPlainObject(obj)) {
for (const key in obj) {
results = new Set([
key,
...results,
...extractStringsFromUserInput(obj[key]),
]);
}
}

if (Array.isArray(obj)) {
for (const element of obj) {
results = new Set([...results, ...extractStringsFromUserInput(element)]);
}
}

if (typeof obj == "string") {
const jwt = tryDecodeAsJWT(obj);
if (jwt.jwt) {
results = new Set([
...results,
...extractStringsFromUserInput(jwt.object),
]);
} else {
results.add(obj);
}
}

return Array.from(results);
}
1 change: 1 addition & 0 deletions library/src/helpers/ipAddress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ import { ip } from "./ipAddress";

t.test("it works", async () => {
t.same(ip(), "10.206.52.79");
t.same(ip("eth0"), undefined);
});
14 changes: 3 additions & 11 deletions library/src/helpers/ipAddress.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
/**
* The ipAddress module exports only one function : {@link ip}
* @module helpers/ipAddress
*/

import * as os from "os";

/**
* Get the IP Address of the machine running this code
* @param interfaceName The name of the networking interface you want the IP Address from
* @returns The ip address associated to that networking interface
* @example
* ip(); // Returns your IP address
* @example
* ip('eth0'); // Return the IP address on the interface eth0
*/
export function ip(interfaceName?: string) {
const item = getInterfaceAddress("IPv4", interfaceName);
Expand All @@ -28,6 +17,7 @@ function getDefaultInterfaceName() {
} else if (platform === "win32") {
val = undefined;
}

return val;
}

Expand All @@ -38,9 +28,11 @@ function matchName(
if (expectedFamily === "IPv4") {
return actualFamily === "IPv4" || actualFamily === 4;
}

if (expectedFamily === "IPv6") {
return actualFamily === "IPv6" || actualFamily === 6;
}

return actualFamily === expectedFamily;
}

Expand Down
4 changes: 4 additions & 0 deletions library/src/helpers/satisfiesVersion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ t.test("it returns false if invalid range", async () => {
t.equal(satisfiesVersion("1.0.0", "1.0.0"), false);
});

t.test("it returns false if invalid range", async () => {
t.equal(satisfiesVersion("^1.0", "1.0.0"), false);
});

t.test("it matches single range", async () => {
t.equal(satisfiesVersion("^1.0.0", "1.0.0"), true);
t.equal(satisfiesVersion("^1.0.0", "1.1.0"), true);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as t from "tap";
import { tryDecodeAsJWT } from "./jwt";
import { tryDecodeAsJWT } from "./tryDecodeAsJWT";

t.test("it returns undefined for empty string", async () => {
t.same(tryDecodeAsJWT(""), { jwt: false });
Expand Down
File renamed without changes.
Loading

0 comments on commit 9de3ded

Please sign in to comment.