diff --git a/packages/hoppscotch-sh-admin/locales/en.json b/packages/hoppscotch-sh-admin/locales/en.json index cb06e5e591..68ce20b675 100644 --- a/packages/hoppscotch-sh-admin/locales/en.json +++ b/packages/hoppscotch-sh-admin/locales/en.json @@ -52,7 +52,8 @@ "title": "Server is restarting" }, "save_changes": "Save Changes", - "title": "Configurations" + "title": "Configurations", + "update_failure": "Failed to update server configurations" }, "data_sharing": { "description": "Share anonymous data usage to improve Hoppscotch", diff --git a/packages/hoppscotch-sh-admin/src/components/settings/AuthProvider.vue b/packages/hoppscotch-sh-admin/src/components/settings/AuthProvider.vue index 5875921cf4..a7a6c5edbe 100644 --- a/packages/hoppscotch-sh-admin/src/components/settings/AuthProvider.vue +++ b/packages/hoppscotch-sh-admin/src/components/settings/AuthProvider.vue @@ -69,18 +69,18 @@ import { useVModel } from '@vueuse/core'; import { reactive } from 'vue'; import { useI18n } from '~/composables/i18n'; -import { Config, SsoAuthProviders } from '~/composables/useConfigHandler'; +import { ServerConfigs, SsoAuthProviders } from '~/helpers/configs'; import IconEye from '~icons/lucide/eye'; import IconEyeOff from '~icons/lucide/eye-off'; const t = useI18n(); const props = defineProps<{ - config: Config; + config: ServerConfigs; }>(); const emit = defineEmits<{ - (e: 'update:config', v: Config): void; + (e: 'update:config', v: ServerConfigs): void; }>(); const workingConfigs = useVModel(props, 'config', emit); @@ -93,7 +93,7 @@ const capitalize = (text: string) => type ProviderFieldKeys = keyof ProviderFields; type ProviderFields = { - [Field in keyof Config['providers'][SsoAuthProviders]['fields']]: boolean; + [Field in keyof ServerConfigs['providers'][SsoAuthProviders]['fields']]: boolean; } & Partial<{ tenant: boolean }>; type ProviderFieldMetadata = { diff --git a/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue b/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue index c1267fcb59..796ea1993e 100644 --- a/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue +++ b/packages/hoppscotch-sh-admin/src/components/settings/Configurations.vue @@ -9,14 +9,14 @@ diff --git a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts index b4a7f5959e..e7d0c80ff2 100644 --- a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts +++ b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts @@ -1,83 +1,39 @@ import { AnyVariables, UseMutationResponse } from '@urql/vue'; import { cloneDeep } from 'lodash-es'; -import { computed, onMounted, ref } from 'vue'; +import { onMounted, ref } from 'vue'; + import { useI18n } from '~/composables/i18n'; import { AllowedAuthProvidersDocument, + AuthProvider, EnableAndDisableSsoArgs, EnableAndDisableSsoMutation, InfraConfigArgs, InfraConfigEnum, InfraConfigsDocument, ResetInfraConfigsMutation, + ServiceStatus, ToggleAnalyticsCollectionMutation, UpdateInfraConfigsMutation, } from '~/helpers/backend/graphql'; +import { + ALL_CONFIGS, + ConfigSection, + ConfigTransform, + GITHUB_CONFIGS, + GOOGLE_CONFIGS, + MAIL_CONFIGS, + MICROSOFT_CONFIGS, + ServerConfigs, + UpdatedConfigs, +} from '~/helpers/configs'; import { useToast } from './toast'; import { useClientHandler } from './useClientHandler'; -// Types -export type SsoAuthProviders = 'google' | 'microsoft' | 'github'; - -export type Config = { - providers: { - google: { - name: SsoAuthProviders; - enabled: boolean; - fields: { - client_id: string; - client_secret: string; - callback_url: string; - scope: string; - }; - }; - github: { - name: SsoAuthProviders; - enabled: boolean; - fields: { - client_id: string; - client_secret: string; - callback_url: string; - scope: string; - }; - }; - microsoft: { - name: SsoAuthProviders; - enabled: boolean; - fields: { - client_id: string; - client_secret: string; - callback_url: string; - scope: string; - tenant: string; - }; - }; - }; - - mailConfigs: { - name: string; - enabled: boolean; - fields: { - mailer_smtp_url: string; - mailer_from_address: string; - }; - }; - - dataSharingConfigs: { - name: string; - enabled: boolean; - }; -}; - -type UpdatedConfigs = { - name: string; - value: string; -}; - /** Composable that handles all operations related to server configurations * @param updatedConfigs A Config Object contatining the updated configs */ -export function useConfigHandler(updatedConfigs?: Config) { +export function useConfigHandler(updatedConfigs?: ServerConfigs) { const t = useI18n(); const toast = useToast(); @@ -90,24 +46,9 @@ export function useConfigHandler(updatedConfigs?: Config) { } = useClientHandler( InfraConfigsDocument, { - configNames: [ - 'GOOGLE_CLIENT_ID', - 'GOOGLE_CLIENT_SECRET', - 'GOOGLE_CALLBACK_URL', - 'GOOGLE_SCOPE', - 'MICROSOFT_CLIENT_ID', - 'MICROSOFT_CLIENT_SECRET', - 'MICROSOFT_CALLBACK_URL', - 'MICROSOFT_SCOPE', - 'MICROSOFT_TENANT', - 'GITHUB_CLIENT_ID', - 'GITHUB_CLIENT_SECRET', - 'GITHUB_CALLBACK_URL', - 'GITHUB_SCOPE', - 'MAILER_SMTP_URL', - 'MAILER_ADDRESS_FROM', - 'ALLOW_ANALYTICS_COLLECTION', - ] as InfraConfigEnum[], + configNames: ALL_CONFIGS.flat().map( + ({ name }) => name + ) as InfraConfigEnum[], }, (x) => x.infraConfigs ); @@ -125,14 +66,14 @@ export function useConfigHandler(updatedConfigs?: Config) { ); // Current and working configs - const currentConfigs = ref(); - const workingConfigs = ref(); + const currentConfigs = ref(); + const workingConfigs = ref(); onMounted(async () => { await fetchInfraConfigs(); await fetchAllowedAuthProviders(); - const getFieldValue = (name: string) => + const getFieldValue = (name: InfraConfigEnum) => infraConfigs.value.find((x) => x.name === name)?.value ?? ''; // Transforming the fetched data into a Configs object @@ -140,42 +81,42 @@ export function useConfigHandler(updatedConfigs?: Config) { providers: { google: { name: 'google', - enabled: allowedAuthProviders.value.includes('GOOGLE'), + enabled: allowedAuthProviders.value.includes(AuthProvider.Google), fields: { - client_id: getFieldValue('GOOGLE_CLIENT_ID'), - client_secret: getFieldValue('GOOGLE_CLIENT_SECRET'), - callback_url: getFieldValue('GOOGLE_CALLBACK_URL'), - scope: getFieldValue('GOOGLE_SCOPE'), + client_id: getFieldValue(InfraConfigEnum.GoogleClientId), + client_secret: getFieldValue(InfraConfigEnum.GoogleClientSecret), + callback_url: getFieldValue(InfraConfigEnum.GoogleCallbackUrl), + scope: getFieldValue(InfraConfigEnum.GoogleScope), }, }, github: { name: 'github', - enabled: allowedAuthProviders.value.includes('GITHUB'), + enabled: allowedAuthProviders.value.includes(AuthProvider.Github), fields: { - client_id: getFieldValue('GITHUB_CLIENT_ID'), - client_secret: getFieldValue('GITHUB_CLIENT_SECRET'), - callback_url: getFieldValue('GITHUB_CALLBACK_URL'), - scope: getFieldValue('GITHUB_SCOPE'), + client_id: getFieldValue(InfraConfigEnum.GithubClientId), + client_secret: getFieldValue(InfraConfigEnum.GithubClientSecret), + callback_url: getFieldValue(InfraConfigEnum.GoogleCallbackUrl), + scope: getFieldValue(InfraConfigEnum.GithubScope), }, }, microsoft: { name: 'microsoft', - enabled: allowedAuthProviders.value.includes('MICROSOFT'), + enabled: allowedAuthProviders.value.includes(AuthProvider.Microsoft), fields: { - client_id: getFieldValue('MICROSOFT_CLIENT_ID'), - client_secret: getFieldValue('MICROSOFT_CLIENT_SECRET'), - callback_url: getFieldValue('MICROSOFT_CALLBACK_URL'), - scope: getFieldValue('MICROSOFT_SCOPE'), - tenant: getFieldValue('MICROSOFT_TENANT'), + client_id: getFieldValue(InfraConfigEnum.MicrosoftClientId), + client_secret: getFieldValue(InfraConfigEnum.MicrosoftClientSecret), + callback_url: getFieldValue(InfraConfigEnum.MicrosoftCallbackUrl), + scope: getFieldValue(InfraConfigEnum.MicrosoftScope), + tenant: getFieldValue(InfraConfigEnum.MicrosoftTenant), }, }, }, mailConfigs: { name: 'email', - enabled: allowedAuthProviders.value.includes('EMAIL'), + enabled: allowedAuthProviders.value.includes(AuthProvider.Email), fields: { - mailer_smtp_url: getFieldValue('MAILER_SMTP_URL'), - mailer_from_address: getFieldValue('MAILER_ADDRESS_FROM'), + mailer_smtp_url: getFieldValue(InfraConfigEnum.MailerSmtpUrl), + mailer_from_address: getFieldValue(InfraConfigEnum.MailerAddressFrom), }, }, dataSharingConfigs: { @@ -191,138 +132,13 @@ export function useConfigHandler(updatedConfigs?: Config) { workingConfigs.value = cloneDeep(currentConfigs.value); }); - // Transforming the working configs back into the format required by the mutations - const updatedInfraConfigs = computed(() => { - let config: UpdatedConfigs[] = [ - { - name: '', - value: '', - }, - ]; - - if (updatedConfigs?.providers.google.enabled) { - config.push( - { - name: 'GOOGLE_CLIENT_ID', - value: updatedConfigs?.providers.google.fields.client_id ?? '', - }, - { - name: 'GOOGLE_CLIENT_SECRET', - value: updatedConfigs?.providers.google.fields.client_secret ?? '', - }, - { - name: 'GOOGLE_CALLBACK_URL', - value: updatedConfigs?.providers.google.fields.callback_url ?? '', - }, - { - name: 'GOOGLE_SCOPE', - value: updatedConfigs?.providers.google.fields.scope ?? '', - } - ); - } else { - config = config.filter( - (item) => - item.name !== 'GOOGLE_CLIENT_ID' && - item.name !== 'GOOGLE_CLIENT_SECRET' && - item.name !== 'GOOGLE_CALLBACK_URL' && - item.name !== 'GOOGLE_SCOPE' - ); - } - if (updatedConfigs?.providers.microsoft.enabled) { - config.push( - { - name: 'MICROSOFT_CLIENT_ID', - value: updatedConfigs?.providers.microsoft.fields.client_id ?? '', - }, - { - name: 'MICROSOFT_CLIENT_SECRET', - value: updatedConfigs?.providers.microsoft.fields.client_secret ?? '', - }, - { - name: 'MICROSOFT_CALLBACK_URL', - value: updatedConfigs?.providers.microsoft.fields.callback_url ?? '', - }, - { - name: 'MICROSOFT_SCOPE', - value: updatedConfigs?.providers.microsoft.fields.scope ?? '', - }, - { - name: 'MICROSOFT_TENANT', - value: updatedConfigs?.providers.microsoft.fields.tenant ?? '', - } - ); - } else { - config = config.filter( - (item) => - item.name !== 'MICROSOFT_CLIENT_ID' && - item.name !== 'MICROSOFT_CLIENT_SECRET' && - item.name !== 'MICROSOFT_CALLBACK_URL' && - item.name !== 'MICROSOFT_SCOPE' && - item.name !== 'MICROSOFT_TENANT' - ); - } - - if (updatedConfigs?.providers.github.enabled) { - config.push( - { - name: 'GITHUB_CLIENT_ID', - value: updatedConfigs?.providers.github.fields.client_id ?? '', - }, - { - name: 'GITHUB_CLIENT_SECRET', - value: updatedConfigs?.providers.github.fields.client_secret ?? '', - }, - { - name: 'GITHUB_CALLBACK_URL', - value: updatedConfigs?.providers.github.fields.callback_url ?? '', - }, - { - name: 'GITHUB_SCOPE', - value: updatedConfigs?.providers.github.fields.scope ?? '', - } - ); - } else { - config = config.filter( - (item) => - item.name !== 'GITHUB_CLIENT_ID' && - item.name !== 'GITHUB_CLIENT_SECRET' && - item.name !== 'GITHUB_CALLBACK_URL' && - item.name !== 'GITHUB_SCOPE' - ); - } - - if (updatedConfigs?.mailConfigs.enabled) { - config.push( - { - name: 'MAILER_SMTP_URL', - value: updatedConfigs?.mailConfigs.fields.mailer_smtp_url ?? '', - }, - { - name: 'MAILER_ADDRESS_FROM', - value: updatedConfigs?.mailConfigs.fields.mailer_from_address ?? '', - } - ); - } else { - config = config.filter( - (item) => - item.name !== 'MAILER_SMTP_URL' && item.name !== 'MAILER_ADDRESS_FROM' - ); - } + /* + Check if any of the config fields are empty + */ - config = config.filter((item) => item.name !== ''); - - return config; - }); - - // Checking if any of the config fields are empty const isFieldEmpty = (field: string) => field.trim() === ''; - type ConfigSection = { - enabled: boolean; - fields: Record; - }; - - const AreAnyConfigFieldsEmpty = (config: Config): boolean => { + const AreAnyConfigFieldsEmpty = (config: ServerConfigs): boolean => { const sections: Array = [ config.providers.github, config.providers.google, @@ -337,28 +153,44 @@ export function useConfigHandler(updatedConfigs?: Config) { }; // Transforming the working configs back into the format required by the mutations - const updatedAllowedAuthProviders = computed(() => { - return [ + const transformInfraConfigs = () => { + const updatedWorkingConfigs: ConfigTransform[] = [ { - provider: 'GOOGLE', - status: updatedConfigs?.providers.google.enabled ? 'ENABLE' : 'DISABLE', + config: GOOGLE_CONFIGS, + enabled: updatedConfigs?.providers.google.enabled, + fields: updatedConfigs?.providers.google.fields, }, { - provider: 'MICROSOFT', - status: updatedConfigs?.providers.microsoft.enabled - ? 'ENABLE' - : 'DISABLE', + config: GITHUB_CONFIGS, + enabled: updatedConfigs?.providers.github.enabled, + fields: updatedConfigs?.providers.github.fields, }, { - provider: 'GITHUB', - status: updatedConfigs?.providers.github.enabled ? 'ENABLE' : 'DISABLE', + config: MICROSOFT_CONFIGS, + enabled: updatedConfigs?.providers.microsoft.enabled, + fields: updatedConfigs?.providers.microsoft.fields, }, { - provider: 'EMAIL', - status: updatedConfigs?.mailConfigs.enabled ? 'ENABLE' : 'DISABLE', + config: MAIL_CONFIGS, + enabled: updatedConfigs?.mailConfigs.enabled, + fields: updatedConfigs?.mailConfigs.fields, }, ]; - }); + + const transformedConfigs: UpdatedConfigs[] = []; + + updatedWorkingConfigs.forEach(({ config, enabled, fields }) => { + config.forEach(({ name, key }) => { + if (enabled && fields) { + const value = + typeof fields === 'string' ? fields : String(fields[key]); + transformedConfigs.push({ name, value }); + } + }); + }); + + return transformedConfigs; + }; // Generic function to handle mutation execution and error handling const executeMutation = async ( @@ -379,27 +211,59 @@ export function useConfigHandler(updatedConfigs?: Config) { // Updating the auth provider configurations const updateAuthProvider = ( updateProviderStatus: UseMutationResponse - ) => - executeMutation( + ) => { + const updatedAllowedAuthProviders: EnableAndDisableSsoArgs[] = [ + { + provider: AuthProvider.Google, + status: updatedConfigs?.providers.google.enabled + ? ServiceStatus.Enable + : ServiceStatus.Disable, + }, + { + provider: AuthProvider.Microsoft, + status: updatedConfigs?.providers.microsoft.enabled + ? ServiceStatus.Enable + : ServiceStatus.Disable, + }, + { + provider: AuthProvider.Github, + status: updatedConfigs?.providers.github.enabled + ? ServiceStatus.Enable + : ServiceStatus.Disable, + }, + { + provider: AuthProvider.Email, + status: updatedConfigs?.mailConfigs.enabled + ? ServiceStatus.Enable + : ServiceStatus.Disable, + }, + ]; + + return executeMutation( updateProviderStatus, { - providerInfo: - updatedAllowedAuthProviders.value as EnableAndDisableSsoArgs[], + providerInfo: updatedAllowedAuthProviders, }, 'configs.auth_providers.update_failure' ); + }; // Updating the infra configurations const updateInfraConfigs = ( updateInfraConfigsMutation: UseMutationResponse - ) => - executeMutation( + ) => { + const infraConfigs: InfraConfigArgs[] = updatedConfigs + ? transformInfraConfigs() + : []; + + return executeMutation( updateInfraConfigsMutation, { - infraConfigs: updatedInfraConfigs.value as InfraConfigArgs[], + infraConfigs, }, - 'configs.mail_configs.update_failure' + 'configs.update_failure' ); + }; // Resetting the infra configurations const resetInfraConfigs = ( @@ -411,7 +275,6 @@ export function useConfigHandler(updatedConfigs?: Config) { 'configs.reset.failure' ); - // Updating the data sharing configurations const updateDataSharingConfigs = ( toggleDataSharingMutation: UseMutationResponse ) => @@ -419,8 +282,8 @@ export function useConfigHandler(updatedConfigs?: Config) { toggleDataSharingMutation, { status: updatedConfigs?.dataSharingConfigs.enabled - ? 'ENABLE' - : 'DISABLE', + ? ServiceStatus.Enable + : ServiceStatus.Disable, }, 'configs.data_sharing.update_failure' ); @@ -428,8 +291,6 @@ export function useConfigHandler(updatedConfigs?: Config) { return { currentConfigs, workingConfigs, - updatedInfraConfigs, - updatedAllowedAuthProviders, updateAuthProvider, updateDataSharingConfigs, updateInfraConfigs, diff --git a/packages/hoppscotch-sh-admin/src/helpers/configs.ts b/packages/hoppscotch-sh-admin/src/helpers/configs.ts new file mode 100644 index 0000000000..7398ed867a --- /dev/null +++ b/packages/hoppscotch-sh-admin/src/helpers/configs.ts @@ -0,0 +1,160 @@ +import { InfraConfigEnum } from './backend/graphql'; + +export type SsoAuthProviders = 'google' | 'microsoft' | 'github'; + +export type ServerConfigs = { + providers: { + google: { + name: SsoAuthProviders; + enabled: boolean; + fields: { + client_id: string; + client_secret: string; + callback_url: string; + scope: string; + }; + }; + github: { + name: SsoAuthProviders; + enabled: boolean; + fields: { + client_id: string; + client_secret: string; + callback_url: string; + scope: string; + }; + }; + microsoft: { + name: SsoAuthProviders; + enabled: boolean; + fields: { + client_id: string; + client_secret: string; + callback_url: string; + scope: string; + tenant: string; + }; + }; + }; + + mailConfigs: { + name: string; + enabled: boolean; + fields: { + mailer_smtp_url: string; + mailer_from_address: string; + }; + }; + + dataSharingConfigs: { + name: string; + enabled: boolean; + }; +}; + +export type UpdatedConfigs = { + name: InfraConfigEnum; + value: string; +}; + +export type ConfigTransform = { + config: Config[]; + enabled?: boolean; + fields?: Record | string; +}; + +export type ConfigSection = { + enabled: boolean; + fields: Record; +}; + +export type Config = { + name: InfraConfigEnum; + key: string; +}; + +export const GOOGLE_CONFIGS: Config[] = [ + { + name: InfraConfigEnum.GoogleClientId, + key: 'client_id', + }, + { + name: InfraConfigEnum.GoogleClientSecret, + key: 'client_secret', + }, + { + name: InfraConfigEnum.GoogleCallbackUrl, + key: 'callback_url', + }, + { + name: InfraConfigEnum.GoogleScope, + key: 'scope', + }, +]; + +export const MICROSOFT_CONFIGS: Config[] = [ + { + name: InfraConfigEnum.MicrosoftClientId, + key: 'client_id', + }, + { + name: InfraConfigEnum.MicrosoftClientSecret, + key: 'client_secret', + }, + { + name: InfraConfigEnum.MicrosoftCallbackUrl, + key: 'callback_url', + }, + { + name: InfraConfigEnum.MicrosoftScope, + key: 'scope', + }, + { + name: InfraConfigEnum.MicrosoftTenant, + key: 'tenant', + }, +]; + +export const GITHUB_CONFIGS: Config[] = [ + { + name: InfraConfigEnum.GithubClientId, + key: 'client_id', + }, + { + name: InfraConfigEnum.GithubClientSecret, + key: 'client_secret', + }, + { + name: InfraConfigEnum.GithubCallbackUrl, + key: 'callback_url', + }, + { + name: InfraConfigEnum.GithubScope, + key: 'scope', + }, +]; + +export const MAIL_CONFIGS: Config[] = [ + { + name: InfraConfigEnum.MailerSmtpUrl, + key: 'mailer_smtp_url', + }, + { + name: InfraConfigEnum.MailerAddressFrom, + key: 'mailer_from_address', + }, +]; + +const DATA_SHARING_CONFIGS: Omit[] = [ + { + name: InfraConfigEnum.AllowAnalyticsCollection, + }, +]; + +export const ALL_CONFIGS = [ + GOOGLE_CONFIGS, + MICROSOFT_CONFIGS, + GITHUB_CONFIGS, + MAIL_CONFIGS, + DATA_SHARING_CONFIGS, +]; diff --git a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue index 4bd9700d67..414e7d05d1 100644 --- a/packages/hoppscotch-sh-admin/src/pages/users/_id.vue +++ b/packages/hoppscotch-sh-admin/src/pages/users/_id.vue @@ -208,13 +208,14 @@ const deleteUserMutation = async (id: string | null) => { if (result.error) { toast.error(t('state.delete_user_failure')); } else { - const deletedUsers = result.data?.removeUsersByAdmin || []; + const deletedUser = result.data?.removeUsersByAdmin || []; + handleUserDeletion(deletedUser); - handleUserDeletion(deletedUsers); + const { isDeleted } = deletedUser[0]; + if (isDeleted) router.push('/users'); } + confirmDeletion.value = false; deleteUserUID.value = null; - - !result.error && router.push('/users'); };