diff --git a/auth/auth.go b/auth/auth.go index cef0319..58c4206 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -21,11 +21,25 @@ const ( COOKIE_SECRET_KEY_LENGHT = 32 ) +type User struct { + Username string `json:"username"` + Email string `json:"email"` + Name string `json:"name"` + Groups []string `json:"groups"` +} +type UserClaimsConfig struct { + OIDCClaimUsernameField string + OIDCClaimEmailField string + OIDCClaimNameField string + OIDCClaimGroupsField string +} + type Authenticator struct { Cookiejar cookies.ICookieJar OIDCconfig *oidc.Config OauthConfig *oauth2.Config verifierProvider *oidc.Provider + userclaimConfig *UserClaimsConfig skipTLSVerify bool } @@ -72,10 +86,22 @@ func SetupOIDCAuthHandler() *Authenticator { Endpoint: provider.Endpoint(), Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, } + userClaimsConfig := &UserClaimsConfig{ + OIDCClaimUsernameField: "preferred_username", + OIDCClaimEmailField: "email", + OIDCClaimNameField: "name", + OIDCClaimGroupsField: "groups", + } cookieJar := cookies.NewCookieJar([]byte(env.cookieJarSecret), []byte(env.cookieEncryptionKey)) - return NewAuthenticator(cookieJar, oidcConfig, oauthConfig, provider, skipTLS) + return NewAuthenticator( + cookieJar, + oidcConfig, + oauthConfig, + provider, + skipTLS, + userClaimsConfig) } func validateEnvVariables(env struct { @@ -111,12 +137,13 @@ func setupHTTPClient(skipTLS bool) *http.Client { return http.DefaultClient } -func NewAuthenticator(cj cookies.ICookieJar, OIDCconfig *oidc.Config, OauthConfig *oauth2.Config, verifierProvider *oidc.Provider, skipTLSVerify bool) *Authenticator { +func NewAuthenticator(cj cookies.ICookieJar, OIDCconfig *oidc.Config, OauthConfig *oauth2.Config, verifierProvider *oidc.Provider, skipTLSVerify bool, userClaims *UserClaimsConfig) *Authenticator { return &Authenticator{ Cookiejar: cj, OIDCconfig: OIDCconfig, OauthConfig: OauthConfig, verifierProvider: verifierProvider, + userclaimConfig: userClaims, skipTLSVerify: skipTLSVerify, } } diff --git a/auth/cookies/cookie.go b/auth/cookies/cookie.go index 59fb01b..c69e360 100644 --- a/auth/cookies/cookie.go +++ b/auth/cookies/cookie.go @@ -16,6 +16,7 @@ type ICookieJar interface { SetCallBackState(context *gin.Context, stateValue string) error GetStateSession(context *gin.Context) (value string, isNew bool) GetNonceSession(context *gin.Context) (value string, isNew bool) + GetUserToken(context *gin.Context) (value string, isNew bool) SetUserToken(context *gin.Context, token string) error DeleteStateSession(context *gin.Context) error DeleteNonceSession(context *gin.Context) error @@ -44,6 +45,10 @@ func (cj *CookieJar) GetNonceSession(context *gin.Context) (value string, isNew return cj.getStateSession(context, CALLBACK_NONCE) } +func (cj *CookieJar) GetUserToken(context *gin.Context) (value string, isNew bool) { + return cj.getStateSession(context, CALLBACK_NONCE) +} + func (cj *CookieJar) SetUserToken(context *gin.Context, token string) error { session := sessions.NewSession(cj.store, USER_TOKEN) session.Values["token"] = token diff --git a/auth/gin_oidc.go b/auth/gin_oidc.go index d86ea46..932be54 100644 --- a/auth/gin_oidc.go +++ b/auth/gin_oidc.go @@ -85,6 +85,11 @@ func (auth *Authenticator) OIDCCallBack(gc *gin.Context) { func (auth *Authenticator) sessionAuth(gc *gin.Context) gin.HandlerFunc { return func(gc *gin.Context) { - return + tokenCookie, noCookie := auth.Cookiejar.GetUserToken(gc) + if noCookie { + gc.Redirect(http.StatusOK, "/") + return + } + username, role, err := auth.VerifyClaims(gc*gin.Context, tokenCookie) } } diff --git a/auth/jwt.go b/auth/jwt.go deleted file mode 100644 index 8832b06..0000000 --- a/auth/jwt.go +++ /dev/null @@ -1 +0,0 @@ -package auth diff --git a/auth/verify.go b/auth/verify.go new file mode 100644 index 0000000..a1de4a8 --- /dev/null +++ b/auth/verify.go @@ -0,0 +1,47 @@ +package auth + +import ( + "errors" + "fmt" + + "github.com/gin-gonic/gin" +) + +func (auth *Authenticator) VerifyClaims(gc *gin.Context, token string) (name string, role string, err error) { + verifier := auth.GetTokenVerifier() + accessToken, err := verifier.Verify(gc, token) + if err != nil { + return "", "", errors.New(fmt.Sprintf("could not obtain token from cookie: %w", err)) + } + var claims map[string]any + if err := accessToken.Claims(&claims); err != nil { + return "", "", errors.New(fmt.Sprintf("could not map clains: %w", err)) + } + if _, ok := claims["iss"]; !ok { + return "", "", errors.New("no issues in claim") + } + + return "", "", nil +} + +func (auth *Authenticator) mapClaimsToUser(claims map[string]any) (*User, error) { + user := &User{} + + if username, ok := claims[auth.userclaimConfig.OIDCClaimUsernameField].(string); ok { + user.Username = username + } + if email, ok := claims[auth.userclaimConfig.OIDCClaimEmailField].(string); ok { + user.Email = email + } + if name, ok := claims[auth.userclaimConfig.OIDCClaimNameField].(string); ok { + user.Name = name + } + if groups, ok := claims[auth.userclaimConfig.OIDCClaimGroupsField].([]interface{}); ok { + user.Groups = make([]string, len(groups)) + for i, g := range groups { + user.Groups[i] = g.(string) + } + } + + return user, nil +} diff --git a/auth/verify_test.go b/auth/verify_test.go new file mode 100644 index 0000000..e03678d --- /dev/null +++ b/auth/verify_test.go @@ -0,0 +1,37 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMapClaimsToUser_AllFieldsMappedCorrectly(t *testing.T) { + UserClaimsConfig := UserClaimsConfig{ + OIDCClaimUsernameField: "preferred_username", + OIDCClaimEmailField: "email", + OIDCClaimNameField: "name", + OIDCClaimGroupsField: "groups", + } + claims := map[string]interface{}{ + "preferred_username": "johndoe", + "email": "john@example.com", + "name": "John Doe", + "groups": []interface{}{"users", "admins"}, + } + expectedUser := &User{ + Username: "johndoe", + Email: "john@example.com", + Name: "John Doe", + Groups: []string{"users", "admins"}, + } + + auth := &Authenticator{ + userclaimConfig: &UserClaimsConfig, + } + + user, err := auth.mapClaimsToUser(claims) + + assert.NoError(t, err) + assert.Equal(t, expectedUser, user) +} diff --git a/go.mod b/go.mod index c4e8f56..9240555 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/a-h/templ v0.2.771 github.com/coreos/go-oidc/v3 v3.11.0 github.com/gin-gonic/gin v1.10.0 + github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.4.0 github.com/stretchr/testify v1.9.0 golang.org/x/oauth2 v0.21.0 @@ -16,7 +17,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect - github.com/gorilla/securecookie v1.1.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect )