From 42d294941c0483090b1584a89cb32fc79c70e8eb Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 25 Dec 2025 11:33:34 +0100 Subject: [PATCH] Replace CSRF cookie with `CrossOriginProtection` (#36183) Removes the CSRF cookie in favor of [`CrossOriginProtection`](https://pkg.go.dev/net/http#CrossOriginProtection) which relies purely on HTTP headers. Fixes: https://github.com/go-gitea/gitea/issues/11188 Fixes: https://github.com/go-gitea/gitea/issues/30333 Helps: https://github.com/go-gitea/gitea/issues/35107 TODOs: - [x] Fix tests - [ ] Ideally add tests to validates the protection --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- custom/conf/app.example.ini | 3 - modules/setting/markup.go | 2 +- modules/setting/oauth2.go | 2 +- modules/setting/security.go | 3 - routers/common/auth.go | 8 +- routers/web/auth/auth.go | 7 - routers/web/auth/oauth.go | 3 - routers/web/githttp.go | 2 +- routers/web/user/setting/keys.go | 2 +- routers/web/web.go | 33 ++-- services/auth/auth.go | 6 - services/context/api.go | 2 +- services/context/context.go | 22 +-- services/context/context_cookie.go | 2 - services/context/csrf.go | 168 ------------------ services/context/xsrf.go | 99 ----------- services/context/xsrf_test.go | 91 ---------- templates/admin/auth/edit.tmpl | 1 - templates/admin/auth/new.tmpl | 1 - templates/admin/config.tmpl | 2 - templates/admin/cron.tmpl | 1 - templates/admin/dashboard.tmpl | 1 - templates/admin/emails/list.tmpl | 2 - templates/admin/notice.tmpl | 1 - templates/admin/packages/list.tmpl | 2 - templates/admin/queue_manage.tmpl | 2 - templates/admin/repo/list.tmpl | 1 - templates/admin/repo/unadopted.tmpl | 2 - templates/admin/user/edit.tmpl | 3 - templates/admin/user/new.tmpl | 1 - templates/base/head.tmpl | 2 +- templates/base/head_navbar.tmpl | 2 - templates/base/head_script.tmpl | 1 - templates/org/create.tmpl | 1 - templates/org/settings/options.tmpl | 2 - .../org/settings/options_dangerzone.tmpl | 3 - templates/org/team/invite.tmpl | 1 - templates/org/team/members.tmpl | 2 - templates/org/team/new.tmpl | 1 - templates/org/team/repositories.tmpl | 2 - templates/org/team/sidebar.tmpl | 1 - templates/org/team/teams.tmpl | 1 - templates/package/settings.tmpl | 2 - templates/package/shared/cargo.tmpl | 2 - .../package/shared/cleanup_rules/edit.tmpl | 1 - templates/projects/new.tmpl | 1 - templates/repo/actions/workflow_dispatch.tmpl | 1 - templates/repo/branch/list.tmpl | 2 - templates/repo/commit_page.tmpl | 2 - templates/repo/create.tmpl | 1 - templates/repo/diff/comment_form.tmpl | 1 - templates/repo/diff/new_review.tmpl | 1 - templates/repo/editor/cherry_pick.tmpl | 1 - templates/repo/editor/delete.tmpl | 1 - templates/repo/editor/edit.tmpl | 1 - templates/repo/editor/fork.tmpl | 1 - templates/repo/editor/patch.tmpl | 1 - templates/repo/editor/upload.tmpl | 1 - templates/repo/header.tmpl | 2 - .../repo/issue/labels/label_edit_modal.tmpl | 1 - .../issue/labels/label_load_template.tmpl | 1 - templates/repo/issue/milestone_new.tmpl | 1 - templates/repo/issue/new_form.tmpl | 1 - templates/repo/issue/sidebar/due_date.tmpl | 1 - .../issue/sidebar/issue_dependencies.tmpl | 2 - .../repo/issue/sidebar/issue_management.tmpl | 3 - .../repo/issue/sidebar/reviewer_list.tmpl | 1 - .../issue/sidebar/stopwatch_timetracker.tmpl | 2 - templates/repo/issue/view_content.tmpl | 1 - .../issue/view_content/pull_merge_box.tmpl | 1 - .../view_content/reference_issue_dialog.tmpl | 1 - .../view_content/update_branch_by_merge.tmpl | 1 - templates/repo/migrate/codebase.tmpl | 1 - templates/repo/migrate/codecommit.tmpl | 1 - templates/repo/migrate/git.tmpl | 1 - templates/repo/migrate/gitbucket.tmpl | 1 - templates/repo/migrate/gitea.tmpl | 1 - templates/repo/migrate/github.tmpl | 1 - templates/repo/migrate/gitlab.tmpl | 1 - templates/repo/migrate/gogs.tmpl | 1 - templates/repo/migrate/migrating.tmpl | 2 - templates/repo/migrate/onedev.tmpl | 1 - templates/repo/pulls/fork.tmpl | 1 - templates/repo/release/new.tmpl | 1 - templates/repo/settings/actions_general.tmpl | 2 - templates/repo/settings/branches.tmpl | 1 - templates/repo/settings/collaboration.tmpl | 2 - templates/repo/settings/deploy_keys.tmpl | 1 - templates/repo/settings/githook_edit.tmpl | 1 - templates/repo/settings/lfs.tmpl | 1 - templates/repo/settings/lfs_locks.tmpl | 2 - templates/repo/settings/lfs_pointers.tmpl | 1 - templates/repo/settings/options.tmpl | 19 -- templates/repo/settings/protected_branch.tmpl | 1 - templates/repo/settings/public_access.tmpl | 1 - .../repo/settings/push_mirror_sync_modal.tmpl | 1 - templates/repo/settings/tags.tmpl | 2 - templates/repo/settings/webhook/dingtalk.tmpl | 1 - templates/repo/settings/webhook/discord.tmpl | 1 - templates/repo/settings/webhook/feishu.tmpl | 1 - templates/repo/settings/webhook/gitea.tmpl | 1 - templates/repo/settings/webhook/gogs.tmpl | 1 - templates/repo/settings/webhook/history.tmpl | 1 - templates/repo/settings/webhook/matrix.tmpl | 1 - templates/repo/settings/webhook/msteams.tmpl | 1 - .../repo/settings/webhook/packagist.tmpl | 1 - templates/repo/settings/webhook/slack.tmpl | 1 - templates/repo/settings/webhook/telegram.tmpl | 1 - .../repo/settings/webhook/wechatwork.tmpl | 1 - templates/repo/wiki/new.tmpl | 1 - templates/shared/actions/runner_edit.tmpl | 1 - templates/shared/secrets/add_list.tmpl | 1 - templates/shared/user/block_user_dialog.tmpl | 1 - templates/shared/user/blocked_users.tmpl | 3 - templates/shared/variables/variable_list.tmpl | 1 - templates/user/auth/activate.tmpl | 1 - templates/user/auth/change_passwd_inner.tmpl | 1 - templates/user/auth/forgot_passwd.tmpl | 1 - templates/user/auth/grant.tmpl | 1 - templates/user/auth/reset_passwd.tmpl | 1 - templates/user/auth/signin_inner.tmpl | 1 - templates/user/auth/signin_openid.tmpl | 1 - templates/user/auth/signup_inner.tmpl | 1 - .../user/auth/signup_openid_connect.tmpl | 1 - .../user/auth/signup_openid_register.tmpl | 1 - templates/user/auth/twofa.tmpl | 1 - templates/user/auth/twofa_scratch.tmpl | 1 - .../user/notification/notification_div.tmpl | 2 - templates/user/settings/account.tmpl | 5 - templates/user/settings/appearance.tmpl | 3 - templates/user/settings/applications.tmpl | 1 - .../applications_oauth2_edit_form.tmpl | 3 - .../settings/applications_oauth2_list.tmpl | 1 - templates/user/settings/keys_gpg.tmpl | 2 - templates/user/settings/keys_principal.tmpl | 1 - templates/user/settings/keys_ssh.tmpl | 2 - templates/user/settings/notifications.tmpl | 2 - templates/user/settings/organization.tmpl | 1 - templates/user/settings/packages.tmpl | 1 - templates/user/settings/profile.tmpl | 2 - templates/user/settings/repos.tmpl | 2 - templates/user/settings/security/openid.tmpl | 2 - templates/user/settings/security/twofa.tmpl | 2 - .../user/settings/security/twofa_enroll.tmpl | 1 - tests/integration/actions_approve_test.go | 5 +- tests/integration/actions_concurrency_test.go | 110 ++++-------- tests/integration/actions_delete_run_test.go | 24 +-- tests/integration/actions_inputs_test.go | 5 +- tests/integration/actions_rerun_test.go | 18 +- .../integration/actions_runner_modify_test.go | 5 +- tests/integration/actions_settings_test.go | 5 +- tests/integration/actions_trigger_test.go | 12 +- tests/integration/actions_variables_test.go | 9 +- tests/integration/admin_user_test.go | 7 +- tests/integration/api_httpsig_test.go | 2 - .../api_packages_container_test.go | 2 - tests/integration/api_repo_languages_test.go | 1 - tests/integration/api_repo_license_test.go | 1 - tests/integration/attachment_test.go | 10 +- tests/integration/auth_ldap_test.go | 16 +- tests/integration/branches_test.go | 4 +- .../integration/change_default_branch_test.go | 6 - tests/integration/create_no_session_test.go | 2 - tests/integration/csrf_test.go | 34 ---- tests/integration/delete_user_test.go | 10 +- tests/integration/editor_test.go | 10 +- tests/integration/empty_repo_test.go | 4 - tests/integration/git_general_test.go | 9 +- tests/integration/html_helper.go | 5 - tests/integration/integration_test.go | 27 +-- tests/integration/issue_test.go | 47 +---- tests/integration/migrate_test.go | 1 - tests/integration/mirror_push_test.go | 3 - tests/integration/nonascii_branches_test.go | 2 - tests/integration/oauth_test.go | 1 - tests/integration/org_project_test.go | 2 - tests/integration/org_team_invite_test.go | 31 +--- tests/integration/privateactivity_test.go | 1 - tests/integration/project_test.go | 5 +- tests/integration/pull_comment_test.go | 1 - tests/integration/pull_compare_test.go | 2 - tests/integration/pull_create_test.go | 21 +-- tests/integration/pull_merge_test.go | 66 ++----- tests/integration/pull_review_test.go | 28 +-- tests/integration/pull_status_test.go | 3 - tests/integration/release_test.go | 1 - tests/integration/rename_branch_test.go | 30 +--- tests/integration/repo_branch_test.go | 14 -- tests/integration/repo_fork_test.go | 1 - tests/integration/repo_generate_test.go | 1 - tests/integration/repo_merge_upstream_test.go | 9 +- tests/integration/repo_migrate_test.go | 1 - tests/integration/repo_test.go | 5 +- tests/integration/repo_webhook_test.go | 16 +- tests/integration/timetracking_test.go | 8 +- tests/integration/user_avatar_test.go | 2 - tests/integration/user_settings_test.go | 53 +----- tests/integration/user_test.go | 7 - tests/integration/xss_test.go | 1 - .../js/components/PullRequestMergeForm.vue | 4 +- .../js/components/RepoBranchTagSelector.vue | 2 - web_src/js/features/dropzone.ts | 3 +- web_src/js/features/pull-view-file.ts | 2 +- web_src/js/features/repo-settings.ts | 3 +- web_src/js/modules/fetch.ts | 6 - web_src/js/types.ts | 1 - web_src/js/vitest.setup.ts | 1 - 207 files changed, 178 insertions(+), 1196 deletions(-) delete mode 100644 services/context/csrf.go delete mode 100644 services/context/xsrf.go delete mode 100644 services/context/xsrf_test.go delete mode 100644 tests/integration/csrf_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 475c62ab6c..40c066c2b1 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -503,9 +503,6 @@ INTERNAL_TOKEN = ;; Password Hash algorithm, either "argon2", "pbkdf2", "scrypt" or "bcrypt" ;PASSWORD_HASH_ALGO = pbkdf2 ;; -;; Set false to allow JavaScript to read CSRF cookie -;CSRF_COOKIE_HTTP_ONLY = true -;; ;; Validate against https://haveibeenpwned.com/Passwords to see if a password has been exposed ;PASSWORD_CHECK_PWN = false ;; diff --git a/modules/setting/markup.go b/modules/setting/markup.go index e105506fc0..caf0d5f8d9 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -255,7 +255,7 @@ func newMarkupRenderer(name string, sec ConfigSection) { } // ATTENTION! at the moment, only a safe set like "allow-scripts" are allowed for sandbox mode. - // "allow-same-origin" should never be used, it leads to XSS attack, and it makes the JS in iframe can access parent window's config and CSRF token + // "allow-same-origin" should NEVER be used, it leads to XSS attack: makes the JS in iframe can access parent window's config and send requests with user's credentials. renderContentSandbox := sec.Key("RENDER_CONTENT_SANDBOX").MustString("allow-scripts allow-popups") if renderContentSandbox == "disabled" { renderContentSandbox = "" diff --git a/modules/setting/oauth2.go b/modules/setting/oauth2.go index ae2a9d7bee..2dfe77dda9 100644 --- a/modules/setting/oauth2.go +++ b/modules/setting/oauth2.go @@ -133,7 +133,7 @@ func loadOAuth2From(rootCfg ConfigProvider) { // FIXME: at the moment, no matter oauth2 is enabled or not, it must generate a "oauth2 JWT_SECRET" // Because this secret is also used as GeneralTokenSigningSecret (as a quick not-that-breaking fix for some legacy problems). - // Including: CSRF token, account validation token, etc ... + // Including: account validation token, etc ... // In main branch, the signing token should be refactored (eg: one unique for LFS/OAuth2/etc ...) jwtSecretBase64 := loadSecret(sec, "JWT_SECRET_URI", "JWT_SECRET") if InstallLock { diff --git a/modules/setting/security.go b/modules/setting/security.go index 153b6bc944..d60cfbbfc8 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -36,8 +36,6 @@ var ( PasswordCheckPwn bool SuccessfulTokensCacheSize int DisableQueryAuthToken bool - CSRFCookieName = "_csrf" - CSRFCookieHTTPOnly = true RecordUserSignupMetadata = false TwoFactorAuthEnforced = false ) @@ -139,7 +137,6 @@ func loadSecurityFrom(rootCfg ConfigProvider) { log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString("")) } - CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true) PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false) SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20) diff --git a/routers/common/auth.go b/routers/common/auth.go index 115d65ed10..491ca6f870 100644 --- a/routers/common/auth.go +++ b/routers/common/auth.go @@ -38,8 +38,8 @@ func AuthShared(ctx *context.Base, sessionStore auth_service.SessionStore, authM // VerifyOptions contains required or check options type VerifyOptions struct { - SignInRequired bool - SignOutRequired bool - AdminRequired bool - DisableCSRF bool + SignInRequired bool + SignOutRequired bool + AdminRequired bool + DisableCrossOriginProtection bool } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 2ccd1c71b5..d36fb5bab7 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -102,7 +102,6 @@ func autoSignIn(ctx *context.Context) (bool, error) { return false, err } - ctx.Csrf.PrepareForSessionUser(ctx) return true, nil } @@ -357,9 +356,6 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe ctx.Locale = middleware.Locale(ctx.Resp, ctx.Req) } - // force to generate a new CSRF token - ctx.Csrf.PrepareForSessionUser(ctx) - // Register last login if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil { ctx.ServerError("UpdateUser", err) @@ -403,7 +399,6 @@ func HandleSignOut(ctx *context.Context) { _ = ctx.Session.Flush() _ = ctx.Session.Destroy(ctx.Resp, ctx.Req) ctx.DeleteSiteCookie(setting.CookieRememberName) - ctx.Csrf.DeleteCookie(ctx) middleware.DeleteRedirectToCookie(ctx.Resp) } @@ -811,8 +806,6 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { return } - ctx.Csrf.PrepareForSessionUser(ctx) - if err := resetLocale(ctx, user); err != nil { ctx.ServerError("resetLocale", err) return diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index f7ce5875ca..5eab7ffeb4 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -393,9 +393,6 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m return } - // force to generate a new CSRF token - ctx.Csrf.PrepareForSessionUser(ctx) - if err := resetLocale(ctx, u); err != nil { ctx.ServerError("resetLocale", err) return diff --git a/routers/web/githttp.go b/routers/web/githttp.go index 06de811f16..ed3c56b07b 100644 --- a/routers/web/githttp.go +++ b/routers/web/githttp.go @@ -22,5 +22,5 @@ func addOwnerRepoGitHTTPRouters(m *web.Router) { m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38,62}}", repo.GetLooseObject) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.pack", repo.GetPackFile) m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40,64}}.idx", repo.GetIdxFile) - }, optSignInIgnoreCsrf, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context.UserAssignmentWeb()) + }, repo.HTTPGitEnabledHandler, repo.CorsHandler(), optSignInFromAnyOrigin, context.UserAssignmentWeb()) } diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index 633a881095..13aa4a471b 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -325,7 +325,7 @@ func loadKeysData(ctx *context.Context) { ctx.Data["GPGKeys"] = gpgkeys tokenToSign := asymkey_model.VerificationToken(ctx.Doer, 1) - // generate a new aes cipher using the csrfToken + // generate a new aes cipher using the token ctx.Data["TokenToSign"] = tokenToSign principals, err := db.Find[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{ diff --git a/routers/web/web.go b/routers/web/web.go index 86e51d607e..4da8cdb581 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -129,13 +129,13 @@ func webAuth(authMethod auth_service.Method) func(*context.Context) { // ensure the session uid is deleted _ = ctx.Session.Delete("uid") } - - ctx.Csrf.PrepareForSessionUser(ctx) } } // verifyAuthWithOptions checks authentication according to options func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Context) { + crossOriginProtection := http.NewCrossOriginProtection() + return func(ctx *context.Context) { // Check prohibit login users. if ctx.IsSigned { @@ -178,9 +178,9 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont return } - if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == http.MethodPost { - ctx.Csrf.Validate(ctx) - if ctx.Written() { + if !options.SignOutRequired && !options.DisableCrossOriginProtection { + if err := crossOriginProtection.Check(ctx.Req); err != nil { + http.Error(ctx.Resp, err.Error(), http.StatusForbidden) return } } @@ -292,7 +292,12 @@ func Routes() *web.Router { return routes } -var optSignInIgnoreCsrf = verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true}) +// optSignInFromAnyOrigin means that the user can (optionally) be signed in from any origin (no cross-origin protection) +// - With CORS middleware: CORS middleware does the preflight request handling, the requests has Sec-Fetch-Site header. +// The CORS mechanism already protects cross-origin requests, and the CrossOriginProtection has no "allowed origin" list, so disable CrossOriginProtection. +// - For non-browser client requests: git clone via http, no Sec-Fetch-Site header. +// Such requests are not cross-origin requests, so disable CrossOriginProtection. +var optSignInFromAnyOrigin = verifyAuthWithOptions(&common.VerifyOptions{DisableCrossOriginProtection: true}) // registerWebRoutes register routes func registerWebRoutes(m *web.Router) { @@ -489,7 +494,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/-/markup", reqSignIn, web.Bind(structs.MarkupOption{}), misc.Markup) m.Get("/-/web-theme/list", misc.WebThemeList) - m.Post("/-/web-theme/apply", optSignInIgnoreCsrf, misc.WebThemeApply) + m.Post("/-/web-theme/apply", optSignIn, misc.WebThemeApply) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { @@ -565,12 +570,14 @@ func registerWebRoutes(m *web.Router) { m.Post("/grant", web.Bind(forms.GrantApplicationForm{}), auth.GrantApplicationOAuth) // TODO manage redirection m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth) - }, optSignInIgnoreCsrf, reqSignIn) + }, reqSignIn) - m.Methods("GET, POST, OPTIONS", "/userinfo", optionsCorsHandler(), optSignInIgnoreCsrf, auth.InfoOAuth) - m.Methods("POST, OPTIONS", "/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), optSignInIgnoreCsrf, auth.AccessTokenOAuth) - m.Methods("GET, OPTIONS", "/keys", optionsCorsHandler(), optSignInIgnoreCsrf, auth.OIDCKeys) - m.Methods("POST, OPTIONS", "/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), optSignInIgnoreCsrf, auth.IntrospectOAuth) + m.Group("", func() { + m.Methods("GET, POST, OPTIONS", "/userinfo", auth.InfoOAuth) + m.Methods("POST, OPTIONS", "/access_token", web.Bind(forms.AccessTokenForm{}), auth.AccessTokenOAuth) + m.Methods("GET, OPTIONS", "/keys", auth.OIDCKeys) + m.Methods("POST, OPTIONS", "/introspect", web.Bind(forms.IntrospectTokenForm{}), auth.IntrospectOAuth) + }, optionsCorsHandler(), optSignInFromAnyOrigin) }, oauth2Enabled) m.Group("/user/settings", func() { @@ -1653,7 +1660,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/action/{action:accept_transfer|reject_transfer}", reqSignIn, repo.ActionTransfer) }, optSignIn, context.RepoAssignment) - common.AddOwnerRepoGitLFSRoutes(m, optSignInIgnoreCsrf, lfsServerEnabled) // "/{username}/{reponame}/{lfs-paths}": git-lfs support + common.AddOwnerRepoGitLFSRoutes(m, lfsServerEnabled, repo.CorsHandler(), optSignInFromAnyOrigin) // "/{username}/{reponame}/{lfs-paths}": git-lfs support, see also addOwnerRepoGitHTTPRouters addOwnerRepoGitHTTPRouters(m) // "/{username}/{reponame}/{git-paths}": git http support diff --git a/services/auth/auth.go b/services/auth/auth.go index 291e78a735..90e2115bc5 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -19,7 +19,6 @@ import ( "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web/middleware" - gitea_context "code.gitea.io/gitea/services/context" user_service "code.gitea.io/gitea/services/user" ) @@ -162,9 +161,4 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore } middleware.SetLocaleCookie(resp, user.Language, 0) - - // force to generate a new CSRF token - if ctx := gitea_context.GetWebContext(req.Context()); ctx != nil { - ctx.Csrf.PrepareForSessionUser(ctx) - } } diff --git a/services/context/api.go b/services/context/api.go index d698b91163..591efadf37 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -227,7 +227,7 @@ func APIContexter() func(http.Handler) http.Handler { ctx.SetContextValue(apiContextKey, ctx) - // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. + // FIXME: GLOBAL-PARSE-FORM: see more details in another FIXME comment if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if !ctx.ParseMultipartForm() { return diff --git a/services/context/context.go b/services/context/context.go index 26b5bd3775..420b2aefa8 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -6,14 +6,12 @@ package context import ( "context" - "encoding/hex" "fmt" "html/template" "io" "net/http" "net/url" "strings" - "time" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" @@ -48,7 +46,6 @@ type Context struct { PageData map[string]any // data used by JavaScript modules in one page, it's `window.config.pageData` Cache cache.StringCache - Csrf CSRFProtector Flash *middleware.Flash Session session.Store @@ -143,18 +140,6 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context { // Contexter initializes a classic context for a request. func Contexter() func(next http.Handler) http.Handler { rnd := templates.HTMLRenderer() - csrfOpts := CsrfOptions{ - Secret: hex.EncodeToString(setting.GetGeneralTokenSigningSecret()), - Cookie: setting.CSRFCookieName, - Secure: setting.SessionConfig.Secure, - CookieHTTPOnly: setting.CSRFCookieHTTPOnly, - CookieDomain: setting.SessionConfig.Domain, - CookiePath: setting.SessionConfig.CookiePath, - SameSite: setting.SessionConfig.SameSite, - } - if !setting.IsProd { - CsrfTokenRegenerationInterval = 5 * time.Second // in dev, re-generate the tokens more aggressively for debug purpose - } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { base := NewBaseContext(resp, req) @@ -167,8 +152,6 @@ func Contexter() func(next http.Handler) http.Handler { ctx.PageData = map[string]any{} ctx.Data["PageData"] = ctx.PageData - ctx.Csrf = NewCSRFProtector(csrfOpts) - // get the last flash message from cookie lastFlashCookie, lastFlashMsg := middleware.GetSiteCookieFlashMessage(ctx, ctx.Req, CookieNameFlash) if vals, _ := url.ParseQuery(lastFlashCookie); len(vals) > 0 { @@ -184,7 +167,10 @@ func Contexter() func(next http.Handler) http.Handler { } }) - // If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid. + // FIXME: GLOBAL-PARSE-FORM: this ParseMultipartForm was used for parsing the csrf token from multipart/form-data + // We have dropped the csrf token, so ideally this global ParseMultipartForm should be removed. + // When removing this, we need to avoid regressions in the handler functions because Golang's http framework is quite fragile + // and developers sometimes need to manually parse the form before accessing some values. if ctx.Req.Method == http.MethodPost && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") { if !ctx.ParseMultipartForm() { return diff --git a/services/context/context_cookie.go b/services/context/context_cookie.go index b6f8dadb56..a28ae3b33d 100644 --- a/services/context/context_cookie.go +++ b/services/context/context_cookie.go @@ -25,13 +25,11 @@ func removeSessionCookieHeader(w http.ResponseWriter) { } // SetSiteCookie convenience function to set most cookies consistently -// CSRF and a few others are the exception here func (ctx *Context) SetSiteCookie(name, value string, maxAge int) { middleware.SetSiteCookie(ctx.Resp, name, value, maxAge) } // DeleteSiteCookie convenience function to delete most cookies consistently -// CSRF and a few others are the exception here func (ctx *Context) DeleteSiteCookie(name string) { middleware.SetSiteCookie(ctx.Resp, name, "", -1) } diff --git a/services/context/csrf.go b/services/context/csrf.go deleted file mode 100644 index aa99f34b03..0000000000 --- a/services/context/csrf.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2013 Martini Authors -// Copyright 2014 The Macaron Authors -// Copyright 2021 The Gitea Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. -// SPDX-License-Identifier: Apache-2.0 - -// a middleware that generates and validates CSRF tokens. - -package context - -import ( - "html/template" - "net/http" - "strconv" - "time" - - "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/util" -) - -const ( - CsrfHeaderName = "X-Csrf-Token" - CsrfFormName = "_csrf" -) - -// CSRFProtector represents a CSRF protector and is used to get the current token and validate the token. -type CSRFProtector interface { - // PrepareForSessionUser prepares the csrf protector for the current session user. - PrepareForSessionUser(ctx *Context) - // Validate validates the csrf token in http context. - Validate(ctx *Context) - // DeleteCookie deletes the csrf cookie - DeleteCookie(ctx *Context) -} - -type csrfProtector struct { - opt CsrfOptions - // id must be unique per user. - id string - // token is the valid one which will be used by end user and passed via header, cookie, or hidden form value. - token string -} - -// CsrfOptions maintains options to manage behavior of Generate. -type CsrfOptions struct { - // The global secret value used to generate Tokens. - Secret string - // Cookie value used to set and get token. - Cookie string - // Cookie domain. - CookieDomain string - // Cookie path. - CookiePath string - CookieHTTPOnly bool - // SameSite set the cookie SameSite type - SameSite http.SameSite - // Set the Secure flag to true on the cookie. - Secure bool - // sessionKey is the key used for getting the unique ID per user. - sessionKey string - // oldSessionKey saves old value corresponding to sessionKey. - oldSessionKey string -} - -func newCsrfCookie(opt *CsrfOptions, value string) *http.Cookie { - return &http.Cookie{ - Name: opt.Cookie, - Value: value, - Path: opt.CookiePath, - Domain: opt.CookieDomain, - MaxAge: int(CsrfTokenTimeout.Seconds()), - Secure: opt.Secure, - HttpOnly: opt.CookieHTTPOnly, - SameSite: opt.SameSite, - } -} - -func NewCSRFProtector(opt CsrfOptions) CSRFProtector { - if opt.Secret == "" { - panic("CSRF secret is empty but it must be set") // it shouldn't happen because it is always set in code - } - opt.Cookie = util.IfZero(opt.Cookie, "_csrf") - opt.CookiePath = util.IfZero(opt.CookiePath, "/") - opt.sessionKey = "uid" - opt.oldSessionKey = "_old_" + opt.sessionKey - return &csrfProtector{opt: opt} -} - -func (c *csrfProtector) PrepareForSessionUser(ctx *Context) { - c.id = "0" - if uidAny := ctx.Session.Get(c.opt.sessionKey); uidAny != nil { - switch uidVal := uidAny.(type) { - case string: - c.id = uidVal - case int64: - c.id = strconv.FormatInt(uidVal, 10) - default: - log.Error("invalid uid type in session: %T", uidAny) - } - } - - oldUID := ctx.Session.Get(c.opt.oldSessionKey) - uidChanged := oldUID == nil || oldUID.(string) != c.id - cookieToken := ctx.GetSiteCookie(c.opt.Cookie) - - needsNew := true - if uidChanged { - _ = ctx.Session.Set(c.opt.oldSessionKey, c.id) - } else if cookieToken != "" { - // If cookie token present, re-use existing unexpired token, else generate a new one. - if issueTime, ok := ParseCsrfToken(cookieToken); ok { - dur := time.Since(issueTime) // issueTime is not a monotonic-clock, the server time may change a lot to an early time. - if dur >= -CsrfTokenRegenerationInterval && dur <= CsrfTokenRegenerationInterval { - c.token = cookieToken - needsNew = false - } - } - } - - if needsNew { - c.token = GenerateCsrfToken(c.opt.Secret, c.id, "POST", time.Now()) - ctx.Resp.Header().Add("Set-Cookie", newCsrfCookie(&c.opt, c.token).String()) - } - - ctx.Data["CsrfToken"] = c.token - ctx.Data["CsrfTokenHtml"] = template.HTML(``) -} - -func (c *csrfProtector) validateToken(ctx *Context, token string) { - if !ValidCsrfToken(token, c.opt.Secret, c.id, "POST", time.Now()) { - c.DeleteCookie(ctx) - // currently, there should be no access to the APIPath with CSRF token. because templates shouldn't use the `/api/` endpoints. - // FIXME: distinguish what the response is for: HTML (web page) or JSON (fetch) - http.Error(ctx.Resp, "Invalid CSRF token.", http.StatusBadRequest) - } -} - -// Validate should be used as a per route middleware. It attempts to get a token from an "X-Csrf-Token" -// HTTP header and then a "_csrf" form value. If one of these is found, the token will be validated. -// If this validation fails, http.StatusBadRequest is sent. -func (c *csrfProtector) Validate(ctx *Context) { - if token := ctx.Req.Header.Get(CsrfHeaderName); token != "" { - c.validateToken(ctx, token) - return - } - if token := ctx.Req.FormValue(CsrfFormName); token != "" { - c.validateToken(ctx, token) - return - } - c.validateToken(ctx, "") // no csrf token, use an empty token to respond error -} - -func (c *csrfProtector) DeleteCookie(ctx *Context) { - cookie := newCsrfCookie(&c.opt, "") - cookie.MaxAge = -1 - ctx.Resp.Header().Add("Set-Cookie", cookie.String()) -} diff --git a/services/context/xsrf.go b/services/context/xsrf.go deleted file mode 100644 index 15e36d1859..0000000000 --- a/services/context/xsrf.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// Copyright 2014 The Macaron Authors -// Copyright 2020 The Gitea Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// SPDX-License-Identifier: Apache-2.0 - -package context - -import ( - "bytes" - "crypto/hmac" - "crypto/sha1" - "crypto/subtle" - "encoding/base64" - "fmt" - "strconv" - "strings" - "time" -) - -// CsrfTokenTimeout represents the duration that XSRF tokens are valid. -// It is exported so clients may set cookie timeouts that match generated tokens. -const CsrfTokenTimeout = 24 * time.Hour - -// CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout -var CsrfTokenRegenerationInterval = 10 * time.Minute - -var csrfTokenSep = []byte(":") - -// GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours. -// key is a secret key for your application. -// userID is a unique identifier for the user. -// actionID is the action the user is taking (e.g. POSTing to a particular path). -func GenerateCsrfToken(key, userID, actionID string, now time.Time) string { - nowUnixNano := now.UnixNano() - nowUnixNanoStr := strconv.FormatInt(nowUnixNano, 10) - h := hmac.New(sha1.New, []byte(key)) - h.Write([]byte(strings.ReplaceAll(userID, ":", "_"))) - h.Write(csrfTokenSep) - h.Write([]byte(strings.ReplaceAll(actionID, ":", "_"))) - h.Write(csrfTokenSep) - h.Write([]byte(nowUnixNanoStr)) - tok := fmt.Sprintf("%s:%s", h.Sum(nil), nowUnixNanoStr) - return base64.RawURLEncoding.EncodeToString([]byte(tok)) -} - -func ParseCsrfToken(token string) (issueTime time.Time, ok bool) { - data, err := base64.RawURLEncoding.DecodeString(token) - if err != nil { - return time.Time{}, false - } - - pos := bytes.LastIndex(data, csrfTokenSep) - if pos == -1 { - return time.Time{}, false - } - nanos, err := strconv.ParseInt(string(data[pos+1:]), 10, 64) - if err != nil { - return time.Time{}, false - } - return time.Unix(0, nanos), true -} - -// ValidCsrfToken returns true if token is a valid and unexpired token returned by Generate. -func ValidCsrfToken(token, key, userID, actionID string, now time.Time) bool { - issueTime, ok := ParseCsrfToken(token) - if !ok { - return false - } - - // Check that the token is not expired. - if now.Sub(issueTime) >= CsrfTokenTimeout { - return false - } - - // Check that the token is not from the future. - // Allow 1-minute grace period in case the token is being verified on a - // machine whose clock is behind the machine that issued the token. - if issueTime.After(now.Add(1 * time.Minute)) { - return false - } - - expected := GenerateCsrfToken(key, userID, actionID, issueTime) - - // Check that the token matches the expected value. - // Use constant time comparison to avoid timing attacks. - return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1 -} diff --git a/services/context/xsrf_test.go b/services/context/xsrf_test.go deleted file mode 100644 index 21cda5d5d4..0000000000 --- a/services/context/xsrf_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2012 Google Inc. All Rights Reserved. -// Copyright 2014 The Macaron Authors -// Copyright 2020 The Gitea Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// SPDX-License-Identifier: Apache-2.0 - -package context - -import ( - "encoding/base64" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -const ( - key = "quay" - userID = "12345678" - actionID = "POST /form" -) - -var ( - now = time.Now() - oneMinuteFromNow = now.Add(1 * time.Minute) -) - -func Test_ValidToken(t *testing.T) { - t.Run("Validate token", func(t *testing.T) { - tok := GenerateCsrfToken(key, userID, actionID, now) - assert.True(t, ValidCsrfToken(tok, key, userID, actionID, oneMinuteFromNow)) - assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(CsrfTokenTimeout-1*time.Nanosecond))) - assert.True(t, ValidCsrfToken(tok, key, userID, actionID, now.Add(-1*time.Minute))) - }) -} - -// Test_SeparatorReplacement tests that separators are being correctly substituted -func Test_SeparatorReplacement(t *testing.T) { - t.Run("Test two separator replacements", func(t *testing.T) { - assert.NotEqual(t, GenerateCsrfToken("foo:bar", "baz", "wah", now), - GenerateCsrfToken("foo", "bar:baz", "wah", now)) - }) -} - -func Test_InvalidToken(t *testing.T) { - t.Run("Test invalid tokens", func(t *testing.T) { - invalidTokenTests := []struct { - name, key, userID, actionID string - t time.Time - }{ - {"Bad key", "foobar", userID, actionID, oneMinuteFromNow}, - {"Bad userID", key, "foobar", actionID, oneMinuteFromNow}, - {"Bad actionID", key, userID, "foobar", oneMinuteFromNow}, - {"Expired", key, userID, actionID, now.Add(CsrfTokenTimeout)}, - {"More than 1 minute from the future", key, userID, actionID, now.Add(-1*time.Nanosecond - 1*time.Minute)}, - } - - tok := GenerateCsrfToken(key, userID, actionID, now) - for _, itt := range invalidTokenTests { - assert.False(t, ValidCsrfToken(tok, itt.key, itt.userID, itt.actionID, itt.t)) - } - }) -} - -// Test_ValidateBadData primarily tests that no unexpected panics are triggered during parsing -func Test_ValidateBadData(t *testing.T) { - t.Run("Validate bad data", func(t *testing.T) { - badDataTests := []struct { - name, tok string - }{ - {"Invalid Base64", "ASDab24(@)$*=="}, - {"No delimiter", base64.URLEncoding.EncodeToString([]byte("foobar12345678"))}, - {"Invalid time", base64.URLEncoding.EncodeToString([]byte("foobar:foobar"))}, - } - - for _, bdt := range badDataTests { - assert.False(t, ValidCsrfToken(bdt.tok, key, userID, actionID, oneMinuteFromNow)) - } - }) -} diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 7b96b4e94f..d29a52b76b 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -6,7 +6,6 @@
{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
diff --git a/templates/admin/auth/new.tmpl b/templates/admin/auth/new.tmpl index be4995c784..29eea06f55 100644 --- a/templates/admin/auth/new.tmpl +++ b/templates/admin/auth/new.tmpl @@ -6,7 +6,6 @@
{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 080b2cd3d6..57631fd9c6 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -228,7 +228,6 @@
{{ctx.Locale.Tr "admin.config.send_test_mail"}}
- {{.CsrfTokenHtml}}
@@ -260,7 +259,6 @@
{{ctx.Locale.Tr "admin.config.cache_test"}}
- {{.CsrfTokenHtml}}
diff --git a/templates/admin/cron.tmpl b/templates/admin/cron.tmpl index 309cbce814..4d01ce51eb 100644 --- a/templates/admin/cron.tmpl +++ b/templates/admin/cron.tmpl @@ -32,7 +32,6 @@ - {{.CsrfTokenHtml}}
diff --git a/templates/admin/dashboard.tmpl b/templates/admin/dashboard.tmpl index 2426a43b15..834c56672f 100644 --- a/templates/admin/dashboard.tmpl +++ b/templates/admin/dashboard.tmpl @@ -10,7 +10,6 @@
- {{.CsrfTokenHtml}} diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index b4335aeeec..50e453916d 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -83,8 +83,6 @@

{{ctx.Locale.Tr "admin.emails.change_email_text"}}

- {{$.CsrfTokenHtml}} - diff --git a/templates/admin/notice.tmpl b/templates/admin/notice.tmpl index 6231369d39..0499b0adbb 100644 --- a/templates/admin/notice.tmpl +++ b/templates/admin/notice.tmpl @@ -34,7 +34,6 @@ diff --git a/templates/repo/settings/lfs_pointers.tmpl b/templates/repo/settings/lfs_pointers.tmpl index 4cfc0fc673..2138aadc53 100644 --- a/templates/repo/settings/lfs_pointers.tmpl +++ b/templates/repo/settings/lfs_pointers.tmpl @@ -5,7 +5,6 @@ {{if gt .NumAssociatable 0}}
- {{.CsrfTokenHtml}} {{range .Pointers}} {{if .Associatable}} diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index d40ce77127..1784118280 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -6,7 +6,6 @@
{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
@@ -38,7 +37,6 @@
- {{.CsrfTokenHtml}}
{{template "shared/avatar_upload_crop" dict "LabelText" (ctx.Locale.Tr "settings.choose_new_avatar")}}
@@ -119,7 +117,6 @@
- {{.CsrfTokenHtml}} {{DateUtils.TimeSince .Created}} - {{$.CsrfTokenHtml}} {{DateUtils.FullTime .PullMirror.UpdatedUnix}} - {{.CsrfTokenHtml}} @@ -129,7 +126,6 @@
{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
@@ -226,13 +222,11 @@ {{svg "octicon-pencil" 14}} - {{$.CsrfTokenHtml}}
- {{$.CsrfTokenHtml}} @@ -249,7 +243,6 @@
{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
@@ -299,7 +292,6 @@
- {{.CsrfTokenHtml}} {{$isCodeEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeCode}} @@ -647,7 +639,6 @@
- {{.CsrfTokenHtml}}

@@ -694,7 +685,6 @@
- {{.CsrfTokenHtml}}
@@ -710,7 +700,6 @@
- {{.CsrfTokenHtml}} {{if .IsRepoIndexerEnabled}}

{{ctx.Locale.Tr "repo.settings.admin_code_indexer"}}

@@ -815,7 +804,6 @@
{{if .RepoTransfer}} - {{.CsrfTokenHtml}} @@ -883,7 +871,6 @@ {{ctx.Locale.Tr "repo.settings.convert_notices_1"}}
- {{.CsrfTokenHtml}}
- {{.CsrfTokenHtml}}
- {{.CsrfTokenHtml}}
- {{.CsrfTokenHtml}}
- {{.CsrfTokenHtml}} {{template "base/modal_actions_confirm" .}} @@ -1045,7 +1028,6 @@ {{ctx.Locale.Tr "repo.settings.wiki_delete_notices_1" .Repository.Name}}
- {{.CsrfTokenHtml}}
- {{.CsrfTokenHtml}} {{template "base/modal_actions_confirm" .}} diff --git a/templates/repo/settings/protected_branch.tmpl b/templates/repo/settings/protected_branch.tmpl index 3c311c18c3..daa1d5f3f9 100644 --- a/templates/repo/settings/protected_branch.tmpl +++ b/templates/repo/settings/protected_branch.tmpl @@ -27,7 +27,6 @@

{{ctx.Locale.Tr "repo.settings.protect_unprotected_file_patterns_desc" "https://pkg.go.dev/github.com/gobwas/glob#Compile" "github.com/gobwas/glob"}}

- {{.CsrfTokenHtml}}
{{ctx.Locale.Tr "repo.settings.event_push"}}
diff --git a/templates/repo/settings/public_access.tmpl b/templates/repo/settings/public_access.tmpl index c1c198bcce..cfd9bf5cb1 100644 --- a/templates/repo/settings/public_access.tmpl +++ b/templates/repo/settings/public_access.tmpl @@ -12,7 +12,6 @@ {{$paEveryoneRead := "everyone-read"}} {{$paEveryoneWrite := "everyone-write"}} - {{.CsrfTokenHtml}} diff --git a/templates/repo/settings/push_mirror_sync_modal.tmpl b/templates/repo/settings/push_mirror_sync_modal.tmpl index 3bd624fab7..0ae7393ca9 100644 --- a/templates/repo/settings/push_mirror_sync_modal.tmpl +++ b/templates/repo/settings/push_mirror_sync_modal.tmpl @@ -3,7 +3,6 @@ {{ctx.Locale.Tr "repo.settings.mirror_settings.push_mirror.edit_sync_time"}} - {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/tags.tmpl b/templates/repo/settings/tags.tmpl index 12ec9102b7..af03b3598f 100644 --- a/templates/repo/settings/tags.tmpl +++ b/templates/repo/settings/tags.tmpl @@ -14,7 +14,6 @@
- {{.CsrfTokenHtml}}
{{ctx.Locale.Tr "edit"}} - {{$.CsrfTokenHtml}} diff --git a/templates/repo/settings/webhook/dingtalk.tmpl b/templates/repo/settings/webhook/dingtalk.tmpl index dd208cde17..ef96972a0a 100644 --- a/templates/repo/settings/webhook/dingtalk.tmpl +++ b/templates/repo/settings/webhook/dingtalk.tmpl @@ -1,7 +1,6 @@ {{if eq .HookType "dingtalk"}}

{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://dingtalk.com" (ctx.Locale.Tr "repo.settings.web_hook_name_dingtalk")}}

- {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/webhook/discord.tmpl b/templates/repo/settings/webhook/discord.tmpl index fa66249fa5..9dca83a497 100644 --- a/templates/repo/settings/webhook/discord.tmpl +++ b/templates/repo/settings/webhook/discord.tmpl @@ -1,7 +1,6 @@ {{if eq .HookType "discord"}}

{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://discord.com" (ctx.Locale.Tr "repo.settings.web_hook_name_discord")}}

- {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/webhook/feishu.tmpl b/templates/repo/settings/webhook/feishu.tmpl index 13bd0d92a1..84ffff364f 100644 --- a/templates/repo/settings/webhook/feishu.tmpl +++ b/templates/repo/settings/webhook/feishu.tmpl @@ -4,7 +4,6 @@ {{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://larksuite.com" (ctx.Locale.Tr "repo.settings.web_hook_name_larksuite")}}

- {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl index 30f14d609b..9f5f86d9ee 100644 --- a/templates/repo/settings/webhook/gitea.tmpl +++ b/templates/repo/settings/webhook/gitea.tmpl @@ -2,7 +2,6 @@

{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://docs.gitea.com/usage/webhooks" (ctx.Locale.Tr "repo.settings.web_hook_name_gitea")}}

{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/webhook/gogs.tmpl b/templates/repo/settings/webhook/gogs.tmpl index c0e054602a..f08114f654 100644 --- a/templates/repo/settings/webhook/gogs.tmpl +++ b/templates/repo/settings/webhook/gogs.tmpl @@ -2,7 +2,6 @@

{{ctx.Locale.Tr "repo.settings.add_web_hook_desc" "https://docs.gitea.com/usage/webhooks" (ctx.Locale.Tr "repo.settings.web_hook_name_gogs")}}

{{template "base/disable_form_autofill"}} - {{.CsrfTokenHtml}}
diff --git a/templates/repo/settings/webhook/history.tmpl b/templates/repo/settings/webhook/history.tmpl index d27c1fb8b1..fb23c7634f 100644 --- a/templates/repo/settings/webhook/history.tmpl +++ b/templates/repo/settings/webhook/history.tmpl @@ -52,7 +52,6 @@ {{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin $.PageIsUserSettings}}