From 4a323368f71d091fc50c5559cd8723938b6a1138 Mon Sep 17 00:00:00 2001 From: WeatherGod3218 Date: Fri, 19 Jun 2026 19:10:48 -0500 Subject: [PATCH 1/3] fix? --- Containerfile | 2 +- main.go | 2 +- templates/header.tmpl | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Containerfile b/Containerfile index be7dc8d..e6647c7 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.25-alpine AS build +FROM docker.io/golang:1.25.7-alpine AS build WORKDIR /src/ RUN apk add git diff --git a/main.go b/main.go index e6f8529..de6dde7 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func main() { router.GET("/auth/callback", auth.HandleCallback) // This endpoint should match the path for callbackURL router.GET("/auth/logout", auth.HandleLogout) - router.Use(auth.HeaderMiddleware()) + router.Use(auth.CookieMiddleware()) router.GET("/", GetHomePage) router.GET("/counters/:id", LoadCounter) diff --git a/templates/header.tmpl b/templates/header.tmpl index a4b60b5..04c44f1 100644 --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -36,7 +36,6 @@
From e13ec0d0d02063d57592258d1420c15dc1a32e34 Mon Sep 17 00:00:00 2001 From: WeatherGod3218 Date: Wed, 1 Jul 2026 17:53:34 -0500 Subject: [PATCH 2/3] fix: CSH Auth now uses passed middleware --- api.go | 93 +++++++++++++++++------ go.mod | 2 +- go.sum | 2 + main.go | 14 ++-- users.go | 223 ------------------------------------------------------- util.go | 32 ++++++++ 6 files changed, 110 insertions(+), 256 deletions(-) delete mode 100644 users.go create mode 100644 util.go diff --git a/api.go b/api.go index 5491f03..5608db7 100644 --- a/api.go +++ b/api.go @@ -3,37 +3,31 @@ package main import ( "net/http" "sort" - "time" "github.com/WeatherGod3218/counters/database" "github.com/gin-gonic/gin" + cshAuth "github.com/computersciencehouse/csh-auth/v2" "go.mongodb.org/mongo-driver/v2/bson" ) -func translateTime(inputTime string) time.Time { - timeZone, _ := time.LoadLocation("America/New_York") - - currentTime := time.Now() +func GetHomePage(c *gin.Context) { + userAny, exists := c.Get("cshauth") - timeConverted, err := time.ParseInLocation("2006-01-02T15:04", inputTime, timeZone) - if err != nil { - timeConverted = currentTime + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return } - - if timeConverted.After(currentTime) { - timeConverted = currentTime + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return } - return timeConverted -} - -func GetHomePage(c *gin.Context) { - user := GetUserData(c) counters, err := database.GetAllCounters(c) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load counters!"}) return } @@ -51,7 +45,17 @@ func GetHomePage(c *gin.Context) { } func GetCreatePage(c *gin.Context) { - user := GetUserData(c) + userAny, exists := c.Get("cshauth") + + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } c.HTML(http.StatusOK, "create.tmpl", gin.H{ "Username": user.Username, @@ -62,7 +66,17 @@ func GetCreatePage(c *gin.Context) { } func GetResetPage(c *gin.Context) { - user := GetUserData(c) + userAny, exists := c.Get("cshauth") + + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } idToUse := c.Param("id") c.HTML(http.StatusOK, "reset.tmpl", gin.H{ @@ -75,8 +89,17 @@ func GetResetPage(c *gin.Context) { } func LoadCounter(c *gin.Context) { + userAny, exists := c.Get("cshauth") + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } idToUse := c.Param("id") - user := GetUserData(c) counter, err := database.GetCounterFromId(c, idToUse) if err != nil { @@ -106,8 +129,19 @@ func LoadCounter(c *gin.Context) { } func ResetCounter(c *gin.Context) { - resetTime := translateTime(c.PostForm("reset-time")) - user := GetUserData(c) + userAny, exists := c.Get("cshauth") + + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + + resetTime := TranslateTime(c.PostForm("reset-time")) reset := &database.Reset{ Id: bson.NewObjectID(), @@ -128,8 +162,19 @@ func ResetCounter(c *gin.Context) { } func CreateCounter(c *gin.Context) { - resetTime := translateTime(c.PostForm("reset-time")) - user := GetUserData(c) + userAny, exists := c.Get("cshauth") + + if !exists { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + user, ok := userAny.(*cshAuth.Claims) + if !ok { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to load CSH context!"}) + return + } + + resetTime := TranslateTime(c.PostForm("reset-time")) reset := &database.Reset{ Id: bson.NewObjectID(), diff --git a/go.mod b/go.mod index 4496206..997d4f9 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/WeatherGod3218/counters go 1.25.7 require ( - github.com/computersciencehouse/csh-auth/v2 v2.0.1 + github.com/computersciencehouse/csh-auth/v2 v2.0.2 github.com/gin-gonic/gin v1.12.0 github.com/sirupsen/logrus v1.9.4 go.mongodb.org/mongo-driver/v2 v2.6.0 diff --git a/go.sum b/go.sum index 454c9e7..297248e 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/computersciencehouse/csh-auth/v2 v2.0.0 h1:uRP8Ti/pb1SaSSBvhk3dLVpHag github.com/computersciencehouse/csh-auth/v2 v2.0.0/go.mod h1:JelmcPFBLpo8wnmErF4Zoa4UJKi0psgUDpcuTq6pWbY= github.com/computersciencehouse/csh-auth/v2 v2.0.1 h1:rtGa9cBDZJZZJ8aeGefwAM/rzPMxWNAGDwuVDZI80ro= github.com/computersciencehouse/csh-auth/v2 v2.0.1/go.mod h1:uL/2UDr9GJlAQORsn+rmFc18YkBsZPymYJbwb+uTRig= +github.com/computersciencehouse/csh-auth/v2 v2.0.2 h1:xlxv2w2cmiWriVNhwA8Io1FWI54sDmv7c12gkVAcK30= +github.com/computersciencehouse/csh-auth/v2 v2.0.2/go.mod h1:uL/2UDr9GJlAQORsn+rmFc18YkBsZPymYJbwb+uTRig= github.com/coreos/go-oidc v2.5.0+incompatible h1:6W0vGJR3Tu0r0PwfmjOrRZSlfxeEln8dsejt3ZWIvwo= github.com/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-oidc/v3 v3.18.0 h1:V9orjXynvu5wiC9SemFTWnG4F45v403aIcjWo0d41+A= diff --git a/main.go b/main.go index de6dde7..19f88d7 100644 --- a/main.go +++ b/main.go @@ -15,20 +15,18 @@ import ( var DEV_FORCE_IS_EBOARD bool = os.Getenv("DEV_FORCE_IS_EBOARD") == "true" -var oidcClient = OIDCClient{} - func main() { database.Client = database.Connect() + hostUrl := os.Getenv("SERVER_HOST") auth, err := cshAuth.Init( os.Getenv("AUTH_OIDC_ID"), os.Getenv("AUTH_OIDC_SECRET"), - os.Getenv("SERVER_HOST"), - os.Getenv("SERVER_HOST")+"/auth/login", - os.Getenv("SERVER_HOST")+"/auth/callback", + hostUrl, + hostUrl+"/auth/login", + hostUrl+"/auth/callback", []string{"profile", "email", "groups"}, ) - oidcClient.setupOidcClient(os.Getenv("AUTH_OIDC_ID"), os.Getenv("AUTH_OIDC_SECRET")) if err != nil { logging.Logger.WithFields(logrus.Fields{"error": err, "module": "main", "method": "main"}).Fatal("error initializing csh-auth") @@ -39,8 +37,8 @@ func main() { router.StaticFS("/static", http.Dir("static")) router.LoadHTMLGlob("templates/*") - router.GET("/auth/login", auth.HandleLogin) // This endpoint should match the path for loginURL - router.GET("/auth/callback", auth.HandleCallback) // This endpoint should match the path for callbackURL + router.GET("/auth/login", auth.HandleLogin) + router.GET("/auth/callback", auth.HandleCallback) router.GET("/auth/logout", auth.HandleLogout) router.Use(auth.CookieMiddleware()) diff --git a/users.go b/users.go deleted file mode 100644 index c55a212..0000000 --- a/users.go +++ /dev/null @@ -1,223 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "slices" - "strings" - "time" - - "github.com/WeatherGod3218/counters/logging" - cshAuth "github.com/computersciencehouse/csh-auth/v2" - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -type OIDCClient struct { - oidcClientId string - oidcClientSecret string - - accessToken string - providerBase string - quit chan struct{} -} - -type OIDCUser struct { - Uuid string `json:"id"` - Username string `json:"username"` - Gatekeep bool `json:"result"` - SlackUID string `json:"slackuid"` -} - -var groupCache map[string]string - -func (client *OIDCClient) setupOidcClient(oidcClientId, oidcClientSecret string) { - client.oidcClientId = oidcClientId - client.oidcClientSecret = oidcClientSecret - parse, err := url.Parse(cshAuth.ProviderURI) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "setupOidcClient"}).Error(err) - return - } - groupCache = make(map[string]string) - client.providerBase = parse.Scheme + "://" + parse.Host - exp := client.getAccessToken() - ticker := time.NewTicker(time.Duration(exp) * time.Second) - // this will async get the token - go func() { - for { - select { - case <-ticker.C: - exp = client.getAccessToken() - ticker.Reset(time.Duration(exp) * time.Second) - case <-client.quit: - ticker.Stop() - return - } - } - }() -} - -func (client *OIDCClient) getAccessToken() int { - htclient := http.DefaultClient - //request body - authData := url.Values{} - authData.Set("client_id", client.oidcClientId) - authData.Set("client_secret", client.oidcClientSecret) - authData.Set("grant_type", "client_credentials") - resp, err := htclient.PostForm(cshAuth.ProviderURI+"/protocol/openid-connect/token", authData) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "getAccessToken"}).Error(err) - return 0 - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - logging.Logger.WithFields(logrus.Fields{"method": "getAccessToken", "statusCode": resp.StatusCode}).Error(resp.Status) - return 0 - } - respData := make(map[string]interface{}) - err = json.NewDecoder(resp.Body).Decode(&respData) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "getAccessToken"}).Error(err) - return 0 - } - if respData["error"] != nil { - logging.Logger.WithFields(logrus.Fields{"method": "getAccessToken"}).Error(respData) - return 0 - } - client.accessToken = respData["access_token"].(string) - return int(respData["expires_in"].(float64)) -} - -func (client *OIDCClient) GetActiveUsers() []OIDCUser { - return client.GetOIDCGroup("a97a191e-5668-43f5-bc0c-6eefc2b958a7") -} - -func (client *OIDCClient) GetEBoard() []OIDCUser { - return client.GetOIDCGroup("47dd1a94-853c-426d-b181-6d0714074892") -} - -func (client *OIDCClient) FindOIDCGroupID(name string) string { - if groupCache[name] != "" { - return groupCache[name] - } - htclient := &http.Client{} - //active - req, err := http.NewRequest("GET", client.providerBase+"/auth/admin/realms/csh/groups?exact=true&search="+name, nil) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "FindOIDCGroupID"}).Error(err) - return "" - } - req.Header.Add("Authorization", "Bearer "+client.accessToken) - resp, err := htclient.Do(req) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "FindOIDCGroupID"}).Error(err) - return "" - } - defer resp.Body.Close() - ret := make([]map[string]any, 0) - err = json.NewDecoder(resp.Body).Decode(&ret) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "FindOIDCGroupID"}).Error(err) - return "" - } - //Example: - //[{"id":"47dd1a94-853c-426d-b181-6d0714074892","name":"eboard","path":"/eboard","subGroups":[{"id":"66b9578a-2b58-46a6-8040-59388e57e830","name":"eboard-opcomm","path":"/eboard/eboard-opcomm","subGroups":[]}]}] - //it returns as an array for SOME reason, so we cut to the group we want - group := ret[0] - // and now we have SUBgroups, so we do this fucked parse - subGroups := group["subGroups"].([]any) - fmt.Println(subGroups) - subGroup := subGroups[0].(map[string]interface{}) - fmt.Println(subGroup) - gid := subGroup["id"].(string) - groupCache[name] = gid - return gid - -} - -func (client *OIDCClient) GetOIDCGroup(groupID string) []OIDCUser { - htclient := &http.Client{} - //active - req, err := http.NewRequest("GET", client.providerBase+"/auth/admin/realms/csh/groups/"+groupID+"/members", nil) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetOIDCGroup"}).Error(err) - return nil - } - req.Header.Add("Authorization", "Bearer "+client.accessToken) - resp, err := htclient.Do(req) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetOIDCGroup"}).Error(err) - return nil - } - defer resp.Body.Close() - ret := make([]OIDCUser, 0) - err = json.NewDecoder(resp.Body).Decode(&ret) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetOIDCGroup"}).Error(err) - return nil - } - return ret -} - -func (client *OIDCClient) GetUserInfo(user *OIDCUser) { - htclient := &http.Client{} - arg := "" - if len(user.Uuid) == 0 { - arg = "?username=" + user.Username - } - req, err := http.NewRequest("GET", client.providerBase+"/auth/admin/realms/csh/users/"+user.Uuid+arg, nil) - // also "users/{user-id}/groups" - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetUserInfo"}).Error(err) - return - } - req.Header.Add("Authorization", "Bearer "+client.accessToken) - resp, err := htclient.Do(req) - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetUserInfo"}).Error(err) - return - } - defer resp.Body.Close() - b, _ := io.ReadAll(resp.Body) - if strings.Contains(string(b), "error") { - logging.Logger.WithFields(logrus.Fields{"method": "GetUserInfo"}).Error(string(b)) - return - } - if len(arg) > 0 { - userData := make([]map[string]any, 0) - err = json.Unmarshal(b, &userData) - // userdata attributes are a KV pair of string:[]any, this casts attributes, finds the specific attribute, casts it to a list of any, and then pulls the first field since there will only ever be one - userAttributes := userData[0]["attributes"].(map[string]any) - if slackIDRaw, exists := userAttributes["slackuid"]; exists { - user.SlackUID = slackIDRaw.([]any)[0].(string) - } else { - logging.Logger.WithFields(logrus.Fields{"method": "GetUserInfo"}).Error("User " + user.Username + " does not have a SlackUID. Skipping messaging.") - } - } else { - err = json.Unmarshal(b, &user) - } - if err != nil { - logging.Logger.WithFields(logrus.Fields{"method": "GetUserInfo"}).Error(err) - } -} - -// GetUserData Retreives information about a specific CSH user account -func GetUserData(c *gin.Context) cshAuth.UserInfo { - cl, _ := c.Get("cshauth") - user := cl.(*cshAuth.Claims).UserInfo - return user -} - -// IsEboard determines if the current user is on eboard, allowing for a dev mode override -func IsEboard(user cshAuth.UserInfo) bool { - return DEV_FORCE_IS_EBOARD || slices.Contains(user.Groups, "eboard") -} - -// IsActiveRTP Determines whether the user is an active RTP, based on user groups from OIDC -func IsActiveRTP(user cshAuth.UserInfo) bool { - return slices.Contains(user.Groups, "active-rtp") -} diff --git a/util.go b/util.go new file mode 100644 index 0000000..ccf0544 --- /dev/null +++ b/util.go @@ -0,0 +1,32 @@ +package main + +import ( + "slices" + "time" + + cshAuth "github.com/computersciencehouse/csh-auth/v2" +) + +func IsEboard(user *cshAuth.Claims) bool { + return slices.Contains(user.Groups, "eboard") +} + +func IsActiveRTP(user *cshAuth.Claims) bool { + return slices.Contains(user.Groups, "active-rtp") +} + +func TranslateTime(inputTime string) time.Time { + timeZone, _ := time.LoadLocation("America/New_York") + + currentTime := time.Now() + + timeConverted, err := time.ParseInLocation("2006-01-02T15:04", inputTime, timeZone) + if err != nil { + timeConverted = currentTime + } + + if timeConverted.After(currentTime) { + timeConverted = currentTime + } + return timeConverted +} From 57993b3eeea8abc37b9849dfe5e538566d250a05 Mon Sep 17 00:00:00 2001 From: WeatherGod3218 Date: Wed, 1 Jul 2026 17:54:25 -0500 Subject: [PATCH 3/3] chore: Update to go 1.26 --- Containerfile | 2 +- README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Containerfile b/Containerfile index e6647c7..3780ff6 100644 --- a/Containerfile +++ b/Containerfile @@ -1,4 +1,4 @@ -FROM docker.io/golang:1.25.7-alpine AS build +FROM docker.io/golang:1.26.2-alpine AS build WORKDIR /src/ RUN apk add git diff --git a/README.md b/README.md index 2925e45..723dc94 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,11 @@ Counters also has support for Docker Compose, a extended version of docker that (This is a really cool thing! If you use docker often, check it out!) ``` - docker compose up + docker compose up --build ``` ## TODO +- [] Add Swagger - [] Add API Integration - [] Add Logging (similar to Votes "Actions") - [] Time Zone support