137 lines
4.3 KiB
Go
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
|
|
}
|