diff --git a/packages/hoppscotch-sh-admin/locales/en.json b/packages/hoppscotch-sh-admin/locales/en.json index 11f3503e..f06ec9cc 100644 --- a/packages/hoppscotch-sh-admin/locales/en.json +++ b/packages/hoppscotch-sh-admin/locales/en.json @@ -24,6 +24,9 @@ "email_auth_enabled": "Email authentication is enabled and ready to use.", "email_auth_smtp_note": "To use email-based authentication, you need to enable and configure SMTP in the SMTP tab first.", "enable_email_auth": "Enable Email Authentication", + "enable_local_auth": "Enable Username and Password", + "local_auth": "Username and Password", + "local_auth_description": "Enable or disable local username and password authentication for your users.", "smtp_required": "SMTP must be enabled and configured to use email based authentication." }, "configs": { @@ -36,6 +39,7 @@ "description": "Configure authentication providers for your server", "email": "Email", "github_enterprise": "Enable Github Enterprise", + "local": "Username and Password", "oauth": "OAuth", "oauth_providers": "OAuth Providers", "provider_not_specified": "Please enable at least one authentication provider", diff --git a/packages/hoppscotch-sh-admin/locales/fr.json b/packages/hoppscotch-sh-admin/locales/fr.json index 7b4a08b1..8ce34a6f 100644 --- a/packages/hoppscotch-sh-admin/locales/fr.json +++ b/packages/hoppscotch-sh-admin/locales/fr.json @@ -1,4 +1,14 @@ { + "auth": { + "enable_local_auth": "Activer identifiant et mot de passe", + "local_auth": "Identifiant et mot de passe", + "local_auth_description": "Activer ou désactiver l’authentification locale par identifiant et mot de passe pour les utilisateurs." + }, + "configs": { + "auth_providers": { + "local": "Identifiant et mot de passe" + } + }, "onboarding": { "local": { "confirm_password": "Confirmer le mot de passe", diff --git a/packages/hoppscotch-sh-admin/src/components/settings/AuthConfigurations.vue b/packages/hoppscotch-sh-admin/src/components/settings/AuthConfigurations.vue index 6ba2318f..a78e7bb8 100644 --- a/packages/hoppscotch-sh-admin/src/components/settings/AuthConfigurations.vue +++ b/packages/hoppscotch-sh-admin/src/components/settings/AuthConfigurations.vue @@ -62,6 +62,36 @@ + +
+
+

{{ t('auth.local_auth') }}

+

+ {{ t('auth.local_auth_description') }} +

+
+ +
+
+

+ {{ t('auth.local_auth') }} +

+ +
+
+ + {{ t('auth.enable_local_auth') }} + +
+
+
+
+
+
+
@@ -88,14 +118,14 @@ const emit = defineEmits<{ }>(); // Auth Sub Tabs -type AuthSubTabs = 'auth-providers' | 'email-auth' | 'token'; +type AuthSubTabs = 'auth-providers' | 'email-auth' | 'local-auth' | 'token'; const selectedAuthSubTab = ref('auth-providers'); const workingConfigs = useVModel(props, 'config', emit); // Check if SMTP is activated but not saved yet. Used to track if SMTP was enabled after the last save. const isSMTPActivated = computed( - () => workingConfigs.value?.mailConfigs.enabled ?? false + () => workingConfigs.value?.mailConfigs.enabled ?? false, ); // Check if Email authentication is enabled @@ -111,6 +141,15 @@ const toggleEmailAuth = () => { } }; +const isLocalAuthEnabled = computed(() => { + return workingConfigs.value?.localAuth.enabled ?? false; +}); + +const toggleLocalAuth = () => { + workingConfigs.value.localAuth.enabled = + !workingConfigs.value.localAuth.enabled; +}; + // Check if SMTP is enabled on mount const isSMTPEnabled = ref(false); diff --git a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts index 1eda5199..7f667eff 100644 --- a/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts +++ b/packages/hoppscotch-sh-admin/src/composables/useConfigHandler.ts @@ -38,7 +38,7 @@ import { useClientHandler } from './useClientHandler'; const COOKIE_NAME_REGEX = /^[A-Za-z0-9_-]+$/; const OPTIONAL_TOKEN_FIELD_KEYS = new Set( - TOKEN_VALIDATION_CONFIGS.filter((cfg) => cfg.optional).map((cfg) => cfg.key) + TOKEN_VALIDATION_CONFIGS.filter((cfg) => cfg.optional).map((cfg) => cfg.key), ); /** Composable that handles all operations related to server configurations @@ -58,10 +58,10 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { InfraConfigsDocument, { configNames: ALL_CONFIGS.flat().map( - ({ name }) => name + ({ name }) => name, ) as InfraConfigEnum[], }, - (x) => x.infraConfigs + (x) => x.infraConfigs, ); // Fetching allowed auth providers @@ -73,7 +73,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { } = useClientHandler( AllowedAuthProvidersDocument, {}, - (x) => x.allowedAuthProviders + (x) => x.allowedAuthProviders, ); // Current and working configs @@ -133,7 +133,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { mailer_smtp_port: getFieldValue(InfraConfigEnum.MailerSmtpPort), mailer_smtp_user: getFieldValue(InfraConfigEnum.MailerSmtpUser), mailer_smtp_password: getFieldValue( - InfraConfigEnum.MailerSmtpPassword + InfraConfigEnum.MailerSmtpPassword, ), mailer_smtp_secure: getFieldValue(InfraConfigEnum.MailerSmtpSecure) === 'true', @@ -147,37 +147,42 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { mailer_smtp_auth_type: getFieldValue(InfraConfigEnum.MailerSmtpAuthType) || 'login', mailer_smtp_oauth2_user: getFieldValue( - InfraConfigEnum.MailerSmtpOauth2User + InfraConfigEnum.MailerSmtpOauth2User, ), mailer_smtp_oauth2_client_id: getFieldValue( - InfraConfigEnum.MailerSmtpOauth2ClientId + InfraConfigEnum.MailerSmtpOauth2ClientId, ), mailer_smtp_oauth2_client_secret: getFieldValue( - InfraConfigEnum.MailerSmtpOauth2ClientSecret + InfraConfigEnum.MailerSmtpOauth2ClientSecret, ), mailer_smtp_oauth2_refresh_token: getFieldValue( - InfraConfigEnum.MailerSmtpOauth2RefreshToken + InfraConfigEnum.MailerSmtpOauth2RefreshToken, ), mailer_smtp_oauth2_access_url: getFieldValue( - InfraConfigEnum.MailerSmtpOauth2AccessUrl + InfraConfigEnum.MailerSmtpOauth2AccessUrl, ), }, }, + localAuth: { + name: 'local', + enabled: allowedAuthProviders.value.includes(AuthProvider.Local), + fields: {}, + }, tokenConfigs: { name: 'token', fields: { jwt_secret: getFieldValue(InfraConfigEnum.JwtSecret), token_salt_complexity: getFieldValue( - InfraConfigEnum.TokenSaltComplexity + InfraConfigEnum.TokenSaltComplexity, ), magic_link_token_validity: getFieldValue( - InfraConfigEnum.MagicLinkTokenValidity + InfraConfigEnum.MagicLinkTokenValidity, ), refresh_token_validity: getFieldValue( - InfraConfigEnum.RefreshTokenValidity + InfraConfigEnum.RefreshTokenValidity, ), access_token_validity: getFieldValue( - InfraConfigEnum.AccessTokenValidity + InfraConfigEnum.AccessTokenValidity, ), session_secret: getFieldValue(InfraConfigEnum.SessionSecret), session_cookie_name: getFieldValue(InfraConfigEnum.SessionCookieName), @@ -186,7 +191,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { dataSharingConfigs: { name: 'data_sharing', enabled: !!infraConfigs.value.find( - (x) => x.name === 'ALLOW_ANALYTICS_COLLECTION' && x.value === 'true' + (x) => x.name === 'ALLOW_ANALYTICS_COLLECTION' && x.value === 'true', ), }, historyConfig: { @@ -194,7 +199,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { enabled: !!infraConfigs.value.find( (config) => config.name === 'USER_HISTORY_STORE_ENABLED' && - config.value === 'ENABLE' + config.value === 'ENABLE', ), }, rateLimitConfigs: { @@ -208,7 +213,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { name: 'mock_server', fields: { mock_server_wildcard_domain: getFieldValue( - InfraConfigEnum.MockServerWildcardDomain + InfraConfigEnum.MockServerWildcardDomain, ), }, }, @@ -316,8 +321,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { return ( section.enabled && Object.entries(otherFields).some( - ([key, value]) => - isFieldEmpty(value) && !excludeKeys.includes(key) + ([key, value]) => isFieldEmpty(value) && !excludeKeys.includes(key), ) ); } @@ -327,7 +331,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { if (section.name === 'token') { return Object.entries(section.fields).some( ([key, value]) => - !OPTIONAL_TOKEN_FIELD_KEYS.has(key) && isFieldNotValid(value) + !OPTIONAL_TOKEN_FIELD_KEYS.has(key) && isFieldNotValid(value), ); } @@ -368,20 +372,20 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { (x) => x.key === key && key !== 'mailer_smtp_url' && - key !== 'mailer_smtp_enabled' + key !== 'mailer_smtp_enabled', ); } else return MAIL_CONFIGS.some( - (x) => x.key === key && key !== 'mailer_smtp_enabled' + (x) => x.key === key && key !== 'mailer_smtp_enabled', ); - }) + }), ); // Extract the custom mail config fields const customMailConfigFields = Object.fromEntries( Object.entries(updatedConfigs?.mailConfigs.fields ?? {}).filter(([key]) => - CUSTOM_MAIL_CONFIGS.some((x) => x.key === key) - ) + CUSTOM_MAIL_CONFIGS.some((x) => x.key === key), + ), ); // Transforming the working configs back into the format required by the mutations @@ -441,7 +445,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { const executeMutation = async ( mutation: UseMutationResponse, variables: AnyVariables = undefined, - errorMessage: string + errorMessage: string, ): Promise => { const result = await mutation.executeMutation(variables); @@ -460,7 +464,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { // Updating the auth provider configurations const updateAuthProvider = ( - updateProviderStatus: UseMutationResponse + updateProviderStatus: UseMutationResponse, ) => { const updatedAllowedAuthProviders: EnableAndDisableSsoArgs[] = [ { @@ -487,6 +491,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { ? ServiceStatus.Enable : ServiceStatus.Disable, }, + { + provider: AuthProvider.Local, + status: updatedConfigs?.localAuth.enabled + ? ServiceStatus.Enable + : ServiceStatus.Disable, + }, ]; return executeMutation( @@ -494,13 +504,13 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { { providerInfo: updatedAllowedAuthProviders, }, - 'configs.auth_providers.update_failure' + 'configs.auth_providers.update_failure', ); }; // Updating the infra configurations const updateInfraConfigs = ( - updateInfraConfigsMutation: UseMutationResponse + updateInfraConfigsMutation: UseMutationResponse, ) => { const infraConfigs: InfraConfigArgs[] = updatedConfigs ? transformInfraConfigs() @@ -511,23 +521,23 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { { infraConfigs, }, - 'configs.update_failure' + 'configs.update_failure', ); }; // Resetting the infra configurations const resetInfraConfigs = ( - resetInfraConfigsMutation: UseMutationResponse + resetInfraConfigsMutation: UseMutationResponse, ) => executeMutation( resetInfraConfigsMutation, undefined, - 'configs.reset.failure' + 'configs.reset.failure', ); // Toggle Data Sharing const updateDataSharingConfigs = ( - toggleDataSharingMutation: UseMutationResponse + toggleDataSharingMutation: UseMutationResponse, ) => executeMutation( toggleDataSharingMutation, @@ -536,12 +546,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { ? ServiceStatus.Enable : ServiceStatus.Disable, }, - 'configs.data_sharing.update_failure' + 'configs.data_sharing.update_failure', ); // Toggle SMTP const toggleSMTPConfigs = ( - toggleSMTP: UseMutationResponse + toggleSMTP: UseMutationResponse, ) => executeMutation( toggleSMTP, @@ -550,12 +560,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { ? ServiceStatus.Enable : ServiceStatus.Disable, }, - 'configs.mail_configs.toggle_failure' + 'configs.mail_configs.toggle_failure', ); // Toggle User History Store const toggleUserHistoryStore = ( - toggleUserHistoryStore: UseMutationResponse + toggleUserHistoryStore: UseMutationResponse, ) => executeMutation( toggleUserHistoryStore, @@ -564,11 +574,11 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { ? ServiceStatus.Enable : ServiceStatus.Disable, }, - 'configs.user_history_store.toggle_failure' + 'configs.user_history_store.toggle_failure', ); const updateRateLimitConfigs = ( - updateRateLimitMutation: UseMutationResponse + updateRateLimitMutation: UseMutationResponse, ) => { if (!updatedConfigs?.rateLimitConfigs) { toast.error(t('configs.rate_limit.input_validation_error')); @@ -576,10 +586,10 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { } const rateLimitTtl = String( - updatedConfigs?.rateLimitConfigs.fields.rate_limit_ttl + updatedConfigs?.rateLimitConfigs.fields.rate_limit_ttl, ); const rateLimitMax = String( - updatedConfigs?.rateLimitConfigs.fields.rate_limit_max + updatedConfigs?.rateLimitConfigs.fields.rate_limit_max, ); if (isFieldEmpty(rateLimitTtl) || isFieldEmpty(rateLimitMax)) { @@ -603,12 +613,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { { infraConfigs: rateLimitConfigs, }, - 'configs.rate_limit.update_failure' + 'configs.rate_limit.update_failure', ); }; const updateAuthTokenConfigs = ( - updateAuthTokenMutation: UseMutationResponse + updateAuthTokenMutation: UseMutationResponse, ) => { if (!updatedConfigs?.tokenConfigs) { toast.error(t('configs.auth_providers.token.update_failure')); @@ -617,26 +627,28 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { const jwtSecret = String(updatedConfigs?.tokenConfigs.fields.jwt_secret); const tokenSaltComplexity = String( - updatedConfigs?.tokenConfigs.fields.token_salt_complexity + updatedConfigs?.tokenConfigs.fields.token_salt_complexity, ); const magicLinkTokenValidity = String( - updatedConfigs?.tokenConfigs.fields.magic_link_token_validity + updatedConfigs?.tokenConfigs.fields.magic_link_token_validity, ); const refreshTokenValidity = String( - updatedConfigs?.tokenConfigs.fields.refresh_token_validity + updatedConfigs?.tokenConfigs.fields.refresh_token_validity, ); const accessTokenValidity = String( - updatedConfigs?.tokenConfigs.fields.access_token_validity + updatedConfigs?.tokenConfigs.fields.access_token_validity, ); const sessionSecret = String( - updatedConfigs?.tokenConfigs.fields.session_secret + updatedConfigs?.tokenConfigs.fields.session_secret, ); const sessionCookieName = String( - updatedConfigs?.tokenConfigs.fields.session_cookie_name || '' + updatedConfigs?.tokenConfigs.fields.session_cookie_name || '', ); // Validate cookie name: allow empty (falls back to default), else enforce pattern if (sessionCookieName && !COOKIE_NAME_REGEX.test(sessionCookieName)) { - toast.error(t('configs.auth_providers.token.session_cookie_name_invalid')); + toast.error( + t('configs.auth_providers.token.session_cookie_name_invalid'), + ); return false; } if ( @@ -687,7 +699,7 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) { { infraConfigs: authTokenConfigs, }, - 'configs.auth_providers.token.update_failure' + 'configs.auth_providers.token.update_failure', ); }; diff --git a/packages/hoppscotch-sh-admin/src/helpers/configs.ts b/packages/hoppscotch-sh-admin/src/helpers/configs.ts index 14f51204..eafdff7b 100644 --- a/packages/hoppscotch-sh-admin/src/helpers/configs.ts +++ b/packages/hoppscotch-sh-admin/src/helpers/configs.ts @@ -65,6 +65,12 @@ export type ServerConfigs = { }; }; + localAuth: { + name: string; + enabled: boolean; + fields: Record; + }; + tokenConfigs: { name: string; fields: {