diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index 7523ebaed0..1702950da8 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -208,7 +208,6 @@ func createDescriptionLink(href, content string) *html.Node { Attr: []html.Attribute{ {Key: "href", Val: href}, {Key: "target", Val: "_blank"}, - {Key: "rel", Val: "noopener noreferrer"}, }, } textNode.Parent = linkNode diff --git a/modules/markup/sanitizer_description_test.go b/modules/markup/sanitizer_description_test.go index ca72491f26..51833414f4 100644 --- a/modules/markup/sanitizer_description_test.go +++ b/modules/markup/sanitizer_description_test.go @@ -16,7 +16,7 @@ func TestDescriptionSanitizer(t *testing.T) { `THUMBS UP`, `THUMBS UP`, `Hello World`, `Hello World`, `
`, ``, - `https://example.com`, `https://example.com`, + `https://example.com`, `https://example.com`, `data`, `data`, `Important!`, `Important!`, `
Click me! Nothing to see here.
`, `Click me! Nothing to see here.`, diff --git a/modules/web/middleware/cookie.go b/modules/web/middleware/cookie.go index ad9aee6478..f98aceba10 100644 --- a/modules/web/middleware/cookie.go +++ b/modules/web/middleware/cookie.go @@ -14,14 +14,24 @@ import ( "code.gitea.io/gitea/modules/util" ) +const cookieRedirectTo = "redirect_to" + +func GetRedirectToCookie(req *http.Request) string { + return GetSiteCookie(req, cookieRedirectTo) +} + // SetRedirectToCookie convenience function to set the RedirectTo cookie consistently func SetRedirectToCookie(resp http.ResponseWriter, value string) { - SetSiteCookie(resp, "redirect_to", value, 0) + SetSiteCookie(resp, cookieRedirectTo, value, 0) } // DeleteRedirectToCookie convenience function to delete most cookies consistently func DeleteRedirectToCookie(resp http.ResponseWriter) { - SetSiteCookie(resp, "redirect_to", "", -1) + SetSiteCookie(resp, cookieRedirectTo, "", -1) +} + +func RedirectLinkUserLogin(req *http.Request) string { + return setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(setting.AppSubURL+req.URL.RequestURI()) } // GetSiteCookie returns given cookie value from request header. diff --git a/routers/common/blockexpensive.go b/routers/common/blockexpensive.go index ebec0a2e5b..fec364351c 100644 --- a/routers/common/blockexpensive.go +++ b/routers/common/blockexpensive.go @@ -24,7 +24,7 @@ func BlockExpensive() func(next http.Handler) http.Handler { ret := determineRequestPriority(reqctx.FromContext(req.Context())) if !ret.SignedIn { if ret.Expensive || ret.LongPolling { - http.Redirect(w, req, setting.AppSubURL+"/user/login", http.StatusSeeOther) + http.Redirect(w, req, middleware.RedirectLinkUserLogin(req), http.StatusSeeOther) return } } diff --git a/routers/web/auth/2fa.go b/routers/web/auth/2fa.go index 1f087a7897..a19c9d7aca 100644 --- a/routers/web/auth/2fa.go +++ b/routers/web/auth/2fa.go @@ -26,7 +26,7 @@ var ( func TwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("twofa") - if CheckAutoLogin(ctx) { + if performAutoLogin(ctx) { return } @@ -99,7 +99,7 @@ func TwoFactorPost(ctx *context.Context) { func TwoFactorScratch(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("twofa_scratch") - if CheckAutoLogin(ctx) { + if performAutoLogin(ctx) { return } @@ -151,7 +151,7 @@ func TwoFactorScratchPost(ctx *context.Context) { return } - handleSignInFull(ctx, u, remember, false) + handleSignInFull(ctx, u, remember) if ctx.Written() { return } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index d36fb5bab7..bc0939d92a 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -9,6 +9,7 @@ import ( "fmt" "html/template" "net/http" + "net/url" "strings" "code.gitea.io/gitea/models/auth" @@ -126,20 +127,47 @@ func resetLocale(ctx *context.Context, u *user_model.User) error { return nil } -func RedirectAfterLogin(ctx *context.Context) { +func rememberAuthRedirectLink(ctx *context.Context) { redirectTo := ctx.FormString("redirect_to") if redirectTo == "" { - redirectTo = ctx.GetSiteCookie("redirect_to") + if ref, err := url.Parse(ctx.Req.Referer()); err == nil && httplib.IsCurrentGiteaSiteURL(ctx, ctx.Req.Referer()) { + // the request paths starting with "/user/" are either: + // * auth related pages: don't redirect back to them + // * user settings pages: they have "require sign-in" protection already, no "referer redirect" would happen + skipRefererRedirect := strings.HasPrefix(ref.Path, setting.AppSubURL+"/user/") + if !skipRefererRedirect { + redirectTo = ref.RequestURI() + } + } } - middleware.DeleteRedirectToCookie(ctx.Resp) - nextRedirectTo := setting.AppSubURL + string(setting.LandingPageURL) - if setting.LandingPageURL == setting.LandingPageLogin { - nextRedirectTo = setting.AppSubURL + "/" // do not cycle-redirect to the login page + if redirectTo != "" { + middleware.SetRedirectToCookie(ctx.Resp, redirectTo) } - ctx.RedirectToCurrentSite(redirectTo, nextRedirectTo) } -func CheckAutoLogin(ctx *context.Context) bool { +func consumeAuthRedirectLink(ctx *context.Context) string { + redirects := []string{ctx.FormString("redirect_to"), middleware.GetRedirectToCookie(ctx.Req)} + middleware.DeleteRedirectToCookie(ctx.Resp) + if setting.LandingPageURL == setting.LandingPageLogin { + redirects = append(redirects, setting.AppSubURL+"/") // do not cycle-redirect to the login page + } else { + redirects = append(redirects, setting.AppSubURL+string(setting.LandingPageURL)) + } + for _, link := range redirects { + if link != "" && httplib.IsCurrentGiteaSiteURL(ctx, link) { + return link + } + } + return setting.AppSubURL + "/" +} + +func redirectAfterAuth(ctx *context.Context) { + ctx.RedirectToCurrentSite(consumeAuthRedirectLink(ctx)) +} + +func performAutoLogin(ctx *context.Context) bool { + rememberAuthRedirectLink(ctx) + isSucceed, err := autoSignIn(ctx) // try to auto-login if err != nil { if errors.Is(err, auth_service.ErrAuthTokenInvalidHash) { @@ -150,13 +178,8 @@ func CheckAutoLogin(ctx *context.Context) bool { return true } - redirectTo := ctx.FormString("redirect_to") - if len(redirectTo) > 0 { - middleware.SetRedirectToCookie(ctx.Resp, redirectTo) - } - if isSucceed { - RedirectAfterLogin(ctx) + redirectAfterAuth(ctx) return true } @@ -181,11 +204,11 @@ func prepareSignInPageData(ctx *context.Context) { // SignIn render sign in page func SignIn(ctx *context.Context) { - if CheckAutoLogin(ctx) { + if performAutoLogin(ctx) { return } if ctx.IsSigned { - RedirectAfterLogin(ctx) + redirectAfterAuth(ctx) return } prepareSignInPageData(ctx) @@ -295,19 +318,19 @@ func SignInPost(ctx *context.Context) { // This handles the final part of the sign-in process of the user. func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) { - redirect := handleSignInFull(ctx, u, remember, true) + handleSignInFull(ctx, u, remember) if ctx.Written() { return } - ctx.Redirect(redirect) + redirectAfterAuth(ctx) } -func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string { +func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool) { if remember { nt, token, err := auth_service.CreateAuthTokenForUserID(ctx, u.ID) if err != nil { ctx.ServerError("CreateAuthTokenForUserID", err) - return setting.AppSubURL + "/" + return } ctx.SetSiteCookie(setting.CookieRememberName, nt.ID+":"+token, setting.LogInRememberDays*timeutil.Day) @@ -316,7 +339,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe userHasTwoFactorAuth, err := auth.HasTwoFactorOrWebAuthn(ctx, u.ID) if err != nil { ctx.ServerError("HasTwoFactorOrWebAuthn", err) - return setting.AppSubURL + "/" + return } if err := updateSession(ctx, []string{ @@ -335,7 +358,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe session.KeyUserHasTwoFactorAuth: userHasTwoFactorAuth, }); err != nil { ctx.ServerError("RegenerateSession", err) - return setting.AppSubURL + "/" + return } // Language setting of the user overwrites the one previously set @@ -346,7 +369,7 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe } if err := user_service.UpdateUser(ctx, u, opts); err != nil { ctx.ServerError("UpdateUser Language", fmt.Errorf("Error updating user language [user: %d, locale: %s]", u.ID, ctx.Locale.Language())) - return setting.AppSubURL + "/" + return } } @@ -359,21 +382,8 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe // Register last login if err := user_service.UpdateUser(ctx, u, &user_service.UpdateOptions{SetLastLogin: true}); err != nil { ctx.ServerError("UpdateUser", err) - return setting.AppSubURL + "/" + return } - - if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" && httplib.IsCurrentGiteaSiteURL(ctx, redirectTo) { - middleware.DeleteRedirectToCookie(ctx.Resp) - if obeyRedirect { - ctx.RedirectToCurrentSite(redirectTo) - } - return redirectTo - } - - if obeyRedirect { - ctx.Redirect(setting.AppSubURL + "/") - } - return setting.AppSubURL + "/" } // extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user. @@ -436,10 +446,7 @@ func SignUp(ctx *context.Context) { // Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration - redirectTo := ctx.FormString("redirect_to") - if len(redirectTo) > 0 { - middleware.SetRedirectToCookie(ctx.Resp, redirectTo) - } + rememberAuthRedirectLink(ctx) ctx.HTML(http.StatusOK, tplSignUp) } @@ -817,13 +824,7 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { } ctx.Flash.Success(ctx.Tr("auth.account_activated")) - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 { - middleware.DeleteRedirectToCookie(ctx.Resp) - ctx.RedirectToCurrentSite(redirectTo) - return - } - - ctx.Redirect(setting.AppSubURL + "/") + redirectAfterAuth(ctx) } // ActivateEmail render the activate email page diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 5eab7ffeb4..b96ea17bc3 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -21,7 +21,6 @@ import ( "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/session" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/web/middleware" source_service "code.gitea.io/gitea/services/auth/source" "code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/context" @@ -42,10 +41,7 @@ func SignInOAuth(ctx *context.Context) { return } - redirectTo := ctx.FormString("redirect_to") - if len(redirectTo) > 0 { - middleware.SetRedirectToCookie(ctx.Resp, redirectTo) - } + rememberAuthRedirectLink(ctx) // try to do a direct callback flow, so we don't authenticate the user again but use the valid accesstoken to get the user user, gothUser, err := oAuth2UserLoginCallback(ctx, authSource, ctx.Req, ctx.Resp) @@ -398,13 +394,7 @@ func handleOAuth2SignIn(ctx *context.Context, authSource *auth.Source, u *user_m return } - if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 { - middleware.DeleteRedirectToCookie(ctx.Resp) - ctx.RedirectToCurrentSite(redirectTo) - return - } - - ctx.Redirect(setting.AppSubURL + "/") + redirectAfterAuth(ctx) return } diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 4ef4c96ccc..948e65366e 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -35,7 +35,7 @@ func SignInOpenID(ctx *context.Context) { return } - if CheckAutoLogin(ctx) { + if performAutoLogin(ctx) { return } diff --git a/routers/web/auth/password.go b/routers/web/auth/password.go index 537ad4b994..61c6119470 100644 --- a/routers/web/auth/password.go +++ b/routers/web/auth/password.go @@ -16,7 +16,6 @@ import ( "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/web" - "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/mailer" @@ -236,7 +235,7 @@ func ResetPasswdPost(ctx *context.Context) { return } - handleSignInFull(ctx, u, remember, false) + handleSignInFull(ctx, u, remember) if ctx.Written() { return } @@ -308,11 +307,5 @@ func MustChangePasswordPost(ctx *context.Context) { log.Trace("User updated password: %s", ctx.Doer.Name) - if redirectTo := ctx.GetSiteCookie("redirect_to"); redirectTo != "" { - middleware.DeleteRedirectToCookie(ctx.Resp) - ctx.RedirectToCurrentSite(redirectTo) - return - } - - ctx.Redirect(setting.AppSubURL + "/") + redirectAfterAuth(ctx) } diff --git a/routers/web/auth/webauthn.go b/routers/web/auth/webauthn.go index dacb6be225..cae726b8bf 100644 --- a/routers/web/auth/webauthn.go +++ b/routers/web/auth/webauthn.go @@ -26,7 +26,7 @@ var tplWebAuthn templates.TplName = "user/auth/webauthn" func WebAuthn(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("twofa") - if CheckAutoLogin(ctx) { + if performAutoLogin(ctx) { return } @@ -156,12 +156,8 @@ func WebAuthnPasskeyLogin(ctx *context.Context) { } remember := false // TODO: implement remember me - redirect := handleSignInFull(ctx, user, remember, false) - if redirect == "" { - redirect = setting.AppSubURL + "/" - } - - ctx.JSONRedirect(redirect) + handleSignInFull(ctx, user, remember) + ctx.JSONRedirect(consumeAuthRedirectLink(ctx)) } // WebAuthnLoginAssertion submits a WebAuthn challenge to the browser @@ -274,11 +270,7 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) { } remember := ctx.Session.Get("twofaRemember").(bool) - redirect := handleSignInFull(ctx, user, remember, false) - if redirect == "" { - redirect = setting.AppSubURL + "/" - } + handleSignInFull(ctx, user, remember) _ = ctx.Session.Delete("twofaUid") - - ctx.JSONRedirect(redirect) + ctx.JSONRedirect(consumeAuthRedirectLink(ctx)) } diff --git a/routers/web/repo/issue_view.go b/routers/web/repo/issue_view.go index 803afbffe4..a97f32e018 100644 --- a/routers/web/repo/issue_view.go +++ b/routers/web/repo/issue_view.go @@ -7,7 +7,6 @@ import ( "fmt" "math/big" "net/http" - "net/url" "sort" "strconv" @@ -33,6 +32,7 @@ import ( "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates/vars" "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web/middleware" asymkey_service "code.gitea.io/gitea/services/asymkey" "code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context/upload" @@ -408,7 +408,7 @@ func ViewIssue(ctx *context.Context) { } ctx.Data["Reference"] = issue.Ref - ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(ctx.Data["Link"].(string)) + ctx.Data["SignInLink"] = middleware.RedirectLinkUserLogin(ctx.Req) ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.Doer.ID) ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects) diff --git a/routers/web/web.go b/routers/web/web.go index 4da8cdb581..64137876e0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -159,9 +159,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont } ctx.Data["Title"] = ctx.Tr("auth.must_change_password") ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } + middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) ctx.Redirect(setting.AppSubURL + "/user/settings/change_password") return } @@ -172,7 +170,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont } } - // Redirect to dashboard (or alternate location) if user tries to visit any non-login page. + // When a signed-in user visits a page that requires sign-out (e.g.: "/user/login"), redirect to home (or alternate location) if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { ctx.RedirectToCurrentSite(ctx.FormString("redirect_to")) return @@ -187,10 +185,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont if options.SignInRequired { if !ctx.IsSigned { - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } - ctx.Redirect(setting.AppSubURL + "/user/login") + ctx.Redirect(middleware.RedirectLinkUserLogin(ctx.Req)) return } else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { ctx.Data["Title"] = ctx.Tr("auth.active_your_account") @@ -200,12 +195,8 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont } // Redirect to log in page if auto-signin info is provided and has not signed in. - if !options.SignOutRequired && !ctx.IsSigned && - ctx.GetSiteCookie(setting.CookieRememberName) != "" { - if ctx.Req.URL.Path != "/user/events" { - middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) - } - ctx.Redirect(setting.AppSubURL + "/user/login") + if !options.SignOutRequired && !ctx.IsSigned && ctx.GetSiteCookie(setting.CookieRememberName) != "" { + ctx.Redirect(middleware.RedirectLinkUserLogin(ctx.Req)) return } diff --git a/services/context/base.go b/services/context/base.go index 8bd66bed09..4baea95ccf 100644 --- a/services/context/base.go +++ b/services/context/base.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" ) @@ -147,10 +148,7 @@ func (b *Base) PlainText(status int, text string) { // Redirect redirects the request func (b *Base) Redirect(location string, status ...int) { - code := http.StatusSeeOther - if len(status) == 1 { - code = status[0] - } + code := util.OptionalArg(status, http.StatusSeeOther) if !httplib.IsRelativeURL(location) { // Some browsers (Safari) have buggy behavior for Cookie + Cache + External Redirection, eg: /my-path => https://other/path diff --git a/services/context/context.go b/services/context/context.go index 420b2aefa8..e12a97eeef 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -145,7 +145,6 @@ func Contexter() func(next http.Handler) http.Handler { base := NewBaseContext(resp, req) ctx := NewWebContext(base, rnd, session.GetContextSession(req)) ctx.Data.MergeFrom(middleware.CommonTemplateContextData()) - ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI() ctx.Data["Link"] = ctx.Link // PageData is passed by reference, and it will be rendered to `window.config.pageData` in `head.tmpl` for JavaScript modules diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index df437badf6..66c9d718ea 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,7 +1,7 @@