feat: style oauth auto redirect screen

This commit is contained in:
Stavros
2025-09-12 17:30:30 +03:00
parent e001f63eb5
commit 060e20e578
6 changed files with 49 additions and 4 deletions

View File

@@ -52,6 +52,7 @@ var rootCmd = &cobra.Command{
} }
func Execute() { func Execute() {
rootCmd.FParseErrWhitelist.UnknownFlags = true
err := rootCmd.Execute() err := rootCmd.Execute()
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("Failed to execute command") log.Fatal().Err(err).Msg("Failed to execute command")
@@ -92,7 +93,7 @@ func init() {
{"ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup."}, {"ldap-search-filter", "(uid=%s)", "LDAP search filter for user lookup."},
{"resources-dir", "/data/resources", "Path to a directory containing custom resources (e.g. background image)."}, {"resources-dir", "/data/resources", "Path to a directory containing custom resources (e.g. background image)."},
{"database-path", "/data/tinyauth.db", "Path to the Sqlite database file."}, {"database-path", "/data/tinyauth.db", "Path to the Sqlite database file."},
{"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses) for correct client IP detection and for header ACLs."}, {"trusted-proxies", "", "Comma separated list of trusted proxies (IP addresses or CIDRs) for correct client IP detection."},
} }
for _, opt := range configOptions { for _, opt := range configOptions {

View File

@@ -14,6 +14,9 @@
"loginOauthFailSubtitle": "Failed to get OAuth URL", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Continue", "continueTitle": "Continue",
"continueRedirectingTitle": "Redirecting...", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon", "continueRedirectingSubtitle": "You should be redirected to the app soon",

View File

@@ -14,6 +14,9 @@
"loginOauthFailSubtitle": "Failed to get OAuth URL", "loginOauthFailSubtitle": "Failed to get OAuth URL",
"loginOauthSuccessTitle": "Redirecting", "loginOauthSuccessTitle": "Redirecting",
"loginOauthSuccessSubtitle": "Redirecting to your OAuth provider", "loginOauthSuccessSubtitle": "Redirecting to your OAuth provider",
"loginOauthAutoRedirectTitle": "OAuth Auto Redirect",
"loginOauthAutoRedirectSubtitle": "You will be automatically redirected to your OAuth provider to authenticate.",
"loginOauthAutoRedirectButton": "Redirect now",
"continueTitle": "Continue", "continueTitle": "Continue",
"continueRedirectingTitle": "Redirecting...", "continueRedirectingTitle": "Redirecting...",
"continueRedirectingSubtitle": "You should be redirected to the app soon", "continueRedirectingSubtitle": "You should be redirected to the app soon",

View File

@@ -70,7 +70,7 @@ export const ContinuePage = () => {
const reveal = setTimeout(() => { const reveal = setTimeout(() => {
setLoading(false); setLoading(false);
setShowRedirectButton(true); setShowRedirectButton(true);
}, 1000); }, 5000);
return () => { return () => {
clearTimeout(auto); clearTimeout(auto);

View File

@@ -5,12 +5,14 @@ import { MicrosoftIcon } from "@/components/icons/microsoft";
import { OAuthIcon } from "@/components/icons/oauth"; import { OAuthIcon } from "@/components/icons/oauth";
import { PocketIDIcon } from "@/components/icons/pocket-id"; import { PocketIDIcon } from "@/components/icons/pocket-id";
import { TailscaleIcon } from "@/components/icons/tailscale"; import { TailscaleIcon } from "@/components/icons/tailscale";
import { Button } from "@/components/ui/button";
import { import {
Card, Card,
CardHeader, CardHeader,
CardTitle, CardTitle,
CardDescription, CardDescription,
CardContent, CardContent,
CardFooter,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { OAuthButton } from "@/components/ui/oauth-button"; import { OAuthButton } from "@/components/ui/oauth-button";
import { SeperatorWithChildren } from "@/components/ui/separator"; import { SeperatorWithChildren } from "@/components/ui/separator";
@@ -20,7 +22,7 @@ import { useIsMounted } from "@/lib/hooks/use-is-mounted";
import { LoginSchema } from "@/schemas/login-schema"; import { LoginSchema } from "@/schemas/login-schema";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import axios, { AxiosError } from "axios"; import axios, { AxiosError } from "axios";
import { useEffect, useRef } from "react"; import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Navigate, useLocation } from "react-router"; import { Navigate, useLocation } from "react-router";
import { toast } from "sonner"; import { toast } from "sonner";
@@ -39,8 +41,12 @@ export const LoginPage = () => {
const { search } = useLocation(); const { search } = useLocation();
const { t } = useTranslation(); const { t } = useTranslation();
const isMounted = useIsMounted(); const isMounted = useIsMounted();
const [oauthAutoRedirectHandover, setOauthAutoRedirectHandover] =
useState(false);
const [showRedirectButton, setShowRedirectButton] = useState(false);
const redirectTimer = useRef<number | null>(null); const redirectTimer = useRef<number | null>(null);
const redirectButtonTimer = useRef<number | null>(null);
const searchParams = new URLSearchParams(search); const searchParams = new URLSearchParams(search);
const redirectUri = searchParams.get("redirect_uri"); const redirectUri = searchParams.get("redirect_uri");
@@ -67,6 +73,7 @@ export const LoginPage = () => {
}, 500); }, 500);
}, },
onError: () => { onError: () => {
setOauthAutoRedirectHandover(false);
toast.error(t("loginOauthFailTitle"), { toast.error(t("loginOauthFailTitle"), {
description: t("loginOauthFailSubtitle"), description: t("loginOauthFailSubtitle"),
}); });
@@ -112,7 +119,11 @@ export const LoginPage = () => {
!isLoggedIn && !isLoggedIn &&
redirectUri redirectUri
) { ) {
setOauthAutoRedirectHandover(true);
oauthMutation.mutate(oauthAutoRedirect); oauthMutation.mutate(oauthAutoRedirect);
redirectButtonTimer.current = window.setTimeout(() => {
setShowRedirectButton(true);
}, 5000);
} }
} }
}, []); }, []);
@@ -120,6 +131,8 @@ export const LoginPage = () => {
useEffect( useEffect(
() => () => { () => () => {
if (redirectTimer.current) clearTimeout(redirectTimer.current); if (redirectTimer.current) clearTimeout(redirectTimer.current);
if (redirectButtonTimer.current)
clearTimeout(redirectButtonTimer.current);
}, },
[], [],
); );
@@ -137,6 +150,31 @@ export const LoginPage = () => {
return <Navigate to="/logout" replace />; return <Navigate to="/logout" replace />;
} }
if (oauthAutoRedirectHandover) {
return (
<Card className="min-w-xs sm:min-w-sm">
<CardHeader>
<CardTitle className="text-3xl">
{t("loginOauthAutoRedirectTitle")}
</CardTitle>
<CardDescription>
{t("loginOauthAutoRedirectSubtitle")}
</CardDescription>
</CardHeader>
{showRedirectButton && (
<CardFooter className="flex flex-col items-stretch">
<Button
onClick={() => {
window.location.replace(oauthMutation.data?.data.url);
}}
>
{t("loginOauthAutoRedirectButton")}
</Button>
</CardFooter>
)}
</Card>
);
}
return ( return (
<Card className="min-w-xs sm:min-w-sm"> <Card className="min-w-xs sm:min-w-sm">
<CardHeader> <CardHeader>

View File

@@ -22,7 +22,7 @@ type Config struct {
UsersFile string `mapstructure:"users-file"` UsersFile string `mapstructure:"users-file"`
SecureCookie bool `mapstructure:"secure-cookie"` SecureCookie bool `mapstructure:"secure-cookie"`
OAuthWhitelist string `mapstructure:"oauth-whitelist"` OAuthWhitelist string `mapstructure:"oauth-whitelist"`
OAuthAutoRedirect string `mapstructure:"oauth-auto-redirect" validate:"oneof=none github google generic"` OAuthAutoRedirect string `mapstructure:"oauth-auto-redirect"`
SessionExpiry int `mapstructure:"session-expiry"` SessionExpiry int `mapstructure:"session-expiry"`
LogLevel string `mapstructure:"log-level" validate:"oneof=trace debug info warn error fatal panic"` LogLevel string `mapstructure:"log-level" validate:"oneof=trace debug info warn error fatal panic"`
Title string `mapstructure:"app-title"` Title string `mapstructure:"app-title"`