From 0f2544d95201666e869c209457a44f844890bb5a Mon Sep 17 00:00:00 2001 From: Noah Hanford Date: Sun, 23 Nov 2025 22:53:37 -0500 Subject: [PATCH 01/18] fixed bug where it would crash if you filled in all canidates + write in (#46) --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 45c7596..5895938 100644 --- a/main.go +++ b/main.go @@ -299,7 +299,6 @@ func main() { } maxNum := len(poll.Options) - voted := make([]bool, maxNum) // process write-in if c.PostForm("writeinOption") != "" && c.PostForm("writein") != "" { for candidate := range vote.Options { @@ -321,6 +320,8 @@ func main() { maxNum += 1 //you can rank all options in the poll PLUS one } + voted := make([]bool, maxNum) + for _, opt := range poll.Options { option := c.PostForm(opt) rank, err := strconv.Atoi(option) From 9aa78473e36d940a3176b074f821ed7b178791f7 Mon Sep 17 00:00:00 2001 From: Tyler Allen Date: Mon, 24 Nov 2025 21:03:55 -0500 Subject: [PATCH 02/18] Check that candidates are not ranked identically (#47) * Check that candidates are not ranked identically * fix bug where write in candidates weren't checked for duplicate rankings * simplified range cause yeah * i think i actually fixed it this time * http response for consistency * removed extra period --------- Co-authored-by: Noah --- main.go | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/main.go b/main.go index 5895938..0a95d6e 100644 --- a/main.go +++ b/main.go @@ -67,6 +67,7 @@ func main() { r.GET("/auth/callback", csh.AuthCallback) r.GET("/auth/logout", csh.AuthLogout) + // TODO: change ALL the response codes to use http.(actual description) r.GET("/", csh.AuthWrapper(func(c *gin.Context) { cl, _ := c.Get("cshauth") claims := cl.(cshAuth.CSHClaims) @@ -298,7 +299,23 @@ func main() { UserId: claims.UserInfo.Username, } - maxNum := len(poll.Options) + fmt.Println(poll.Options) + + for _, option := range poll.Options { + optionRankStr := c.PostForm(option) + optionRank, err := strconv.Atoi(optionRankStr) + + if len(optionRankStr) < 1 { + continue + } + if err != nil { + c.JSON(400, gin.H{"error": "non-number ranking"}) + return + } + + vote.Options[option] = optionRank + } + // process write-in if c.PostForm("writeinOption") != "" && c.PostForm("writein") != "" { for candidate := range vote.Options { @@ -317,29 +334,24 @@ func main() { return } vote.Options[c.PostForm("writeinOption")] = rank - maxNum += 1 //you can rank all options in the poll PLUS one } + maxNum := len(vote.Options) voted := make([]bool, maxNum) - for _, opt := range poll.Options { - option := c.PostForm(opt) - rank, err := strconv.Atoi(option) - if len(option) < 1 { - continue - } - if err != nil { - c.JSON(400, gin.H{"error": "non-number ranking"}) - return - } + for _, rank := range vote.Options { if rank > 0 && rank <= maxNum { - vote.Options[opt] = rank + if voted[rank-1] { + c.JSON(400, gin.H{"error": "You ranked two or more candidates at the same level"}) + return + } voted[rank-1] = true } else { c.JSON(400, gin.H{"error": fmt.Sprintf("votes must be from 1 - %d", maxNum)}) return } } + rankedCandidates := len(vote.Options) for _, voteOpt := range vote.Options { if voteOpt > rankedCandidates { From 6227034b4a45777b49eee24946380b4892a14343 Mon Sep 17 00:00:00 2001 From: Tyler Allen Date: Mon, 1 Dec 2025 20:40:50 -0500 Subject: [PATCH 03/18] Always stop once the majority is hit (#50) @tallen42 * Always stop at majority * End is always true if finalResult is calculated --- database/poll.go | 1 - 1 file changed, 1 deletion(-) diff --git a/database/poll.go b/database/poll.go index bc1ab84..01c2b7c 100644 --- a/database/poll.go +++ b/database/poll.go @@ -312,7 +312,6 @@ func (poll *Poll) GetResult(ctx context.Context) ([]map[string]int, error) { // In that case, it's a tie? if val != minVote { end = false - break } } if end { From 56c99a46a563c06600ff104fb97e00d65845fb4f Mon Sep 17 00:00:00 2001 From: Tyler Allen Date: Mon, 1 Dec 2025 20:41:24 -0500 Subject: [PATCH 04/18] If hidden, say results soon, otherwise URL (#51) @tallen42 --- constitutional.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/constitutional.go b/constitutional.go index 70ce6c2..d9c98f6 100644 --- a/constitutional.go +++ b/constitutional.go @@ -151,8 +151,14 @@ func EvaluatePolls() { logging.Logger.WithFields(logrus.Fields{"method": "EvaluatePolls close"}).Error(err) continue } + announceStr := "The vote \"" + poll.ShortDescription + "\" has closed." + if !poll.Hidden { + announceStr += " Check out the results at " + pollLink + } else { + announceStr += " Results will be posted shortly." + } _, _, _, err = slackData.Client.SendMessage(slackData.AnnouncementsChannel, - slack.MsgOptionText("The vote \""+poll.ShortDescription+"\" has closed. Check out the results at "+pollLink, false)) + slack.MsgOptionText(announceStr, false)) if err != nil { logging.Logger.WithFields(logrus.Fields{"method": "EvaluatePolls announce"}).Error(err) } From 2db8443eb4c7147da0bcbb7c253ca4c8feff50ec Mon Sep 17 00:00:00 2001 From: William Hellinger <111201130+Will-Hellinger@users.noreply.github.com> Date: Thu, 4 Dec 2025 21:28:35 +0000 Subject: [PATCH 05/18] yippee sonarqube support (#54) @Will-Hellinger --- .github/workflows/sonarqube.yml | 21 +++++++++++++++++++++ sonar-project.properties | 1 + 2 files changed, 22 insertions(+) create mode 100644 .github/workflows/sonarqube.yml create mode 100644 sonar-project.properties diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000..78b4c46 --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,21 @@ +name: SonarQube + +on: + push: + branches: + - dev + + +jobs: + build: + name: Build and analyze + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: SonarSource/sonarqube-scan-action@v6 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..24c2592 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1 @@ +sonar.projectKey=ComputerScienceHouse_vote_c5ba863d-30d7-4fa9-97dd-4f2c58f8f5fa \ No newline at end of file From 8f34619f0c2ad85dabb20f1b1c359603de31f140 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 11:30:11 -0500 Subject: [PATCH 06/18] Set up golang CI --- .github/workflows/go-lint.yml | 31 +++++++++++++++++++++++++++++++ README.md | 11 +++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/workflows/go-lint.yml diff --git a/.github/workflows/go-lint.yml b/.github/workflows/go-lint.yml new file mode 100644 index 0000000..1187b31 --- /dev/null +++ b/.github/workflows/go-lint.yml @@ -0,0 +1,31 @@ +name: Golang Linting + +on: + push: + branches: [master, dev] + pull_request: + branches: [master, dev] + +jobs: + golang-checks: + runs-on: ubuntu-latest + + strategy: + matrix: + go-version: [1.24] + + steps: + - uses: actions/checkout@v2 + - name: Setup Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + - name: Install dependencies + run: | + go mod download + - name: Tidy dependencies + run: | + go mod tidy -diff + - name: Check Format + run: | + gofmt -s -l database logging sse *.go diff --git a/README.md b/README.md index 5d8fc2e..b9dcae5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,17 @@ VOTE_SLACK_APP_TOKEN= VOTE_SLACK_BOT_TOKEN= ``` +## Linting +These will be checked by CI + +``` +# tidy dependencies +go mod tidy + +# format all code according to go standards +gofmt -w -s *.go logging sse database +``` + ## To-Dos - [ ] Don't let the user fuck it up From f51338ba11f44c7229f27342ece32750f76056d4 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 11:27:52 -0500 Subject: [PATCH 07/18] go mod tidy --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b7787fe..cb655bd 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/computersciencehouse/csh-auth v0.1.0 github.com/gin-gonic/gin v1.11.0 github.com/sirupsen/logrus v1.9.3 + github.com/slack-go/slack v0.17.3 go.mongodb.org/mongo-driver v1.17.6 mvdan.cc/xurls/v2 v2.6.0 ) @@ -38,7 +39,6 @@ require ( github.com/pquerna/cachecontrol v0.2.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.55.0 // indirect - github.com/slack-go/slack v0.17.3 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect diff --git a/go.sum b/go.sum index 8a1d4c0..ae41f3b 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -97,8 +99,6 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= -go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= From 0e1e280fe5b4e529a20dec0e7e637a087e50a123 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 11:19:43 -0500 Subject: [PATCH 08/18] gofmt --- database/ranked_vote.go | 1 - main.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/database/ranked_vote.go b/database/ranked_vote.go index 1ca69e0..b9761e7 100644 --- a/database/ranked_vote.go +++ b/database/ranked_vote.go @@ -13,7 +13,6 @@ type RankedVote struct { Options map[string]int `bson:"options"` } - func CastRankedVote(ctx context.Context, vote *RankedVote, voter *Voter) error { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() diff --git a/main.go b/main.go index 0a95d6e..2c2e63f 100644 --- a/main.go +++ b/main.go @@ -67,7 +67,7 @@ func main() { r.GET("/auth/callback", csh.AuthCallback) r.GET("/auth/logout", csh.AuthLogout) - // TODO: change ALL the response codes to use http.(actual description) + // TODO: change ALL the response codes to use http.(actual description) r.GET("/", csh.AuthWrapper(func(c *gin.Context) { cl, _ := c.Get("cshauth") claims := cl.(cshAuth.CSHClaims) From 7999ae384ad13261fbdc53ec1ff952c475c43764 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 00:29:03 -0500 Subject: [PATCH 09/18] Allow hiding results while poll is open this replaces the current hide functionality --- database/poll.go | 19 ++++--------------- main.go | 10 +--------- templates/create.tmpl | 8 ++++++++ templates/result.tmpl | 6 ++++++ 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/database/poll.go b/database/poll.go index 01c2b7c..29f5bb7 100644 --- a/database/poll.go +++ b/database/poll.go @@ -21,8 +21,11 @@ type Poll struct { Gatekeep bool `bson:"gatekeep"` QuorumType float64 `bson:"quorumType"` AllowedUsers []string `bson:"allowedUsers"` - Hidden bool `bson:"hidden"` AllowWriteIns bool `bson:"writeins"` + + // Prevent this poll from having progress displayed + // This is important for events like elections where the results shouldn't be visible mid vote + Hidden bool `bson:"hidden"` } const POLL_TYPE_SIMPLE = "simple" @@ -69,20 +72,6 @@ func (poll *Poll) Hide(ctx context.Context) error { return nil } -func (poll *Poll) Reveal(ctx context.Context) error { - ctx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - - objId, _ := primitive.ObjectIDFromHex(poll.Id) - - _, err := Client.Database(db).Collection("polls").UpdateOne(ctx, map[string]interface{}{"_id": objId}, map[string]interface{}{"$set": map[string]interface{}{"hidden": false}}) - if err != nil { - return err - } - - return nil -} - func CreatePoll(ctx context.Context, poll *Poll) (string, error) { ctx, cancel := context.WithTimeout(ctx, 10*time.Second) defer cancel() diff --git a/main.go b/main.go index 2c2e63f..0f16f65 100644 --- a/main.go +++ b/main.go @@ -157,9 +157,9 @@ func main() { OpenedTime: time.Now(), Open: true, QuorumType: quorum, - Hidden: false, Gatekeep: c.PostForm("gatekeep") == "true", AllowWriteIns: c.PostForm("allowWriteIn") == "true", + Hidden: c.PostForm("hidden") == "true", } if c.PostForm("rankedChoice") == "true" { poll.VoteType = database.POLL_TYPE_RANKED @@ -392,14 +392,6 @@ func main() { return } - if poll.Hidden && poll.CreatedBy != claims.UserInfo.Username { - c.HTML(403, "hidden.tmpl", gin.H{ - "Username": claims.UserInfo.Username, - "FullName": claims.UserInfo.FullName, - }) - return - } - results, err := poll.GetResult(c) if err != nil { c.JSON(500, gin.H{"error": err.Error()}) diff --git a/templates/create.tmpl b/templates/create.tmpl index d892a3f..cd7d869 100644 --- a/templates/create.tmpl +++ b/templates/create.tmpl @@ -72,6 +72,14 @@ > Ranked Choice Vote +
+ + Hide Results Until Vote is Complete +
{{ if .IsEvals }}

+ {{ if and $.IsHidden $.IsOpen }} +
+

Results will be available once the poll closes

+
+ {{ else }}
{{ range $i, $val := .Results }} {{ if eq $.VoteType "ranked" }} @@ -46,6 +51,7 @@ {{ end }} {{ end }}
+ {{ end }} {{ if and (.CanModify) (.IsHidden) }}

From 22310ff788b6fa2776ea8817b248d45fadf2d76d Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 00:46:25 -0500 Subject: [PATCH 10/18] Clarify dev setup for compose --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b9dcae5..39ca4ac 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ Implementation ## Configuration -You'll need to set up these values in your environment. Ask an RTP for OIDC credentials. A docker-compose file is provided for convenience. Otherwise, I trust you to figure it out! +If you're using the compose file, you'll need to ask an RTP for the vote-dev OIDC secret, and set it as `VOTE_OIDC_SECRET` in your environment + +If you're not using the compose file, you'll need more of these ``` VOTE_HOST=http://localhost:8080 From 77889e723bd72edc1de47a713af54f48402a1033 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 00:47:20 -0500 Subject: [PATCH 11/18] go mod download as a separate layer for caching --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 19ec15d..4cf406a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,12 @@ FROM docker.io/golang:1.24-alpine AS build WORKDIR /src/ RUN apk add git -COPY go* . -COPY *.go . +COPY go.* . +RUN go mod download # do this before build for caching COPY database database COPY logging logging COPY sse sse +COPY *.go . RUN go build -v -o vote FROM docker.io/alpine From 77ec426f44bc1387942a4c60262ae75c082bcded Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 00:48:16 -0500 Subject: [PATCH 12/18] Allow alumni to dev --- README.md | 2 ++ docker-compose.yaml | 1 + main.go | 7 ++++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39ca4ac..a0f8494 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ VOTE_SLACK_APP_TOKEN= VOTE_SLACK_BOT_TOKEN= ``` +You can also set `DEV_DISABLE_ACTIVE_FILTERS="true"` to disable the requirements that you be active + ## Linting These will be checked by CI diff --git a/docker-compose.yaml b/docker-compose.yaml index a64adf3..66457a9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -12,6 +12,7 @@ services: VOTE_OIDC_ID: vote-dev VOTE_OIDC_SECRET: "${VOTE_OIDC_SECRET}" VOTE_STATE: 27a28540e47ec786b7bdad03f83171b3 + DEV_DISABLE_ACTIVE_FILTERS: "${DEV_DISABLE_ACTIVE_FILTERS}" ports: - "127.0.0.1:8080:8080" diff --git a/main.go b/main.go index 0f16f65..e614a66 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,7 @@ import ( var VOTE_TOKEN = os.Getenv("VOTE_TOKEN") var CONDITIONAL_GATEKEEP_URL = os.Getenv("VOTE_CONDITIONAL_URL") var VOTE_HOST = os.Getenv("VOTE_HOST") +var DEV_DISABLE_ACTIVE_FILTERS bool = os.Getenv("DEV_DISABLE_ACTIVE_FILTERS") == "true" func inc(x int) string { return strconv.Itoa(x + 1) @@ -111,7 +112,7 @@ func main() { r.GET("/create", csh.AuthWrapper(func(c *gin.Context) { cl, _ := c.Get("cshauth") claims := cl.(cshAuth.CSHClaims) - if !slices.Contains(claims.UserInfo.Groups, "active") { + if !DEV_DISABLE_ACTIVE_FILTERS && !slices.Contains(claims.UserInfo.Groups, "active") { c.HTML(403, "unauthorized.tmpl", gin.H{ "Username": claims.UserInfo.Username, "FullName": claims.UserInfo.FullName, @@ -129,7 +130,7 @@ func main() { r.POST("/create", csh.AuthWrapper(func(c *gin.Context) { cl, _ := c.Get("cshauth") claims := cl.(cshAuth.CSHClaims) - if !slices.Contains(claims.UserInfo.Groups, "active") { + if !DEV_DISABLE_ACTIVE_FILTERS && !slices.Contains(claims.UserInfo.Groups, "active") { c.HTML(403, "unauthorized.tmpl", gin.H{ "Username": claims.UserInfo.Username, "FullName": claims.UserInfo.FullName, @@ -551,7 +552,7 @@ func main() { // TODO: use the return value to influence messages shown on results page func canVote(user cshAuth.CSHUserInfo, poll database.Poll, allowedUsers []string) int { // always false if user is not active - if !slices.Contains(user.Groups, "active") { + if !DEV_DISABLE_ACTIVE_FILTERS && !slices.Contains(user.Groups, "active") { return 3 } voted, err := database.HasVoted(context.Background(), poll.Id, user.Username) From 4bab2987670fc4c3001da91b0aceb4cd3b3de5f4 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 01:30:47 -0500 Subject: [PATCH 13/18] Make polls modifiable by removing Reveal Polls that are hidden really shouldn't be revealed --- README.md | 2 +- main.go | 43 ------------------------------------------- templates/result.tmpl | 7 ------- 3 files changed, 1 insertion(+), 51 deletions(-) diff --git a/README.md b/README.md index a0f8494..c20574b 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,6 @@ gofmt -w -s *.go logging sse database - [ ] Don't let the user fuck it up - [ ] Show E-Board polls with a higher priority -- [ ] Move Hide Vote to create instead of after you vote :skull: +- [x] Move Hide Vote to create instead of after you vote :skull: - [ ] Display the reason why a user is on the results page of a running poll - [ ] Display minimum time left that a poll is open diff --git a/main.go b/main.go index e614a66..3a0b41a 100644 --- a/main.go +++ b/main.go @@ -230,9 +230,6 @@ func main() { } canModify := containsString(claims.UserInfo.Groups, "active_rtp") || containsString(claims.UserInfo.Groups, "eboard") || poll.CreatedBy == claims.UserInfo.Username - if poll.Gatekeep { - canModify = false - } c.HTML(200, "poll.tmpl", gin.H{ "Id": poll.Id, @@ -400,9 +397,6 @@ func main() { } canModify := containsString(claims.UserInfo.Groups, "active_rtp") || containsString(claims.UserInfo.Groups, "eboard") || poll.CreatedBy == claims.UserInfo.Username - if poll.Gatekeep { - canModify = false - } c.HTML(200, "result.tmpl", gin.H{ "Id": poll.Id, @@ -455,43 +449,6 @@ func main() { c.Redirect(302, "/results/"+poll.Id) })) - r.POST("/poll/:id/reveal", csh.AuthWrapper(func(c *gin.Context) { - cl, _ := c.Get("cshauth") - claims := cl.(cshAuth.CSHClaims) - - poll, err := database.GetPoll(c, c.Param("id")) - if err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - - if poll.CreatedBy != claims.UserInfo.Username { - c.JSON(403, gin.H{"error": "Only the creator can reveal a poll result"}) - return - } - - err = poll.Reveal(c) - if err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - pId, _ := primitive.ObjectIDFromHex(poll.Id) - action := database.Action{ - Id: "", - PollId: pId, - Date: primitive.NewDateTimeFromTime(time.Now()), - User: claims.UserInfo.Username, - Action: "Reveal Results", - } - err = database.WriteAction(c, &action) - if err != nil { - c.JSON(500, gin.H{"error": err.Error()}) - return - } - - c.Redirect(302, "/results/"+poll.Id) - })) - r.POST("/poll/:id/close", csh.AuthWrapper(func(c *gin.Context) { cl, _ := c.Get("cshauth") claims := cl.(cshAuth.CSHClaims) diff --git a/templates/result.tmpl b/templates/result.tmpl index 2eb499f..f3542a6 100644 --- a/templates/result.tmpl +++ b/templates/result.tmpl @@ -52,13 +52,6 @@ {{ end }}
{{ end }} - {{ if and (.CanModify) (.IsHidden) }} -
-
-
- -
- {{ end }} {{ if and (.CanModify) (not .IsHidden) }}

From 4ee1749fa76ffab3291db9125ae47f8672c23790 Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 11:15:23 -0500 Subject: [PATCH 14/18] Create dev mode evals override --- README.md | 4 +++- docker-compose.yaml | 1 + main.go | 12 ++++++++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c20574b..a8deb9f 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,9 @@ VOTE_SLACK_APP_TOKEN= VOTE_SLACK_BOT_TOKEN= ``` -You can also set `DEV_DISABLE_ACTIVE_FILTERS="true"` to disable the requirements that you be active +### Dev Overrides +`DEV_DISABLE_ACTIVE_FILTERS="true"` will disable the requirements that you be active to vote +`DEV_FORCE_IS_EVALS="true"` will force vote to treat all users as the Evals director ## Linting These will be checked by CI diff --git a/docker-compose.yaml b/docker-compose.yaml index 66457a9..22f5f75 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -13,6 +13,7 @@ services: VOTE_OIDC_SECRET: "${VOTE_OIDC_SECRET}" VOTE_STATE: 27a28540e47ec786b7bdad03f83171b3 DEV_DISABLE_ACTIVE_FILTERS: "${DEV_DISABLE_ACTIVE_FILTERS}" + DEV_FORCE_IS_EVALS: "${DEV_FORCE_IS_EVALS}" ports: - "127.0.0.1:8080:8080" diff --git a/main.go b/main.go index 3a0b41a..4ea1fe1 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,10 @@ import ( var VOTE_TOKEN = os.Getenv("VOTE_TOKEN") var CONDITIONAL_GATEKEEP_URL = os.Getenv("VOTE_CONDITIONAL_URL") var VOTE_HOST = os.Getenv("VOTE_HOST") + +// Dev mode flags var DEV_DISABLE_ACTIVE_FILTERS bool = os.Getenv("DEV_DISABLE_ACTIVE_FILTERS") == "true" +var DEV_FORCE_IS_EVALS bool = os.Getenv("DEV_FORCE_IS_EVALS") == "true" func inc(x int) string { return strconv.Itoa(x + 1) @@ -123,7 +126,7 @@ func main() { c.HTML(200, "create.tmpl", gin.H{ "Username": claims.UserInfo.Username, "FullName": claims.UserInfo.FullName, - "IsEvals": containsString(claims.UserInfo.Groups, "eboard-evaluations"), + "IsEvals": isEvals(claims.UserInfo), }) })) @@ -184,7 +187,7 @@ func main() { poll.Options = []string{"Pass", "Fail", "Abstain"} } if poll.Gatekeep { - if !slices.Contains(claims.UserInfo.Groups, "eboard-evaluations") { + if !isEvals(claims.UserInfo) { c.HTML(403, "unauthorized.tmpl", gin.H{ "Username": claims.UserInfo.Username, "FullName": claims.UserInfo.FullName, @@ -503,6 +506,11 @@ func main() { r.Run() } +// isEvals determines if the current user is evals, allowing for a dev mode override +func isEvals(user cshAuth.CSHUserInfo) bool { + return DEV_FORCE_IS_EVALS || containsString(user.Groups, "eboard-evaluations") +} + // canVote determines whether a user can cast a vote. // // returns an integer value: 0 is success, 1 is database error, 3 is not active, 4 is gatekept, 9 is already voted From b27bfe503531f46bcb3028c57d8f987c9378ab8e Mon Sep 17 00:00:00 2001 From: Max Meinhold Date: Wed, 26 Nov 2025 11:16:37 -0500 Subject: [PATCH 15/18] Clarify gatekeep autoclose for evals --- main.go | 1 + templates/create.tmpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 4ea1fe1..89b8c04 100644 --- a/main.go +++ b/main.go @@ -412,6 +412,7 @@ func main() { "CanModify": canModify, "Username": claims.UserInfo.Username, "FullName": claims.UserInfo.FullName, + "Gatekeep": poll.Gatekeep, }) })) diff --git a/templates/create.tmpl b/templates/create.tmpl index cd7d869..1fbeeba 100644 --- a/templates/create.tmpl +++ b/templates/create.tmpl @@ -89,7 +89,7 @@ value="true" onchange="onGatekeepChange()" > - Gatekeep Required + Gatekeep Required (Require Quorum, Limit Voters, Force Automatic Close)