Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f45b594
testing db on demand
cowsed Jul 30, 2026
4b57155
graphql for groups init
cowsed Aug 10, 2026
bee6014
Merge branch 'cowsed/group_testing' into cowsed/groups_graphql
cowsed Aug 10, 2026
96558de
more graphql and db tests
cowsed Aug 10, 2026
e95c5ea
finished round one of resolvers
cowsed Aug 10, 2026
d0c264d
changes to group repo interface for resolvers
cowsed Aug 10, 2026
6c5ecff
moved test stuff around
cowsed Aug 10, 2026
ea5c686
more resolver testing
cowsed Aug 10, 2026
4b0675d
yet more group resolver testing
cowsed Aug 10, 2026
ef61311
actually doing graphql things
cowsed Aug 10, 2026
040da44
Merge branch 'main' into cowsed/groups_graphql
cowsed Aug 18, 2026
01ac06b
Update group resolver tests
cowsed Aug 18, 2026
f12b99b
Tests both unit and using DB
cowsed Aug 18, 2026
09689d2
start adding test makerspace
cowsed Aug 18, 2026
89cc49d
Checks to prevent infinite cycles, group schema tidbits, extracting c…
cowsed Aug 18, 2026
aa920ad
a groups are real?!?!?!?
cowsed Aug 18, 2026
4694195
integrating anonymous groups
cowsed Aug 19, 2026
adb2389
more agroup gqls and tests
cowsed Aug 19, 2026
30d9797
more testing of agroups and their queries and mutations as well as ch…
cowsed Aug 19, 2026
df859a8
More protections for graphql endpoints
cowsed Aug 19, 2026
cc90a60
Stricter permissions and enforcing that only root group can hve null …
cowsed Aug 19, 2026
68f71b9
allow gqlgen to format
cowsed Aug 19, 2026
36190cd
AuditLogs for new resolvers
cowsed Aug 19, 2026
b469cda
makerspace directives for permission checks
cowsed Aug 20, 2026
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
54 changes: 53 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ require (
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.12.3
github.com/pressly/goose/v3 v3.27.2
github.com/testcontainers/testcontainers-go v0.43.0
github.com/testcontainers/testcontainers-go/modules/postgres v0.43.0
github.com/vektah/gqlparser/v2 v2.5.35
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
Expand All @@ -26,6 +28,56 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect
)

require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/dockercfg v0.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/ebitengine/purego v0.10.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/magiconair/properties v1.8.10 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.55.0 // indirect
github.com/moby/moby/client v0.5.0 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/moby/term v0.5.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
github.com/shirou/gopsutil/v4 v4.26.5 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/tklauser/go-sysconf v0.3.16 // indirect
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
github.com/agnivade/levenshtein v1.2.1 // indirect
github.com/alexedwards/scs/postgresstore v0.0.0-20251002162104-209de6e426de
Expand All @@ -52,7 +104,7 @@ require (
golang.org/x/crypto v0.53.0
golang.org/x/mod v0.37.0 // indirect
golang.org/x/sync v0.21.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/text v0.38.0
golang.org/x/tools v0.46.0 // indirect
)

Expand Down
118 changes: 114 additions & 4 deletions go.sum

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions internal/auth/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@ package auth

import (
"context"
"errors"
"net/http"

"github.com/alexedwards/scs/v2"
)

type UserContextKey struct{}

var ErrNotAuthenticated error = errors.New("not authenticated")

func UserIDFromContext(ctx context.Context) *int {
userVal := ctx.Value(UserContextKey{})
if userVal == nil {
return nil
}
userID := userVal.(int)
return &userID

}

func OptionalAuthMiddleware(next http.Handler, sessionManager *scs.SessionManager) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

Expand Down
164 changes: 164 additions & 0 deletions internal/database/group_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package database

import (
"log/slog"
"testing"
)

func TestUserGroupVisibilityGood(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, data := VerifyTestDb(slog.Default())
user := data.Users[0]
group := data.BeatlesMusicians

// brian can see musicians
visible, err := store.Groups.IsGroupVisibleToUser(t.Context(), user.Id, group.Id)
if err != nil {
t.Fatalf("Failed to check group visibility of %+v to see %+v: %v", user, group, err)
}
if visible != true {
t.Fatalf("%v should be able to see %v but couldn't", user, group)
}
}
func TestUserGroupVisibilityBad(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, data := VerifyTestDb(slog.Default())
user := data.Users[1]
group := data.BeatlesManagers

// musicians can't see up (unless explicitly allowed to)
visible, err := store.Groups.IsGroupVisibleToUser(t.Context(), user.Id, group.Id)
if err != nil {
t.Fatalf("Failed to check group visibility of %+v to see %+v: %v", user, group, err)
}
if visible != false {
t.Fatalf("%v should NOT be able to see %v but could", user, group)
}
}

func TestAddedUsers(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, _ := VerifyTestDb(slog.Default())
for _, user := range localContext.Users {
user, err := store.Users.GetUserByEmail(t.Context(), user.Email)
if err != nil {
t.Fatalf("Failed to find user with email '%s': %v", user.Email, err)
}
}
}

func TestGroupMembership(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, _ := VerifyTestDb(slog.Default())
for _, email := range []string{
"john@beatles.com",
"paul@beatles.com",
"george@beatles.com",
"ringo@beatles.com",
} {
user, err := store.Users.GetUserByEmail(t.Context(), email)
if err != nil {
t.Fatalf("couldn't find user with email '%s': %v", email, err)
}
inGroup, _, err := store.Groups.IsUserInGroup(t.Context(), user.Id, localContext.BeatlesMusicians.Id)
if err != nil {
t.Fatalf("couldn't check user in group for email '%s': %v", email, err)
}
if !inGroup {
t.Fatalf("user with email '%s' should be in group '%s' but wasn't", email, localContext.BeatlesMusicians.Name)
}
}
}

func TestSubgroupMembership(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, _ := VerifyTestDb(slog.Default())
for _, email := range []string{
"john@beatles.com",
"paul@beatles.com",
"george@beatles.com",
"ringo@beatles.com",
"brian@example.com",
} {
user, err := store.Users.GetUserByEmail(t.Context(), email)
if err != nil {
t.Fatalf("couldn't find user with email '%s': %v", email, err)
}
inGroup, _, err := store.Groups.IsUserInGroup(t.Context(), user.Id, localContext.Brits.Id)
if err != nil {
t.Fatalf("couldn't check user in group for email '%s': %v", email, err)
}
if !inGroup {
t.Fatalf("user with email '%s' should be in group '%s' via subgroup but wasn't", email, localContext.BeatlesMusicians.Name)
}
}
}

func TestCanGroupManageAnonymousGroup(t *testing.T) {
if testing.Short() {
t.Skip("Skipping db test in short mode.")
}
_, store, data := VerifyTestDb(slog.Default())
rootGroupId, err := store.Groups.GetAdminGroupId(t.Context())
if err != nil {
t.Fatalf("couldn't get root group ID to continue test")
}
tests := []struct {
testName string
// params
managerGroupId int
anonymousGroupId int
want bool
wantErr bool
}{
{
"beatles managers manage parlophone so can manage staff of parlophone",
data.BeatlesManagers.Id,
data.Parlophone.StaffAgroupId,
true,
false,
},
{
"beatles musicians DONT manage parlophone so CANT manage staff of parlophone",
data.BeatlesMusicians.Id,
data.Parlophone.StaffAgroupId,
false,
false,
},
{
"root group can manage any agroup so can manage staff of parlophone",
rootGroupId,
data.Parlophone.StaffAgroupId,
true,
false,
},
}
for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
got, gotErr := store.Groups.CanGroupManageAnonymousGroup(t.Context(), tt.managerGroupId, tt.anonymousGroupId)
if gotErr != nil {
if !tt.wantErr {
t.Errorf("CanGroupManageAnonymousGroup() failed: %v", gotErr)
}
return
}
if tt.wantErr {
t.Fatal("CanGroupManageAnonymousGroup() succeeded unexpectedly")
}
// TODO: update the condition below to compare got with tt.want.
if got != tt.want {
t.Errorf("CanGroupManageAnonymousGroup() = %v, want %v", got, tt.want)
}
})
}
}
Loading
Loading