diff --git a/internal/webui/webui.go b/internal/webui/webui.go
index 47507b0..5b8a11b 100644
--- a/internal/webui/webui.go
+++ b/internal/webui/webui.go
@@ -205,7 +205,8 @@ func (h *Handler) postRepo(w http.ResponseWriter, r *http.Request) {
h.renderRepos(w, r, "", "owner is reserved: "+owner)
return
}
- if _, err := h.md.GetUser(r.Context(), owner); errors.Is(err, meta.ErrNotFound) {
+ ownerUser, err := h.md.GetUser(r.Context(), owner)
+ if errors.Is(err, meta.ErrNotFound) {
h.renderRepos(w, r, "", "no such user for owner: "+owner)
return
} else if err != nil {
@@ -214,10 +215,17 @@ func (h *Handler) postRepo(w http.ResponseWriter, r *http.Request) {
}
full := owner + "/" + name
- if _, err := h.md.CreateRepo(r.Context(), full, branch); err != nil {
+ repo, err := h.md.CreateRepo(r.Context(), full, branch)
+ if err != nil {
h.renderRepos(w, r, "", "create repo: "+err.Error())
return
}
+ // Grant the owner admin on their new repo so it is immediately usable
+ // (clone/push) without a separate ACL step, mirroring bootstrap.
+ if err := h.md.SetACL(r.Context(), repo.ID, ownerUser.ID, meta.PermAdmin); err != nil {
+ h.serverError(w, "grant owner acl", err)
+ return
+ }
h.renderRepos(w, r, "created "+full, "")
}
diff --git a/internal/webui/webui_test.go b/internal/webui/webui_test.go
index e66bc67..05e5568 100644
--- a/internal/webui/webui_test.go
+++ b/internal/webui/webui_test.go
@@ -161,6 +161,32 @@ func TestGoldenPath(t *testing.T) {
}
}
+// TestCreateRepoGrantsOwnerAdmin: creating a repo through the UI grants the owner
+// admin on it, so it is immediately usable (clone/push) without a separate ACL
+// step — the gap that left a freshly created repo 403-ing every push.
+func TestCreateRepoGrantsOwnerAdmin(t *testing.T) {
+ x := newHarness(t)
+ ctx := context.Background()
+ session := x.login(x.mintTokenFor(x.admin.ID))
+
+ if rec := x.do(http.MethodPost, "/ui/users", url.Values{"handle": {"bob"}}, session); rec.Code != http.StatusOK {
+ t.Fatalf("create user: %d (%s)", rec.Code, rec.Body)
+ }
+ if rec := x.do(http.MethodPost, "/ui/repos",
+ url.Values{"owner": {"bob"}, "name": {"proj"}, "default_branch": {"main"}}, session); rec.Code != http.StatusOK {
+ t.Fatalf("create repo: %d (%s)", rec.Code, rec.Body)
+ }
+
+ bob, _ := x.md.GetUser(ctx, "bob")
+ repo, err := x.md.GetRepo(ctx, "bob/proj")
+ if err != nil {
+ t.Fatalf("repo not created: %v", err)
+ }
+ if perm, err := x.md.GetACL(ctx, repo.ID, bob.ID); err != nil || perm != meta.PermAdmin {
+ t.Errorf("owner ACL after create = %q, %v; want admin", perm, err)
+ }
+}
+
func TestUnauthenticatedDenied(t *testing.T) {
x := newHarness(t)