loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'webhook: improve UX for sourcehut and matrix' (#3156) from oliverpool/forgejo:webhook_sourcehut_polish into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3156
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>

+82 -59
+9
models/webhook/webhook.go
··· 361 361 return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted) 362 362 } 363 363 364 + // HeaderAuthorizationTrimPrefix returns the decrypted Authorization with a specified prefix trimmed. 365 + func (w Webhook) HeaderAuthorizationTrimPrefix(prefix string) (string, error) { 366 + s, err := w.HeaderAuthorization() 367 + if err != nil { 368 + return "", err 369 + } 370 + return strings.TrimPrefix(s, prefix), nil 371 + } 372 + 364 373 // SetHeaderAuthorization encrypts and sets the Authorization header. 365 374 func (w *Webhook) SetHeaderAuthorization(cleartext string) error { 366 375 if cleartext == "" {
+5 -1
options/locale/locale_en-US.ini
··· 561 561 AuthName = Authorization name 562 562 AdminEmail = Admin email 563 563 To = Branch name 564 + AccessToken = Access token 564 565 565 566 NewBranchName = New branch name 566 567 CommitSummary = Commit summary ··· 2378 2379 settings.slack_domain = Domain 2379 2380 settings.slack_channel = Channel 2380 2381 settings.add_web_hook_desc = Integrate <a target="_blank" rel="noreferrer" href="%s">%s</a> into your repository. 2382 + settings.graphql_url = GraphQL URL 2381 2383 settings.web_hook_name_gitea = Gitea 2382 2384 settings.web_hook_name_forgejo = Forgejo 2383 2385 settings.web_hook_name_gogs = Gogs ··· 2397 2399 settings.packagist_package_url = Packagist package URL 2398 2400 settings.web_hook_name_sourcehut_builds = SourceHut Builds 2399 2401 settings.sourcehut_builds.manifest_path = Build manifest path 2400 - settings.sourcehut_builds.graphql_url = GraphQL URL (e.g. https://builds.sr.ht/query) 2401 2402 settings.sourcehut_builds.visibility = Job visibility 2402 2403 settings.sourcehut_builds.secrets = Secrets 2403 2404 settings.sourcehut_builds.secrets_helper = Give the job access to the build secrets (requires the SECRETS:RO grant) 2405 + settings.sourcehut_builds.access_token_helper = Access token that has JOBS:RW grant. Generate a <a target="_blank" rel="noopener noreferrer" href="%s">builds.sr.ht token</a> or a <a target="_blank" rel="noopener noreferrer" href="%s">builds.sr.ht token with secrets access</a> on meta.sr.ht. 2404 2406 settings.deploy_keys = Deploy keys 2405 2407 settings.add_deploy_key = Add deploy key 2406 2408 settings.deploy_key_desc = Deploy keys have read-only pull access to the repository. ··· 2507 2509 settings.matrix.homeserver_url = Homeserver URL 2508 2510 settings.matrix.room_id = Room ID 2509 2511 settings.matrix.message_type = Message type 2512 + settings.matrix.access_token_helper = It is recommended to setup a dedicated Matrix account for this. The access token can be retrieved from the Element web client (in a private/incognito tab) > User menu (top left) > All settings > Help & About > Advanced > Access Token (right below the Homeserver URL). Close the private/incognito tab (logging out would invalidate the token). 2513 + settings.matrix.room_id_helper = The Room ID can be retrieved from the Element web client > Room Settings > Advanced > Internal room ID. Example: %s. 2510 2514 settings.archive.button = Archive repo 2511 2515 settings.archive.header = Archive this repo 2512 2516 settings.archive.text = Archiving the repo will make it entirely read-only. It will be hidden from the dashboard. Nobody (not even you!) will be able to make new commits, or open any issues or pull requests.
+7 -1
services/webhook/deliver.go
··· 78 78 } 79 79 if authorization != "" { 80 80 req.Header.Set("Authorization", authorization) 81 - t.RequestInfo.Headers["Authorization"] = "******" 81 + redacted := "******" 82 + if strings.HasPrefix(authorization, "Bearer ") { 83 + redacted = "Bearer " + redacted 84 + } else if strings.HasPrefix(authorization, "Basic ") { 85 + redacted = "Basic " + redacted 86 + } 87 + t.RequestInfo.Headers["Authorization"] = redacted 82 88 } 83 89 84 90 t.ResponseInfo = &webhook_model.HookResponse{
+1 -2
services/webhook/deliver_test.go
··· 132 132 } 133 133 134 134 assert.True(t, hookTask.IsSucceed) 135 - assert.Equal(t, "******", hookTask.RequestInfo.Headers["Authorization"]) 135 + assert.Equal(t, "Bearer ******", hookTask.RequestInfo.Headers["Authorization"]) 136 136 } 137 137 138 138 func TestWebhookDeliverHookTask(t *testing.T) { ··· 152 152 153 153 case "/webhook/6db5dc1e282529a8c162c7fe93dd2667494eeb51": 154 154 // Version 2 155 - assert.Equal(t, "push", r.Header.Get("X-GitHub-Event")) 156 155 assert.Equal(t, "application/json", r.Header.Get("Content-Type")) 157 156 body, err := io.ReadAll(r.Body) 158 157 assert.NoError(t, err)
+5 -6
services/webhook/matrix.go
··· 42 42 HomeserverURL string `binding:"Required;ValidUrl"` 43 43 RoomID string `binding:"Required"` 44 44 MessageType int 45 - 46 - // enforce requirement of authorization_header 47 - // (value will still be set in the embedded WebhookCoreForm) 48 - AuthorizationHeader string `binding:"Required"` 45 + AccessToken string `binding:"Required"` 49 46 } 50 47 bind(&form) 48 + form.AuthorizationHeader = "Bearer " + strings.TrimSpace(form.AccessToken) 51 49 50 + // https://spec.matrix.org/v1.10/client-server-api/#sending-events-to-a-room 52 51 return forms.WebhookForm{ 53 52 WebhookCoreForm: form.WebhookCoreForm, 54 - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), 53 + URL: fmt.Sprintf("%s/_matrix/client/v3/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), 55 54 ContentType: webhook_model.ContentTypeJSON, 56 55 Secret: "", 57 56 HTTPMethod: http.MethodPut, ··· 91 90 } 92 91 req.Header.Set("Content-Type", "application/json") 93 92 94 - return req, body, shared.AddDefaultHeaders(req, []byte(w.Secret), t, body) // likely useless, but has always been sent historially 93 + return req, body, nil 95 94 } 96 95 97 96 const matrixPayloadSizeLimit = 1024 * 64
+2 -3
services/webhook/matrix_test.go
··· 201 201 RepoID: 3, 202 202 IsActive: true, 203 203 Type: webhook_module.MATRIX, 204 - URL: "https://matrix.example.com/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message", 204 + URL: "https://matrix.example.com/_matrix/client/v3/rooms/ROOM_ID/send/m.room.message", 205 205 Meta: `{"message_type":0}`, // text 206 206 } 207 207 task := &webhook_model.HookTask{ ··· 217 217 require.NoError(t, err) 218 218 219 219 assert.Equal(t, "PUT", req.Method) 220 - assert.Equal(t, "/_matrix/client/r0/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path) 221 - assert.Equal(t, "sha256=", req.Header.Get("X-Hub-Signature-256")) 220 + assert.Equal(t, "/_matrix/client/v3/rooms/ROOM_ID/send/m.room.message/6db5dc1e282529a8c162c7fe93dd2667494eeb51", req.URL.Path) 222 221 assert.Equal(t, "application/json", req.Header.Get("Content-Type")) 223 222 var body MatrixPayload 224 223 err = json.NewDecoder(req.Body).Decode(&body)
+2 -7
services/webhook/sourcehut/builds.go
··· 49 49 ManifestPath string `binding:"Required"` 50 50 Visibility string `binding:"Required;In(PUBLIC,UNLISTED,PRIVATE)"` 51 51 Secrets bool 52 + AccessToken string `binding:"Required"` 52 53 } 53 54 54 55 var _ binding.Validator = &buildsForm{} ··· 63 64 Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_path"), 64 65 }) 65 66 } 66 - if !strings.HasPrefix(f.AuthorizationHeader, "Bearer ") { 67 - errs = append(errs, binding.Error{ 68 - FieldNames: []string{"AuthorizationHeader"}, 69 - Classification: "", 70 - Message: ctx.Locale.TrString("form.required_prefix", "Bearer "), 71 - }) 72 - } 67 + f.AuthorizationHeader = "Bearer " + strings.TrimSpace(f.AccessToken) 73 68 return errs 74 69 } 75 70
+8 -7
templates/repo/settings/webhook/settings.tmpl templates/webhook/shared-settings.tmpl
··· 258 258 <span class="help">{{ctx.Locale.Tr "repo.settings.branch_filter_desc"}}</span> 259 259 </div> 260 260 261 - <!-- Authorization Header --> 262 - <div class="field{{if eq .HookType "matrix"}} required{{end}} {{if .Err_AuthorizationHeader}}error{{end}}"> 263 - <label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label> 264 - <input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"{{if eq .HookType "matrix"}} placeholder="Bearer $access_token" required{{end}}> 265 - {{if ne .HookType "matrix"}}{{/* Matrix doesn't make the authorization optional but it is implied by the help string, should be changed.*/}} 261 + {{$skipAuthorizationHeader := or (eq .HookType "sourcehut_builds") (eq .HookType "matrix")}} 262 + {{if not $skipAuthorizationHeader}} 263 + <!-- Authorization Header --> 264 + <div class="field {{if .Err_AuthorizationHeader}}error{{end}}"> 265 + <label for="authorization_header">{{ctx.Locale.Tr "repo.settings.authorization_header"}}</label> 266 + <input id="authorization_header" name="authorization_header" type="text" value="{{.Webhook.HeaderAuthorization}}"> 266 267 <span class="help">{{ctx.Locale.Tr "repo.settings.authorization_header_desc" ("<code>Bearer token123456</code>, <code>Basic YWxhZGRpbjpvcGVuc2VzYW1l</code>" | SafeHTML)}}</span> 267 - {{end}} 268 - </div> 268 + </div> 269 + {{end}} 269 270 270 271 <div class="divider"></div> 271 272
+1 -1
templates/webhook/new/dingtalk.tmpl
··· 5 5 <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> 6 6 <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> 7 7 </div> 8 - {{template "repo/settings/webhook/settings" .}} 8 + {{template "webhook/shared-settings" .}} 9 9 </form>
+1 -1
templates/webhook/new/discord.tmpl
··· 13 13 <label for="icon_url">{{ctx.Locale.Tr "repo.settings.discord_icon_url"}}</label> 14 14 <input id="icon_url" name="icon_url" value="{{.HookMetadata.IconURL}}" placeholder="https://example.com/assets/img/logo.svg"> 15 15 </div> 16 - {{template "repo/settings/webhook/settings" .}} 16 + {{template "webhook/shared-settings" .}} 17 17 </form>
+1 -1
templates/webhook/new/feishu.tmpl
··· 6 6 <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> 7 7 <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> 8 8 </div> 9 - {{template "repo/settings/webhook/settings" .}} 9 + {{template "webhook/shared-settings" .}} 10 10 </form>
+1 -1
templates/webhook/new/forgejo.tmpl
··· 34 34 <label for="secret">{{ctx.Locale.Tr "repo.settings.secret"}}</label> 35 35 <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> 36 36 </div> 37 - {{template "repo/settings/webhook/settings" .}} 37 + {{template "webhook/shared-settings" .}} 38 38 </form>
+1 -1
templates/webhook/new/gitea.tmpl
··· 34 34 <label for="secret">{{ctx.Locale.Tr "repo.settings.secret"}}</label> 35 35 <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> 36 36 </div> 37 - {{template "repo/settings/webhook/settings" .}} 37 + {{template "webhook/shared-settings" .}} 38 38 </form>
+1 -1
templates/webhook/new/gogs.tmpl
··· 22 22 <label for="secret">{{ctx.Locale.Tr "repo.settings.secret"}}</label> 23 23 <input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off"> 24 24 </div> 25 - {{template "repo/settings/webhook/settings" .}} 25 + {{template "webhook/shared-settings" .}} 26 26 </form>
+9 -2
templates/webhook/new/matrix.tmpl
··· 5 5 <label for="homeserver_url">{{ctx.Locale.Tr "repo.settings.matrix.homeserver_url"}}</label> 6 6 <input id="homeserver_url" name="homeserver_url" type="url" value="{{.HookMetadata.HomeserverURL}}" autofocus required> 7 7 </div> 8 + <!-- Access Token --> 9 + <div class="field required {{if .Err_AccessToken}}error{{end}}"> 10 + <label for="access_token">{{ctx.Locale.Tr "form.AccessToken"}}</label> 11 + <input id="access_token" name="access_token" type="password" value="{{.Webhook.HeaderAuthorizationTrimPrefix "Bearer "}}" required> 12 + <span class="help">{{ctx.Locale.Tr "repo.settings.matrix.access_token_helper"}}</span> 13 + </div> 8 14 <div class="required field {{if .Err_Room}}error{{end}}"> 9 15 <label for="room_id">{{ctx.Locale.Tr "repo.settings.matrix.room_id"}}</label> 10 - <input id="room_id" name="room_id" type="text" value="{{.HookMetadata.Room}}" required> 16 + <input id="room_id" name="room_id" type="text" value="{{.HookMetadata.Room}}" placeholder="!opaque_id:domain" pattern="^!.+:.+$" maxlength="255" required> 17 + <span class="help">{{ctx.Locale.Tr "repo.settings.matrix.room_id_helper" ("<code>!opaque_id:example.org</code>"|SafeHTML)}}</span> 11 18 </div> 12 19 <div class="field"> 13 20 <label>{{ctx.Locale.Tr "repo.settings.matrix.message_type"}}</label> ··· 21 28 </div> 22 29 </div> 23 30 </div> 24 - {{template "repo/settings/webhook/settings" .}} 31 + {{template "webhook/shared-settings" .}} 25 32 </form>
+1 -1
templates/webhook/new/msteams.tmpl
··· 5 5 <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> 6 6 <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> 7 7 </div> 8 - {{template "repo/settings/webhook/settings" .}} 8 + {{template "webhook/shared-settings" .}} 9 9 </form>
+1 -1
templates/webhook/new/packagist.tmpl
··· 13 13 <label for="package_url">{{ctx.Locale.Tr "repo.settings.packagist_package_url"}}</label> 14 14 <input id="package_url" name="package_url" value="{{.HookMetadata.PackageURL}}" placeholder="https://packagist.org/packages/laravel/framework" required> 15 15 </div> 16 - {{template "repo/settings/webhook/settings" .}} 16 + {{template "webhook/shared-settings" .}} 17 17 </form>
+1 -1
templates/webhook/new/slack.tmpl
··· 22 22 <label for="color">{{ctx.Locale.Tr "repo.settings.slack_color"}}</label> 23 23 <input id="color" name="color" value="{{.HookMetadata.Color}}" placeholder="#dd4b39, good, warning, danger"> 24 24 </div> 25 - {{template "repo/settings/webhook/settings" .}} 25 + {{template "webhook/shared-settings" .}} 26 26 </form>
+10 -4
templates/webhook/new/sourcehut_builds.tmpl
··· 2 2 <form class="ui form" action="{{.BaseLink}}/{{or .Webhook.ID "sourcehut_builds/new"}}" method="post"> 3 3 {{.CsrfTokenHtml}} 4 4 <div class="required field {{if .Err_PayloadURL}}error{{end}}"> 5 - <label for="payload_url">{{ctx.Locale.Tr "repo.settings.sourcehut_builds.graphql_url"}}</label> 6 - <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> 5 + <label for="payload_url">{{ctx.Locale.Tr "repo.settings.graphql_url"}}</label> 6 + <input id="payload_url" name="payload_url" type="url" value="{{or .Webhook.URL "https://builds.sr.ht/query"}}" autofocus required> 7 7 </div> 8 8 <div class="required field {{if .Err_ManifestPath}}error{{end}}"> 9 9 <label for="manifest_path">{{ctx.Locale.Tr "repo.settings.sourcehut_builds.manifest_path"}}</label> 10 - <input id="manifest_path" name="manifest_path" type="text" value="{{.HookMetadata.ManifestPath}}" required> 10 + <input id="manifest_path" name="manifest_path" type="text" value="{{or .HookMetadata.ManifestPath ".build.yml"}}" required> 11 11 </div> 12 12 <div class="field"> 13 13 <label>{{ctx.Locale.Tr "repo.settings.sourcehut_builds.visibility"}}</label> ··· 29 29 <span class="help">{{ctx.Locale.Tr "repo.settings.sourcehut_builds.secrets_helper"}}</span> 30 30 </div> 31 31 </div> 32 - {{template "repo/settings/webhook/settings" .}} 32 + <!-- Access Token --> 33 + <div class="field required {{if .Err_AccessToken}}error{{end}}"> 34 + <label for="access_token">{{ctx.Locale.Tr "form.AccessToken"}}</label> 35 + <input id="access_token" name="access_token" type="password" value="{{.Webhook.HeaderAuthorizationTrimPrefix "Bearer "}}" required> 36 + <span class="help">{{ctx.Locale.Tr "repo.settings.sourcehut_builds.access_token_helper" "https://meta.sr.ht/oauth2/personal-token?grants=builds.sr.ht/JOBS:RW" "https://meta.sr.ht/oauth2/personal-token?grants=builds.sr.ht/JOBS:RW+builds.sr.ht/SECRETS:RO"}}</span> 37 + </div> 38 + {{template "webhook/shared-settings" .}} 33 39 </form>
+1 -1
templates/webhook/new/telegram.tmpl
··· 13 13 <label for="thread_id">{{ctx.Locale.Tr "repo.settings.thread_id"}}</label> 14 14 <input id="thread_id" name="thread_id" type="text" value="{{.HookMetadata.ThreadID}}"> 15 15 </div> 16 - {{template "repo/settings/webhook/settings" .}} 16 + {{template "webhook/shared-settings" .}} 17 17 </form>
+1 -1
templates/webhook/new/wechatwork.tmpl
··· 5 5 <label for="payload_url">{{ctx.Locale.Tr "repo.settings.payload_url"}}</label> 6 6 <input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required> 7 7 </div> 8 - {{template "repo/settings/webhook/settings" .}} 8 + {{template "webhook/shared-settings" .}} 9 9 </form>
+13 -15
tests/integration/repo_webhook_test.go
··· 227 227 })) 228 228 229 229 t.Run("matrix/required", testWebhookForms("matrix", session, map[string]string{ 230 - "homeserver_url": "https://matrix.example.com", 231 - "room_id": "123", 232 - "authorization_header": "Bearer 123456", 230 + "homeserver_url": "https://matrix.example.com", 231 + "access_token": "123456", 232 + "room_id": "123", 233 233 }, map[string]string{ 234 - "authorization_header": "", 234 + "access_token": "", 235 235 })) 236 236 t.Run("matrix/optional", testWebhookForms("matrix", session, map[string]string{ 237 237 "homeserver_url": "https://matrix.example.com", 238 + "access_token": "123456", 238 239 "room_id": "123", 239 240 "message_type": "1", // m.text 240 241 241 - "branch_filter": "matrix/*", 242 - "authorization_header": "Bearer 123456", 242 + "branch_filter": "matrix/*", 243 243 })) 244 244 245 245 t.Run("wechatwork/required", testWebhookForms("wechatwork", session, map[string]string{ ··· 267 267 })) 268 268 269 269 t.Run("sourcehut_builds/required", testWebhookForms("sourcehut_builds", session, map[string]string{ 270 - "payload_url": "https://sourcehut_builds.example.com", 271 - "manifest_path": ".build.yml", 272 - "visibility": "PRIVATE", 273 - "authorization_header": "Bearer 123456", 274 - }, map[string]string{ 275 - "authorization_header": "", 270 + "payload_url": "https://sourcehut_builds.example.com", 271 + "manifest_path": ".build.yml", 272 + "visibility": "PRIVATE", 273 + "access_token": "123456", 276 274 }, map[string]string{ 277 - "authorization_header": "token ", 275 + "access_token": "", 278 276 }, map[string]string{ 279 277 "manifest_path": "", 280 278 }, map[string]string{ ··· 289 287 "manifest_path": ".build.yml", 290 288 "visibility": "PRIVATE", 291 289 "secrets": "on", 290 + "access_token": "123456", 292 291 293 - "branch_filter": "srht/*", 294 - "authorization_header": "Bearer 123456", 292 + "branch_filter": "srht/*", 295 293 })) 296 294 } 297 295