From 4093a91e12e1cabefca76f29acaa043c80f6cd82 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sun, 31 Aug 2025 01:11:18 +0300 Subject: [PATCH] wip --- cmd/root.go | 1 - .../domain-warning/domain-warning.tsx | 36 ++++++++ frontend/src/components/layout/layout.tsx | 16 +++- frontend/src/components/ui/button.tsx | 2 +- frontend/src/lib/i18n/locales/en-US.json | 5 +- frontend/src/lib/i18n/locales/en.json | 5 +- frontend/src/pages/continue-page.tsx | 82 +++++++++++-------- frontend/src/schemas/app-context-schema.ts | 4 +- internal/bootstrap/app_bootstrap.go | 4 +- internal/config/config.go | 1 - internal/controller/context_controller.go | 15 ++-- 11 files changed, 117 insertions(+), 54 deletions(-) create mode 100644 frontend/src/components/domain-warning/domain-warning.tsx diff --git a/cmd/root.go b/cmd/root.go index 3ae7292..171e043 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,7 +95,6 @@ func init() { {"generic-user-url", "", "Generic OAuth user info URL."}, {"generic-name", "Generic", "Generic OAuth provider name."}, {"generic-skip-ssl", false, "Skip SSL verification for the generic OAuth provider."}, - {"disable-continue", false, "Disable continue screen and redirect to app directly."}, {"oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth."}, {"oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)"}, {"session-expiry", 86400, "Session (cookie) expiration time in seconds."}, diff --git a/frontend/src/components/domain-warning/domain-warning.tsx b/frontend/src/components/domain-warning/domain-warning.tsx new file mode 100644 index 0000000..10c836e --- /dev/null +++ b/frontend/src/components/domain-warning/domain-warning.tsx @@ -0,0 +1,36 @@ +import { + Card, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "../ui/card"; +import { Button } from "../ui/button"; + +interface Props { + onClick: () => void; + appUrl: string; + currentUrl: string; +} + +export const DomainWarning = (props: Props) => { + const { onClick, appUrl, currentUrl } = props; + + return ( + + + Incorrect Domain + + This instance is configured to be accessed from {appUrl}, + but {currentUrl} is being used. Authentication will most + likely fail if you proceed. + + + + + + + ); +}; diff --git a/frontend/src/components/layout/layout.tsx b/frontend/src/components/layout/layout.tsx index 773185b..e14e015 100644 --- a/frontend/src/components/layout/layout.tsx +++ b/frontend/src/components/layout/layout.tsx @@ -1,9 +1,13 @@ import { useAppContext } from "@/context/app-context"; import { LanguageSelector } from "../language/language"; import { Outlet } from "react-router"; +import { useState } from "react"; +import { DomainWarning } from "../domain-warning/domain-warning"; export const Layout = () => { - const { backgroundImage } = useAppContext(); + const { backgroundImage, appUrl } = useAppContext(); + const [ignoreDomainWarning, setIgnoreDomainWarning] = useState(false); + const currentUrl = window.location.origin; return (
{ }} > - + {appUrl !== currentUrl && !ignoreDomainWarning ? ( + setIgnoreDomainWarning(true)} + appUrl={appUrl} + currentUrl={currentUrl} + /> + ) : ( + + )}
); }; diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index fbb5b27..4badcc1 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -22,7 +22,7 @@ const buttonVariants = cva( "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", link: "text-primary underline-offset-4 hover:underline", warning: - "bg-amber-500 text-white shadow-xs hover:bg-amber-400 focus-visible:ring-amber-200/20 dark:focus-visible:ring-amber-400/40 dark:bg-amber-600", + "bg-amber-500 text-white shadow-xs hover:bg-amber-400 focus-visible:ring-amber-200/20 dark:focus-visible:ring-amber-400/40", }, size: { default: "h-9 px-4 py-2 has-[>svg]:px-3", diff --git a/frontend/src/lib/i18n/locales/en-US.json b/frontend/src/lib/i18n/locales/en-US.json index 74e422f..a90d581 100644 --- a/frontend/src/lib/i18n/locales/en-US.json +++ b/frontend/src/lib/i18n/locales/en-US.json @@ -20,8 +20,7 @@ "continueInvalidRedirectSubtitle": "The redirect URL is invalid", "continueInsecureRedirectTitle": "Insecure redirect", "continueInsecureRedirectSubtitle": "You are trying to redirect from https to http which is not secure. Are you sure you want to continue?", - "continueTitle": "Continue", - "continueSubtitle": "Click the button to continue to your app.", + "continueRedirectManually": "Redirect me manually", "logoutFailTitle": "Failed to log out", "logoutFailSubtitle": "Please try again", "logoutSuccessTitle": "Logged out", @@ -45,7 +44,7 @@ "unauthorizedIpSubtitle": "Your IP address {{ip}} is not authorized to access the resource {{resource}}.", "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?", + "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain ({{rootDomain}}). Are you sure you want to continue?", "cancelTitle": "Cancel", "forgotPasswordTitle": "Forgot your password?", "failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.", diff --git a/frontend/src/lib/i18n/locales/en.json b/frontend/src/lib/i18n/locales/en.json index 74e422f..a90d581 100644 --- a/frontend/src/lib/i18n/locales/en.json +++ b/frontend/src/lib/i18n/locales/en.json @@ -20,8 +20,7 @@ "continueInvalidRedirectSubtitle": "The redirect URL is invalid", "continueInsecureRedirectTitle": "Insecure redirect", "continueInsecureRedirectSubtitle": "You are trying to redirect from https to http which is not secure. Are you sure you want to continue?", - "continueTitle": "Continue", - "continueSubtitle": "Click the button to continue to your app.", + "continueRedirectManually": "Redirect me manually", "logoutFailTitle": "Failed to log out", "logoutFailSubtitle": "Please try again", "logoutSuccessTitle": "Logged out", @@ -45,7 +44,7 @@ "unauthorizedIpSubtitle": "Your IP address {{ip}} is not authorized to access the resource {{resource}}.", "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?", + "untrustedRedirectSubtitle": "You are trying to redirect to a domain that does not match your configured domain ({{rootDomain}}). Are you sure you want to continue?", "cancelTitle": "Cancel", "forgotPasswordTitle": "Forgot your password?", "failedToFetchProvidersTitle": "Failed to load authentication providers. Please check your configuration.", diff --git a/frontend/src/pages/continue-page.tsx b/frontend/src/pages/continue-page.tsx index cc4d432..f97752f 100644 --- a/frontend/src/pages/continue-page.tsx +++ b/frontend/src/pages/continue-page.tsx @@ -12,7 +12,7 @@ import { isValidUrl } from "@/lib/utils"; import { Trans, useTranslation } from "react-i18next"; import { Navigate, useLocation, useNavigate } from "react-router"; import DOMPurify from "dompurify"; -import { useState } from "react"; +import { useEffect, useState } from "react"; export const ContinuePage = () => { const { isLoggedIn } = useUserContext(); @@ -21,9 +21,10 @@ export const ContinuePage = () => { return ; } - const { domain, disableContinue } = useAppContext(); + const { rootDomain } = useAppContext(); const { search } = useLocation(); const [loading, setLoading] = useState(false); + const [showRedirectButton, setShowRedirectButton] = useState(false); const searchParams = new URLSearchParams(search); const redirectURI = searchParams.get("redirect_uri"); @@ -36,21 +37,20 @@ export const ContinuePage = () => { return ; } - const handleRedirect = () => { - setLoading(true); - window.location.href = DOMPurify.sanitize(redirectURI); - } - - if (disableContinue) { - handleRedirect(); - } - const { t } = useTranslation(); const navigate = useNavigate(); - const url = new URL(redirectURI); + const handleRedirect = () => { + setLoading(true); + window.location.href = DOMPurify.sanitize(redirectURI); + }; - if (!(url.hostname == domain) && !url.hostname.endsWith(`.${domain}`)) { + const redirectURLObj = new URL(redirectURI); + + if ( + !(redirectURLObj.hostname == rootDomain) && + !redirectURLObj.hostname.endsWith(`.${rootDomain}`) + ) { return ( @@ -64,7 +64,7 @@ export const ContinuePage = () => { components={{ code: , }} - values={{ domain }} + values={{ rootDomain }} /> @@ -76,7 +76,11 @@ export const ContinuePage = () => { > {t("continueTitle")} - @@ -84,7 +88,10 @@ export const ContinuePage = () => { ); } - if (url.protocol === "http:" && window.location.protocol === "https:") { + if ( + redirectURLObj.protocol === "http:" && + window.location.protocol === "https:" + ) { return ( @@ -102,14 +109,14 @@ export const ContinuePage = () => { - - @@ -117,20 +124,31 @@ export const ContinuePage = () => { ); } + useEffect(() => { + setTimeout(() => { + handleRedirect(); + }, 100); + setTimeout(() => { + setLoading(false); + setShowRedirectButton(true); + }, 1000); + }, []); + return ( - {t("continueTitle")} - {t("continueSubtitle")} + + {t("continueRedirectingTitle")} + + {t("continueRedirectingSubtitle")} - - - + {showRedirectButton && ( + + + + )} ); }; diff --git a/frontend/src/schemas/app-context-schema.ts b/frontend/src/schemas/app-context-schema.ts index 31ded49..c5d6d85 100644 --- a/frontend/src/schemas/app-context-schema.ts +++ b/frontend/src/schemas/app-context-schema.ts @@ -2,10 +2,10 @@ import { z } from "zod"; export const appContextSchema = z.object({ configuredProviders: z.array(z.string()), - disableContinue: z.boolean(), title: z.string(), genericName: z.string(), - domain: z.string(), + appUrl: z.string(), + rootDomain: z.string(), forgotPasswordMessage: z.string(), oauthAutoRedirect: z.enum(["none", "github", "google", "generic"]), backgroundImage: z.string(), diff --git a/internal/bootstrap/app_bootstrap.go b/internal/bootstrap/app_bootstrap.go index af75aa6..e941772 100644 --- a/internal/bootstrap/app_bootstrap.go +++ b/internal/bootstrap/app_bootstrap.go @@ -177,10 +177,10 @@ func (app *BootstrapApp) Setup() error { // Create controllers contextController := controller.NewContextController(controller.ContextControllerConfig{ ConfiguredProviders: configuredProviders, - DisableContinue: app.Config.DisableContinue, Title: app.Config.Title, GenericName: app.Config.GenericName, - Domain: domain, + AppURL: app.Config.AppURL, + RootDomain: domain, ForgotPasswordMessage: app.Config.ForgotPasswordMessage, BackgroundImage: app.Config.BackgroundImage, OAuthAutoRedirect: app.Config.OAuthAutoRedirect, diff --git a/internal/config/config.go b/internal/config/config.go index c959e26..82050de 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -36,7 +36,6 @@ type Config struct { GenericUserURL string `mapstructure:"generic-user-url"` GenericName string `mapstructure:"generic-name"` GenericSkipSSL bool `mapstructure:"generic-skip-ssl"` - DisableContinue bool `mapstructure:"disable-continue"` OAuthWhitelist string `mapstructure:"oauth-whitelist"` OAuthAutoRedirect string `mapstructure:"oauth-auto-redirect" validate:"oneof=none github google generic"` SessionExpiry int `mapstructure:"session-expiry"` diff --git a/internal/controller/context_controller.go b/internal/controller/context_controller.go index c7570f0..f2bd068 100644 --- a/internal/controller/context_controller.go +++ b/internal/controller/context_controller.go @@ -15,7 +15,7 @@ type UserContextResponse struct { Name string `json:"name"` Email string `json:"email"` Provider string `json:"provider"` - Oauth bool `json:"oauth"` + OAuth bool `json:"oauth"` TotpPending bool `json:"totpPending"` } @@ -23,10 +23,10 @@ type AppContextResponse 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"` + AppURL string `json:"appUrl"` + RootDomain string `json:"rootDomain"` ForgotPasswordMessage string `json:"forgotPasswordMessage"` BackgroundImage string `json:"backgroundImage"` OAuthAutoRedirect string `json:"oauthAutoRedirect"` @@ -37,7 +37,8 @@ type ContextControllerConfig struct { DisableContinue bool Title string GenericName string - Domain string + AppURL string + RootDomain string ForgotPasswordMessage string BackgroundImage string OAuthAutoRedirect string @@ -72,7 +73,7 @@ func (controller *ContextController) userContextHandler(c *gin.Context) { Name: context.Name, Email: context.Email, Provider: context.Provider, - Oauth: context.OAuth, + OAuth: context.OAuth, TotpPending: context.TotpPending, } @@ -93,10 +94,10 @@ func (controller *ContextController) appContextHandler(c *gin.Context) { Status: 200, Message: "Success", ConfiguredProviders: controller.Config.ConfiguredProviders, - DisableContinue: controller.Config.DisableContinue, Title: controller.Config.Title, GenericName: controller.Config.GenericName, - Domain: controller.Config.Domain, + AppURL: controller.Config.AppURL, + RootDomain: controller.Config.RootDomain, ForgotPasswordMessage: controller.Config.ForgotPasswordMessage, BackgroundImage: controller.Config.BackgroundImage, OAuthAutoRedirect: controller.Config.OAuthAutoRedirect,