Files
ersteller/authentication/keycloak.go
T
2025-07-27 19:12:46 +02:00

137 lines
4.3 KiB
Go

package authentication
import (
"context"
"ersteller-lib"
"github.com/gorilla/sessions"
"github.com/labstack/echo/v4"
"github.com/markbates/goth"
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/openidConnect"
)
const OpenIdConnectPath = "/auth/openid-connect"
type KeycloakEnv struct {
SessionSecret string
BaseUrl string
SessionName string
Keycloak struct {
CLientId string
ClientSecret string
DiscoveryUrl string
}
EmailSessionKey string
UserIdSessionKey string
}
// https://keycloak-dev.deploy.ersteller.gorlug.de/realms/master/.well-known/openid-configuration
// https://go.dev/play/p/-RtLSPL4Wsj
func RunKeycloakAuth(e *echo.Echo, environment KeycloakEnv, cookieStore *sessions.CookieStore, userRepo UserRepository) {
sessionStore := sessions.NewFilesystemStore("store", []byte(environment.SessionSecret))
sessionStore.MaxLength(8192)
gothic.Store = sessionStore
// OpenID Connect is based on OpenID Connect Auto Discovery URL (https://openid.net/specs/openid-connect-discovery-1_0-17.html)
// because the OpenID Connect provider initialize itself in the New(), it can return an error which should be handled or ignored
// ignore the error for now
openid, err := openidConnect.New(environment.Keycloak.CLientId, environment.Keycloak.ClientSecret, environment.BaseUrl+"/auth/openid-connect/callback", environment.Keycloak.DiscoveryUrl)
if err != nil {
ersteller_lib.Error("Error while initializing OpenID Connect provider: ", err)
panic(err)
}
if openid != nil {
goth.UseProviders(openid)
}
e.GET("/auth/openid-connect/callback", func(c echo.Context) error {
user, err := gothic.CompleteUserAuth(c.Response(), c.Request())
if err != nil {
return err
}
userId, err := userRepo.GetUserId(user.Email)
if err != nil {
if userId == -1 {
userId, err = createUser(user, userRepo)
if err != nil {
ersteller_lib.LogError("Failed to create user: %v", err)
return err
}
ersteller_lib.LogDebug("Created user with id %d", userId)
} else {
ersteller_lib.LogError("Failed to get user id: %v", err)
return err
}
}
err = saveEmailToSessionStore(c, cookieStore, user.Email, userId, environment)
if err != nil {
return err
}
return c.Redirect(302, "/")
})
e.GET("/logout", func(c echo.Context) error {
// Get the session
session, err := cookieStore.Get(c.Request(), environment.SessionName)
if err != nil {
ersteller_lib.LogError("Failed to get session during logout: %v", err)
} else {
// Clear session values
session.Values = make(map[interface{}]interface{})
// Set MaxAge to -1 to delete the cookie
session.Options.MaxAge = -1
// Save the session (this will delete the cookie)
err = session.Save(c.Request(), c.Response())
if err != nil {
ersteller_lib.LogError("Failed to clear session during logout: %v", err)
}
}
// Also call gothic logout for OpenID Connect cleanup
gothic.Logout(c.Response(), c.Request())
// Redirect to login page
return c.Redirect(302, "/login")
})
e.GET("/logout/openid-connect", func(c echo.Context) error {
return gothic.Logout(c.Response(), c.Request())
})
e.GET(OpenIdConnectPath, func(c echo.Context) error {
ctx := context.WithValue(c.Request().Context(), gothic.ProviderParamKey, "openid-connect")
request := c.Request().WithContext(ctx)
// try to get the user without re-authenticating
if gothUser, err := gothic.CompleteUserAuth(c.Response(), c.Request()); err == nil {
ersteller_lib.Debug(gothUser)
return nil
} else {
gothic.BeginAuthHandler(c.Response(), request)
return nil
}
})
}
func createUser(gothUser goth.User, repo UserRepository) (int, error) {
return repo.Create(gothUser.Email)
}
func saveEmailToSessionStore(c echo.Context, sessionStore *sessions.CookieStore, email string, userId int, environment KeycloakEnv) error {
session, err := sessionStore.New(c.Request(), environment.SessionName)
if err != nil {
ersteller_lib.LogError("Failed to create session: %v", err)
return err
}
session.Values = map[interface{}]interface{}{
environment.EmailSessionKey: email,
environment.UserIdSessionKey: userId,
}
err = session.Save(c.Request(), c.Response())
if err != nil {
ersteller_lib.LogError("Failed to save session: %v", err)
return err
}
return nil
}