Skip to content

Commit

Permalink
feat(swagger): handle redirects from OIDC IdP and initialize swagger (#…
Browse files Browse the repository at this point in the history
…467)

Co-authored-by: Devon Langendoerfer <[email protected]>
  • Loading branch information
andrewwylde and 425devon authored Mar 22, 2024
1 parent 64f4b17 commit 27a830f
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 22 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"@kong-ui-public/analytics-utilities": "0.10.1",
"@kong-ui-public/copy-uuid": "1.1.5",
"@kong-ui-public/document-viewer": "2.0.16",
"@kong-ui-public/spec-renderer": "2.1.0",
"@kong-ui-public/spec-renderer": "2.1.5",
"@kong/kong-auth-elements": "2.11.1",
"@kong/kongponents": "8.127.0",
"@kong/sdk-portal-js": "2.10.0",
Expand Down
8 changes: 8 additions & 0 deletions src/locales/ca_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export const ca_ES: I18nType = {
resetPasswordTitle: 'Restablir la contrasenya',
catalogTitleProduct: 'Catàleg de productes',
specTitle: "Especificació de l'API",
oauth2RedirectTitle: translationNeeded(en.router.oauth2RedirectTitle),
docsTitle: "Documentació de l'API",
appsTitle: 'Les meves aplicacions',
createAppTitle: 'Crear nova aplicació',
Expand All @@ -338,5 +339,12 @@ export const ca_ES: I18nType = {
notFoundTitle: 'No trobat',
forbiddenTitle: 'Prohibit',
errorTitle: 'Error'
},
oauth2: {
dataNotFound: translationNeeded(en.oauth2.authMaybeUnsafe),
noDescription: translationNeeded(en.oauth2.noDescription),
moreInfo: translationNeeded(en.oauth2.moreInfo),
authMaybeUnsafe: translationNeeded(en.oauth2.authMaybeUnsafe),
defaultError: translationNeeded(en.oauth2.defaultError)
}
}
8 changes: 8 additions & 0 deletions src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export const de: I18nType = {
resetPasswordTitle: 'Passwort zurücksetzen',
catalogTitleProduct: 'Produktkatalog',
specTitle: 'API Spezifikation',
oauth2RedirectTitle: translationNeeded(en.router.oauth2RedirectTitle),
docsTitle: 'API Dokumentation',
appsTitle: 'Meine Applikationen',
createAppTitle: 'Neue Applikation anlegen',
Expand All @@ -338,5 +339,12 @@ export const de: I18nType = {
notFoundTitle: 'Nicht gefunden',
forbiddenTitle: 'Zugriff verweigert',
errorTitle: 'Fehler'
},
oauth2: {
dataNotFound: translationNeeded(en.oauth2.dataNotFound),
moreInfo: translationNeeded(en.oauth2.moreInfo),
noDescription: translationNeeded(en.oauth2.noDescription),
authMaybeUnsafe: translationNeeded(en.oauth2.authMaybeUnsafe),
defaultError: translationNeeded(en.oauth2.defaultError)
}
}
8 changes: 8 additions & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export const en = {
resetPasswordTitle: 'Reset Password',
catalogTitleProduct: 'Product Catalog',
specTitle: 'API Spec',
oauth2RedirectTitle: 'OAuth2 Authorization',
docsTitle: 'API Docs',
appsTitle: 'My Apps',
createAppTitle: 'Create New Application',
Expand All @@ -334,5 +335,12 @@ export const en = {
notFoundTitle: 'Not Found',
forbiddenTitle: 'Forbidden',
errorTitle: 'Error'
},
oauth2: {
dataNotFound: 'OAuth data not found',
noDescription: 'No description',
moreInfo: 'More info: ',
authMaybeUnsafe: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server",
defaultError: '[Authorization failed]: no accessCode received from the server'
}
}
8 changes: 8 additions & 0 deletions src/locales/es_ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export const es_ES: I18nType = {
resetPasswordTitle: 'Restablecer contraseña',
catalogTitleProduct: 'Catálogo de productos',
specTitle: 'Especificación de la API',
oauth2RedirectTitle: translationNeeded(en.router.oauth2RedirectTitle),
docsTitle: 'Documentación de la API',
appsTitle: 'Mis aplicacione',
createAppTitle: 'Crear nueva aplicación',
Expand All @@ -338,5 +339,12 @@ export const es_ES: I18nType = {
notFoundTitle: 'No encontrado',
forbiddenTitle: 'Prohibido',
errorTitle: 'Error'
},
oauth2: {
authMaybeUnsafe: translationNeeded(en.oauth2.authMaybeUnsafe),
defaultError: translationNeeded(en.oauth2.defaultError),
dataNotFound: translationNeeded(en.oauth2.dataNotFound),
moreInfo: translationNeeded(en.oauth2.moreInfo),
noDescription: translationNeeded(en.oauth2.noDescription)
}
}
8 changes: 8 additions & 0 deletions src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ export const fr: I18nType = {
resetPasswordTitle: 'Réinitialisation du mot de passe',
catalogTitleProduct: 'Catalogue des produits',
specTitle: 'Spécification de l\'API',
oauth2RedirectTitle: translationNeeded(en.router.oauth2RedirectTitle),
docsTitle: 'Documentation de l\'API',
appsTitle: 'Mes applications',
createAppTitle: 'Créer une nouvelle application',
Expand All @@ -338,5 +339,12 @@ export const fr: I18nType = {
notFoundTitle: 'Page non trouvée',
forbiddenTitle: 'Accès interdit',
errorTitle: 'Erreur'
},
oauth2: {
authMaybeUnsafe: translationNeeded(en.oauth2.authMaybeUnsafe),
defaultError: translationNeeded(en.oauth2.defaultError),
dataNotFound: translationNeeded(en.oauth2.dataNotFound),
moreInfo: translationNeeded(en.oauth2.moreInfo),
noDescription: translationNeeded(en.oauth2.noDescription)
}
}
8 changes: 8 additions & 0 deletions src/locales/i18n-type.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ export interface I18nType {
resetPasswordTitle: string;
catalogTitleProduct: string;
specTitle: string;
oauth2RedirectTitle: string;
docsTitle: string;
appsTitle: string;
createAppTitle: string;
Expand All @@ -335,4 +336,11 @@ export interface I18nType {
forbiddenTitle: string;
errorTitle: string;
};
oauth2: {
dataNotFound: string;
noDescription: string;
moreInfo: string;
authMaybeUnsafe: string;
defaultError: string;
}
}
8 changes: 8 additions & 0 deletions src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export const portalRouter = () => {
},
component: () => import('../views/Spec.vue')
},
{
path: '/spec/:product/oauth2-redirect.html',
name: 'oauth2-redirect',
component: () => import('../views/OAuth2Redirect.vue'),
meta: {
title: helpText.oauth2RedirectTitle
}
},
{
path: '/docs/:product/:slug*',
name: 'api-documentation-page',
Expand Down
118 changes: 118 additions & 0 deletions src/views/OAuth2Redirect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<template>
<KSkeleton
:rows="5"
:columns="5"
:width="100"
:height="20"
/>
</template>

<script lang="ts" setup>
import { useI18nStore } from '@/stores'
import { onMounted } from 'vue'
import { LocationQuery, useRoute } from 'vue-router'
interface OAuth2 {
state: string;
redirectUrl: string;
auth: {
name: string;
schema: Map<string, string>;
code?: string;
};
callback: Function;
errCb: Function;
}
const helpText = useI18nStore().state.helpText.oauth2
const route = useRoute()
const parseRouteData = (routeData: string | LocationQuery): Record<string, string> => {
if (!routeData) return {}
// handle locationQuery
if (typeof routeData === 'object') {
// decode any URI components:
const routeDataDecoded = Object.keys(routeData).reduce((acc, key) => {
acc[key] = decodeURIComponent((routeData[key] && routeData[key].toString()) || '')
return acc
}, {})
return routeDataDecoded
}
const data = routeData.startsWith('#') ? routeData.substring(1) : routeData
const pairs = data.split('&')
const result = {}
pairs.forEach(pair => {
const [key, value] = pair.split('=')
result[key] = decodeURIComponent(value || '')
})
return result
}
const handleOAuthRedirect = () => {
const oauth2 = window.opener ? (window.opener as any).swaggerUIRedirectOauth2 as OAuth2 : null
if (!oauth2) {
console.error(helpText.dataNotFound)
return
}
const sentState = oauth2.state
const redirectUrl = oauth2.redirectUrl
const params = {
...parseRouteData(route.query),
...parseRouteData(route.hash)
}
const isValid = params.state === sentState
if (isAuthorizationCodeFlow(oauth2) && !oauth2.auth.code) {
if (!isValid) {
reportError(oauth2, helpText.authMaybeUnsafe, 'warning')
}
if (params.code) {
delete oauth2.state
oauth2.auth.code = params.code.toString()
oauth2.callback({ auth: oauth2.auth, redirectUrl })
} else {
reportError(oauth2, helpText.defaultError, 'error', params)
}
} else {
oauth2.callback({ auth: oauth2.auth, token: params, isValid, redirectUrl })
}
window.close()
}
const isAuthorizationCodeFlow = (oauth2: OAuth2): boolean => {
const flow = oauth2.auth.schema.get('flow')
return flow === 'accessCode' || flow === 'authorizationCode' || flow === 'authorization_code'
}
const reportError = (oauth2: OAuth2, message: string, level: string, errorParams: Record<string, string> = {}): void => {
const error = {
authId: oauth2.auth.name,
source: 'auth',
level,
message
}
if (errorParams.error) {
error.message += ` [${errorParams.error}]: ${errorParams.error_description || helpText.noDescription} ${errorParams.error_uri ? helpText.moreInfo + errorParams.error_uri : ''}`
}
oauth2.errCb(error)
}
onMounted(() => {
handleOAuthRedirect()
})
</script>
19 changes: 19 additions & 0 deletions src/views/Spec.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@

<SpecDetails
v-else-if="spec"
ref="specDetailsRef"
class="w-100"
:document="spec"
:has-sidebar="false"
Expand Down Expand Up @@ -235,6 +236,19 @@ export default defineComponent({
const $route = useRoute()
const { portalApiV2 } = usePortalApi()
const specDetailsRef = ref(null)
watch(() => specDetailsRef.value, (newValue, oldValue) => {
if (newValue && newValue !== oldValue) {
newValue.swaggerInstance.instance.initOAuth({
usePkceWithAuthorizationCodeGrant: true,
additionalQueryStringParams: {
nonce: Math.random().toString(36).substring(7)
}
})
}
})
// fallback in case the operations are loaded in after the spec.
watch(() => sidebarOperations.value, async () => {
if (sidebarOperations.value?.length) {
Expand Down Expand Up @@ -541,6 +555,7 @@ export default defineComponent({
}
return {
specDetailsRef,
authMethodLabelObj,
helpText,
viewSpecModalIsVisible,
Expand Down Expand Up @@ -643,5 +658,9 @@ export default defineComponent({
border: 1px solid transparent;
color: var(--button_colors-primary-text, #fff);
}
.swagger-ui .auth-container .errors {
word-wrap: break-word;
}
}
</style>
Loading

0 comments on commit 27a830f

Please sign in to comment.