Add support for user hooks
4 files changed, 147 insertions(+), 32 deletions(-) M go.mod M go.sum M main.go M templates/dashboard.html
M go.mod => go.mod +2 -2
@@ 3,7 3,7 @@ module git.sr.ht/~emersion/yojo go 1.20 require ( code.gitea.io/sdk/gitea v0.17.0 code.gitea.io/sdk/gitea v0.17.2-0.20240125160400-53f735b9110e git.sr.ht/~emersion/go-oauth2 v0.0.0-20231107122354-9bc3ac409ebf git.sr.ht/~emersion/go-scfg v0.0.0-20231211181832-0b4e72d8ec3c @@ git.sr.ht/~emersion/gqlclient v0.0.0-20230820050442-8873fe0204b9 20,6 20,6 @@ require ( github.com/hashicorp/go-version v1.6.0 // indirect github.com/stretchr/testify v1.8.1 // indirect github.com/vektah/gqlparser/v2 v2.5.10 // indirect golang.org/x/crypto v0.16.0 // indirect golang.org/x/crypto v0.17.0 // indirect golang.org/x/sys v0.15.0 // indirect )
M go.sum => go.sum +31 -9
@@ 1,5 1,5 @@ code.gitea.io/sdk/gitea v0.17.0 h1:8JPBss4+Jf7AE1YcfyiGrngTXE8dFSG3si/bypsTH34= code.gitea.io/sdk/gitea v0.17.0/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg= code.gitea.io/sdk/gitea v0.17.2-0.20240125160400-53f735b9110e h1:QY6Ce4kLEUy478ufVu5OhLldJzzrKkxq3j7W7vyeblo= code.gitea.io/sdk/gitea v0.17.2-0.20240125160400-53f735b9110e/go.mod h1:aCnBqhHpoEWA180gMbaCtdX9Pl6BWBAuuP2miadoTNM= git.sr.ht/~emersion/go-oauth2 v0.0.0-20231107122354-9bc3ac409ebf h1:JCWWRdr1xVC9kw5oKcjACyxcWq19UF0U6kdX0GcWfJY= git.sr.ht/~emersion/go-oauth2 v0.0.0-20231107122354-9bc3ac409ebf/go.mod h1:VHj0jSCLIkrfEwmOvJ4+ykpoVbD/YLN7BM523oKKBHc= @@ git.sr.ht/~emersion/go-scfg v0.0.0-20231211181832-0b4e72d8ec3c h1:Cjy9/qASF8hogbKbWXgEQZxbYHrM9ksl76sGzsP8Zqo= 24,7 24,6 @@ github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= 42,28 41,51 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vektah/gqlparser/v2 v2.5.10 h1:6zSM4azXC9u4Nxy5YmdmGu4uKamfwsdKTwp5zsEealU= github.com/vektah/gqlparser/v2 v2.5.10/go.mod h1:1rCcfwB2ekJofmluGWXMSEnPMZgbxzwj6FaZ/4OT8Cc= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
M main.go => main.go +106 -21
@@ 44,7 44,7 @@ const ( var ( srhtAuthScope = []string{"builds.sr.ht/PROFILE:RO", "builds.sr.ht/JOBS:RW"} giteaAuthScope = []string{"repo:status", "admin:repo_hook", "admin:org_hook"} giteaAuthScope = []string{"repo:status", "admin:repo_hook", "admin:user_hook", "admin:org_hook"} ) @@ var ( 219,6 219,12 @@ func main() { return } userHooks, err := listUserHooks(giteaClient, getOrigin(r)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var ( repos []*gitea.Repository @@ page int = 1 257,6 263,7 @@ func main() { GiteaUsername string GiteaHostname string GiteaName string UserEnabled bool Repositories []*gitea.Repository }{ @@ SrhtUsername: sessionData.SrhtToken.Username, 264,6 271,7 @@ func main() { GiteaUsername: sessionData.GiteaToken.Username, GiteaHostname: giteaHostname, GiteaName: config.Gitea.Name, UserEnabled: len(userHooks) > 0, Repositories: repos, } @@ if err := tpl.ExecuteTemplate(w, "dashboard.html", &data); err != nil { 280,6 288,23 @@ func main() { } }) r.Post("/", func(w http.ResponseWriter, r *http.Request) { r.ParseForm() var err error if r.Form.Has("disable_user") { err = disableUser(r.Context(), getOrigin(r)) } else { err = enableUser(r.Context(), getOrigin(r)) } if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } http.Redirect(w, r, r.URL.Path, http.StatusSeeOther) }) r.Get("/logout", func(w http.ResponseWriter, r *http.Request) { sessionFromContext(r.Context()).Delete() @@ http.Redirect(w, r, "/", http.StatusTemporaryRedirect) 389,7 414,7 @@ func main() { return } hooks, err := listHooks(giteaClient, repo.Owner.UserName, repo.Name, getOrigin(r)) hooks, err := listRepoHooks(giteaClient, repo.Owner.UserName, repo.Name, getOrigin(r)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ return 586,12 611,7 @@ func getHookEndpoint(origin string) string { return origin + "/webhook" } func listHooks(giteaClient *gitea.Client, owner, repo, origin string) ([]*gitea.Hook, error) { hooks, _, err := giteaClient.ListRepoHooks(owner, repo, gitea.ListHooksOptions{}) if err != nil { return nil, fmt.Errorf("failed to fetch Gitea repo webhooks: %v", err) } func filterHooks(hooks []*gitea.Hook, origin string) []*gitea.Hook { hookURLPrefix := getHookEndpoint(origin) + "?" var ourHooks []*gitea.Hook @@ for _, hook := range hooks { 600,26 620,19 @@ func listHooks(giteaClient *gitea.Client, owner, repo, origin string) ([]*gitea. ourHooks = append(ourHooks, hook) } } return ourHooks, nil return ourHooks } func enableRepo(ctx context.Context, owner, repo, origin string) error { sessionData := sessionFromContext(ctx).Load() giteaClient, err := newGiteaClient(ctx, sessionData.GiteaToken) if err != nil { return fmt.Errorf("failed to create Gitea client: %v", err) } func prepareHook(ctx context.Context, sessionData *sessionData, origin string) (*gitea.CreateHookOption, error) { installation := &Installation{ SrhtToken: *sessionData.SrhtToken, GiteaToken: *sessionData.GiteaToken, } if err := db.StoreInstallation(ctx, installation); err != nil { return fmt.Errorf("failed to store installation into DB: %v", err) return nil, fmt.Errorf("failed to store installation into DB: %v", err) } _, _, err = giteaClient.CreateRepoHook(owner, repo, gitea.CreateHookOption{ return &gitea.CreateHookOption{ Type: gitea.HookTypeGitea, Config: map[string]string{ @@ "url": getHookEndpoint(origin) + "?installation_token=" + installation.WebhookToken, 629,7 642,30 @@ func enableRepo(ctx context.Context, owner, repo, origin string) error { Events: []string{"push", "pull_request_only", "pull_request_sync"}, BranchFilter: "*", Active: true, }) }, nil } func listRepoHooks(giteaClient *gitea.Client, owner, repo, origin string) ([]*gitea.Hook, error) { hooks, _, err := giteaClient.ListRepoHooks(owner, repo, gitea.ListHooksOptions{}) if err != nil { return nil, fmt.Errorf("failed to fetch Gitea repo webhooks: %v", err) } return filterHooks(hooks, origin), nil } func enableRepo(ctx context.Context, owner, repo, origin string) error { sessionData := sessionFromContext(ctx).Load() giteaClient, err := newGiteaClient(ctx, sessionData.GiteaToken) if err != nil { return fmt.Errorf("failed to create Gitea client: %v", err) } hookOptions, err := prepareHook(ctx, sessionData, origin) if err != nil { return err } _, _, err = giteaClient.CreateRepoHook(owner, repo, *hookOptions) if err != nil { return fmt.Errorf("failed to create repo hook: %v", err) @@ } 644,7 680,7 @@ func disableRepo(ctx context.Context, owner, repo, origin string) error { return fmt.Errorf("failed to create Gitea client: %v", err) } hooks, err := listHooks(giteaClient, owner, repo, origin) hooks, err := listRepoHooks(giteaClient, owner, repo, origin) if err != nil { return err @@ } 659,6 695,55 @@ func disableRepo(ctx context.Context, owner, repo, origin string) error { return nil } func listUserHooks(giteaClient *gitea.Client, origin string) ([]*gitea.Hook, error) { hooks, _, err := giteaClient.ListMyHooks(gitea.ListHooksOptions{}) if err != nil { return nil, fmt.Errorf("failed to fetch Gitea user webhooks: %v", err) } return filterHooks(hooks, origin), nil } func enableUser(ctx context.Context, origin string) error { sessionData := sessionFromContext(ctx).Load() giteaClient, err := newGiteaClient(ctx, sessionData.GiteaToken) if err != nil { return fmt.Errorf("failed to create Gitea client: %v", err) } hookOptions, err := prepareHook(ctx, sessionData, origin) if err != nil { return err } _, _, err = giteaClient.CreateMyHook(*hookOptions) if err != nil { return fmt.Errorf("failed to create user hook: %v", err) } return nil } func disableUser(ctx context.Context, origin string) error { sessionData := sessionFromContext(ctx).Load() giteaClient, err := newGiteaClient(ctx, sessionData.GiteaToken) if err != nil { return fmt.Errorf("failed to create Gitea client: %v", err) } hooks, err := listUserHooks(giteaClient, origin) if err != nil { return err } for _, hook := range hooks { _, err := giteaClient.DeleteMyHook(hook.ID) if err != nil { return fmt.Errorf("failed to delete Gitea user webhook: %v", err) } } return nil } func sanityCheckEndpoint(ctx context.Context, endpoint string) error { u, err := url.Parse(endpoint) if err != nil {
M templates/dashboard.html => templates/dashboard.html +8 -0
@@ 11,6 11,14 @@ <strong>{{.SrhtUsername}}</strong> and to {{.GiteaName}} as <strong>{{.GiteaUsername}}</strong>.</p> <form method="post" action=""> {{if .UserEnabled}} <button type="submit" name="disable_user" class="install-btn">Disable for all my repositories</button> {{else}} <button type="submit" name="enable_user" class="install-btn">Enable for all my repositories</button> {{end}} </form> <p>Please select a repository to configure:</p> <ul class="codeberg-repos">