diff --git a/example/bundle-hub2.css b/example/bundle-hub2.css
index d7d5092cd..9ecc75c21 100644
--- a/example/bundle-hub2.css
+++ b/example/bundle-hub2.css
@@ -2965,7 +2965,7 @@ img.mfp-img {
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
-
+
/* Styles */
background: #fff;
border-radius: 7px;
@@ -10284,7 +10284,8 @@ body.page-password footer,
text-overflow: ellipsis;
overflow: hidden;
}
-#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=text] {
+#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=text],
+#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=password] {
border: 1px solid #ddd;
border-radius: 2px;
padding: 5px 7px;
@@ -10294,6 +10295,8 @@ body.page-password footer,
color: #666;
}
#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=text]:focus,
+#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=password]:focus,
+#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=password]:active,
#hub-reference .hub-api .api-definition .hub-auth-dropdown > div input[type=text]:active {
outline: 0 none;
border: 1px solid #888;
@@ -11282,7 +11285,7 @@ div.simple-dropdown.open > div {
font-variant: normal;
text-transform: none;
line-height: 1;
-
+
/* Enable Ligatures ================ */
letter-spacing: 0;
-webkit-font-feature-settings: "liga";
diff --git a/example/src/Demo.jsx b/example/src/Demo.jsx
index 050541562..95442eafb 100644
--- a/example/src/Demo.jsx
+++ b/example/src/Demo.jsx
@@ -56,8 +56,8 @@ function Demo({ fetchSwagger, status, docs, oas, oauth }) {
// user: { user: '123456', pass: 'abc', apiKey: '123456' },
user: {
keys: [
- { name: 'project1', apiKey: '123', user: 'user1', pass: 'pass1' },
- { name: 'project2', apiKey: '456', user: 'user2', pass: 'pass2' },
+ { id: 'asdfghjkl', name: 'project1', apiKey: '123', user: 'user1', pass: 'pass1' },
+ { id: 'zxcvbnm', name: 'project2', apiKey: '456', user: 'user2', pass: 'pass2' },
],
},
defaults: [],
diff --git a/example/swagger-files/multiple-securities.json b/example/swagger-files/multiple-securities.json
index 696ba73d1..31f57144c 100644
--- a/example/swagger-files/multiple-securities.json
+++ b/example/swagger-files/multiple-securities.json
@@ -23,7 +23,6 @@
],
"summary": "or security",
"description": "",
- "operationId": "addThings",
"parameters": [],
"responses": {
"405": {
@@ -38,12 +37,12 @@
"security": [
{
"oauth": ["write:things"],
- "apiKey": []
+ "apiKey": [],
+ "basicAuth": []
}
],
"summary": "and security",
"description": "",
- "operationId": "addAndSecurity",
"parameters": [],
"responses": {
"405": {
@@ -66,7 +65,6 @@
],
"summary": "and or security",
"description": "",
- "operationId": "addAndOrSecurity",
"parameters": [],
"responses": {
"405": {
@@ -88,7 +86,6 @@
],
"summary": "or security",
"description": "",
- "operationId": "addOrSecurity",
"parameters": [],
"responses": {
"405": {
@@ -107,7 +104,6 @@
],
"summary": "one security for endpoint",
"description": "",
- "operationId": "addSingleAuth",
"parameters": [],
"responses": {
"405": {
@@ -121,7 +117,6 @@
"operationId": "noAuth",
"summary": "no security needed",
"description": "",
- "operationId": "addNoSecurity",
"parameters": [],
"responses": {
"405": {
@@ -143,13 +138,12 @@
],
"summary": "or security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": []
}
},
"/multiple-combo-auths": {
"post": {
- "operationId": "unsupported scheme in the and",
+ "operationId": "unsupportedSchemeInTheAnd",
"security": [
{
"oauth": ["write:things", "read:things"],
@@ -161,7 +155,6 @@
],
"summary": "second and does not show security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": [],
"responses": {
"405": {
@@ -172,7 +165,7 @@
},
"/multiple-combo-auths-schemes": {
"post": {
- "operationId": "nonexistent scheme in the or",
+ "operationId": "nonexistentSchemeInTheOr",
"security": [
{
"oauth": ["write:things", "read:things"],
@@ -184,7 +177,6 @@
],
"summary": " one or security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": [],
"responses": {
"405": {
@@ -203,7 +195,6 @@
],
"summary": "unknown auth type",
"description": "",
- "operationId": "unknownAuthType",
"parameters": []
}
},
@@ -217,7 +208,6 @@
],
"summary": "this scheme doesnt exist",
"description": "",
- "operationId": "unknownScheme",
"parameters": []
}
}
@@ -258,6 +248,11 @@
"type": "demigorgon",
"name": "eleven",
"in": "header"
+ },
+ "basicAuth": {
+ "type": "http",
+ "scheme": "basic",
+ "in": "header"
}
},
"responses": {},
diff --git a/packages/api-explorer/__tests__/AuthBox.test.jsx b/packages/api-explorer/__tests__/AuthBox.test.jsx
index 0ff11d812..45623fb7a 100644
--- a/packages/api-explorer/__tests__/AuthBox.test.jsx
+++ b/packages/api-explorer/__tests__/AuthBox.test.jsx
@@ -14,6 +14,7 @@ const props = {
open: false,
operation: oas.operation('/or-security', 'post'),
onChange: () => {},
+ onGroupChange: () => {},
onSubmit: () => {},
toggle: () => {},
};
@@ -46,6 +47,12 @@ test.skip('should display a dropdown for when multiple oauths are present', () =
]);
});
+test('should mask password inputs for basic http auth', () => {
+ const authBox = mount();
+ expect(authBox.find('h3')).toHaveLength(3);
+ expect(authBox.find('input[type="password"]')).toHaveLength(1);
+});
+
test('should not display authentication warning if authData is passed', () => {
const authBox = mount();
@@ -68,3 +75,29 @@ test('should display multiple securities', () => {
expect(authBox.find('SecurityInput')).toHaveLength(2);
});
+
+describe('group selection', () => {
+ const groupProps = {
+ group: 'someid',
+ groups: [
+ { id: '1230', name: 'someid' },
+ { id: '7000', name: 'anotherId' },
+ ],
+ };
+
+ it('should only display a single dropdown regardless of the number of securities', () => {
+ const comp = mount();
+
+ expect(comp.find('select')).toHaveLength(1);
+ });
+
+ it('should update auth on changes', () => {
+ const comp = mount();
+
+ const select = comp.find('select');
+ select.instance().value = '7000';
+ select.simulate('change');
+
+ expect(comp.props().onGroupChange).toHaveBeenCalledWith('7000');
+ });
+});
diff --git a/packages/api-explorer/__tests__/fixtures/multiple-securities/oas.json b/packages/api-explorer/__tests__/fixtures/multiple-securities/oas.json
index 39cf176fc..571b61070 100644
--- a/packages/api-explorer/__tests__/fixtures/multiple-securities/oas.json
+++ b/packages/api-explorer/__tests__/fixtures/multiple-securities/oas.json
@@ -23,7 +23,6 @@
],
"summary": "or security",
"description": "",
- "operationId": "addThings",
"parameters": [],
"responses": {
"405": {
@@ -38,12 +37,12 @@
"security": [
{
"oauthScheme": ["write:things"],
- "apiKeyScheme": []
+ "apiKeyScheme": [],
+ "basicAuth": []
}
],
"summary": "and security",
"description": "",
- "operationId": "addAndSecurity",
"parameters": [],
"responses": {
"405": {
@@ -66,7 +65,6 @@
],
"summary": "and or security",
"description": "",
- "operationId": "addAndOrSecurity",
"parameters": [],
"responses": {
"405": {
@@ -85,7 +83,6 @@
],
"summary": "one security for endpoint",
"description": "",
- "operationId": "addSingleAuth",
"parameters": [],
"responses": {
"405": {
@@ -99,7 +96,6 @@
"operationId": "noAuth",
"summary": "no security needed",
"description": "",
- "operationId": "addNoSecurity",
"parameters": [],
"responses": {
"405": {
@@ -121,7 +117,6 @@
],
"summary": "or security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": [],
"responses": {
"405": {
@@ -144,7 +139,6 @@
],
"summary": "second and does not show security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": [],
"responses": {
"405": {
@@ -167,7 +161,6 @@
],
"summary": "one or security",
"description": "",
- "operationId": "addMultipleOAuths",
"parameters": [],
"responses": {
"405": {
@@ -186,7 +179,6 @@
],
"summary": "unknown auth type",
"description": "",
- "operationId": "unknownAuthType",
"parameters": []
}
},
@@ -200,7 +192,6 @@
],
"summary": "this scheme doesnt exist",
"description": "",
- "operationId": "unknownScheme",
"parameters": []
}
}
@@ -241,6 +232,11 @@
"type": "demigorgon",
"name": "eleven",
"in": "header"
+ },
+ "basicAuth": {
+ "type": "http",
+ "scheme": "basic",
+ "in": "header"
}
},
"responses": {},
diff --git a/packages/api-explorer/__tests__/lib/get-auth.test.js b/packages/api-explorer/__tests__/lib/get-auth.test.js
index 5f1cd03f4..da4c8c91b 100644
--- a/packages/api-explorer/__tests__/lib/get-auth.test.js
+++ b/packages/api-explorer/__tests__/lib/get-auth.test.js
@@ -13,9 +13,24 @@ test('should fetch all auths from the OAS files', () => {
oauthDiff: '',
apiKeyScheme: 'apikey',
unknownAuthType: '',
+ basicAuth: {
+ pass: '',
+ user: '',
+ },
});
});
+test('should fetch auths from selected app', () => {
+ const user = {
+ keys: [
+ { oauthScheme: '111', name: 'app-1' },
+ { oauthScheme: '222', name: 'app-2' },
+ ],
+ };
+
+ expect(getAuth(user, { 'api-setting': oas }, 'app-2').oauthScheme).toBe('222');
+});
+
test('should not error if oas.components is not set', () => {
expect(() => {
getAuth({ oauthScheme: 'oauth', apiKeyScheme: 'apikey' }, { 'api-setting': {} });
@@ -36,81 +51,85 @@ test('should not error if oas.components is not set', () => {
}).not.toThrow();
});
-const { getSingle } = getAuth;
-
-const topLevelUser = { apiKey: '123456', user: 'user', pass: 'pass' };
-const keysUser = {
- keys: [
- { apiKey: '123456', name: 'app-1' },
- { apiKey: '7890', name: 'app-2' },
- ],
-};
-const topLevelSchemeUser = { schemeName: 'scheme-key' };
-const keysSchemeUser = {
- keys: [
- { schemeName: 'scheme-key-1', name: 'app-1' },
- { schemeName: 'scheme-key-2', name: 'app-2' },
- { schemeName: { user: 'user', pass: 'pass' }, name: 'app-3' },
- ],
-};
-
-test('should return apiKey property for oauth', () => {
- expect(getSingle(topLevelUser, { type: 'oauth2' })).toBe('123456');
-});
+describe('#getSingle', () => {
+ const { getSingle } = getAuth;
+
+ const topLevelUser = { apiKey: '123456', user: 'user', pass: 'pass' };
+ const keysUser = {
+ keys: [
+ { apiKey: '123456', name: 'app-1' },
+ { apiKey: '7890', name: 'app-2' },
+ ],
+ };
+ const topLevelSchemeUser = { schemeName: 'scheme-key' };
+ const keysSchemeUser = {
+ keys: [
+ { schemeName: 'scheme-key-1', name: 'app-1' },
+ { schemeName: 'scheme-key-2', name: 'app-2' },
+ { schemeName: { user: 'user', pass: 'pass' }, name: 'app-3' },
+ ],
+ };
+
+ it('should return apiKey property for oauth', () => {
+ expect(getSingle(topLevelUser, { type: 'oauth2' })).toBe('123456');
+ });
-test('should return apiKey property for apiKey', () => {
- expect(getSingle(topLevelUser, { type: 'oauth2' })).toBe('123456');
-});
+ it('should return apiKey property for apiKey', () => {
+ expect(getSingle(topLevelUser, { type: 'oauth2' })).toBe('123456');
+ });
-test('should return a default value if scheme is sec0 and default auth provided', () => {
- expect(getSingle({}, { type: 'apiKey', _key: 'sec0', 'x-default': 'default' })).toBe('default');
-});
+ it('should return a default value if scheme is sec0 and default auth provided', () => {
+ expect(getSingle({}, { type: 'apiKey', _key: 'sec0', 'x-default': 'default' })).toBe('default');
+ });
-test('should return apiKey property for bearer', () => {
- expect(getSingle(topLevelUser, { type: 'http', scheme: 'bearer' })).toBe('123456');
-});
+ it('should return apiKey property for bearer', () => {
+ expect(getSingle(topLevelUser, { type: 'http', scheme: 'bearer' })).toBe('123456');
+ });
-test('should return user/pass properties for basic auth', () => {
- expect(getSingle(topLevelUser, { type: 'http', scheme: 'basic' })).toStrictEqual({
- user: 'user',
- pass: 'pass',
+ it('should return user/pass properties for basic auth', () => {
+ expect(getSingle(topLevelUser, { type: 'http', scheme: 'basic' })).toStrictEqual({
+ user: 'user',
+ pass: 'pass',
+ });
});
-});
-test('should return first item from keys array if no app selected', () => {
- expect(getSingle(keysUser, { type: 'oauth2' })).toBe('123456');
-});
+ it('should return first item from keys array if no app selected', () => {
+ expect(getSingle(keysUser, { type: 'oauth2' })).toBe('123456');
+ });
-test('should return selected app from keys array if app provided', () => {
- expect(getSingle(keysUser, { type: 'oauth2' }, 'app-2')).toBe('7890');
-});
+ it('should return selected app from keys array if app provided', () => {
+ expect(getSingle(keysUser, { type: 'oauth2' }, 'app-2')).toBe('7890');
+ });
-test('should return item by scheme name if no apiKey/user/pass', () => {
- expect(getSingle(topLevelSchemeUser, { type: 'oauth2', _key: 'schemeName' })).toBe('scheme-key');
- expect(
- getSingle(topLevelSchemeUser, { type: 'http', scheme: 'bearer', _key: 'schemeName' }),
- ).toBe('scheme-key');
- expect(getSingle(keysSchemeUser, { type: 'oauth2', _key: 'schemeName' })).toBe('scheme-key-1');
- expect(getSingle(keysSchemeUser, { type: 'oauth2', _key: 'schemeName' }, 'app-2')).toBe(
- 'scheme-key-2',
- );
- expect(
- getSingle(keysSchemeUser, { type: 'http', scheme: 'basic', _key: 'schemeName' }, 'app-3'),
- ).toStrictEqual({
- user: 'user',
- pass: 'pass',
+ it('should return item by scheme name if no apiKey/user/pass', () => {
+ expect(getSingle(topLevelSchemeUser, { type: 'oauth2', _key: 'schemeName' })).toBe(
+ 'scheme-key',
+ );
+ expect(
+ getSingle(topLevelSchemeUser, { type: 'http', scheme: 'bearer', _key: 'schemeName' }),
+ ).toBe('scheme-key');
+ expect(getSingle(keysSchemeUser, { type: 'oauth2', _key: 'schemeName' })).toBe('scheme-key-1');
+ expect(getSingle(keysSchemeUser, { type: 'oauth2', _key: 'schemeName' }, 'app-2')).toBe(
+ 'scheme-key-2',
+ );
+ expect(
+ getSingle(keysSchemeUser, { type: 'http', scheme: 'basic', _key: 'schemeName' }, 'app-3'),
+ ).toStrictEqual({
+ user: 'user',
+ pass: 'pass',
+ });
});
-});
-test('should return emptystring for anything else', () => {
- expect(getSingle(topLevelUser, { type: 'unknown' })).toBe('');
- expect(getSingle({}, { type: 'http', scheme: 'basic' })).toStrictEqual({ user: '', pass: '' });
- expect(getSingle({}, { type: 'http', scheme: 'bearer' })).toBe('');
- expect(getSingle({}, { type: 'http', scheme: 'unknown' })).toBe('');
- expect(getSingle(keysUser, { type: 'unknown' })).toBe('');
- expect(getSingle(keysUser, { type: 'unknown' }, 'app-2')).toBe('');
-});
+ it('should return emptystring for anything else', () => {
+ expect(getSingle(topLevelUser, { type: 'unknown' })).toBe('');
+ expect(getSingle({}, { type: 'http', scheme: 'basic' })).toStrictEqual({ user: '', pass: '' });
+ expect(getSingle({}, { type: 'http', scheme: 'bearer' })).toBe('');
+ expect(getSingle({}, { type: 'http', scheme: 'unknown' })).toBe('');
+ expect(getSingle(keysUser, { type: 'unknown' })).toBe('');
+ expect(getSingle(keysUser, { type: 'unknown' }, 'app-2')).toBe('');
+ });
-test('should allow scheme to be undefined', () => {
- expect(getSingle(topLevelUser)).toBe('');
+ it('should allow scheme to be undefined', () => {
+ expect(getSingle(topLevelUser)).toBe('');
+ });
});
diff --git a/packages/api-explorer/__tests__/lib/is-auth-ready.test.js b/packages/api-explorer/__tests__/lib/is-auth-ready.test.js
index d2320c70e..08aae70ce 100644
--- a/packages/api-explorer/__tests__/lib/is-auth-ready.test.js
+++ b/packages/api-explorer/__tests__/lib/is-auth-ready.test.js
@@ -15,6 +15,7 @@ describe('isAuthReady', () => {
isAuthReady(operation, {
oauthScheme: 'bearer',
apiKeyScheme: 'bearer',
+ basicAuth: { user: 'test', password: 'pass' },
}),
).toBe(true);
});
diff --git a/packages/api-explorer/package-lock.json b/packages/api-explorer/package-lock.json
index a14669b60..8a66888f7 100644
--- a/packages/api-explorer/package-lock.json
+++ b/packages/api-explorer/package-lock.json
@@ -1402,6 +1402,200 @@
"react": "^16.4.2"
}
},
+ "@readme/markdown": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@readme/markdown/-/markdown-4.12.1.tgz",
+ "integrity": "sha512-5QdaXBZF+b89lqqzaH4qrs0Yvr2Nc2CaIdT9bGTmtHl3S37Z0F5FgbPWUn6ONBfy/qxZtKgS4FxQR2eb2/RRCg==",
+ "requires": {
+ "@readme/syntax-highlighter": "^4.12.1",
+ "@readme/variable": "^4.12.1",
+ "hast-util-sanitize": "^1.2.0",
+ "prop-types": "^15.7.2",
+ "react": "^16.4.2",
+ "rehype-raw": "^4.0.1",
+ "rehype-react": "^3.1.0",
+ "rehype-sanitize": "^2.0.1",
+ "remark-breaks": "^1.0.0",
+ "remark-parse": "^7.0.2",
+ "remark-rehype": "^5.0.0",
+ "unified": "^8.4.0"
+ }
+ },
+ "@readme/oas-extensions": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@readme/oas-extensions/-/oas-extensions-4.12.1.tgz",
+ "integrity": "sha512-f3PHVkei6ZlJKHQpruMGoHPTEh8GtzWAG9iC5H8ImZ255sW8j01i/ablDWLDymEsGZdZRZWDYHaSQbcY+6PPbg=="
+ },
+ "@readme/oas-to-har": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@readme/oas-to-har/-/oas-to-har-4.12.1.tgz",
+ "integrity": "sha512-sN0zu9yZ5S7wH71H4fux97CKY6u8lp7x3xdvGIMYCCqOf72gAlJBohWjT/INlc2GDQcet7tyfaWoTsGUvKO4Hg==",
+ "requires": {
+ "@readme/oas-extensions": "^4.12.1",
+ "oas": "^1.0.1",
+ "querystring": "^0.2.0"
+ },
+ "dependencies": {
+ "ansi-escapes": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
+ "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4="
+ },
+ "cli-cursor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+ "requires": {
+ "restore-cursor": "^1.0.1"
+ }
+ },
+ "external-editor": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz",
+ "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=",
+ "requires": {
+ "extend": "^3.0.0",
+ "spawn-sync": "^1.0.15",
+ "tmp": "^0.0.29"
+ }
+ },
+ "inquirer": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz",
+ "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=",
+ "requires": {
+ "ansi-escapes": "^1.1.0",
+ "chalk": "^1.0.0",
+ "cli-cursor": "^1.0.1",
+ "cli-width": "^2.0.0",
+ "external-editor": "^1.1.0",
+ "figures": "^1.3.5",
+ "lodash": "^4.3.0",
+ "mute-stream": "0.0.6",
+ "pinkie-promise": "^2.0.0",
+ "run-async": "^2.2.0",
+ "rx": "^4.1.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.0",
+ "through": "^2.3.6"
+ },
+ "dependencies": {
+ "figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+ "requires": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ }
+ }
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+ },
+ "mute-stream": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz",
+ "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s="
+ },
+ "oas": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/oas/-/oas-1.0.1.tgz",
+ "integrity": "sha512-3cKnwLbILqe83pydC24V7dqr3343hsx0zdJbmHcg3tX90//b20B2dC5pQGOSH5BgzdH1S9817PzdUuEjzvIBfA==",
+ "requires": {
+ "cardinal": "^2.1.1",
+ "colors": "^1.1.2",
+ "figures": "^3.0.0",
+ "glob": "^7.1.2",
+ "inquirer": "^1.2.1",
+ "json2yaml": "^1.1.0",
+ "jsonfile": "^2.3.1",
+ "lodash": "^4.17.11",
+ "minimist": "^1.2.0",
+ "node-status": "^1.0.0",
+ "oas-normalize": "0.0.5",
+ "open": "^6.1.0",
+ "prompt-sync": "^4.1.4",
+ "request": "^2.88.0",
+ "swagger-inline": "1.0.5",
+ "uslug": "^1.0.4",
+ "yamljs": "^0.2.8"
+ }
+ },
+ "onetime": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+ "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k="
+ },
+ "restore-cursor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+ "requires": {
+ "exit-hook": "^1.0.0",
+ "onetime": "^1.0.0"
+ }
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "tmp": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz",
+ "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=",
+ "requires": {
+ "os-tmpdir": "~1.0.1"
+ }
+ },
+ "yamljs": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.2.10.tgz",
+ "integrity": "sha1-SBzHwlynOvWfWR8MluPOVsdXpA8=",
+ "requires": {
+ "argparse": "^1.0.7",
+ "glob": "^7.0.5"
+ }
+ }
+ }
+ },
+ "@readme/syntax-highlighter": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@readme/syntax-highlighter/-/syntax-highlighter-4.12.1.tgz",
+ "integrity": "sha512-l9DB8eKP8zr7AO6DTv2sntXyqSTX5o3yVq3YAj60etU0KnD2QqDUFkFmhwovHrE9pBWhB2tXySx2QlVZd9tHDg==",
+ "requires": {
+ "@readme/variable": "^4.12.1",
+ "codemirror": "^5.48.2",
+ "react": "^16.4.2"
+ }
+ },
+ "@readme/variable": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmjs.org/@readme/variable/-/variable-4.12.1.tgz",
+ "integrity": "sha512-q8d/L8uQpmY2TtsIZyt8ZJK2BvN3bAy34n/A4eGB4EIYL1jG913OLuTdkgTRycrZpFojsubhZV5J0sTWPq6UTw==",
+ "requires": {
+ "classnames": "^2.2.6",
+ "prop-types": "^15.7.2",
+ "react": "^16.4.2"
+ }
+ },
"@types/babel__core": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz",
diff --git a/packages/api-explorer/src/AuthBox.jsx b/packages/api-explorer/src/AuthBox.jsx
deleted file mode 100644
index 1caa848e9..000000000
--- a/packages/api-explorer/src/AuthBox.jsx
+++ /dev/null
@@ -1,109 +0,0 @@
-const React = require('react');
-const PropTypes = require('prop-types');
-const classNames = require('classnames');
-const { Operation } = require('oas');
-
-const SecurityInput = require('./SecurityInput');
-
-function Securities({ authInputRef, operation, onChange, oauth, auth, onSubmit }) {
- const securityTypes = operation.prepareSecurity();
- return Object.keys(securityTypes).map(type => {
- const securities = securityTypes[type];
- return (
-
- );
- });
-}
-
-function AuthBox({
- authInputRef,
- operation,
- onSubmit,
- onChange,
- open,
- needsAuth,
- toggle,
- oauth,
- auth,
-}) {
- if (Object.keys(operation.prepareSecurity()).length === 0) return null;
-
- return (
-
- {/* eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid */}
-
-
-
-
-
- {
- e.preventDefault();
- onSubmit();
- }}
- operation={operation}
- />
-
-
-
-
- Authentication is required for this endpoint
-
-
-
-
- );
-}
-
-AuthBox.propTypes = {
- auth: PropTypes.shape({}),
- authInputRef: PropTypes.func,
- needsAuth: PropTypes.bool,
- oauth: PropTypes.bool.isRequired,
- onChange: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
- open: PropTypes.bool,
- operation: PropTypes.instanceOf(Operation).isRequired,
- toggle: PropTypes.func.isRequired,
-};
-
-AuthBox.defaultProps = {
- auth: {},
- authInputRef: () => {},
- needsAuth: false,
- open: false,
-};
-
-module.exports = AuthBox;
diff --git a/packages/api-explorer/src/AuthBox/index.jsx b/packages/api-explorer/src/AuthBox/index.jsx
new file mode 100644
index 000000000..0ad4f3379
--- /dev/null
+++ b/packages/api-explorer/src/AuthBox/index.jsx
@@ -0,0 +1,177 @@
+require('./style.scss');
+
+const React = require('react');
+const PropTypes = require('prop-types');
+const classNames = require('classnames');
+const { Operation } = require('oas');
+
+const SecurityInput = require('../SecurityInput');
+
+function GroupsList({ group, groups, onGroupChange }) {
+ function onSelect({ target }) {
+ onGroupChange(target.value);
+ }
+
+ if (!groups || (groups && groups.length <= 1)) {
+ return false;
+ }
+
+ return (
+
+ );
+}
+
+function Securities({
+ auth,
+ authInputRef,
+ group,
+ groups,
+ oauth,
+ onChange,
+ onGroupChange,
+ onSubmit,
+ operation,
+}) {
+ const securityTypes = operation.prepareSecurity();
+ return (
+
+ {groups && groups.length > 1 && (
+
+
+
+ )}
+ {Object.keys(securityTypes).map(type => {
+ const securities = securityTypes[type];
+ return (
+
+
+ {`${type} Auth`}
+
+
+
+ );
+ })}
+
+ );
+}
+
+function AuthBox({
+ auth,
+ authInputRef,
+ group,
+ groups,
+ needsAuth,
+ oauth,
+ onChange,
+ onGroupChange,
+ onSubmit,
+ open,
+ operation,
+ toggle,
+}) {
+ if (Object.keys(operation.prepareSecurity()).length === 0) return null;
+
+ return (
+
+ {/* eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/anchor-is-valid */}
+
+
+
+
+
+ {
+ e.preventDefault();
+ onSubmit();
+ }}
+ operation={operation}
+ />
+
+
+
+
+ Authentication is required for this endpoint
+
+
+
+
+ );
+}
+
+const commonProps = {
+ group: PropTypes.string,
+ groups: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ }),
+ ),
+ onGroupChange: PropTypes.func.isRequired,
+};
+
+GroupsList.propTypes = {
+ ...commonProps,
+};
+
+AuthBox.propTypes = {
+ ...commonProps,
+ auth: PropTypes.shape({}),
+ authInputRef: PropTypes.func,
+ needsAuth: PropTypes.bool,
+ oauth: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ open: PropTypes.bool,
+ operation: PropTypes.instanceOf(Operation).isRequired,
+ toggle: PropTypes.func.isRequired,
+};
+
+AuthBox.defaultProps = {
+ auth: {},
+ authInputRef: () => {},
+ needsAuth: false,
+ open: false,
+};
+
+Securities.propTypes = { ...AuthBox.propTypes };
+Securities.defaultProps = { ...AuthBox.defaultProps };
+
+module.exports = AuthBox;
diff --git a/packages/api-explorer/src/AuthBox/style.scss b/packages/api-explorer/src/AuthBox/style.scss
new file mode 100644
index 000000000..c9ec38db7
--- /dev/null
+++ b/packages/api-explorer/src/AuthBox/style.scss
@@ -0,0 +1,92 @@
+@mixin AuthBox {
+ $bg: #fbfbfb;
+ $pop: #018ef5;
+ $text: #555;
+ $edge: lighten($text, 57%);
+ $fade: border .3s ease-out;
+ & {
+ display: flex;
+ flex-flow: nowrap column;
+ overflow: hidden;
+ border-radius: 3px;
+ }
+ .GroupsList {
+ display: flex;
+ justify-content: center;
+ padding: .75rem;
+ background: linear-gradient(to right, white 10%, darken($bg, 1.25%) 75%);
+ >select {
+ flex: 1;
+ font: inherit;
+ color: $text;
+ border-color: darken($edge, 5%);
+ outline-color: none;
+ background: #fff;
+ background-color: #fff;
+ &:focus { border-color: $pop }
+ }
+ }
+ details {
+ position: relative;
+ border: solid transparent;
+ margin: -1px 0;
+ border-width: 1px 0;
+ transition: $fade;
+ &:last-child {
+ margin-bottom: -2px;
+ }
+ >summary:first-child {
+ display: flex;
+ align-items: center;
+ color: $text;
+ font-weight: normal;
+ padding: 0 0 0 1rem;
+ border-radius: 0;
+ background: linear-gradient(to right, white 10%, $bg 75%);
+ cursor: pointer;
+ user-select: none;
+ > h3 {
+ flex: 1;
+ margin-bottom: 0;
+ margin-left: .25rem;
+ padding-left: .25rem;
+ padding-right: .25rem;
+ background: transparent;
+ border-top: none;
+ border-color: lighten($edge, 2%) !important;
+ transition: $fade;
+ }
+ &:focus {
+ outline: none;
+ box-shadow:
+ inset 0 0 0 1px $pop,
+ 0 0 0 1px solid $pop;
+ }
+ }
+ &[open] {
+ z-index: 2;
+ border-color: $edge;
+ }
+ &:first-of-type {
+ summary h3 {
+ margin-top: -1px;
+ border-top: 1px solid transparent !important;
+ }
+ &:not([open]) summary h3 {
+ border-top-color: lighten($edge, 2%) !important;
+ }
+ }
+ &:last-of-type:not([open]) summary h3 {
+ border-bottom-color: transparent !important;
+ }
+ }
+ form {
+ input {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.AuthBox {
+ #hub-reference .hub-auth-dropdown & { @include AuthBox }
+}
diff --git a/packages/api-explorer/src/Doc.jsx b/packages/api-explorer/src/Doc.jsx
index 525d47f39..361131089 100644
--- a/packages/api-explorer/src/Doc.jsx
+++ b/packages/api-explorer/src/Doc.jsx
@@ -245,7 +245,7 @@ class Doc extends React.Component {
return (
(this.authInput = el)} // eslint-disable-line no-return-assign
dirty={this.state.dirty}
+ group={this.props.group}
+ groups={this.props.groups}
loading={this.state.loading}
needsAuth={this.state.needsAuth}
oas={this.oas}
oauth={this.props.oauth}
onChange={this.props.onAuthChange}
+ onGroupChange={this.props.onGroupChange}
onSubmit={this.onSubmit}
operation={this.getOperation()}
showAuthBox={this.state.showAuthBox}
@@ -353,7 +356,6 @@ Doc.propTypes = {
}),
auth: PropTypes.shape({}).isRequired,
baseUrl: PropTypes.string,
- changeGroup: PropTypes.func.isRequired,
doc: PropTypes.shape({
api: PropTypes.shape({
examples: PropTypes.shape({
@@ -394,6 +396,7 @@ Doc.propTypes = {
oas: PropTypes.shape({}),
oauth: PropTypes.bool.isRequired,
onAuthChange: PropTypes.func.isRequired,
+ onGroupChange: PropTypes.func.isRequired,
setLanguage: PropTypes.func.isRequired,
suggestedEdits: PropTypes.bool.isRequired,
tryItMetrics: PropTypes.func.isRequired,
diff --git a/packages/api-explorer/src/PathUrl.jsx b/packages/api-explorer/src/PathUrl.jsx
index 4d407021f..962ac6580 100644
--- a/packages/api-explorer/src/PathUrl.jsx
+++ b/packages/api-explorer/src/PathUrl.jsx
@@ -24,18 +24,21 @@ function splitPath(path) {
}
function PathUrl({
- oas,
- operation,
+ auth,
authInputRef,
- loading,
dirty,
+ loading,
+ group,
+ groups,
+ needsAuth,
+ oas,
+ oauth,
onChange,
+ onGroupChange,
+ onSubmit,
+ operation,
showAuthBox,
- needsAuth,
toggleAuth,
- onSubmit,
- oauth,
- auth,
}) {
return (
@@ -46,9 +49,12 @@ function PathUrl({
);
}
diff --git a/packages/api-explorer/src/index.jsx b/packages/api-explorer/src/index.jsx
index d9334d333..30d21c7e4 100644
--- a/packages/api-explorer/src/index.jsx
+++ b/packages/api-explorer/src/index.jsx
@@ -23,16 +23,16 @@ class ApiExplorer extends React.Component {
this.onAuthChange = this.onAuthChange.bind(this);
this.state = {
+ auth: getAuth(this.props.variables.user, this.props.oasFiles),
+ group: this.getGroup(),
language: Cookie.get('readme_language') || this.getDefaultLanguage(),
selectedApp: {
selected: '',
changeSelected: this.changeSelected,
},
- auth: getAuth(this.props.variables.user, this.props.oasFiles),
- group: this.getGroup(),
};
- this.changeGroup = this.changeGroup.bind(this);
+ this.onGroupChange = this.onGroupChange.bind(this);
this.groups =
this.props.variables.user.keys &&
this.props.variables.user.keys.map(key => ({ id: key.id, name: key.name }));
@@ -88,8 +88,24 @@ class ApiExplorer extends React.Component {
return this.props.oasFiles[apiSetting];
}
- changeGroup(group) {
- this.setState({ group });
+ /**
+ * Change the current selected group and refresh the instance auth keys based on that selection.
+ *
+ * @param {string} group
+ */
+ onGroupChange(group) {
+ const { user } = this.props.variables;
+ let groupName = false;
+ if (user.keys) {
+ // We need to remap the incoming group with the groups name so we can pick out the auth
+ // keys in `getAuth`.
+ groupName = user.keys.find(key => key.id === group).name;
+ }
+
+ this.setState({
+ group,
+ auth: getAuth(user, this.props.oasFiles, groupName),
+ });
}
isLazy(index) {
@@ -166,7 +182,6 @@ class ApiExplorer extends React.Component {
appearance={this.props.appearance}
auth={this.state.auth}
baseUrl={this.props.baseUrl.replace(/\/$/, '')}
- changeGroup={this.changeGroup}
doc={doc}
flags={this.props.flags}
group={this.state.group}
@@ -177,6 +192,7 @@ class ApiExplorer extends React.Component {
oas={this.getOas(doc)}
oauth={this.props.oauth}
onAuthChange={this.onAuthChange}
+ onGroupChange={this.onGroupChange}
setLanguage={this.setLanguage}
suggestedEdits={this.props.suggestedEdits}
tryItMetrics={this.props.tryItMetrics}
diff --git a/packages/api-explorer/src/lib/get-auth.js b/packages/api-explorer/src/lib/get-auth.js
index c4df2e622..6f4e322ba 100644
--- a/packages/api-explorer/src/lib/get-auth.js
+++ b/packages/api-explorer/src/lib/get-auth.js
@@ -32,7 +32,7 @@ function getSingle(user, scheme = {}, selectedApp = false) {
return getKey(user, scheme);
}
-function getAuth(user, oasFiles) {
+function getAuth(user, oasFiles, selectedApp = false) {
return Object.keys(oasFiles)
.map(id => {
const oas = oasFiles[id];
@@ -40,16 +40,22 @@ function getAuth(user, oasFiles) {
if (
Object.keys(oas.components || {}).length === 0 ||
Object.keys(oas.components.securitySchemes || {}).length === 0
- )
+ ) {
return {};
+ }
return Object.keys(oas.components.securitySchemes)
.map(scheme => {
return {
- [scheme]: getSingle(user, {
- ...oas.components.securitySchemes[scheme],
- _key: scheme,
- }),
+ [scheme]: getSingle(
+ user,
+ {
+ ...oas.components.securitySchemes[scheme],
+ _key: scheme,
+ selectedApp,
+ },
+ selectedApp,
+ ),
};
})
.reduce((prev, next) => Object.assign(prev, next), {});
diff --git a/packages/api-explorer/src/security-input-types/Basic.jsx b/packages/api-explorer/src/security-input-types/Basic.jsx
index 27bb1a8de..e28843c5b 100644
--- a/packages/api-explorer/src/security-input-types/Basic.jsx
+++ b/packages/api-explorer/src/security-input-types/Basic.jsx
@@ -23,7 +23,7 @@ function Basic({ user, pass, change, authInputRef, Input }) {
inputChange(e.target.name, e.target.value)}
- type="text"
+ type="password"
value={pass}
/>
diff --git a/packages/api-logs/package.json b/packages/api-logs/package.json
index d4a015d5d..a5c041baa 100644
--- a/packages/api-logs/package.json
+++ b/packages/api-logs/package.json
@@ -15,7 +15,7 @@
"pretest": "npm run lint && npm run inspect && npm run prettier",
"prettier": "prettier --list-different --write \"./**/**.{js,jsx}\"",
"test": "jest --coverage --runInBand",
- "watch": "webpack -w"
+ "watch": "webpack -w --progress"
},
"publishConfig": {
"registry": "http://registry.npmjs.org",
diff --git a/scripts/update-example-swagger-files.js b/scripts/update-example-swagger-files.js
index 8af14cbb7..7595ab136 100644
--- a/scripts/update-example-swagger-files.js
+++ b/scripts/update-example-swagger-files.js
@@ -4,20 +4,26 @@ const { readdirSync, writeFileSync } = require('fs');
const dir = join(__dirname, '/../example/swagger-files/');
const files = readdirSync(dir);
-const directory = files.filter(file => file !== 'directory.json').map((file) => {
- // eslint-disable-next-line import/no-dynamic-require, global-require
- const swagger = require(join(dir, file));
+const directory = files
+ .filter(file => file !== 'directory.json' && !file.startsWith('.'))
+ .map(file => {
+ // eslint-disable-next-line import/no-dynamic-require, global-require
+ const swagger = require(join(dir, file));
- return {
- [basename(file)]: {
- preferred: swagger.info.version,
- versions: {
- [swagger.info.version]: {
- swaggerUrl: join('swagger-files', file),
+ return {
+ [basename(file)]: {
+ preferred: swagger.info.version,
+ versions: {
+ [swagger.info.version]: {
+ swaggerUrl: join('swagger-files', file),
+ },
},
},
- },
- };
-}).reduce((prev, next) => Object.assign(prev, next));
+ };
+ })
+ .reduce((prev, next) => Object.assign(prev, next));
-writeFileSync(join(__dirname, '../example/swagger-files/directory.json'), JSON.stringify(directory, null, 2));
+writeFileSync(
+ join(__dirname, '../example/swagger-files/directory.json'),
+ JSON.stringify(directory, null, 2),
+);