Compare commits

..

2 Commits

Author SHA1 Message Date
Stavros
07933ad71c tests: fix tests 2025-05-22 22:30:54 +03:00
Stavros
7151832cc7 feat: generate a unique id for the cookie names based on the domain 2025-05-22 22:21:29 +03:00
13 changed files with 93 additions and 70 deletions

View File

@@ -30,4 +30,3 @@ APP_TITLE=Tinyauth SSO
FORGOT_PASSWORD_MESSAGE=Some message about resetting the password
OAUTH_AUTO_REDIRECT=none
BACKGROUND_IMAGE=some_image_url
GENERIC_SKIP_SSL=false

View File

@@ -52,4 +52,7 @@ COPY --from=builder /tinyauth/tinyauth ./
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=5s \
CMD curl -f http://localhost:3000/api/healthcheck || exit 1
ENTRYPOINT ["./tinyauth"]

View File

@@ -2,6 +2,7 @@ package cmd
import (
"errors"
"fmt"
"os"
"strings"
"time"
@@ -67,6 +68,12 @@ var rootCmd = &cobra.Command{
HandleError(err, "Failed to get upper domain")
log.Info().Str("domain", domain).Msg("Using domain for cookie store")
// Generate cookie name
cookieId := utils.GenerateIdentifier(strings.Split(domain, ".")[0])
sessionCookieName := fmt.Sprintf("%s-%s", constants.SessionCookieName, cookieId)
csrfCookieName := fmt.Sprintf("%s-%s", constants.CsrfCookieName, cookieId)
redirectCookieName := fmt.Sprintf("%s-%s", constants.RedirectCookieName, cookieId)
// Create OAuth config
oauthConfig := types.OAuthConfig{
GithubClientId: config.GithubClientId,
@@ -79,7 +86,6 @@ var rootCmd = &cobra.Command{
GenericAuthURL: config.GenericAuthURL,
GenericTokenURL: config.GenericTokenURL,
GenericUserURL: config.GenericUserURL,
GenericSkipSSL: config.GenericSkipSSL,
AppURL: config.AppURL,
}
@@ -94,6 +100,8 @@ var rootCmd = &cobra.Command{
ForgotPasswordMessage: config.FogotPasswordMessage,
BackgroundImage: config.BackgroundImage,
OAuthAutoRedirect: config.OAuthAutoRedirect,
CsrfCookieName: csrfCookieName,
RedirectCookieName: redirectCookieName,
}
// Create api config
@@ -112,6 +120,7 @@ var rootCmd = &cobra.Command{
Domain: domain,
LoginTimeout: config.LoginTimeout,
LoginMaxRetries: config.LoginMaxRetries,
SessionCookieName: sessionCookieName,
}
// Create hooks config
@@ -198,7 +207,6 @@ func init() {
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("generic-skip-ssl", false, "Skip SSL verification for the generic OAuth provider.")
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().String("oauth-auto-redirect", "none", "Auto redirect to the specified OAuth provider if configured. (available providers: github, google, generic)")
@@ -233,7 +241,6 @@ func init() {
viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL")
viper.BindEnv("generic-user-url", "GENERIC_USER_URL")
viper.BindEnv("generic-name", "GENERIC_NAME")
viper.BindEnv("generic-skip-ssl", "GENERIC_SKIP_SSL")
viper.BindEnv("disable-continue", "DISABLE_CONTINUE")
viper.BindEnv("oauth-whitelist", "OAUTH_WHITELIST")
viper.BindEnv("oauth-auto-redirect", "OAUTH_AUTO_REDIRECT")

View File

@@ -29,7 +29,7 @@ export const LoginPage = () => {
return <Navigate to="/logout" />;
}
const { configuredProviders, title, oauthAutoRedirect, genericName } = useAppContext();
const { configuredProviders, title, oauthAutoRedirect } = useAppContext();
const { search } = useLocation();
const { t } = useTranslation();
const isMounted = useIsMounted();
@@ -138,7 +138,7 @@ export const LoginPage = () => {
)}
{configuredProviders.includes("generic") && (
<OAuthButton
title={genericName}
title="Generic"
icon={<GenericIcon />}
className="w-full"
onClick={() => oauthMutation.mutate("generic")}

1
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/go-playground/validator/v10 v10.26.0
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.6.0
github.com/mdp/qrterminal/v3 v3.2.1
github.com/rs/zerolog v1.34.0
github.com/spf13/cobra v1.9.1

View File

@@ -28,10 +28,16 @@ var apiConfig = types.APIConfig{
// Simple handlers config for tests
var handlersConfig = types.HandlersConfig{
AppURL: "http://localhost:8080",
Domain: "localhost",
DisableContinue: false,
CookieSecure: false,
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Some message",
CsrfCookieName: "tinyauth-csrf",
RedirectCookieName: "tinyauth-redirect",
BackgroundImage: "https://example.com/image.png",
OAuthAutoRedirect: "none",
}
// Simple auth config for tests
@@ -43,6 +49,8 @@ var authConfig = types.AuthConfig{
SessionExpiry: 3600,
LoginTimeout: 0,
LoginMaxRetries: 0,
SessionCookieName: "tinyauth-session",
Domain: "localhost",
}
// Simple hooks config for tests
@@ -206,6 +214,9 @@ func TestAppContext(t *testing.T) {
Title: "Tinyauth",
GenericName: "Generic",
ForgotPasswordMessage: "Some message",
BackgroundImage: "https://example.com/image.png",
OAuthAutoRedirect: "none",
Domain: "localhost",
}
// We should get the username back
@@ -234,7 +245,7 @@ func TestUserContext(t *testing.T) {
// Set the cookie
req.AddCookie(&http.Cookie{
Name: "tinyauth",
Name: "tinyauth-session",
Value: cookie,
})

View File

@@ -45,7 +45,7 @@ func (auth *Auth) GetSession(c *gin.Context) (*sessions.Session, error) {
}
// Get session
session, err := store.Get(c.Request, "tinyauth")
session, err := store.Get(c.Request, auth.Config.SessionCookieName)
if err != nil {
log.Error().Err(err).Msg("Failed to get session")
return nil, err

View File

@@ -21,3 +21,8 @@ type Claims struct {
var Version = "development"
var CommitHash = "n/a"
var BuildTimestamp = "n/a"
// Cookie names
var SessionCookieName = "tinyauth-session"
var CsrfCookieName = "tinyauth-csrf"
var RedirectCookieName = "tinyauth-redirect"

View File

@@ -581,7 +581,7 @@ func (h *Handlers) OauthUrlHandler(c *gin.Context) {
log.Debug().Msg("Got auth URL")
// Set CSRF cookie
c.SetCookie("tinyauth-csrf", state, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.CsrfCookieName, state, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
// Get redirect URI
redirectURI := c.Query("redirect_uri")
@@ -589,7 +589,7 @@ func (h *Handlers) OauthUrlHandler(c *gin.Context) {
// Set redirect cookie if redirect URI is provided
if redirectURI != "" {
log.Debug().Str("redirectURI", redirectURI).Msg("Setting redirect cookie")
c.SetCookie("tinyauth-redirect", redirectURI, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.RedirectCookieName, redirectURI, int(time.Hour.Seconds()), "/", "", h.Config.CookieSecure, true)
}
// Return auth URL
@@ -620,7 +620,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
state := c.Query("state")
// Get CSRF cookie
csrfCookie, err := c.Cookie("tinyauth-csrf")
csrfCookie, err := c.Cookie(h.Config.CsrfCookieName)
if err != nil {
log.Debug().Msg("No CSRF cookie")
@@ -638,7 +638,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
}
// Clean up CSRF cookie
c.SetCookie("tinyauth-csrf", "", -1, "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.CsrfCookieName, "", -1, "/", "", h.Config.CookieSecure, true)
// Get code
code := c.Query("code")
@@ -737,7 +737,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
})
// Check if we have a redirect URI
redirectCookie, err := c.Cookie("tinyauth-redirect")
redirectCookie, err := c.Cookie(h.Config.RedirectCookieName)
if err != nil {
log.Debug().Msg("No redirect cookie")
@@ -762,7 +762,7 @@ func (h *Handlers) OauthCallbackHandler(c *gin.Context) {
}
// Clean up redirect cookie
c.SetCookie("tinyauth-redirect", "", -1, "/", "", h.Config.CookieSecure, true)
c.SetCookie(h.Config.RedirectCookieName, "", -1, "/", "", h.Config.CookieSecure, true)
// Redirect to continue with the redirect URI
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/continue?%s", h.Config.AppURL, queries.Encode()))

View File

@@ -3,17 +3,15 @@ package oauth
import (
"context"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"net/http"
"golang.org/x/oauth2"
)
func NewOAuth(config oauth2.Config, insecureSkipVerify bool) *OAuth {
func NewOAuth(config oauth2.Config) *OAuth {
return &OAuth{
Config: config,
InsecureSkipVerify: insecureSkipVerify,
}
}
@@ -22,29 +20,11 @@ type OAuth struct {
Context context.Context
Token *oauth2.Token
Verifier string
InsecureSkipVerify bool
}
func (oauth *OAuth) Init() {
// Create transport with TLS
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: oauth.InsecureSkipVerify,
MinVersion: tls.VersionTLS12,
},
}
// Create a new context
// Create a new context and verifier
oauth.Context = context.Background()
// Create the HTTP client with the transport
httpClient := &http.Client{
Transport: transport,
}
// Set the HTTP client in the context
oauth.Context = context.WithValue(oauth.Context, oauth2.HTTPClient, httpClient)
// Create the verifier
oauth.Verifier = oauth2.GenerateVerifier()
}

View File

@@ -36,7 +36,7 @@ func (providers *Providers) Init() {
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/github", providers.Config.AppURL),
Scopes: GithubScopes(),
Endpoint: endpoints.GitHub,
}, false)
})
// Initialize the oauth provider
providers.Github.Init()
@@ -53,7 +53,7 @@ func (providers *Providers) Init() {
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/google", providers.Config.AppURL),
Scopes: GoogleScopes(),
Endpoint: endpoints.Google,
}, false)
})
// Initialize the oauth provider
providers.Google.Init()
@@ -73,7 +73,7 @@ func (providers *Providers) Init() {
AuthURL: providers.Config.GenericAuthURL,
TokenURL: providers.Config.GenericTokenURL,
},
}, providers.Config.GenericSkipSSL)
})
// Initialize the oauth provider
providers.Generic.Init()

View File

@@ -24,7 +24,6 @@ type Config struct {
GenericTokenURL string `mapstructure:"generic-token-url"`
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"`
@@ -35,7 +34,7 @@ type Config struct {
LoginTimeout int `mapstructure:"login-timeout"`
LoginMaxRetries int `mapstructure:"login-max-retries"`
FogotPasswordMessage string `mapstructure:"forgot-password-message" validate:"required"`
BackgroundImage string `mapstructure:"background-image" validate:"required"`
BackgroundImage string `mapstructure:"background-image" validate:"required,url"`
}
// Server configuration
@@ -49,6 +48,8 @@ type HandlersConfig struct {
ForgotPasswordMessage string
BackgroundImage string
OAuthAutoRedirect string
CsrfCookieName string
RedirectCookieName string
}
// OAuthConfig is the configuration for the providers
@@ -63,7 +64,6 @@ type OAuthConfig struct {
GenericAuthURL string
GenericTokenURL string
GenericUserURL string
GenericSkipSSL bool
AppURL string
}
@@ -83,6 +83,7 @@ type AuthConfig struct {
Domain string
LoginTimeout int
LoginMaxRetries int
SessionCookieName string
}
// HooksConfig is the configuration for the hooks service

View File

@@ -10,6 +10,7 @@ import (
"tinyauth/internal/constants"
"tinyauth/internal/types"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
)
@@ -344,3 +345,18 @@ func SanitizeHeader(header string) string {
return -1
}, header)
}
// Generate a static identifier from a string
func GenerateIdentifier(str string) string {
// Create a new UUID
uuid := uuid.NewSHA1(uuid.NameSpaceURL, []byte(str))
// Convert the UUID to a string
uuidString := uuid.String()
// Show the UUID
log.Debug().Str("uuid", uuidString).Msg("Generated UUID")
// Convert the UUID to a string
return strings.Split(uuidString, "-")[0]
}