diff --git a/Dockerfile b/Dockerfile index 7dc8da41..c05e0963 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,7 +46,10 @@ ENV VITE_PORTAL_SERVER_URL=$VITE_PORTAL_SERVER_URL \ VITE_APIS_IMAGE_URL=$VITE_APIS_IMAGE_URL \ VITE_LOGO_IMAGE_URL=$VITE_LOGO_IMAGE_URL \ VITE_COMPANY_NAME=$VITE_COMPANY_NAME \ - VITE_CUSTOM_PAGES=$VITE_CUSTOM_PAGES + VITE_CUSTOM_PAGES=$VITE_CUSTOM_PAGES \ + VITE_SWAGGER_PREFILL_API_KEY=$VITE_SWAGGER_PREFILL_API_KEY \ + VITE_SWAGGER_PREFILL_OAUTH=$VITE_SWAGGER_PREFILL_OAUTH \ + VITE_SWAGGER_PREFILL_BASIC=$VITE_SWAGGER_PREFILL_BASIC # Copy the server files, (this includes the UI build). WORKDIR /app @@ -72,4 +75,7 @@ ENTRYPOINT VITE_PORTAL_SERVER_URL=$VITE_PORTAL_SERVER_URL \ VITE_LOGO_IMAGE_URL=$VITE_LOGO_IMAGE_URL \ VITE_COMPANY_NAME=$VITE_COMPANY_NAME \ VITE_CUSTOM_PAGES=$VITE_CUSTOM_PAGES \ + VITE_SWAGGER_PREFILL_API_KEY=$VITE_SWAGGER_PREFILL_API_KEY \ + VITE_SWAGGER_PREFILL_OAUTH=$VITE_SWAGGER_PREFILL_OAUTH \ + VITE_SWAGGER_PREFILL_BASIC=$VITE_SWAGGER_PREFILL_BASIC \ node ./bin/www diff --git a/Makefile b/Makefile index eae3def5..bd89da9b 100644 --- a/Makefile +++ b/Makefile @@ -106,11 +106,32 @@ else ifneq ($(COMPANY_NAME),) endif # # CUSTOM PAGES -ifneq ($(VITE_CUSTOM PAGES),) - UI_ARGS += VITE_CUSTOM PAGES=$(VITE_CUSTOM_PAGES) +ifneq ($(VITE_CUSTOM_PAGES),) + UI_ARGS += VITE_CUSTOM_PAGES=$(VITE_CUSTOM_PAGES) else ifneq ($(CUSTOM_PAGES),) UI_ARGS += VITE_CUSTOM_PAGES=$(CUSTOM_PAGES) endif +# +# SWAGGER_PREFILL_API_KEY +ifneq ($(VITE_SWAGGER_PREFILL_API_KEY),) + UI_ARGS += VITE_SWAGGER_PREFILL_API_KEY=$(VITE_SWAGGER_PREFILL_API_KEY) +else ifneq ($(SWAGGER_PREFILL_API_KEY),) + UI_ARGS += VITE_SWAGGER_PREFILL_API_KEY=$(SWAGGER_PREFILL_API_KEY) +endif +# +# SWAGGER_PREFILL_OAUTH +ifneq ($(VITE_SWAGGER_PREFILL_OAUTH),) + UI_ARGS += VITE_SWAGGER_PREFILL_OAUTH=$(VITE_SWAGGER_PREFILL_OAUTH) +else ifneq ($(SWAGGER_PREFILL_OAUTH),) + UI_ARGS += VITE_SWAGGER_PREFILL_OAUTH=$(SWAGGER_PREFILL_OAUTH) +endif +# +# SWAGGER_PREFILL_BASIC +ifneq ($(VITE_SWAGGER_PREFILL_BASIC),) + UI_ARGS += VITE_SWAGGER_PREFILL_BASIC=$(VITE_SWAGGER_PREFILL_BASIC) +else ifneq ($(SWAGGER_PREFILL_BASIC),) + UI_ARGS += VITE_SWAGGER_PREFILL_BASIC=$(SWAGGER_PREFILL_BASIC) +endif diff --git a/README.md b/README.md index cb201135..3244cf89 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,13 @@ You can add these environment variables to a `.env.local` file in the `projects/ When the website is opened, there should be two new pages in the top navigation bar. ![custom pages example](readme_assets/custom-pages-navbar.png) The custom page's `path` property must be publicly accessible and end with `.md` or `.html`. +- `VITE_SWAGGER_PREFILL_API_KEY` - Prefills the Swagger UI authorization configuration for an API key or Bearer authorization scheme with the specified values. This can be set using the following format: `'["authDefinitionKey", "apiKeyValue"]'`, where "authDefinitionKey" is the key name of the security scheme to use from the API definition. In case of OpenAPI 3.0 Bearer scheme, `apiKeyValue` must contain just the token itself without the Bearer prefix. To use the logged in user's authorization token for the `apiKeyValue`, you may use the following syntax: `'["authDefinitionKey", "{{USER_TOKEN}}"]'`. +- `VITE_SWAGGER_PREFILL_OAUTH` - Prefills the Swagger UI authorization configuration for an OAuth server. This variable should be set to a serialized JSON object that is the OAuth2 configuration. See the [Swagger UI OAuth2 documentation](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/oauth2.md) for more information. + - Converting the example object from the Swagger UI documentation to a string would result in the following: + ``` + VITE_SWAGGER_PREFILL_OAUTH='{"clientId": "your-client-id","clientSecret": "your-client-secret-if-required","realm": "your-realms","appName": "your-app-name","scopeSeparator": " ","scopes": "openid profile","additionalQueryStringParams": {"test": "hello"},"useBasicAuthenticationWithAccessCodeGrant": true,"usePkceWithAuthorizationCodeGrant": true}' + ``` +- `VITE_SWAGGER_PREFILL_BASIC` - Prefills the Swagger UI authorization configuration for a Basic authorization scheme. This can be set using the following format: `'["authDefinitionKey", "username", "password"]'`. #### Environment Variables for PKCE Authorization Flow diff --git a/changelog/v0.0.36/swagger-ui-prefill-token.yaml b/changelog/v0.0.36/swagger-ui-prefill-token.yaml new file mode 100644 index 00000000..4c8ca379 --- /dev/null +++ b/changelog/v0.0.36/swagger-ui-prefill-token.yaml @@ -0,0 +1,7 @@ +changelog: + - type: FIX + issueLink: https://github.com/solo-io/solo-projects/issues/6859 + description: >- + Adds configuration options to pass through to Swagger UI's instance methods + as defined here: https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md#instance-methods + Including options to fill the Authorization Bearer token if the user is logged in. diff --git a/projects/ui/package.json b/projects/ui/package.json index dbed14df..b8f97f89 100644 --- a/projects/ui/package.json +++ b/projects/ui/package.json @@ -1,5 +1,5 @@ { - "name": "gloo-platform-portal-ui", + "name": "dev-portal-starter", "private": true, "version": "0.0.13", "type": "module", diff --git a/projects/ui/src/Components/ApiDetails/shared/ApiSchema/swagger/SwaggerDisplay.tsx b/projects/ui/src/Components/ApiDetails/shared/ApiSchema/swagger/SwaggerDisplay.tsx index 653bd9a0..374e6af7 100644 --- a/projects/ui/src/Components/ApiDetails/shared/ApiSchema/swagger/SwaggerDisplay.tsx +++ b/projects/ui/src/Components/ApiDetails/shared/ApiSchema/swagger/SwaggerDisplay.tsx @@ -1,8 +1,14 @@ -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import SwaggerUIConstructor from "swagger-ui"; import "swagger-ui/dist/swagger-ui.css"; import { ApiVersionSchema } from "../../../../../Apis/api-types"; -import { swaggerConfigURL } from "../../../../../user_variables.tmplr"; +import { AuthContext } from "../../../../../Context/AuthContext"; +import { + swaggerConfigURL, + swaggerPrefillApiKey, + swaggerPrefillBasic, + swaggerPrefillOauth, +} from "../../../../../user_variables.tmplr"; import { SwaggerDisplayContainer } from "./SwaggerDisplay.style"; const sanitize = (id: string) => id.replaceAll(".", "-"); @@ -14,6 +20,8 @@ export function SwaggerDisplay({ apiVersionSpec: ApiVersionSchema | undefined; apiVersionId: string; }) { + const { tokensResponse } = useContext(AuthContext); + // The sanitized dom_id, where all periods are replaced with dashes. This fixes issues where Swagger tries // doing a `querySelector` which fails, due to it treating the period as a class selector, and not part of the ID itself. const [sanitizedDomId, setSanitizedDomId] = useState( @@ -27,13 +35,47 @@ export function SwaggerDisplay({ }, [apiVersionId, sanitizedDomId]); useEffect(() => { - SwaggerUIConstructor({ + const swaggerInstance = SwaggerUIConstructor({ spec: apiVersionSpec, dom_id: `#display-swagger-${sanitizedDomId}`, withCredentials: true, deepLinking: true, configUrl: swaggerConfigURL !== "" ? swaggerConfigURL : undefined, }); + + // Here we pass through user supplied configuration for each of these Swagger UI instance methods: + // https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md#instance-methods + + // API KEY AUTH + if (swaggerPrefillApiKey !== undefined) { + let apiKeyValue = swaggerPrefillApiKey.apiKeyValue; + if (!!tokensResponse?.access_token) { + // Try to find & replace the "{{USER_TOKEN}}" string with this user's access token. + // This is documented in the README.md. + apiKeyValue = apiKeyValue.replace( + "{{USER_TOKEN}}", + tokensResponse.access_token + ); + } + swaggerInstance.preauthorizeApiKey( + swaggerPrefillApiKey.authDefinitionKey, + apiKeyValue + ); + } + + // OAUTH + if (swaggerPrefillOauth !== undefined) { + swaggerInstance.initOAuth(swaggerPrefillOauth); + } + + // BASIC AUTH + if (swaggerPrefillBasic !== undefined) { + swaggerInstance.preauthorizeBasic( + swaggerPrefillBasic.authDefinitionKey, + swaggerPrefillBasic.username, + swaggerPrefillBasic.password + ); + } }, [sanitizedDomId, apiVersionSpec]); return ( diff --git a/projects/ui/src/user_variables.tmplr.ts b/projects/ui/src/user_variables.tmplr.ts index f188ec16..e771d198 100644 --- a/projects/ui/src/user_variables.tmplr.ts +++ b/projects/ui/src/user_variables.tmplr.ts @@ -190,3 +190,59 @@ export const customPages = JSON.parse( ) as Array; // TODO: Check the paths and if any overlap with the dev-portal-starter. // console.log("Loaded custom pages", customPages); + +/** + * This is optional. Check the README for usage. + */ +export const swaggerPrefillApiKey = (() => { + const parsed = JSON.parse( + templateString( + "{{ tmplr.swaggerPrefillApiKey }}", + insertedEnvironmentVariables?.VITE_SWAGGER_PREFILL_API_KEY, + import.meta.env.VITE_SWAGGER_PREFILL_API_KEY, + "[]" + ) + ) as [string, string] | []; + return parsed.length === 2 + ? { + authDefinitionKey: parsed[0], + apiKeyValue: parsed[1], + } + : undefined; +})(); + +/** + * This is optional. Check the README for usage. + */ +export const swaggerPrefillOauth = (() => { + const parsed = JSON.parse( + templateString( + "{{ tmplr.swaggerPrefillOauth }}", + insertedEnvironmentVariables?.VITE_SWAGGER_PREFILL_OAUTH, + import.meta.env.VITE_SWAGGER_PREFILL_OAUTH, + "{}" + ) + ); + return Object.keys(parsed).length > 0 ? parsed : undefined; +})(); + +/** + * This is optional. Check the README for usage. + */ +export const swaggerPrefillBasic = (() => { + const parsed = JSON.parse( + templateString( + "{{ tmplr.swaggerPrefillBasic }}", + insertedEnvironmentVariables?.VITE_SWAGGER_PREFILL_BASIC, + import.meta.env.VITE_SWAGGER_PREFILL_BASIC, + "[]" + ) + ) as [string, string, string] | []; + return parsed.length === 3 + ? { + authDefinitionKey: parsed[0], + username: parsed[1], + password: parsed[2], + } + : undefined; +})();