From db43f1cb7a13562f984897b78bba7e7da2ede898 Mon Sep 17 00:00:00 2001 From: Stavros Date: Wed, 23 Apr 2025 14:31:31 +0300 Subject: [PATCH] feat: add custom forgot password message --- .env.example | 3 ++- cmd/root.go | 15 +++++++----- frontend/bun.lockb | Bin 107200 -> 141204 bytes frontend/package.json | 1 + frontend/src/components/auth/login-forn.tsx | 17 +++++++++---- frontend/src/lib/i18n/locales/en-US.json | 3 ++- frontend/src/lib/i18n/locales/en.json | 3 ++- frontend/src/main.tsx | 2 ++ frontend/src/pages/forgot-password-page.tsx | 25 ++++++++++++++++++++ frontend/src/schemas/app-context-schema.ts | 1 + internal/handlers/handlers.go | 15 ++++++------ internal/types/api.go | 15 ++++++------ internal/types/config.go | 14 ++++++----- 13 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 frontend/src/pages/forgot-password-page.tsx diff --git a/.env.example b/.env.example index ef7f73f..e131e6b 100644 --- a/.env.example +++ b/.env.example @@ -26,4 +26,5 @@ SESSION_EXPIRY=7200 LOGIN_TIMEOUT=300 LOGIN_MAX_RETRIES=5 LOG_LEVEL=0 -APP_TITLE=Tinyauth SSO \ No newline at end of file +APP_TITLE=Tinyauth SSO +FORGOT_PASSWORD_MESSAGE=Some message about resetting the password \ No newline at end of file diff --git a/cmd/root.go b/cmd/root.go index ae3e935..fe10166 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,12 +84,13 @@ var rootCmd = &cobra.Command{ // Create handlers config handlersConfig := types.HandlersConfig{ - AppURL: config.AppURL, - DisableContinue: config.DisableContinue, - Title: config.Title, - GenericName: config.GenericName, - CookieSecure: config.CookieSecure, - Domain: domain, + AppURL: config.AppURL, + DisableContinue: config.DisableContinue, + Title: config.Title, + GenericName: config.GenericName, + CookieSecure: config.CookieSecure, + Domain: domain, + ForgotPasswordMessage: config.FogotPasswordMessage, } // Create api config @@ -196,6 +197,7 @@ func init() { rootCmd.Flags().Int("login-max-retries", 5, "Maximum login attempts before timeout (0 to disable).") rootCmd.Flags().Int("log-level", 1, "Log level.") rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.") + rootCmd.Flags().String("forgot-password-message", "You can reset your password by changing the `USERS` environment variable.", "Message to show on the forgot password page.") // Bind flags to environment viper.BindEnv("port", "PORT") @@ -227,6 +229,7 @@ func init() { viper.BindEnv("app-title", "APP_TITLE") viper.BindEnv("login-timeout", "LOGIN_TIMEOUT") viper.BindEnv("login-max-retries", "LOGIN_MAX_RETRIES") + viper.BindEnv("forgot-password-message", "FORGOT_PASSWORD_MESSAGE") // Bind flags to viper viper.BindPFlags(rootCmd.Flags()) diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 1dd4596c72722ac15c79f68e4f05651ef4ce5877..f5d3b69d28ec64f5ef81c9c4a844c18261042a3f 100755 GIT binary patch delta 40785 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

lqVkhZM{j?!%8od#B{x!A?udlTdFrofj|RH;9}X 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 delta 18659 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 diff --git a/frontend/package.json b/frontend/package.json index 460ee42..8d15784 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.4.1", + "react-markdown": "^10.1.0", "react-router": "^7.1.3", "zod": "^3.24.1" }, diff --git a/frontend/src/components/auth/login-forn.tsx b/frontend/src/components/auth/login-forn.tsx index 0f0616d..33c4c2a 100644 --- a/frontend/src/components/auth/login-forn.tsx +++ b/frontend/src/components/auth/login-forn.tsx @@ -1,4 +1,4 @@ -import { TextInput, PasswordInput, Button } from "@mantine/core"; +import { TextInput, PasswordInput, Button, Anchor, Group, Text } from "@mantine/core"; import { useForm, zodResolver } from "@mantine/form"; import { LoginFormValues, loginSchema } from "../../schemas/login-schema"; import { useTranslation } from "react-i18next"; @@ -26,16 +26,25 @@ export const LoginForm = (props: LoginFormProps) => { + + + {t("loginPassword")} + + + window.location.replace("/forgot-password")} pt={2} fw={500} fz="xs"> + {t('forgotPasswordTitle')} + + {{domain}}). Are you sure you want to continue?", - "cancelTitle": "Cancel" + "cancelTitle": "Cancel", + "forgotPasswordTitle": "Forgot your password?" } \ No newline at end of file diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index b31e2ee..12135fe 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -45,5 +45,6 @@ "unauthorizedButton": "Try again", "untrustedRedirectTitle": "Untrusted redirect", "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain ({{domain}}). Are you sure you want to continue?", - "cancelTitle": "Cancel" + "cancelTitle": "Cancel", + "forgotPasswordTitle": "Forgot your password?" } \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index a5ae8a3..c5a39d6 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -18,6 +18,7 @@ import { InternalServerError } from "./pages/internal-server-error.tsx"; import { TotpPage } from "./pages/totp-page.tsx"; import { AppContextProvider } from "./context/app-context.tsx"; import "./lib/i18n/i18n.ts"; +import { ForgotPasswordPage } from "./pages/forgot-password-page.tsx"; const queryClient = new QueryClient(); @@ -37,6 +38,7 @@ createRoot(document.getElementById("root")!).render( } /> } /> } /> + } /> } /> diff --git a/frontend/src/pages/forgot-password-page.tsx b/frontend/src/pages/forgot-password-page.tsx new file mode 100644 index 0000000..8e06cf2 --- /dev/null +++ b/frontend/src/pages/forgot-password-page.tsx @@ -0,0 +1,25 @@ +import { Paper, Text, TypographyStylesProvider } from "@mantine/core"; +import { Layout } from "../components/layouts/layout"; +import { useTranslation } from "react-i18next"; +import { useAppContext } from "../context/app-context"; +import Markdown from 'react-markdown' + +export const ForgotPasswordPage = () => { + const { t } = useTranslation(); + const { forgotPasswordMessage } = useAppContext(); + + return ( + + + + {t("forgotPasswordTitle")} + + + + {forgotPasswordMessage} + + + + + ); +}; diff --git a/frontend/src/schemas/app-context-schema.ts b/frontend/src/schemas/app-context-schema.ts index 3738c3f..5a2b519 100644 --- a/frontend/src/schemas/app-context-schema.ts +++ b/frontend/src/schemas/app-context-schema.ts @@ -6,6 +6,7 @@ export const appContextSchema = z.object({ title: z.string(), genericName: z.string(), domain: z.string(), + forgotPasswordMessage: z.string(), }); export type AppContextSchemaType = z.infer; diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 9e48764..6e80851 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -440,13 +440,14 @@ func (h *Handlers) AppHandler(c *gin.Context) { // Create app context struct appContext := types.AppContext{ - Status: 200, - Message: "OK", - ConfiguredProviders: configuredProviders, - DisableContinue: h.Config.DisableContinue, - Title: h.Config.Title, - GenericName: h.Config.GenericName, - Domain: h.Config.Domain, + Status: 200, + Message: "OK", + ConfiguredProviders: configuredProviders, + DisableContinue: h.Config.DisableContinue, + Title: h.Config.Title, + GenericName: h.Config.GenericName, + Domain: h.Config.Domain, + ForgotPasswordMessage: h.Config.ForgotPasswordMessage, } // Return app context diff --git a/internal/types/api.go b/internal/types/api.go index e9307a6..144bb56 100644 --- a/internal/types/api.go +++ b/internal/types/api.go @@ -40,13 +40,14 @@ type UserContextResponse struct { // App Context is the response for the app context endpoint type AppContext struct { - Status int `json:"status"` - Message string `json:"message"` - ConfiguredProviders []string `json:"configuredProviders"` - DisableContinue bool `json:"disableContinue"` - Title string `json:"title"` - GenericName string `json:"genericName"` - Domain string `json:"domain"` + Status int `json:"status"` + Message string `json:"message"` + ConfiguredProviders []string `json:"configuredProviders"` + DisableContinue bool `json:"disableContinue"` + Title string `json:"title"` + GenericName string `json:"genericName"` + Domain string `json:"domain"` + ForgotPasswordMessage string `json:"forgotPasswordMessage"` } // Totp request is the request for the totp endpoint diff --git a/internal/types/config.go b/internal/types/config.go index 6092a48..88e9169 100644 --- a/internal/types/config.go +++ b/internal/types/config.go @@ -32,16 +32,18 @@ type Config struct { EnvFile string `mapstructure:"env-file"` LoginTimeout int `mapstructure:"login-timeout"` LoginMaxRetries int `mapstructure:"login-max-retries"` + FogotPasswordMessage string `mapstructure:"forgot-password-message" validate:"required"` } // Server configuration type HandlersConfig struct { - AppURL string - Domain string - CookieSecure bool - DisableContinue bool - GenericName string - Title string + AppURL string + Domain string + CookieSecure bool + DisableContinue bool + GenericName string + Title string + ForgotPasswordMessage string } // OAuthConfig is the configuration for the providers