feat: dynamically load enabled auth providers (#3646)

This commit is contained in:
Akash K
2023-12-13 23:38:21 +05:30
committed by GitHub
parent 47e009267b
commit 5209c0a8ca
14 changed files with 159 additions and 103 deletions

View File

@@ -308,7 +308,8 @@
"proxy_error": "Proxy error", "proxy_error": "Proxy error",
"script_fail": "Could not execute pre-request script", "script_fail": "Could not execute pre-request script",
"something_went_wrong": "Something went wrong", "something_went_wrong": "Something went wrong",
"test_script_fail": "Could not execute post-request script" "test_script_fail": "Could not execute post-request script",
"authproviders_load_error": "Unable to load auth providers"
}, },
"export": { "export": {
"as_json": "Export as JSON", "as_json": "Export as JSON",

View File

@@ -1,7 +1,7 @@
<template> <template>
<AppShortcuts :show="showShortcuts" @close="showShortcuts = false" /> <AppShortcuts :show="showShortcuts" @close="showShortcuts = false" />
<AppShare :show="showShare" @hide-modal="showShare = false" /> <AppShare :show="showShare" @hide-modal="showShare = false" />
<FirebaseLogin :show="showLogin" @hide-modal="showLogin = false" /> <FirebaseLogin v-if="showLogin" @hide-modal="showLogin = false" />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@@ -1,12 +1,18 @@
<template> <template>
<HoppSmartModal <HoppSmartModal
v-if="show"
dialog dialog
:title="`${t('auth.login_to_hoppscotch')}`" :title="`${t('auth.login_to_hoppscotch')}`"
styles="sm:max-w-md" styles="sm:max-w-md"
@close="hideModal" @close="hideModal"
> >
<template #body> <template #body>
<template v-if="isLoadingAllowedAuthProviders">
<div class="flex justify-center">
<HoppSmartSpinner />
</div>
</template>
<template v-else>
<div v-if="mode === 'sign-in'" class="flex flex-col space-y-2"> <div v-if="mode === 'sign-in'" class="flex flex-col space-y-2">
<HoppSmartItem <HoppSmartItem
v-for="provider in allowedAuthProviders" v-for="provider in allowedAuthProviders"
@@ -60,6 +66,7 @@
</div> </div>
</div> </div>
</template> </template>
</template>
<template #footer> <template #footer>
<div <div
v-if="mode === 'sign-in' && tosLink && privacyPolicyLink" v-if="mode === 'sign-in' && tosLink && privacyPolicyLink"
@@ -109,7 +116,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Ref, computed, onMounted, ref } from "vue" import { Ref, onMounted, ref } from "vue"
import { useI18n } from "@composables/i18n" import { useI18n } from "@composables/i18n"
import { useStreamSubscriber } from "@composables/stream" import { useStreamSubscriber } from "@composables/stream"
@@ -127,9 +134,7 @@ import { useService } from "dioc/vue"
import { LoginItemDef } from "~/platform/auth" import { LoginItemDef } from "~/platform/auth"
import { PersistenceService } from "~/services/persistence" import { PersistenceService } from "~/services/persistence"
defineProps<{ import * as E from "fp-ts/Either"
show: boolean
}>()
const emit = defineEmits<{ const emit = defineEmits<{
(e: "hide-modal"): void (e: "hide-modal"): void
@@ -145,6 +150,8 @@ const form = {
email: "", email: "",
} }
const isLoadingAllowedAuthProviders = ref(true)
const signingInWithGoogle = ref(false) const signingInWithGoogle = ref(false)
const signingInWithGitHub = ref(false) const signingInWithGitHub = ref(false)
const signingInWithMicrosoft = ref(false) const signingInWithMicrosoft = ref(false)
@@ -162,21 +169,42 @@ type AuthProviderItem = {
isLoading: Ref<boolean> isLoading: Ref<boolean>
} }
const additonalLoginItems = computed( let allowedAuthProviders: AuthProviderItem[] = []
() => platform.auth.additionalLoginItems ?? [] let additonalLoginItems: LoginItemDef[] = []
)
const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => { const doAdditionalLoginItemClickAction = async (item: LoginItemDef) => {
await item.onClick() await item.onClick()
emit("hide-modal") emit("hide-modal")
} }
onMounted(() => { onMounted(async () => {
const currentUser$ = platform.auth.getCurrentUserStream() const currentUser$ = platform.auth.getCurrentUserStream()
subscribeToStream(currentUser$, (user) => { subscribeToStream(currentUser$, (user) => {
if (user) hideModal() if (user) hideModal()
}) })
const res = await platform.auth.getAllowedAuthProviders()
if (E.isLeft(res)) {
toast.error(`${t("error.authproviders_load_error")}`)
isLoadingAllowedAuthProviders.value = false
return
}
// setup the normal auth providers
const enabledAuthProviders = authProvidersAvailable.filter((provider) =>
res.right.includes(provider.id)
)
allowedAuthProviders = enabledAuthProviders
// setup the additional login items
additonalLoginItems =
platform.auth.additionalLoginItems?.filter((item) =>
res.right.includes(item.id)
) ?? []
isLoadingAllowedAuthProviders.value = false
}) })
const showLoginSuccess = () => { const showLoginSuccess = () => {
@@ -275,14 +303,7 @@ const signInWithEmail = async () => {
}) })
} }
const hideModal = () => { const authProvidersAvailable: AuthProviderItem[] = [
mode.value = "sign-in"
toast.clear()
emit("hide-modal")
}
const authProviders: AuthProviderItem[] = [
{ {
id: "GITHUB", id: "GITHUB",
icon: IconGithub, icon: IconGithub,
@@ -315,19 +336,10 @@ const authProviders: AuthProviderItem[] = [
}, },
] ]
// Do not format the `import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS` call into multiple lines! const hideModal = () => {
// prettier-ignore mode.value = "sign-in"
const allowedAuthProvidersIDsString = toast.clear()
import.meta.env.VITE_ALLOWED_AUTH_PROVIDERS
const allowedAuthProvidersIDs = allowedAuthProvidersIDsString emit("hide-modal")
? allowedAuthProvidersIDsString.split(",") }
: []
const allowedAuthProviders =
allowedAuthProvidersIDs.length > 0
? authProviders.filter((provider) =>
allowedAuthProvidersIDs.includes(provider.id)
)
: authProviders
</script> </script>

View File

@@ -2,6 +2,7 @@ import { ClientOptions } from "@urql/core"
import { Observable } from "rxjs" import { Observable } from "rxjs"
import { Component } from "vue" import { Component } from "vue"
import { getI18n } from "~/modules/i18n" import { getI18n } from "~/modules/i18n"
import * as E from "fp-ts/Either"
/** /**
* A common (and required) set of fields that describe a user. * A common (and required) set of fields that describe a user.
@@ -222,6 +223,11 @@ export type AuthPlatformDef = {
*/ */
setDisplayName: (name: string) => Promise<void> setDisplayName: (name: string) => Promise<void>
/**
* Returns the list of allowed auth providers for the platform ( the currently supported ones are GOOGLE, GITHUB, EMAIL, MICROSOFT, SAML )
*/
getAllowedAuthProviders: () => Promise<E.Either<string, string[]>>
/** /**
* Defines the additional login items that should be shown in the login screen * Defines the additional login items that should be shown in the login screen
*/ */

View File

@@ -37,7 +37,8 @@
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"util": "^0.12.5", "util": "^0.12.5",
"vue": "^3.3.8", "vue": "^3.3.8",
"workbox-window": "^7.0.0" "workbox-window": "^7.0.0",
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@graphql-codegen/add": "^5.0.0", "@graphql-codegen/add": "^5.0.0",

View File

@@ -1,5 +1,5 @@
import { createHoppApp } from "@hoppscotch/common" import { createHoppApp } from "@hoppscotch/common"
import { def as authDef } from "./platform/auth" import { def as authDef } from "./platform/auth/auth.platform"
import { def as environmentsDef } from "./platform/environments/environments.platform" import { def as environmentsDef } from "./platform/environments/environments.platform"
import { def as collectionsDef } from "./platform/collections/collections.platform" import { def as collectionsDef } from "./platform/collections/collections.platform"
import { def as settingsDef } from "./platform/settings/settings.platform" import { def as settingsDef } from "./platform/settings/settings.platform"

View File

@@ -0,0 +1,30 @@
import axios from "axios"
import * as E from "fp-ts/Either"
import { z } from "zod"
const expectedAllowedProvidersSchema = z.object({
// currently supported values are "GOOGLE", "GITHUB", "EMAIL", "MICROSOFT", "SAML"
// keeping it as string to avoid backend accidentally breaking frontend when adding new providers
providers: z.array(z.string()),
})
export const getAllowedAuthProviders = async () => {
try {
const res = await axios.get(
`${import.meta.env.VITE_BACKEND_API_URL}/auth/providers`,
{
withCredentials: true,
}
)
const parseResult = expectedAllowedProvidersSchema.safeParse(res.data)
if (!parseResult.success) {
return E.left("SOMETHING_WENT_WRONG")
}
return E.right(parseResult.data.providers)
} catch (_) {
return E.left("SOMETHING_WENT_WRONG")
}
}

View File

@@ -8,6 +8,7 @@ import { PersistenceService } from "@hoppscotch/common/services/persistence"
import axios from "axios" import axios from "axios"
import { BehaviorSubject, Subject } from "rxjs" import { BehaviorSubject, Subject } from "rxjs"
import { Ref, ref, watch } from "vue" import { Ref, ref, watch } from "vue"
import { getAllowedAuthProviders } from "./auth.api"
export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>() export const authEvents$ = new Subject<AuthEvent | { event: "token_refresh" }>()
const currentUser$ = new BehaviorSubject<HoppUser | null>(null) const currentUser$ = new BehaviorSubject<HoppUser | null>(null)
@@ -341,4 +342,5 @@ export const def: AuthPlatformDef = {
window.location.href = "/" window.location.href = "/"
} }
}, },
getAllowedAuthProviders,
} }

View File

@@ -1,4 +1,4 @@
import { authEvents$, def as platformAuth } from "@platform/auth" import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections" import { CollectionsPlatformDef } from "@hoppscotch/common/platform/collections"
import { runDispatchWithOutSyncing } from "../../lib/sync" import { runDispatchWithOutSyncing } from "../../lib/sync"

View File

@@ -1,4 +1,4 @@
import { authEvents$, def as platformAuth } from "@platform/auth" import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
import { import {
createEnvironment, createEnvironment,
deleteEnvironment, deleteEnvironment,

View File

@@ -1,4 +1,4 @@
import { authEvents$, def as platformAuth } from "@platform/auth" import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
import { import {
restHistoryStore, restHistoryStore,
RESTHistoryEntry, RESTHistoryEntry,

View File

@@ -1,6 +1,6 @@
import { SettingsPlatformDef } from "@hoppscotch/common/platform/settings" import { SettingsPlatformDef } from "@hoppscotch/common/platform/settings"
import { settingsSyncer } from "./settings.sync" import { settingsSyncer } from "./settings.sync"
import { authEvents$, def as platformAuth } from "@platform/auth" import { authEvents$, def as platformAuth } from "@platform/auth/auth.platform"
import { import {
createUserSettings, createUserSettings,
getUserSettings, getUserSettings,

View File

@@ -2,7 +2,7 @@ import { PersistableTabState } from "@hoppscotch/common/services/tab"
import { HoppRESTDocument } from "@hoppscotch/common/helpers/rest/document" import { HoppRESTDocument } from "@hoppscotch/common/helpers/rest/document"
import { HoppUser } from "@hoppscotch/common/platform/auth" import { HoppUser } from "@hoppscotch/common/platform/auth"
import { TabStatePlatformDef } from "@hoppscotch/common/platform/tab" import { TabStatePlatformDef } from "@hoppscotch/common/platform/tab"
import { def as platformAuth } from "@platform/auth" import { def as platformAuth } from "@platform/auth/auth.platform"
import { getCurrentRestSession, updateUserSession } from "./tabState.api" import { getCurrentRestSession, updateUserSession } from "./tabState.api"
import { SessionType } from "../../api/generated/graphql" import { SessionType } from "../../api/generated/graphql"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"

28
pnpm-lock.yaml generated
View File

@@ -1099,6 +1099,9 @@ importers:
workbox-window: workbox-window:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0 version: 7.0.0
zod:
specifier: ^3.22.4
version: 3.22.4
devDependencies: devDependencies:
'@graphql-codegen/add': '@graphql-codegen/add':
specifier: ^5.0.0 specifier: ^5.0.0
@@ -7645,7 +7648,7 @@ packages:
dependencies: dependencies:
'@intlify/bundle-utils': 7.4.0(vue-i18n@9.2.2) '@intlify/bundle-utils': 7.4.0(vue-i18n@9.2.2)
'@intlify/shared': 9.4.1 '@intlify/shared': 9.4.1
'@rollup/pluginutils': 5.0.3(rollup@2.79.1) '@rollup/pluginutils': 5.0.3(rollup@3.29.4)
'@vue/compiler-sfc': 3.3.10 '@vue/compiler-sfc': 3.3.10
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1 fast-glob: 3.3.1
@@ -9365,6 +9368,7 @@ packages:
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 2.3.1 picomatch: 2.3.1
rollup: 2.79.1 rollup: 2.79.1
dev: true
/@rollup/pluginutils@5.0.3(rollup@3.29.4): /@rollup/pluginutils@5.0.3(rollup@3.29.4):
resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==} resolution: {integrity: sha512-hfllNN4a80rwNQ9QCxhxuHCGHMAvabXqxNdaChUSSadMre7t4iEUI6fFAhBOn/eIYTgYVhBv7vCLsAJ4u3lf3g==}
@@ -9379,7 +9383,6 @@ packages:
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 2.3.1 picomatch: 2.3.1
rollup: 3.29.4 rollup: 3.29.4
dev: true
/@rollup/pluginutils@5.1.0(rollup@2.79.1): /@rollup/pluginutils@5.1.0(rollup@2.79.1):
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
@@ -11264,7 +11267,7 @@ packages:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
systemjs: 6.14.2 systemjs: 6.14.2
terser: 5.24.0 terser: 5.24.0
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -11297,7 +11300,7 @@ packages:
vite: ^4.0.0 || ^5.0.0 vite: ^4.0.0 || ^5.0.0
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@5.3.2)
dev: true dev: true
@@ -11857,7 +11860,7 @@ packages:
dependencies: dependencies:
'@vue/compiler-ssr': 3.3.9 '@vue/compiler-ssr': 3.3.9
'@vue/shared': 3.3.9 '@vue/shared': 3.3.9
vue: 3.3.9(typescript@4.9.5) vue: 3.3.9(typescript@5.3.2)
/@vue/shared@3.2.45: /@vue/shared@3.2.45:
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
@@ -23454,6 +23457,7 @@ packages:
chokidar: 3.5.3 chokidar: 3.5.3
immutable: 4.3.2 immutable: 4.3.2
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true
/sax@1.2.4: /sax@1.2.4:
resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==}
@@ -25457,7 +25461,7 @@ packages:
dependencies: dependencies:
fast-glob: 3.3.1 fast-glob: 3.3.1
unplugin: 1.4.0 unplugin: 1.4.0
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
dev: true dev: true
/unplugin-icons@0.14.9(@vue/compiler-sfc@3.2.45)(vite@3.2.4): /unplugin-icons@0.14.9(@vue/compiler-sfc@3.2.45)(vite@3.2.4):
@@ -26279,7 +26283,7 @@ packages:
vite: ^2.0.0 || ^3.0.0 || ^4.0.0 vite: ^2.0.0 || ^3.0.0 || ^4.0.0
dependencies: dependencies:
fast-glob: 3.3.1 fast-glob: 3.3.1
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
dev: true dev: true
/vite-plugin-html-config@1.0.10(vite@3.2.4): /vite-plugin-html-config@1.0.10(vite@3.2.4):
@@ -26297,7 +26301,7 @@ packages:
peerDependencies: peerDependencies:
vite: '>=2.0.0' vite: '>=2.0.0'
dependencies: dependencies:
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
dev: true dev: true
/vite-plugin-inspect@0.7.38(rollup@2.79.1)(vite@4.5.0): /vite-plugin-inspect@0.7.38(rollup@2.79.1)(vite@4.5.0):
@@ -26538,7 +26542,7 @@ packages:
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.2 fast-glob: 3.3.2
pretty-bytes: 6.1.1 pretty-bytes: 6.1.1
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
workbox-build: 7.0.0 workbox-build: 7.0.0
workbox-window: 7.0.0 workbox-window: 7.0.0
transitivePeerDependencies: transitivePeerDependencies:
@@ -26632,7 +26636,7 @@ packages:
'@vue/compiler-sfc': 3.3.8 '@vue/compiler-sfc': 3.3.8
debug: 4.3.4(supports-color@9.2.2) debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.1 fast-glob: 3.3.1
vite: 4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0) vite: 4.5.0(@types/node@17.0.27)(sass@1.53.0)(terser@5.24.0)
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@5.3.2)
vue-router: 4.2.5(vue@3.3.9) vue-router: 4.2.5(vue@3.3.9)
transitivePeerDependencies: transitivePeerDependencies:
@@ -26758,7 +26762,6 @@ packages:
terser: 5.24.0 terser: 5.24.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true
/vite@4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0): /vite@4.5.0(@types/node@17.0.27)(sass@1.69.5)(terser@5.24.0):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
@@ -26790,12 +26793,13 @@ packages:
dependencies: dependencies:
'@types/node': 17.0.27 '@types/node': 17.0.27
esbuild: 0.18.20 esbuild: 0.18.20
postcss: 8.4.31 postcss: 8.4.32
rollup: 3.29.4 rollup: 3.29.4
sass: 1.69.5 sass: 1.69.5
terser: 5.24.0 terser: 5.24.0
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true
/vite@4.5.0(@types/node@18.18.8)(sass@1.69.5)(terser@5.24.0): /vite@4.5.0(@types/node@18.18.8)(sass@1.69.5)(terser@5.24.0):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}