From 30aab17f066cfe063164bc56b44d486e01073f45 Mon Sep 17 00:00:00 2001 From: Stavros Date: Sun, 23 Feb 2025 20:51:56 +0200 Subject: [PATCH] feat: allow custom app and generic oauth title --- cmd/root.go | 8 +++- internal/api/api.go | 59 ++++++++++++++----------- internal/api/api_test.go | 4 +- internal/types/types.go | 20 ++++++++- site/src/pages/login-page.tsx | 6 +-- site/src/pages/logout-page.tsx | 4 +- site/src/schemas/user-context-schema.ts | 2 + 7 files changed, 69 insertions(+), 34 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 4b8f6d8..ac60ce3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -113,7 +113,9 @@ var rootCmd = &cobra.Command{ AppURL: config.AppURL, CookieSecure: config.CookieSecure, DisableContinue: config.DisableContinue, - CookieExpiry: config.SessionExpiry, + SessionExpiry: config.SessionExpiry, + Title: config.Title, + GenericName: config.GenericName, }, hooks, auth, providers) // Setup routes @@ -169,10 +171,12 @@ func init() { rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.") rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.") rootCmd.Flags().String("generic-user-url", "", "Generic OAuth user info URL.") + rootCmd.Flags().String("generic-name", "Generic", "Generic OAuth provider name.") rootCmd.Flags().Bool("disable-continue", false, "Disable continue screen and redirect to app directly.") rootCmd.Flags().String("oauth-whitelist", "", "Comma separated list of email addresses to whitelist when using OAuth.") rootCmd.Flags().Int("session-expiry", 86400, "Session (cookie) expiration time in seconds.") rootCmd.Flags().Int("log-level", 1, "Log level.") + rootCmd.Flags().String("app-title", "Tinyauth", "Title of the app.") // Bind flags to environment viper.BindEnv("port", "PORT") @@ -199,10 +203,12 @@ func init() { viper.BindEnv("generic-auth-url", "GENERIC_AUTH_URL") viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL") viper.BindEnv("generic-user-url", "GENERIC_USER_URL") + viper.BindEnv("generic-name", "GENERIC_NAME") viper.BindEnv("disable-continue", "DISABLE_CONTINUE") viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST") viper.BindEnv("session-expiry", "SESSION_EXPIRY") viper.BindEnv("log-level", "LOG_LEVEL") + viper.BindEnv("app-title", "APP_TITLE") // Bind flags to viper viper.BindPFlags(rootCmd.Flags()) diff --git a/internal/api/api.go b/internal/api/api.go index e126ba1..b95113d 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -84,7 +84,7 @@ func (api *API) Init() { Path: "/", HttpOnly: true, Secure: api.Config.CookieSecure, - MaxAge: api.Config.CookieExpiry, + MaxAge: api.Config.SessionExpiry, }) router.Use(sessions.Sessions("tinyauth", store)) @@ -332,36 +332,45 @@ func (api *API) SetupRoutes() { configuredProviders = append(configuredProviders, "username") } - // We are not logged in so return unauthorized + // Fill status struct with data from user context and api config + status := types.Status{ + Username: userContext.Username, + IsLoggedIn: userContext.IsLoggedIn, + Oauth: userContext.OAuth, + Provider: userContext.Provider, + ConfiguredProviders: configuredProviders, + DisableContinue: api.Config.DisableContinue, + Title: api.Config.Title, + GenericName: api.Config.GenericName, + } + + // If we are not logged in we set the status to 401 and add the WWW-Authenticate header else we set it to 200 if !userContext.IsLoggedIn { log.Debug().Msg("Unauthorized") c.Header("WWW-Authenticate", "Basic realm=\"tinyauth\"") - c.JSON(200, gin.H{ - "status": 200, - "message": "Unauthorized", - "username": "", - "isLoggedIn": false, - "oauth": false, - "provider": "", - "configuredProviders": configuredProviders, - "disableContinue": api.Config.DisableContinue, - }) - return + status.Status = 401 + status.Message = "Unauthorized" + } else { + log.Debug().Interface("userContext", userContext).Strs("configuredProviders", configuredProviders).Bool("disableContinue", api.Config.DisableContinue).Msg("Authenticated") + status.Status = 200 + status.Message = "Authenticated" } - log.Debug().Interface("userContext", userContext).Strs("configuredProviders", configuredProviders).Bool("disableContinue", api.Config.DisableContinue).Msg("Authenticated") + // // Marshall status to JSON + // statusJson, marshalErr := json.Marshal(status) - // We are logged in so return our user context - c.JSON(200, gin.H{ - "status": 200, - "message": "Authenticated", - "username": userContext.Username, - "isLoggedIn": userContext.IsLoggedIn, - "oauth": userContext.OAuth, - "provider": userContext.Provider, - "configuredProviders": configuredProviders, - "disableContinue": api.Config.DisableContinue, - }) + // // Handle error + // if marshalErr != nil { + // log.Error().Err(marshalErr).Msg("Failed to marshal status") + // c.JSON(500, gin.H{ + // "status": 500, + // "message": "Internal Server Error", + // }) + // return + // } + + // Return data + c.JSON(200, status) }) api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) { diff --git a/internal/api/api_test.go b/internal/api/api_test.go index 53151fe..7eb5fe9 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -23,7 +23,7 @@ var apiConfig = types.APIConfig{ Secret: "super-secret-api-thing-for-tests", // It is 32 chars long AppURL: "http://tinyauth.localhost", CookieSecure: false, - CookieExpiry: 3600, + SessionExpiry: 3600, DisableContinue: false, } @@ -55,7 +55,7 @@ func getAPI(t *testing.T) *api.API { Username: user.Username, Password: user.Password, }, - }, nil, apiConfig.CookieExpiry) + }, nil, apiConfig.SessionExpiry) // Create providers service providers := providers.NewProviders(types.OAuthConfig{}) diff --git a/internal/types/types.go b/internal/types/types.go index 591ca63..7a4f7d8 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -48,10 +48,12 @@ type Config struct { GenericAuthURL string `mapstructure:"generic-auth-url"` GenericTokenURL string `mapstructure:"generic-token-url"` GenericUserURL string `mapstructure:"generic-user-url"` + GenericName string `mapstructure:"generic-name"` DisableContinue bool `mapstructure:"disable-continue"` OAuthWhitelist string `mapstructure:"oauth-whitelist"` SessionExpiry int `mapstructure:"session-expiry"` LogLevel int8 `mapstructure:"log-level" validate:"min=-1,max=5"` + Title string `mapstructure:"app-title"` } // UserContext is the context for the user @@ -69,8 +71,10 @@ type APIConfig struct { Secret string AppURL string CookieSecure bool - CookieExpiry int + SessionExpiry int DisableContinue bool + GenericName string + Title string } // OAuthConfig is the configuration for the providers @@ -129,3 +133,17 @@ type TailscaleQuery struct { type Proxy struct { Proxy string `uri:"proxy" binding:"required"` } + +// Status response +type Status struct { + Status int `json:"status"` + Message string `json:"message"` + IsLoggedIn bool `json:"isLoggedIn"` + Username string `json:"username"` + Provider string `json:"provider"` + Oauth bool `json:"oauth"` + ConfiguredProviders []string `json:"configuredProviders"` + DisableContinue bool `json:"disableContinue"` + Title string `json:"title"` + GenericName string `json:"genericName"` +} diff --git a/site/src/pages/login-page.tsx b/site/src/pages/login-page.tsx index 3686133..b11210d 100644 --- a/site/src/pages/login-page.tsx +++ b/site/src/pages/login-page.tsx @@ -27,7 +27,7 @@ export const LoginPage = () => { const params = new URLSearchParams(queryString); const redirectUri = params.get("redirect_uri") ?? ""; - const { isLoggedIn, configuredProviders } = useUserContext(); + const { isLoggedIn, configuredProviders, title, genericName } = useUserContext(); const oauthProviders = configuredProviders.filter( (value) => value !== "username", @@ -111,7 +111,7 @@ export const LoginPage = () => { return ( - Tinyauth + {title} {oauthProviders.length > 0 && ( <> @@ -175,7 +175,7 @@ export const LoginPage = () => { onClick={() => loginOAuthMutation.mutate("generic")} loading={loginOAuthMutation.isLoading} > - Generic + {genericName} )} diff --git a/site/src/pages/logout-page.tsx b/site/src/pages/logout-page.tsx index b02325a..9f36a19 100644 --- a/site/src/pages/logout-page.tsx +++ b/site/src/pages/logout-page.tsx @@ -8,7 +8,7 @@ import { Layout } from "../components/layouts/layout"; import { capitalize } from "../utils/utils"; export const LogoutPage = () => { - const { isLoggedIn, username, oauth, provider } = useUserContext(); + const { isLoggedIn, username, oauth, provider, genericName } = useUserContext(); if (!isLoggedIn) { return ; @@ -45,7 +45,7 @@ export const LogoutPage = () => { You are currently logged in as {username} - {oauth && ` using ${capitalize(provider)} OAuth`}. Click the button + {oauth && ` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}. Click the button below to log out.