diff --git a/components/http/Authorization.vue b/components/http/Authorization.vue index 1139b8a17..c7db46bf9 100644 --- a/components/http/Authorization.vue +++ b/components/http/Authorization.vue @@ -53,9 +53,22 @@ $refs.authTypeOptions.tippy().hide() " /> +
+
- +
+
+ + +
+ +
+
+ {{ $t("helpers.authorization") }} +
+ +
+
@@ -175,6 +206,7 @@ import { computed, defineComponent, Ref, ref } from "@nuxtjs/composition-api" import { HoppRESTAuthBasic, HoppRESTAuthBearer, + HoppRESTAuthOAuth2, } from "~/helpers/types/HoppRESTAuth" import { pluckRef, useStream } from "~/helpers/utils/composables" import { restAuth$, setRESTAuth } from "~/newstore/RESTSession" @@ -192,6 +224,7 @@ export default defineComponent({ const authName = computed(() => { if (authType.value === "basic") return "Basic Auth" else if (authType.value === "bearer") return "Bearer" + else if (authType.value === "oauth-2") return "OAuth 2.0" else return "None" }) const authActive = pluckRef(auth, "authActive") @@ -201,6 +234,8 @@ export default defineComponent({ const bearerToken = pluckRef(auth as Ref, "token") + const oauth2Token = pluckRef(auth as Ref, "token") + const URLExcludes = useSetting("URL_EXCLUDES") const passwordFieldType = ref("password") @@ -226,6 +261,7 @@ export default defineComponent({ basicUsername, basicPassword, bearerToken, + oauth2Token, URLExcludes, passwordFieldType, clearContent, diff --git a/components/http/OAuth2Authorization.vue b/components/http/OAuth2Authorization.vue new file mode 100644 index 000000000..a91f1fe56 --- /dev/null +++ b/components/http/OAuth2Authorization.vue @@ -0,0 +1,136 @@ + + + diff --git a/helpers/oauth.js b/helpers/oauth.js index 019536f22..6a317d4d2 100644 --- a/helpers/oauth.js +++ b/helpers/oauth.js @@ -198,7 +198,7 @@ const tokenRequest = async ({ * Handle the redirect back from the authorization server and * get an access token from the token endpoint * - * @returns {Object} + * @returns {Promise} */ const oauthRedirect = () => { @@ -213,6 +213,7 @@ const oauthRedirect = () => { // Verify state matches what we set at the beginning if (getLocalConfig("pkce_state") !== q.state) { alert("Invalid state") + Promise.reject(tokenResponse) } else { try { // Exchange the authorization code for an access token @@ -225,6 +226,7 @@ const oauthRedirect = () => { }) } catch (e) { console.error(e) + return Promise.reject(tokenResponse) } } // Clean these up since we don't need them anymore @@ -234,7 +236,7 @@ const oauthRedirect = () => { removeLocalConfig("client_id") return tokenResponse } - return tokenResponse + return Promise.reject(tokenResponse) } export { tokenRequest, oauthRedirect } diff --git a/helpers/types/HoppRESTAuth.ts b/helpers/types/HoppRESTAuth.ts index 3076c9d3c..8cbcd92ac 100644 --- a/helpers/types/HoppRESTAuth.ts +++ b/helpers/types/HoppRESTAuth.ts @@ -15,8 +15,20 @@ export type HoppRESTAuthBearer = { token: string } +export type HoppRESTAuthOAuth2 = { + authType: "oauth-2" + + token: string + oidcDiscoveryURL: string + authURL: string + accessTokenURL: string + clientID: string + scope: string +} + export type HoppRESTAuth = { authActive: boolean } & ( | HoppRESTAuthNone | HoppRESTAuthBasic | HoppRESTAuthBearer + | HoppRESTAuthOAuth2 ) diff --git a/helpers/utils/EffectiveURL.ts b/helpers/utils/EffectiveURL.ts index e97466837..b81ed5e0f 100644 --- a/helpers/utils/EffectiveURL.ts +++ b/helpers/utils/EffectiveURL.ts @@ -87,7 +87,10 @@ export function getEffectiveRESTRequest( `${request.auth.username}:${request.auth.password}` )}`, }) - } else if (request.auth.authType === "bearer") { + } else if ( + request.auth.authType === "bearer" || + request.auth.authType === "oauth-2" + ) { effectiveFinalHeaders.push({ active: true, key: "Authorization", diff --git a/newstore/settings.ts b/newstore/settings.ts index b3c32168a..969d61271 100644 --- a/newstore/settings.ts +++ b/newstore/settings.ts @@ -43,6 +43,7 @@ export type SettingsType = { httpUser: boolean httpPassword: boolean bearerToken: boolean + oauth2Token: boolean } THEME_COLOR: HoppAccentColor BG_COLOR: HoppBgColor @@ -68,6 +69,7 @@ export const defaultSettings: SettingsType = { httpUser: true, httpPassword: true, bearerToken: true, + oauth2Token: true, }, THEME_COLOR: "blue", BG_COLOR: "system", diff --git a/pages/index.vue b/pages/index.vue index 6621c124f..746934677 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -84,8 +84,10 @@ import { computed, defineComponent, getCurrentInstance, + onBeforeMount, onBeforeUnmount, onMounted, + Ref, ref, useContext, watch, @@ -94,6 +96,7 @@ import { Splitpanes, Pane } from "splitpanes" import "splitpanes/dist/splitpanes.css" import { map } from "rxjs/operators" import { Subscription } from "rxjs" +import isEqual from "lodash/isEqual" import { useSetting } from "~/newstore/settings" import { restRequest$, @@ -101,9 +104,12 @@ import { restActiveHeadersCount$, getRESTRequest, setRESTRequest, + setRESTAuth, + restAuth$, } from "~/newstore/RESTSession" import { translateExtURLParams } from "~/helpers/RESTExtURLParams" import { + pluckRef, useReadonlyStream, useStream, useStreamSubscriber, @@ -111,6 +117,8 @@ import { import { loadRequestFromSync, startRequestSync } from "~/helpers/fb/request" import { onLoggedIn } from "~/helpers/fb/auth" import { HoppRESTRequest } from "~/helpers/types/HoppRESTRequest" +import { oauthRedirect } from "~/helpers/oauth" +import { HoppRESTAuthOAuth2 } from "~/helpers/types/HoppRESTAuth" function bindRequestToURLParams() { const { @@ -159,12 +167,36 @@ function bindRequestToURLParams() { onMounted(() => { const query = route.value.query - if (Object.keys(query).length === 0) return + // If query params are empty, or contains code or error param (these are from Oauth Redirect) + // We skip URL params parsing + if (Object.keys(query).length === 0 || query.code || query.error) return setRESTRequest(translateExtURLParams(query)) }) } -function setupRequestSync() { +function oAuthURL() { + const auth = useStream( + restAuth$, + { authType: "none", authActive: true }, + setRESTAuth + ) + + const oauth2Token = pluckRef(auth as Ref, "token") + + onBeforeMount(async () => { + const tokenInfo = await oauthRedirect() + if (Object.prototype.hasOwnProperty.call(tokenInfo, "access_token")) { + if (typeof tokenInfo === "object") { + oauth2Token.value = tokenInfo.access_token + } + } + }) +} + +function setupRequestSync( + confirmSync: Ref, + requestForSync: Ref +) { const { route } = useContext() // Subscription to request sync @@ -172,13 +204,18 @@ function setupRequestSync() { // Load request on login resolve and start sync onLoggedIn(async () => { - if (Object.keys(route.value.query).length === 0) { + if ( + Object.keys(route.value.query).length === 0 && + !(route.value.query.code || route.value.query.error) + ) { const request = await loadRequestFromSync() if (request) { console.log("sync le request nnd") - - setRESTRequest(request) - // confirmSync.value = true + // setRESTRequest(request) + if (!isEqual(request, getRESTRequest())) { + requestForSync.value = request + confirmSync.value = true + } } } @@ -194,19 +231,21 @@ function setupRequestSync() { export default defineComponent({ components: { Splitpanes, Pane }, setup() { + const requestForSync = ref(null) + const confirmSync = ref(false) const internalInstance = getCurrentInstance() console.log("yoo", internalInstance) - const syncRequest = (request: HoppRESTRequest) => { + const syncRequest = () => { console.log("syncinggg") - setRESTRequest(request) + setRESTRequest(requestForSync.value!) } const { subscribeToStream } = useStreamSubscriber() - setupRequestSync() + setupRequestSync(confirmSync, requestForSync) bindRequestToURLParams() subscribeToStream(restRequest$, (x) => { @@ -238,6 +277,8 @@ export default defineComponent({ EXPERIMENTAL_URL_BAR_ENABLED: useSetting("EXPERIMENTAL_URL_BAR_ENABLED"), confirmSync, syncRequest, + oAuthURL, + requestForSync, } }, })