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": { "configs": {
"auth_providers": { "auth_providers": {
"callback_url": "CALLBACK URL",
"client_id": "CLIENT ID", "client_id": "CLIENT ID",
"client_secret": "CLIENT SECRET", "client_secret": "CLIENT SECRET",
"description": "Configure authentication providers for your server", "description": "Configure authentication providers for your server",
"scope": "SCOPE",
"tenant": "TENANT",
"title": "Authentication Providers", "title": "Authentication Providers",
"update_failure": "Failed to update authentication provider configurations!!" "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'] HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'] HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] 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'] HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'] IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default'] SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default'] SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']

View File

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

View File

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

View File

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