mirror of
				https://github.com/steveiliop56/tinyauth.git
				synced 2025-10-28 04:35:40 +00:00 
			
		
		
		
	feat: add custom forgot password message
This commit is contained in:
		| @@ -26,4 +26,5 @@ SESSION_EXPIRY=7200 | ||||
| LOGIN_TIMEOUT=300 | ||||
| LOGIN_MAX_RETRIES=5 | ||||
| LOG_LEVEL=0 | ||||
| APP_TITLE=Tinyauth SSO | ||||
| APP_TITLE=Tinyauth SSO | ||||
| FORGOT_PASSWORD_MESSAGE=Some message about resetting the password | ||||
							
								
								
									
										15
									
								
								cmd/root.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								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()) | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -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" | ||||
|   }, | ||||
|   | ||||
| @@ -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) => { | ||||
|       <TextInput | ||||
|         label={t("loginUsername")} | ||||
|         placeholder="username" | ||||
|         required | ||||
|         disabled={isPending} | ||||
|         required | ||||
|         withAsterisk={false} | ||||
|         key={form.key("username")} | ||||
|         {...form.getInputProps("username")} | ||||
|       /> | ||||
|       <Group justify="space-between" mb={5} mt="md"> | ||||
|         <Text component="label" htmlFor=".password-input" size="sm" fw={500}> | ||||
|         {t("loginPassword")} | ||||
|         </Text> | ||||
|  | ||||
|         <Anchor href="#" onClick={() => window.location.replace("/forgot-password")} pt={2} fw={500} fz="xs"> | ||||
|           {t('forgotPasswordTitle')} | ||||
|         </Anchor> | ||||
|       </Group> | ||||
|       <PasswordInput | ||||
|         label={t("loginPassword")} | ||||
|         className="password-input" | ||||
|         placeholder="password" | ||||
|         required | ||||
|         mt="md" | ||||
|         disabled={isPending} | ||||
|         key={form.key("password")} | ||||
|         {...form.getInputProps("password")} | ||||
|   | ||||
| @@ -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 (<Code>{{domain}}</Code>). Are you sure you want to continue?", | ||||
|     "cancelTitle": "Cancel" | ||||
|     "cancelTitle": "Cancel", | ||||
|     "forgotPasswordTitle": "Forgot your password?" | ||||
| } | ||||
| @@ -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 (<Code>{{domain}}</Code>). Are you sure you want to continue?", | ||||
|     "cancelTitle": "Cancel" | ||||
|     "cancelTitle": "Cancel", | ||||
|     "forgotPasswordTitle": "Forgot your password?" | ||||
| } | ||||
| @@ -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( | ||||
|                 <Route path="/continue" element={<ContinuePage />} /> | ||||
|                 <Route path="/unauthorized" element={<UnauthorizedPage />} /> | ||||
|                 <Route path="/error" element={<InternalServerError />} /> | ||||
|                 <Route path="/forgot-password" element={<ForgotPasswordPage />} /> | ||||
|                 <Route path="*" element={<NotFoundPage />} /> | ||||
|               </Routes> | ||||
|             </BrowserRouter> | ||||
|   | ||||
							
								
								
									
										25
									
								
								frontend/src/pages/forgot-password-page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								frontend/src/pages/forgot-password-page.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -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 ( | ||||
|     <Layout> | ||||
|       <Paper shadow="md" p={30} mt={30} radius="md" withBorder> | ||||
|         <Text size="xl" fw={700}> | ||||
|           {t("forgotPasswordTitle")} | ||||
|         </Text> | ||||
|         <TypographyStylesProvider> | ||||
|             <Markdown> | ||||
|                 {forgotPasswordMessage} | ||||
|             </Markdown> | ||||
|         </TypographyStylesProvider> | ||||
|       </Paper> | ||||
|     </Layout> | ||||
|   ); | ||||
| }; | ||||
| @@ -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<typeof appContextSchema>; | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Stavros
					Stavros