Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/benchmark_cmd_app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
go-version-file: 'go.mod'

- name: Download dependencies
run: go mod download
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/build_cmd_app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read

steps:
- uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
go-version-file: 'go.mod'

- name: Download dependencies
run: go mod download
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/scan_cmd_app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Security Scan
on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
security:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- name: Checkout Source
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Run Gosec Security Scanner
uses: securego/gosec@master
with:
args: '-fmt sarif -out gosec.sarif ./...'

- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: gosec.sarif

- name: Run govulncheck
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
4 changes: 3 additions & 1 deletion .github/workflows/test_cmd_app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read

steps:
- uses: actions/checkout@v5

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25"
go-version-file: 'go.mod'

- name: Download dependencies
run: go mod download
Expand Down
2 changes: 1 addition & 1 deletion cmd/app/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (app *app) postSignIn(w http.ResponseWriter, r *http.Request) {
// Prevent timing attacks - use pre-computed bcrypt hash
dummyUser := &users.User{}
dummyUser.Password = users.Password{Hash: []byte(users.PreComputedHash)}
dummyUser.Password.Matches(form.Password)
_, _ = dummyUser.Password.Matches(form.Password) // Intentionally ignored to prevent timing attacks
match = false

app.logger.WarnContext(r.Context(), "error getting user by email", slog.String("msg", err.Error()))
Expand Down
8 changes: 6 additions & 2 deletions cmd/app/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ func (app *app) render(w http.ResponseWriter, r *http.Request, status int, templ

w.WriteHeader(status)

buf.WriteTo(w)
if _, err := buf.WriteTo(w); err != nil {
app.logger.ErrorContext(r.Context(), "failed to write response", slog.String("msg", err.Error()))
}
}

func (app *app) renderError(w http.ResponseWriter, r *http.Request, err error, userMessage string) {
Expand Down Expand Up @@ -144,7 +146,9 @@ func (h *TraceHandler) Handle(ctx context.Context, r slog.Record) error {

func generateTraceID() string {
bytes := make([]byte, 4)
rand.Read(bytes)
if _, err := rand.Read(bytes); err != nil {
panic(err) // crypto/rand should never fail
}
return hex.EncodeToString(bytes)
}

Expand Down
8 changes: 1 addition & 7 deletions cmd/app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,7 @@ func main() {

logger.Info("database version", slog.Int64("version", *dbVersion))

htmxFuncs := template.FuncMap{
"preload": func(event string) template.HTMLAttr {
return template.HTMLAttr(fmt.Sprintf(`preload="%s"`, event))
},
}

templateCache, err := newTemplateCache(WithFunctions(htmxFuncs))
templateCache, err := newTemplateCache()
if err != nil {
logger.Error("error creating template cache", slog.String("msg", err.Error()))
os.Exit(1)
Expand Down
9 changes: 1 addition & 8 deletions cmd/app/testutils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package main

import (
"bytes"
"fmt"
"html/template"
"io"
"log/slog"
"net/http"
Expand Down Expand Up @@ -36,12 +34,7 @@ func newTestApplication(tb testing.TB) *app {
tb.Fatal(err)
}

// Create an instance of the template cache with HTMX functions.
htmxFuncs := template.FuncMap{
"get": func(url string) template.HTMLAttr { return template.HTMLAttr(fmt.Sprintf(`hx-get="%s"`, url)) },
"preload": func(event string) template.HTMLAttr { return template.HTMLAttr(fmt.Sprintf(`preload="%s"`, event)) },
}
templateCache, err := newTemplateCache(WithFunctions(htmxFuncs))
templateCache, err := newTemplateCache()
if err != nil {
tb.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/bit8bytes/goalkeepr

go 1.25.0
go 1.25.3

require (
github.com/alexedwards/scs/v2 v2.9.0
Expand Down
2 changes: 1 addition & 1 deletion internal/data/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (p *SQLiteProvider) Open(path string) (*sql.DB, error) {
defer cancel()
err = db.PingContext(ctx)
if err != nil {
db.Close()
_ = db.Close() // Ignore close error, ping error takes precedence
return nil, err
}

Expand Down
10 changes: 5 additions & 5 deletions ui/html/app/goals.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ <h1 class="text-lg font-bold text-base-content/50">
{{ if eq (mod $index 2) 0 }}
<a
href="/goals/{{ $goal.ID }}"
{{ preload "mouseover" }}
preload="mouseover"
class="timeline-start timeline-box"
>
{{ $goal.Due.Format "2006" }}:
Expand Down Expand Up @@ -67,7 +67,7 @@ <h1 class="text-lg font-bold text-base-content/50">
</div>
<a
href="/goals/{{ $goal.ID }}"
{{ preload "mouseover" }}
preload="mouseover"
class="timeline-end timeline-box"
>{{ $goal.Due.Format "2006" }}: {{ $goal.Goal }}</a
>
Expand All @@ -85,7 +85,7 @@ <h1 class="text-lg font-bold text-base-content/50">
<div class="timeline-middle">
<a
href="/goals/add/"
{{ preload "mouseover" }}
preload="mouseover"
class="btn btn-circle btn-ghost w-8 h-8 opacity-50"
>
<svg
Expand All @@ -108,7 +108,7 @@ <h1 class="text-lg font-bold text-base-content/50">
</ul>
{{ else }}
<div>
<a href="/goals/add/" {{ preload "mouseover" }} class="btn btn-primary">
<a href="/goals/add/" preload="mouseover" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
Expand All @@ -126,7 +126,7 @@ <h1 class="text-lg font-bold text-base-content/50">
</svg>
First Goal
</a>
<a href="/settings#branding" {{ preload "mouseover" }} class="btn">
<a href="/settings#branding" preload="mouseover" class="btn">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
Expand Down