Skip to content

Commit

Permalink
v1.2.4
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewWalsh committed Nov 6, 2023
1 parent c422a06 commit d350fcb
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 135 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ OpenAPI DevTools is a Chrome extension that generates OpenAPI specifications in
- Automatically merges new request & response headers, bodies, and query parameters per endpoint
- Click on a [path parameter](https://www.abstractapi.com/api-glossary/path-parameters) and the app will automatically merge existing and future matching requests
- View the specification inside the tool using [Redoc](https://www.npmjs.com/package/redoc) and download with a click
- Export and save a session at any time, or share it with others

<p align="right">(<a href="#readme-top">back to top</a>)</p>

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "OpenAPI DevTools",
"version": "1.2.2",
"version": "1.2.4",
"devtools_page": "index.html",
"permissions": [],
"icons": {
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,5 @@
"typescript": "^5.2.2",
"vite": "^4.4.11",
"vitest": "^0.34.6"
},
"overrides": {
"@apidevtools/json-schema-ref-parser": "^11.1.0"
}
}
Binary file modified resources/dist.zip
Binary file not shown.
17 changes: 17 additions & 0 deletions src/lib/RequestStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,20 @@ it("can parameterise paths that exist along the same segment", () => {
expect(getResBodyTypes(store, host, "/1/ANY/a")).toBe("string");
expect(getResBodyTypes(store, host, "/1/ANY")).toBe("integer");
});

it("parameterisation works after export and import", () => {
const store = new RequestStore();
const req = createSimpleRequest(`${base}/1/2/a`);
store.insert(req, { foo: 1 });
store.parameterise(2, "/1/2/a", host);
const exported = store.export();
store.import(exported);
store.insert(req, { foo: 1 });
const expected = {
[host]: {
'/1/2/:param2': expect.any(Object),
}
};
// @ts-expect-error accessing private property
expect(store.leafMap).toEqual(expected);
});
8 changes: 4 additions & 4 deletions src/lib/endpoints-to-oai31.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ import {
ParameterObject,
SecuritySchemeObject,
} from "openapi3-ts/oas31";
import { Authentication, AuthType } from "../utils/httpauthentication";
import { Authentication, AuthType } from "../utils/types";

export const createSecuritySchemeTypes = (auth?: Authentication): SecuritySchemeObject | undefined => {
if (!auth) return;
const isBearer = auth.authType === AuthType.BEARER;
const isBasic = auth.authType === AuthType.BASIC;
const isDigest = auth.authType === AuthType.DIGEST;
const isBearer = auth.id === AuthType.BEARER;
const isBasic = auth.id === AuthType.BASIC;
const isDigest = auth.id === AuthType.DIGEST;
if (isBearer || isBasic || isDigest) {
const securitySchemeObject: SecuritySchemeObject = {
type: auth.type,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/endpoints-to-oai31.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import bearer from "./__fixtures__/bearer";
import basic from "./__fixtures__/basic";
import digest from "./__fixtures__/digest";
import { cloneDeep } from "lodash";
import { AuthType } from "../utils/httpauthentication";
import { AuthType } from "../utils/types";
import { defaultOptions } from "./store-helpers/persist-options";

const createRequestStoreWithDefaults = () => {
Expand Down
28 changes: 16 additions & 12 deletions src/lib/endpoints-to-oai31.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Endpoint } from "../utils/types";
import { Endpoint, AuthType } from "../utils/types";
import {
OpenApiBuilder,
PathItemObject,
Expand All @@ -15,9 +15,9 @@ import {
createResponseTypes,
createSecuritySchemeTypes,
} from "./endpoints-to-oai31.helpers";
import { AuthType } from "../utils/httpauthentication";
import { Options } from "./RequestStore";
import { defaultOptions } from "./store-helpers/persist-options";
import { isEmpty } from "lodash";

const endpointsToOAI31 = (endpoints: Array<Endpoint>, options: Options = defaultOptions): OpenApiBuilder => {
const { enableMoreInfo } = options;
Expand All @@ -34,12 +34,14 @@ const endpointsToOAI31 = (endpoints: Array<Endpoint>, options: Options = default

const auth = endpoint.data.authentication;
if (auth) {
const securitySchema = createSecuritySchemeTypes(
endpoint.data.authentication
);
if (securitySchema) {
uniqueAuth.set(auth.authType, securitySchema);
}
Object.values(auth).forEach(value => {
const securitySchema = createSecuritySchemeTypes(
value
);
if (securitySchema) {
uniqueAuth.set(value.id, securitySchema);
}
});
}

for (const [method, statusCodes] of Object.entries(endpoint.data.methods)) {
Expand All @@ -55,10 +57,12 @@ const endpointsToOAI31 = (endpoints: Array<Endpoint>, options: Options = default
statusCode,
mostRecentResponse,
);
const security: SecurityRequirementObject[] | null = endpoint.data
.authentication
? [{ [endpoint.data.authentication.authType]: [] }]
: null;
const security: SecurityRequirementObject[] = [];
if (!isEmpty(endpoint.data.authentication)) {
Object.values(endpoint.data.authentication).forEach(value => {
security.push({ [value.id]: [] });
});
}
const operation: OperationObject = {
summary: fullPath,
description: `**Host**: http://${endpoint.host}`,
Expand Down
51 changes: 51 additions & 0 deletions src/lib/store-helpers/authentication-api-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// import { Authentication, AuthType } from "../../utils/types";
// import { getAuthType } from "./authentication";

// /**
// * Helpers and utilities for endpoint Authentication details
// * Modelled on a Security Scheme Object https://spec.openapis.org/oas/v3.1.0#security-scheme-object
// */
// export interface APIKeyAuth extends Authentication {
// type: "apiKey";
// name: string;
// }

// export interface APIKeyAuthHeader extends APIKeyAuth {
// in: "header";
// }

// export type APIKeyAuthHeaders = APIKeyAuthHeader | undefined;

// export const parseAPIKeyAuthHeader = (
// header: chrome.devtools.network.Request["request"]["headers"][0]
// ): APIKeyAuthHeaders => {
// const authType = getAuthType(header.value);
// if (authType === "basic") {
// const basicAuth: BasicAuthHeader = {
// id: AuthType.BASIC,
// type: "http",
// in: "header",
// scheme: "Basic",
// description: "",
// };
// return basicAuth;
// } else if (authType === "bearer") {
// const bearerAuth: BearerAuthHeader = {
// id: AuthType.BEARER,
// type: "http",
// in: "header",
// scheme: "Bearer",
// description: "",
// };
// return bearerAuth;
// } else if (authType === "digest") {
// const digestAuth: DigestAuthHeader = {
// id: AuthType.DIGEST,
// type: "http",
// in: "header",
// scheme: "Digest",
// description: "",
// };
// return digestAuth;
// }
// };
65 changes: 65 additions & 0 deletions src/lib/store-helpers/authentication-http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Authentication, AuthType } from "../../utils/types";
import { getAuthType } from "./authentication";

/**
* Helpers and utilities for endpoint Authentication details
* Modelled on a Security Scheme Object https://spec.openapis.org/oas/v3.1.0#security-scheme-object
*/
export interface HTTPAuth extends Authentication {
type: "http";
}

export interface BearerAuthHeader extends HTTPAuth {
scheme: "Bearer";
in: "header";
}

export interface BasicAuthHeader extends HTTPAuth {
scheme: "Basic";
in: "header";
}

export interface DigestAuthHeader extends HTTPAuth {
scheme: "Digest";
in: "header";
}

export type HTTPAuthHeaders =
| DigestAuthHeader
| BasicAuthHeader
| BearerAuthHeader
| undefined;

export const parseHTTPAuthHeader = (
header: chrome.devtools.network.Request["request"]["headers"][0]
): HTTPAuthHeaders => {
const authType = getAuthType(header.value);
if (authType === "basic") {
const basicAuth: BasicAuthHeader = {
id: AuthType.BASIC,
type: "http",
in: "header",
scheme: "Basic",
description: "",
};
return basicAuth;
} else if (authType === "bearer") {
const bearerAuth: BearerAuthHeader = {
id: AuthType.BEARER,
type: "http",
in: "header",
scheme: "Bearer",
description: "",
};
return bearerAuth;
} else if (authType === "digest") {
const digestAuth: DigestAuthHeader = {
id: AuthType.DIGEST,
type: "http",
in: "header",
scheme: "Digest",
description: "",
};
return digestAuth;
}
};
25 changes: 25 additions & 0 deletions src/lib/store-helpers/authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { isEmpty } from "lodash";
import { Leaf } from "../../utils/types";
import { parseHTTPAuthHeader } from "./authentication-http";

export const getAuthType = (auth: string) => {
const split = auth.split(" ");
if (!split.length) return "";
return split[0].toLowerCase();
};

const AUTHORIZATION = "authorization";

type DetermineAuthFromHAR = (harRequest: chrome.devtools.network.Request) => Leaf['authentication'] | undefined;
const determineAuthFromHAR: DetermineAuthFromHAR = (harRequest) => {
const finalAuth: Leaf['authentication'] = {};
const foundAuthHeader = harRequest.request.headers.find(
(head) => head.name.toLowerCase() === AUTHORIZATION
);
const authHeaders = foundAuthHeader ? parseHTTPAuthHeader(foundAuthHeader) : undefined;
if (!isEmpty(authHeaders)) finalAuth[authHeaders.id] = authHeaders;
if (isEmpty(finalAuth)) return undefined;
return finalAuth;
};

export default determineAuthFromHAR;
4 changes: 2 additions & 2 deletions src/lib/store-helpers/create-leaf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
parseJSON,
} from "../../utils/helpers";
import { JSONType, Leaf } from "../../utils/types";
import { parseAuthHeader } from "../../utils/httpauthentication";
import determineAuthFromHAR from "./authentication";
import { filterIgnoreHeaders } from "../../utils/headers";
import type { Options } from "../RequestStore";

Expand All @@ -17,7 +17,7 @@ type Params = {

function createLeaf({ harRequest, responseBody, options }: Params): Leaf {
const { enableMoreInfo } = options;
const authentication = parseAuthHeader(harRequest.request.headers);
const authentication = determineAuthFromHAR(harRequest);
harRequest.request.headers = filterIgnoreHeaders(harRequest.request.headers);
harRequest.response.headers = filterIgnoreHeaders(
harRequest.response.headers
Expand Down
1 change: 1 addition & 0 deletions src/lib/store-helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ export { default as upsert } from "./upsert";
export { default as findPathnamesInRouter } from "./find-pathnames-in-router";
export { default as pruneRouter } from "./prune-router";
export { default as persistOptions } from "./persist-options";
export { default as determineAuthFromHAR } from "./authentication";
export * from "./merge";
export * from "./helpers";
32 changes: 19 additions & 13 deletions src/lib/store-helpers/merge.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
import { mergeSchemas } from "genson-js";
import { Schema, mergeSchemas } from "genson-js";
import type { Leaf } from "../../utils/types";

const mergeAuthentication = (dest: Leaf, src: Leaf): void => {
if (!src.authentication) return;
if (!dest.authentication) dest.authentication = {};
Object.entries(src.authentication).forEach(([key, value]) => {
dest.authentication![key] = value;
});
};

export const mergeLeaves = (dest: Leaf, src: Leaf): Leaf => {
dest.pathname = src.pathname;
dest.authentication = src.authentication || dest.authentication;
mergeAuthentication(dest, src);
if (src.mostRecentRequest) dest.mostRecentRequest = src.mostRecentRequest;
if (src.mostRecentResponse) dest.mostRecentResponse = src.mostRecentResponse;
for (const [method, statusCodeObj] of Object.entries(src.methods)) {
if (!dest.methods[method]) {
dest.methods[method] = statusCodeObj;
continue;
}
for (const [statusCode, schemaObj] of Object.entries(statusCodeObj)) {
if (!dest.methods[method][statusCode]) {
const destSchema = dest.methods[method][statusCode];
if (!destSchema) {
dest.methods[method][statusCode] = schemaObj;
continue;
}
for (const [key, schema] of Object.entries(schemaObj)) {
const destSchema = dest.methods[method][statusCode];
type Key = keyof typeof destSchema;
if (destSchema[key as Key]) {
destSchema[key as Key] = mergeSchemas([
destSchema[key as Key]!,
schema,
]);
type Entries = Array<[keyof typeof destSchema, Schema]>;
for (const [key, schema] of Object.entries(schemaObj) as Entries) {
if (destSchema[key]) {
destSchema[key] = mergeSchemas([destSchema[key]!, schema]);
} else {
destSchema[key as Key] = schema;
destSchema[key] = schema;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/store-helpers/upsert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default function upsert({
const router = store[host];
const matchedRoute = router.lookup(pathname);
const nextLeaf = matchedRoute
? mergeLeaves(insertLeaf, matchedRoute.data)
? mergeLeaves(matchedRoute.data, insertLeaf)
: insertLeaf;
const parameterisedPath = matchedRoute?.data.pathname || pathname;
nextLeaf.pathname = parameterisedPath;
Expand Down
Loading

0 comments on commit d350fcb

Please sign in to comment.