feat(sh-admin): introducing additional SSO related server configurations to dashboard (#3737)

Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
This commit is contained in:
Joel Jacob Stephen
2024-03-08 15:18:53 +05:30
committed by GitHub
parent 6d66d12a9e
commit e69d5a6253
5 changed files with 153 additions and 69 deletions

View File

@@ -10,9 +10,12 @@
},
"configs": {
"auth_providers": {
"callback_url": "CALLBACK URL",
"client_id": "CLIENT ID",
"client_secret": "CLIENT SECRET",
"description": "Configure authentication providers for your server",
"scope": "SCOPE",
"tenant": "TENANT",
"title": "Authentication Providers",
"update_failure": "Failed to update authentication provider configurations!!"
},

View File

@@ -17,13 +17,19 @@ declare module '@vue/runtime-core' {
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']

View File

@@ -35,25 +35,29 @@
:key="field.key"
class="mt-5"
>
<label>{{ field.name }}</label>
<span class="flex">
<HoppSmartInput
v-model="provider.fields[field.key]"
:type="
isMasked(provider.name, field.key) ? 'password' : 'text'
"
:disabled="isMasked(provider.name, field.key)"
:autofocus="false"
class="!my-2 !bg-primaryLight"
/>
<HoppButtonSecondary
:icon="
isMasked(provider.name, field.key) ? IconEye : IconEyeOff
"
class="bg-primaryLight h-9 mt-2"
@click="toggleMask(provider.name, field.key)"
/>
</span>
<template
v-if="field.applicableProviders.includes(provider.name)"
>
<label>{{ field.name }}</label>
<span class="flex max-w-lg">
<HoppSmartInput
v-model="provider.fields[field.key as keyof typeof provider['fields']]"
:type="
isMasked(provider.name, field.key) ? 'password' : 'text'
"
:disabled="isMasked(provider.name, field.key)"
:autofocus="false"
class="!my-2 !bg-primaryLight flex-1"
/>
<HoppButtonSecondary
:icon="
isMasked(provider.name, field.key) ? IconEye : IconEyeOff
"
class="bg-primaryLight h-9 mt-2"
@click="toggleMask(provider.name, field.key)"
/>
</span>
</template>
</div>
</div>
</div>
@@ -86,47 +90,76 @@ const workingConfigs = useVModel(props, 'config', emit);
const capitalize = (text: string) =>
text.charAt(0).toUpperCase() + text.slice(1);
// Masking sensitive fields
type Field = {
// Union type for all possible field keys
type ProviderFieldKeys = keyof ProviderFields;
type ProviderFields = {
[Field in keyof Config['providers'][SsoAuthProviders]['fields']]: boolean;
} & Partial<{ tenant: boolean }>;
type ProviderFieldMetadata = {
name: string;
key: keyof Config['providers']['google' | 'github' | 'microsoft']['fields'];
key: ProviderFieldKeys;
applicableProviders: SsoAuthProviders[];
};
const providerConfigFields = reactive<Field[]>([
{ name: t('configs.auth_providers.client_id'), key: 'client_id' },
{ name: t('configs.auth_providers.client_secret'), key: 'client_secret' },
]);
const providerConfigFields = <ProviderFieldMetadata[]>[
{
name: t('configs.auth_providers.client_id'),
key: 'client_id',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.client_secret'),
key: 'client_secret',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.callback_url'),
key: 'callback_url',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.scope'),
key: 'scope',
applicableProviders: ['google', 'github', 'microsoft'],
},
{
name: t('configs.auth_providers.tenant'),
key: 'tenant',
applicableProviders: ['microsoft'],
},
];
const maskState = reactive({
const maskState = reactive<Record<SsoAuthProviders, ProviderFields>>({
google: {
client_id: true,
client_secret: true,
callback_url: true,
scope: true,
},
github: {
client_id: true,
client_secret: true,
callback_url: true,
scope: true,
},
microsoft: {
client_id: true,
client_secret: true,
callback_url: true,
scope: true,
tenant: true,
},
});
const toggleMask = (
provider: SsoAuthProviders,
fieldKey: keyof Config['providers'][
| 'google'
| 'github'
| 'microsoft']['fields']
fieldKey: ProviderFieldKeys
) => {
maskState[provider][fieldKey] = !maskState[provider][fieldKey];
};
const isMasked = (
provider: SsoAuthProviders,
fieldKey: keyof Config['providers'][
| 'google'
| 'github'
| 'microsoft']['fields']
) => maskState[provider][fieldKey];
const isMasked = (provider: SsoAuthProviders, fieldKey: ProviderFieldKeys) =>
maskState[provider][fieldKey];
</script>

View File

@@ -33,13 +33,13 @@
class="mt-5"
>
<label>{{ field.name }}</label>
<span class="flex">
<span class="flex max-w-lg">
<HoppSmartInput
v-model="smtpConfigs.fields[field.key]"
:type="isMasked(field.key) ? 'password' : 'text'"
:disabled="isMasked(field.key)"
:autofocus="false"
class="!my-2 !bg-primaryLight"
class="!my-2 !bg-primaryLight flex-1"
/>
<HoppButtonSecondary
:icon="isMasked(field.key) ? IconEye : IconEyeOff"

View File

@@ -27,6 +27,8 @@ export type Config = {
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
github: {
@@ -35,6 +37,8 @@ export type Config = {
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
microsoft: {
@@ -43,6 +47,9 @@ export type Config = {
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
tenant: string;
};
};
};
@@ -86,10 +93,17 @@ export function useConfigHandler(updatedConfigs?: Config) {
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',
@@ -118,6 +132,9 @@ export function useConfigHandler(updatedConfigs?: Config) {
await fetchInfraConfigs();
await fetchAllowedAuthProviders();
const getFieldValue = (name: string) =>
infraConfigs.value.find((x) => x.name === name)?.value ?? '';
// Transforming the fetched data into a Configs object
currentConfigs.value = {
providers: {
@@ -125,37 +142,31 @@ export function useConfigHandler(updatedConfigs?: Config) {
name: 'google',
enabled: allowedAuthProviders.value.includes('GOOGLE'),
fields: {
client_id:
infraConfigs.value.find((x) => x.name === 'GOOGLE_CLIENT_ID')
?.value ?? '',
client_secret:
infraConfigs.value.find((x) => x.name === 'GOOGLE_CLIENT_SECRET')
?.value ?? '',
client_id: getFieldValue('GOOGLE_CLIENT_ID'),
client_secret: getFieldValue('GOOGLE_CLIENT_SECRET'),
callback_url: getFieldValue('GOOGLE_CALLBACK_URL'),
scope: getFieldValue('GOOGLE_SCOPE'),
},
},
github: {
name: 'github',
enabled: allowedAuthProviders.value.includes('GITHUB'),
fields: {
client_id:
infraConfigs.value.find((x) => x.name === 'GITHUB_CLIENT_ID')
?.value ?? '',
client_secret:
infraConfigs.value.find((x) => x.name === 'GITHUB_CLIENT_SECRET')
?.value ?? '',
client_id: getFieldValue('GITHUB_CLIENT_ID'),
client_secret: getFieldValue('GITHUB_CLIENT_SECRET'),
callback_url: getFieldValue('GITHUB_CALLBACK_URL'),
scope: getFieldValue('GITHUB_SCOPE'),
},
},
microsoft: {
name: 'microsoft',
enabled: allowedAuthProviders.value.includes('MICROSOFT'),
fields: {
client_id:
infraConfigs.value.find((x) => x.name === 'MICROSOFT_CLIENT_ID')
?.value ?? '',
client_secret:
infraConfigs.value.find(
(x) => x.name === 'MICROSOFT_CLIENT_SECRET'
)?.value ?? '',
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'),
},
},
},
@@ -163,12 +174,8 @@ export function useConfigHandler(updatedConfigs?: Config) {
name: 'email',
enabled: allowedAuthProviders.value.includes('EMAIL'),
fields: {
mailer_smtp_url:
infraConfigs.value.find((x) => x.name === 'MAILER_SMTP_URL')
?.value ?? '',
mailer_from_address:
infraConfigs.value.find((x) => x.name === 'MAILER_ADDRESS_FROM')
?.value ?? '',
mailer_smtp_url: getFieldValue('MAILER_SMTP_URL'),
mailer_from_address: getFieldValue('MAILER_ADDRESS_FROM'),
},
},
dataSharingConfigs: {
@@ -184,7 +191,7 @@ export function useConfigHandler(updatedConfigs?: Config) {
workingConfigs.value = cloneDeep(currentConfigs.value);
});
// Trasforming the working configs back into the format required by the mutations
// Transforming the working configs back into the format required by the mutations
const updatedInfraConfigs = computed(() => {
let config: UpdatedConfigs[] = [
{
@@ -202,13 +209,23 @@ export function useConfigHandler(updatedConfigs?: Config) {
{
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_CLIENT_SECRET' &&
item.name !== 'GOOGLE_CALLBACK_URL' &&
item.name !== 'GOOGLE_SCOPE'
);
}
if (updatedConfigs?.providers.microsoft.enabled) {
@@ -220,13 +237,28 @@ export function useConfigHandler(updatedConfigs?: Config) {
{
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_CLIENT_SECRET' &&
item.name !== 'MICROSOFT_CALLBACK_URL' &&
item.name !== 'MICROSOFT_SCOPE' &&
item.name !== 'MICROSOFT_TENANT'
);
}
@@ -239,13 +271,23 @@ export function useConfigHandler(updatedConfigs?: Config) {
{
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_CLIENT_SECRET' &&
item.name !== 'GITHUB_CALLBACK_URL' &&
item.name !== 'GITHUB_SCOPE'
);
}