Skip to content

Commit 1a63acc

Browse files
authored
Merge pull request #52 from Bin-Huang/codex/protect-global-skills-list
[codex] require auth for global skills list
2 parents 8d3ec6b + 9bef515 commit 1a63acc

2 files changed

Lines changed: 112 additions & 1 deletion

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package setup
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"path/filepath"
8+
"strings"
9+
"testing"
10+
11+
"github.com/fastclaw-ai/fastclaw/internal/auth"
12+
"github.com/fastclaw-ai/fastclaw/internal/store"
13+
"github.com/fastclaw-ai/fastclaw/internal/users"
14+
)
15+
16+
func TestListSkillsRequiresAuth(t *testing.T) {
17+
ctx := context.Background()
18+
s, resolver, adminUser, regularUser := newSkillsAuthTestServer(t, ctx)
19+
t.Setenv("FASTCLAW_HOME", t.TempDir())
20+
21+
handler := s.authMiddleware(s.handleListSkills)
22+
23+
t.Run("unauthenticated request is rejected", func(t *testing.T) {
24+
rr := httptest.NewRecorder()
25+
handler(rr, httptest.NewRequest(http.MethodGet, "/api/skills", nil))
26+
if rr.Code != http.StatusUnauthorized {
27+
t.Fatalf("status = %d, want %d", rr.Code, http.StatusUnauthorized)
28+
}
29+
})
30+
31+
t.Run("regular user is allowed", func(t *testing.T) {
32+
rr := httptest.NewRecorder()
33+
handler(rr, skillsListRequest(t, ctx, resolver, regularUser.ID))
34+
if rr.Code != http.StatusOK {
35+
t.Fatalf("status = %d, want %d", rr.Code, http.StatusOK)
36+
}
37+
if got := strings.TrimSpace(rr.Body.String()); got != "[]" {
38+
t.Fatalf("body = %q, want []", got)
39+
}
40+
})
41+
42+
t.Run("super admin is allowed", func(t *testing.T) {
43+
rr := httptest.NewRecorder()
44+
handler(rr, skillsListRequest(t, ctx, resolver, adminUser.ID))
45+
if rr.Code != http.StatusOK {
46+
t.Fatalf("status = %d, want %d", rr.Code, http.StatusOK)
47+
}
48+
if got := strings.TrimSpace(rr.Body.String()); got != "[]" {
49+
t.Fatalf("body = %q, want []", got)
50+
}
51+
})
52+
}
53+
54+
func newSkillsAuthTestServer(t *testing.T, ctx context.Context) (*Server, *auth.Resolver, *users.Account, *users.Account) {
55+
t.Helper()
56+
57+
dbPath := filepath.Join(t.TempDir(), "fastclaw.db")
58+
st, err := store.NewDBStore("sqlite", "file:"+dbPath+"?cache=shared")
59+
if err != nil {
60+
t.Fatalf("NewDBStore: %v", err)
61+
}
62+
t.Cleanup(func() {
63+
_ = st.Close()
64+
})
65+
if err := st.Migrate(ctx); err != nil {
66+
t.Fatalf("Migrate: %v", err)
67+
}
68+
69+
accts, err := users.NewAccounts(st)
70+
if err != nil {
71+
t.Fatalf("NewAccounts: %v", err)
72+
}
73+
adminUser := createSkillsTestUser(t, ctx, accts, "admin", users.RoleSuperAdmin)
74+
regularUser := createSkillsTestUser(t, ctx, accts, "user", users.RoleUser)
75+
resolver, err := auth.NewResolver(st)
76+
if err != nil {
77+
t.Fatalf("NewResolver: %v", err)
78+
}
79+
80+
s := NewServer(0)
81+
s.SetStore(st)
82+
s.SetAuth(resolver)
83+
return s, resolver, adminUser, regularUser
84+
}
85+
86+
func createSkillsTestUser(t *testing.T, ctx context.Context, accts *users.Accounts, username, role string) *users.Account {
87+
t.Helper()
88+
89+
acct, err := accts.Create(ctx, users.CreateInput{
90+
Username: username,
91+
Email: username + "@example.test",
92+
Password: "password",
93+
Role: role,
94+
})
95+
if err != nil {
96+
t.Fatalf("Create(%s): %v", username, err)
97+
}
98+
return acct
99+
}
100+
101+
func skillsListRequest(t *testing.T, ctx context.Context, resolver *auth.Resolver, userID string) *http.Request {
102+
t.Helper()
103+
104+
cookie, err := resolver.IssueSession(ctx, userID)
105+
if err != nil {
106+
t.Fatalf("IssueSession: %v", err)
107+
}
108+
req := httptest.NewRequest(http.MethodGet, "/api/skills", nil)
109+
req.AddCookie(cookie)
110+
return req
111+
}

internal/setup/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ func (s *Server) Run(ctx context.Context) error {
304304
mux.HandleFunc("POST /api/line/webhook/{accountId}", s.handleLINEWebhook)
305305

306306
// Skills
307-
mux.HandleFunc("GET /api/skills", s.handleListSkills)
307+
mux.HandleFunc("GET /api/skills", auth(s.handleListSkills))
308308
mux.HandleFunc("GET /api/skills/search", auth(s.handleSearchSkills))
309309
mux.HandleFunc("POST /api/skills/install", auth(s.handleInstallSkill))
310310
mux.HandleFunc("POST /api/skills/upload", auth(s.handleUploadSkill))

0 commit comments

Comments
 (0)