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.

Update team invitation email link (#26550)

Co-authored-by: Kyle D <kdumontnu@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

authored by

Jack Hay
Kyle D
Jonathan Tran
and committed by
GitHub
c0ab7070 3cae50e8

+346 -10
+11
routers/web/auth/auth.go
··· 398 398 // Show Disabled Registration message if DisableRegistration or AllowOnlyExternalRegistration options are true 399 399 ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration || setting.Service.AllowOnlyExternalRegistration 400 400 401 + redirectTo := ctx.FormString("redirect_to") 402 + if len(redirectTo) > 0 { 403 + middleware.SetRedirectToCookie(ctx.Resp, redirectTo) 404 + } 405 + 401 406 ctx.HTML(http.StatusOK, tplSignUp) 402 407 } 403 408 ··· 729 734 } 730 735 731 736 ctx.Flash.Success(ctx.Tr("auth.account_activated")) 737 + if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 { 738 + middleware.DeleteRedirectToCookie(ctx.Resp) 739 + ctx.RedirectToFirst(redirectTo) 740 + return 741 + } 742 + 732 743 ctx.Redirect(setting.AppSubURL + "/") 733 744 } 734 745
+2 -2
services/auth/middleware.go
··· 120 120 } 121 121 } 122 122 123 - // Redirect to dashboard if user tries to visit any non-login page. 123 + // Redirect to dashboard (or alternate location) if user tries to visit any non-login page. 124 124 if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" { 125 - ctx.Redirect(setting.AppSubURL + "/") 125 + ctx.RedirectToFirst(ctx.FormString("redirect_to")) 126 126 return 127 127 } 128 128
+19
services/mailer/mail_team_invite.go
··· 6 6 import ( 7 7 "bytes" 8 8 "context" 9 + "fmt" 10 + "net/url" 9 11 10 12 org_model "code.gitea.io/gitea/models/organization" 11 13 user_model "code.gitea.io/gitea/models/user" ··· 33 35 34 36 locale := translation.NewLocale(inviter.Language) 35 37 38 + // check if a user with this email already exists 39 + user, err := user_model.GetUserByEmail(ctx, invite.Email) 40 + if err != nil && !user_model.IsErrUserNotExist(err) { 41 + return err 42 + } else if user != nil && user.ProhibitLogin { 43 + return fmt.Errorf("login is prohibited for the invited user") 44 + } 45 + 46 + inviteRedirect := url.QueryEscape(fmt.Sprintf("/org/invite/%s", invite.Token)) 47 + inviteURL := fmt.Sprintf("%suser/sign_up?redirect_to=%s", setting.AppURL, inviteRedirect) 48 + 49 + if err == nil && user != nil { 50 + // user account exists 51 + inviteURL = fmt.Sprintf("%suser/login?redirect_to=%s", setting.AppURL, inviteRedirect) 52 + } 53 + 36 54 subject := locale.Tr("mail.team_invite.subject", inviter.DisplayName(), org.DisplayName()) 37 55 mailMeta := map[string]any{ 38 56 "Inviter": inviter, ··· 40 58 "Team": team, 41 59 "Invite": invite, 42 60 "Subject": subject, 61 + "InviteURL": inviteURL, 43 62 // helper 44 63 "locale": locale, 45 64 "Str2html": templates.Str2html,
+1 -2
templates/mail/team_invite.tmpl
··· 4 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 5 5 <meta name="format-detection" content="telephone=no,date=no,address=no,email=no,url=no"> 6 6 </head> 7 - {{$invite_url := printf "%sorg/invite/%s" AppUrl (QueryEscape .Invite.Token)}} 8 7 <body> 9 8 <p>{{.locale.Tr "mail.team_invite.text_1" (DotEscape .Inviter.DisplayName) (DotEscape .Team.Name) (DotEscape .Organization.DisplayName) | Str2html}}</p> 10 - <p>{{.locale.Tr "mail.team_invite.text_2"}}</p><p><a href="{{$invite_url}}">{{$invite_url}}</a></p> 9 + <p>{{.locale.Tr "mail.team_invite.text_2"}}</p><p><a href="{{.InviteURL}}">{{.InviteURL}}</a></p> 11 10 <p>{{.locale.Tr "mail.link_not_working_do_paste"}}</p> 12 11 <p>{{.locale.Tr "mail.team_invite.text_3" .Invite.Email}}</p> 13 12
+313 -6
tests/integration/org_team_invite_test.go
··· 6 6 import ( 7 7 "fmt" 8 8 "net/http" 9 + "net/url" 10 + "strings" 9 11 "testing" 10 12 11 13 "code.gitea.io/gitea/models/db" ··· 37 39 38 40 session := loginUser(t, "user1") 39 41 40 - url := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 41 - csrf := GetCSRF(t, session, url) 42 - req := NewRequestWithValues(t, "POST", url+"/action/add", map[string]string{ 42 + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 43 + csrf := GetCSRF(t, session, teamURL) 44 + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ 43 45 "_csrf": csrf, 44 46 "uid": "1", 45 47 "uname": user.Email, ··· 56 58 session = loginUser(t, user.Name) 57 59 58 60 // join the team 59 - url = fmt.Sprintf("/org/invite/%s", invites[0].Token) 60 - csrf = GetCSRF(t, session, url) 61 - req = NewRequestWithValues(t, "POST", url, map[string]string{ 61 + inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) 62 + csrf = GetCSRF(t, session, inviteURL) 63 + req = NewRequestWithValues(t, "POST", inviteURL, map[string]string{ 62 64 "_csrf": csrf, 63 65 }) 64 66 resp = session.MakeRequest(t, req, http.StatusSeeOther) ··· 69 71 assert.NoError(t, err) 70 72 assert.True(t, isMember) 71 73 } 74 + 75 + // Check that users are redirected to accept the invitation correctly after login 76 + func TestOrgTeamEmailInviteRedirectsExistingUser(t *testing.T) { 77 + if setting.MailService == nil { 78 + t.Skip() 79 + return 80 + } 81 + 82 + defer tests.PrepareTestEnv(t)() 83 + 84 + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) 85 + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) 86 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) 87 + 88 + isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) 89 + assert.NoError(t, err) 90 + assert.False(t, isMember) 91 + 92 + // create the invite 93 + session := loginUser(t, "user1") 94 + 95 + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 96 + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ 97 + "_csrf": GetCSRF(t, session, teamURL), 98 + "uid": "1", 99 + "uname": user.Email, 100 + }) 101 + resp := session.MakeRequest(t, req, http.StatusSeeOther) 102 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 103 + session.MakeRequest(t, req, http.StatusOK) 104 + 105 + // get the invite token 106 + invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID) 107 + assert.NoError(t, err) 108 + assert.Len(t, invites, 1) 109 + 110 + // accept the invite 111 + inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) 112 + req = NewRequest(t, "GET", fmt.Sprintf("/user/login?redirect_to=%s", url.QueryEscape(inviteURL))) 113 + resp = MakeRequest(t, req, http.StatusOK) 114 + 115 + doc := NewHTMLParser(t, resp.Body) 116 + req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{ 117 + "_csrf": doc.GetCSRF(), 118 + "user_name": "user5", 119 + "password": "password", 120 + }) 121 + for _, c := range resp.Result().Cookies() { 122 + req.AddCookie(c) 123 + } 124 + 125 + resp = MakeRequest(t, req, http.StatusSeeOther) 126 + assert.Equal(t, inviteURL, test.RedirectURL(resp)) 127 + 128 + // complete the login process 129 + ch := http.Header{} 130 + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) 131 + cr := http.Request{Header: ch} 132 + 133 + session = emptyTestSession(t) 134 + baseURL, err := url.Parse(setting.AppURL) 135 + assert.NoError(t, err) 136 + session.jar.SetCookies(baseURL, cr.Cookies()) 137 + 138 + // make the request 139 + req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{ 140 + "_csrf": GetCSRF(t, session, test.RedirectURL(resp)), 141 + }) 142 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 143 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 144 + session.MakeRequest(t, req, http.StatusOK) 145 + 146 + isMember, err = organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) 147 + assert.NoError(t, err) 148 + assert.True(t, isMember) 149 + } 150 + 151 + // Check that newly signed up users are redirected to accept the invitation correctly 152 + func TestOrgTeamEmailInviteRedirectsNewUser(t *testing.T) { 153 + if setting.MailService == nil { 154 + t.Skip() 155 + return 156 + } 157 + 158 + defer tests.PrepareTestEnv(t)() 159 + 160 + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) 161 + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) 162 + 163 + // create the invite 164 + session := loginUser(t, "user1") 165 + 166 + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 167 + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ 168 + "_csrf": GetCSRF(t, session, teamURL), 169 + "uid": "1", 170 + "uname": "doesnotexist@example.com", 171 + }) 172 + resp := session.MakeRequest(t, req, http.StatusSeeOther) 173 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 174 + session.MakeRequest(t, req, http.StatusOK) 175 + 176 + // get the invite token 177 + invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID) 178 + assert.NoError(t, err) 179 + assert.Len(t, invites, 1) 180 + 181 + // accept the invite 182 + inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) 183 + req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) 184 + resp = MakeRequest(t, req, http.StatusOK) 185 + 186 + doc := NewHTMLParser(t, resp.Body) 187 + req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ 188 + "_csrf": doc.GetCSRF(), 189 + "user_name": "doesnotexist", 190 + "email": "doesnotexist@example.com", 191 + "password": "examplePassword!1", 192 + "retype": "examplePassword!1", 193 + }) 194 + for _, c := range resp.Result().Cookies() { 195 + req.AddCookie(c) 196 + } 197 + 198 + resp = MakeRequest(t, req, http.StatusSeeOther) 199 + assert.Equal(t, inviteURL, test.RedirectURL(resp)) 200 + 201 + // complete the signup process 202 + ch := http.Header{} 203 + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) 204 + cr := http.Request{Header: ch} 205 + 206 + session = emptyTestSession(t) 207 + baseURL, err := url.Parse(setting.AppURL) 208 + assert.NoError(t, err) 209 + session.jar.SetCookies(baseURL, cr.Cookies()) 210 + 211 + // make the redirected request 212 + req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{ 213 + "_csrf": GetCSRF(t, session, test.RedirectURL(resp)), 214 + }) 215 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 216 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 217 + session.MakeRequest(t, req, http.StatusOK) 218 + 219 + // get the new user 220 + newUser, err := user_model.GetUserByName(db.DefaultContext, "doesnotexist") 221 + assert.NoError(t, err) 222 + 223 + isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, newUser.ID) 224 + assert.NoError(t, err) 225 + assert.True(t, isMember) 226 + } 227 + 228 + // Check that users are redirected correctly after confirming their email 229 + func TestOrgTeamEmailInviteRedirectsNewUserWithActivation(t *testing.T) { 230 + if setting.MailService == nil { 231 + t.Skip() 232 + return 233 + } 234 + 235 + // enable email confirmation temporarily 236 + defer func(prevVal bool) { 237 + setting.Service.RegisterEmailConfirm = prevVal 238 + }(setting.Service.RegisterEmailConfirm) 239 + setting.Service.RegisterEmailConfirm = true 240 + 241 + defer tests.PrepareTestEnv(t)() 242 + 243 + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) 244 + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) 245 + 246 + // create the invite 247 + session := loginUser(t, "user1") 248 + 249 + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 250 + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ 251 + "_csrf": GetCSRF(t, session, teamURL), 252 + "uid": "1", 253 + "uname": "doesnotexist@example.com", 254 + }) 255 + resp := session.MakeRequest(t, req, http.StatusSeeOther) 256 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 257 + session.MakeRequest(t, req, http.StatusOK) 258 + 259 + // get the invite token 260 + invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID) 261 + assert.NoError(t, err) 262 + assert.Len(t, invites, 1) 263 + 264 + // accept the invite 265 + inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) 266 + req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) 267 + inviteResp := MakeRequest(t, req, http.StatusOK) 268 + 269 + doc := NewHTMLParser(t, resp.Body) 270 + req = NewRequestWithValues(t, "POST", "/user/sign_up", map[string]string{ 271 + "_csrf": doc.GetCSRF(), 272 + "user_name": "doesnotexist", 273 + "email": "doesnotexist@example.com", 274 + "password": "examplePassword!1", 275 + "retype": "examplePassword!1", 276 + }) 277 + for _, c := range inviteResp.Result().Cookies() { 278 + req.AddCookie(c) 279 + } 280 + 281 + resp = MakeRequest(t, req, http.StatusOK) 282 + 283 + user, err := user_model.GetUserByName(db.DefaultContext, "doesnotexist") 284 + assert.NoError(t, err) 285 + 286 + ch := http.Header{} 287 + ch.Add("Cookie", strings.Join(resp.Header()["Set-Cookie"], ";")) 288 + cr := http.Request{Header: ch} 289 + 290 + session = emptyTestSession(t) 291 + baseURL, err := url.Parse(setting.AppURL) 292 + assert.NoError(t, err) 293 + session.jar.SetCookies(baseURL, cr.Cookies()) 294 + 295 + activateURL := fmt.Sprintf("/user/activate?code=%s", user.GenerateEmailActivateCode("doesnotexist@example.com")) 296 + req = NewRequestWithValues(t, "POST", activateURL, map[string]string{ 297 + "password": "examplePassword!1", 298 + }) 299 + 300 + // use the cookies set by the signup request 301 + for _, c := range inviteResp.Result().Cookies() { 302 + req.AddCookie(c) 303 + } 304 + 305 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 306 + // should be redirected to accept the invite 307 + assert.Equal(t, inviteURL, test.RedirectURL(resp)) 308 + 309 + req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{ 310 + "_csrf": GetCSRF(t, session, test.RedirectURL(resp)), 311 + }) 312 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 313 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 314 + session.MakeRequest(t, req, http.StatusOK) 315 + 316 + isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) 317 + assert.NoError(t, err) 318 + assert.True(t, isMember) 319 + } 320 + 321 + // Test that a logged-in user who navigates to the sign-up link is then redirected using redirect_to 322 + // For example: an invite may have been created before the user account was created, but they may be 323 + // accepting the invite after having created an account separately 324 + func TestOrgTeamEmailInviteRedirectsExistingUserWithLogin(t *testing.T) { 325 + if setting.MailService == nil { 326 + t.Skip() 327 + return 328 + } 329 + 330 + defer tests.PrepareTestEnv(t)() 331 + 332 + org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3}) 333 + team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) 334 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) 335 + 336 + isMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) 337 + assert.NoError(t, err) 338 + assert.False(t, isMember) 339 + 340 + // create the invite 341 + session := loginUser(t, "user1") 342 + 343 + teamURL := fmt.Sprintf("/org/%s/teams/%s", org.Name, team.Name) 344 + req := NewRequestWithValues(t, "POST", teamURL+"/action/add", map[string]string{ 345 + "_csrf": GetCSRF(t, session, teamURL), 346 + "uid": "1", 347 + "uname": user.Email, 348 + }) 349 + resp := session.MakeRequest(t, req, http.StatusSeeOther) 350 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 351 + session.MakeRequest(t, req, http.StatusOK) 352 + 353 + // get the invite token 354 + invites, err := organization.GetInvitesByTeamID(db.DefaultContext, team.ID) 355 + assert.NoError(t, err) 356 + assert.Len(t, invites, 1) 357 + 358 + // note: the invited user has logged in 359 + session = loginUser(t, "user5") 360 + 361 + // accept the invite (note: this uses the sign_up url) 362 + inviteURL := fmt.Sprintf("/org/invite/%s", invites[0].Token) 363 + req = NewRequest(t, "GET", fmt.Sprintf("/user/sign_up?redirect_to=%s", url.QueryEscape(inviteURL))) 364 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 365 + assert.Equal(t, inviteURL, test.RedirectURL(resp)) 366 + 367 + // make the request 368 + req = NewRequestWithValues(t, "POST", test.RedirectURL(resp), map[string]string{ 369 + "_csrf": GetCSRF(t, session, test.RedirectURL(resp)), 370 + }) 371 + resp = session.MakeRequest(t, req, http.StatusSeeOther) 372 + req = NewRequest(t, "GET", test.RedirectURL(resp)) 373 + session.MakeRequest(t, req, http.StatusOK) 374 + 375 + isMember, err = organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, user.ID) 376 + assert.NoError(t, err) 377 + assert.True(t, isMember) 378 + }