feat: allow custom app and generic oauth title

This commit is contained in:
Stavros
2025-02-23 20:51:56 +02:00
parent 7ee0b645e6
commit 30aab17f06
7 changed files with 69 additions and 34 deletions

View File

@@ -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())

View File

@@ -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) {

View File

@@ -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{})

View File

@@ -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"`
}

View File

@@ -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 (
<Layout>
<Title ta="center">Tinyauth</Title>
<Title ta="center">{title}</Title>
<Paper shadow="md" p="xl" mt={30} radius="md" withBorder>
{oauthProviders.length > 0 && (
<>
@@ -175,7 +175,7 @@ export const LoginPage = () => {
onClick={() => loginOAuthMutation.mutate("generic")}
loading={loginOAuthMutation.isLoading}
>
Generic
{genericName}
</Button>
</Grid.Col>
)}

View File

@@ -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 <Navigate to="/login" />;
@@ -45,7 +45,7 @@ export const LogoutPage = () => {
</Text>
<Text>
You are currently logged in as <Code>{username}</Code>
{oauth && ` using ${capitalize(provider)} OAuth`}. Click the button
{oauth && ` using ${capitalize(provider === "generic" ? genericName : provider)} OAuth`}. Click the button
below to log out.
</Text>
<Button

View File

@@ -7,6 +7,8 @@ export const userContextSchema = z.object({
provider: z.string(),
configuredProviders: z.array(z.string()),
disableContinue: z.boolean(),
title: z.string(),
genericName: z.string(),
});
export type UserContextSchemaType = z.infer<typeof userContextSchema>;