Browse Source

route: no session for routes without UI (#6066)

Not all routes need session, register session and CSRF middleware as global is a waste of resource, and creating a lot one-time off yet never used session records.
ᴜɴᴋɴᴡᴏɴ 4 years ago
parent
commit
07818d5fa5

+ 3 - 5
internal/app/api.go

@@ -9,8 +9,6 @@ import (
 
 	"github.com/microcosm-cc/bluemonday"
 	"gopkg.in/macaron.v1"
-
-	"gogs.io/gogs/internal/context"
 )
 
 func ipynbSanitizer() *bluemonday.Policy {
@@ -24,13 +22,13 @@ func ipynbSanitizer() *bluemonday.Policy {
 func SanitizeIpynb() macaron.Handler {
 	p := ipynbSanitizer()
 
-	return func(c *context.Context) {
+	return func(c *macaron.Context) {
 		html, err := c.Req.Body().String()
 		if err != nil {
-			c.Error(err, "read body")
+			c.Error(http.StatusInternalServerError, "read body")
 			return
 		}
 
-		c.PlainText(http.StatusOK, p.Sanitize(html))
+		c.PlainText(http.StatusOK, []byte(p.Sanitize(html)))
 	}
 }

+ 8 - 4
internal/app/metrics.go

@@ -9,14 +9,14 @@ import (
 
 	"gopkg.in/macaron.v1"
 
+	"gogs.io/gogs/internal/authutil"
 	"gogs.io/gogs/internal/conf"
-	"gogs.io/gogs/internal/context"
 )
 
 func MetricsFilter() macaron.Handler {
-	return func(c *context.Context) {
+	return func(w http.ResponseWriter, r *http.Request) {
 		if !conf.Prometheus.Enabled {
-			c.Status(http.StatusNotFound)
+			w.WriteHeader(http.StatusNotFound)
 			return
 		}
 
@@ -24,6 +24,10 @@ func MetricsFilter() macaron.Handler {
 			return
 		}
 
-		c.RequireBasicAuth(conf.Prometheus.BasicAuthUsername, conf.Prometheus.BasicAuthPassword)
+		username, password := authutil.DecodeBasic(r.Header)
+		if username != conf.Prometheus.BasicAuthUsername || password != conf.Prometheus.BasicAuthPassword {
+			w.WriteHeader(http.StatusForbidden)
+			return
+		}
 	}
 }

+ 451 - 451
internal/cmd/web.go

@@ -144,25 +144,6 @@ func newMacaron() *macaron.Macaron {
 	m.Use(captcha.Captchaer(captcha.Options{
 		SubURL: conf.Server.Subpath,
 	}))
-	m.Use(session.Sessioner(session.Options{
-		Provider:       conf.Session.Provider,
-		ProviderConfig: conf.Session.ProviderConfig,
-		CookieName:     conf.Session.CookieName,
-		CookiePath:     conf.Server.Subpath,
-		Gclifetime:     conf.Session.GCInterval,
-		Maxlifetime:    conf.Session.MaxLifeTime,
-		Secure:         conf.Session.CookieSecure,
-	}))
-	m.Use(csrf.Csrfer(csrf.Options{
-		Secret:         conf.Security.SecretKey,
-		Header:         "X-CSRF-Token",
-		Cookie:         conf.Session.CSRFCookieName,
-		CookieDomain:   conf.Server.URL.Hostname(),
-		CookiePath:     conf.Server.Subpath,
-		CookieHttpOnly: true,
-		SetCookie:      true,
-		Secure:         conf.Server.URL.Scheme == "https",
-	}))
 	m.Use(toolbox.Toolboxer(m, toolbox.Options{
 		HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
 			{
@@ -171,7 +152,6 @@ func newMacaron() *macaron.Macaron {
 			},
 		},
 	}))
-	m.Use(context.Contexter())
 	return m
 }
 
@@ -185,488 +165,506 @@ func runWeb(c *cli.Context) error {
 
 	reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
 	ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Auth.RequireSigninView})
-	ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
 	reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
 
 	bindIgnErr := binding.BindIgnErr
 
 	m.SetAutoHead(true)
 
-	// FIXME: not all route need go through same middlewares.
-	// Especially some AJAX requests, we can reduce middleware number to improve performance.
-	// Routers.
-	m.Get("/", ignSignIn, route.Home)
-	m.Group("/explore", func() {
-		m.Get("", func(c *context.Context) {
-			c.Redirect(conf.Server.Subpath + "/explore/repos")
-		})
-		m.Get("/repos", route.ExploreRepos)
-		m.Get("/users", route.ExploreUsers)
-		m.Get("/organizations", route.ExploreOrganizations)
-	}, ignSignIn)
-	m.Combo("/install", route.InstallInit).Get(route.Install).
-		Post(bindIgnErr(form.Install{}), route.InstallPost)
-	m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
-
-	// ***** START: User *****
-	m.Group("/user", func() {
-		m.Group("/login", func() {
-			m.Combo("").Get(user.Login).
-				Post(bindIgnErr(form.SignIn{}), user.LoginPost)
-			m.Combo("/two_factor").Get(user.LoginTwoFactor).Post(user.LoginTwoFactorPost)
-			m.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost)
-		})
-
-		m.Get("/sign_up", user.SignUp)
-		m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
-		m.Get("/reset_password", user.ResetPasswd)
-		m.Post("/reset_password", user.ResetPasswdPost)
-	}, reqSignOut)
-
-	m.Group("/user/settings", func() {
-		m.Get("", user.Settings)
-		m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost)
-		m.Combo("/avatar").Get(user.SettingsAvatar).
-			Post(binding.MultipartForm(form.Avatar{}), user.SettingsAvatarPost)
-		m.Post("/avatar/delete", user.SettingsDeleteAvatar)
-		m.Combo("/email").Get(user.SettingsEmails).
-			Post(bindIgnErr(form.AddEmail{}), user.SettingsEmailPost)
-		m.Post("/email/delete", user.DeleteEmail)
-		m.Get("/password", user.SettingsPassword)
-		m.Post("/password", bindIgnErr(form.ChangePassword{}), user.SettingsPasswordPost)
-		m.Combo("/ssh").Get(user.SettingsSSHKeys).
-			Post(bindIgnErr(form.AddSSHKey{}), user.SettingsSSHKeysPost)
-		m.Post("/ssh/delete", user.DeleteSSHKey)
-		m.Group("/security", func() {
-			m.Get("", user.SettingsSecurity)
-			m.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable).
-				Post(user.SettingsTwoFactorEnablePost)
-			m.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes).
-				Post(user.SettingsTwoFactorRecoveryCodesPost)
-			m.Post("/two_factor_disable", user.SettingsTwoFactorDisable)
-		})
-		m.Group("/repositories", func() {
-			m.Get("", user.SettingsRepos)
-			m.Post("/leave", user.SettingsLeaveRepo)
-		})
-		m.Group("/organizations", func() {
-			m.Get("", user.SettingsOrganizations)
-			m.Post("/leave", user.SettingsLeaveOrganization)
-		})
-		m.Combo("/applications").Get(user.SettingsApplications).
-			Post(bindIgnErr(form.NewAccessToken{}), user.SettingsApplicationsPost)
-		m.Post("/applications/delete", user.SettingsDeleteApplication)
-		m.Route("/delete", "GET,POST", user.SettingsDelete)
-	}, reqSignIn, func(c *context.Context) {
-		c.Data["PageIsUserSettings"] = true
-	})
-
-	m.Group("/user", func() {
-		m.Any("/activate", user.Activate)
-		m.Any("/activate_email", user.ActivateEmail)
-		m.Get("/email2user", user.Email2User)
-		m.Get("/forget_password", user.ForgotPasswd)
-		m.Post("/forget_password", user.ForgotPasswdPost)
-		m.Post("/logout", user.SignOut)
-	})
-	// ***** END: User *****
-
-	reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
-
-	// ***** START: Admin *****
-	m.Group("/admin", func() {
-		m.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin"
-		m.Get("/config", admin.Config)
-		m.Post("/config/test_mail", admin.SendTestMail)
-		m.Get("/monitor", admin.Monitor)
-
-		m.Group("/users", func() {
-			m.Get("", admin.Users)
-			m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(form.AdminCrateUser{}), admin.NewUserPost)
-			m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(form.AdminEditUser{}), admin.EditUserPost)
-			m.Post("/:userid/delete", admin.DeleteUser)
-		})
-
-		m.Group("/orgs", func() {
-			m.Get("", admin.Organizations)
-		})
-
-		m.Group("/repos", func() {
-			m.Get("", admin.Repos)
-			m.Post("/delete", admin.DeleteRepo)
-		})
+	m.Group("", func() {
+		m.Get("/", ignSignIn, route.Home)
+		m.Group("/explore", func() {
+			m.Get("", func(c *context.Context) {
+				c.Redirect(conf.Server.Subpath + "/explore/repos")
+			})
+			m.Get("/repos", route.ExploreRepos)
+			m.Get("/users", route.ExploreUsers)
+			m.Get("/organizations", route.ExploreOrganizations)
+		}, ignSignIn)
+		m.Combo("/install", route.InstallInit).Get(route.Install).
+			Post(bindIgnErr(form.Install{}), route.InstallPost)
+		m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
+
+		// ***** START: User *****
+		m.Group("/user", func() {
+			m.Group("/login", func() {
+				m.Combo("").Get(user.Login).
+					Post(bindIgnErr(form.SignIn{}), user.LoginPost)
+				m.Combo("/two_factor").Get(user.LoginTwoFactor).Post(user.LoginTwoFactorPost)
+				m.Combo("/two_factor_recovery_code").Get(user.LoginTwoFactorRecoveryCode).Post(user.LoginTwoFactorRecoveryCodePost)
+			})
 
-		m.Group("/auths", func() {
-			m.Get("", admin.Authentications)
-			m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(form.Authentication{}), admin.NewAuthSourcePost)
-			m.Combo("/:authid").Get(admin.EditAuthSource).
-				Post(bindIgnErr(form.Authentication{}), admin.EditAuthSourcePost)
-			m.Post("/:authid/delete", admin.DeleteAuthSource)
+			m.Get("/sign_up", user.SignUp)
+			m.Post("/sign_up", bindIgnErr(form.Register{}), user.SignUpPost)
+			m.Get("/reset_password", user.ResetPasswd)
+			m.Post("/reset_password", user.ResetPasswdPost)
+		}, reqSignOut)
+
+		m.Group("/user/settings", func() {
+			m.Get("", user.Settings)
+			m.Post("", bindIgnErr(form.UpdateProfile{}), user.SettingsPost)
+			m.Combo("/avatar").Get(user.SettingsAvatar).
+				Post(binding.MultipartForm(form.Avatar{}), user.SettingsAvatarPost)
+			m.Post("/avatar/delete", user.SettingsDeleteAvatar)
+			m.Combo("/email").Get(user.SettingsEmails).
+				Post(bindIgnErr(form.AddEmail{}), user.SettingsEmailPost)
+			m.Post("/email/delete", user.DeleteEmail)
+			m.Get("/password", user.SettingsPassword)
+			m.Post("/password", bindIgnErr(form.ChangePassword{}), user.SettingsPasswordPost)
+			m.Combo("/ssh").Get(user.SettingsSSHKeys).
+				Post(bindIgnErr(form.AddSSHKey{}), user.SettingsSSHKeysPost)
+			m.Post("/ssh/delete", user.DeleteSSHKey)
+			m.Group("/security", func() {
+				m.Get("", user.SettingsSecurity)
+				m.Combo("/two_factor_enable").Get(user.SettingsTwoFactorEnable).
+					Post(user.SettingsTwoFactorEnablePost)
+				m.Combo("/two_factor_recovery_codes").Get(user.SettingsTwoFactorRecoveryCodes).
+					Post(user.SettingsTwoFactorRecoveryCodesPost)
+				m.Post("/two_factor_disable", user.SettingsTwoFactorDisable)
+			})
+			m.Group("/repositories", func() {
+				m.Get("", user.SettingsRepos)
+				m.Post("/leave", user.SettingsLeaveRepo)
+			})
+			m.Group("/organizations", func() {
+				m.Get("", user.SettingsOrganizations)
+				m.Post("/leave", user.SettingsLeaveOrganization)
+			})
+			m.Combo("/applications").Get(user.SettingsApplications).
+				Post(bindIgnErr(form.NewAccessToken{}), user.SettingsApplicationsPost)
+			m.Post("/applications/delete", user.SettingsDeleteApplication)
+			m.Route("/delete", "GET,POST", user.SettingsDelete)
+		}, reqSignIn, func(c *context.Context) {
+			c.Data["PageIsUserSettings"] = true
 		})
 
-		m.Group("/notices", func() {
-			m.Get("", admin.Notices)
-			m.Post("/delete", admin.DeleteNotices)
-			m.Get("/empty", admin.EmptyNotices)
+		m.Group("/user", func() {
+			m.Any("/activate", user.Activate)
+			m.Any("/activate_email", user.ActivateEmail)
+			m.Get("/email2user", user.Email2User)
+			m.Get("/forget_password", user.ForgotPasswd)
+			m.Post("/forget_password", user.ForgotPasswdPost)
+			m.Post("/logout", user.SignOut)
 		})
-	}, reqAdmin)
-	// ***** END: Admin *****
+		// ***** END: User *****
+
+		reqAdmin := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
+
+		// ***** START: Admin *****
+		m.Group("/admin", func() {
+			m.Combo("").Get(admin.Dashboard).Post(admin.Operation) // "/admin"
+			m.Get("/config", admin.Config)
+			m.Post("/config/test_mail", admin.SendTestMail)
+			m.Get("/monitor", admin.Monitor)
+
+			m.Group("/users", func() {
+				m.Get("", admin.Users)
+				m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(form.AdminCrateUser{}), admin.NewUserPost)
+				m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(form.AdminEditUser{}), admin.EditUserPost)
+				m.Post("/:userid/delete", admin.DeleteUser)
+			})
 
-	m.Group("", func() {
-		m.Group("/:username", func() {
-			m.Get("", user.Profile)
-			m.Get("/followers", user.Followers)
-			m.Get("/following", user.Following)
-			m.Get("/stars", user.Stars)
-		}, context.InjectParamsUser())
-
-		m.Get("/attachments/:uuid", func(c *context.Context) {
-			attach, err := db.GetAttachmentByUUID(c.Params(":uuid"))
-			if err != nil {
-				c.NotFoundOrError(err, "get attachment by UUID")
-				return
-			} else if !com.IsFile(attach.LocalPath()) {
-				c.NotFound()
-				return
-			}
+			m.Group("/orgs", func() {
+				m.Get("", admin.Organizations)
+			})
 
-			fr, err := os.Open(attach.LocalPath())
-			if err != nil {
-				c.Error(err, "open attachment file")
-				return
-			}
-			defer fr.Close()
+			m.Group("/repos", func() {
+				m.Get("", admin.Repos)
+				m.Post("/delete", admin.DeleteRepo)
+			})
 
-			c.Header().Set("Cache-Control", "public,max-age=86400")
-			c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
+			m.Group("/auths", func() {
+				m.Get("", admin.Authentications)
+				m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(form.Authentication{}), admin.NewAuthSourcePost)
+				m.Combo("/:authid").Get(admin.EditAuthSource).
+					Post(bindIgnErr(form.Authentication{}), admin.EditAuthSourcePost)
+				m.Post("/:authid/delete", admin.DeleteAuthSource)
+			})
 
-			if _, err = io.Copy(c.Resp, fr); err != nil {
-				c.Error(err, "copy from file to response")
-				return
-			}
-		})
-		m.Post("/issues/attachments", repo.UploadIssueAttachment)
-		m.Post("/releases/attachments", repo.UploadReleaseAttachment)
-	}, ignSignIn)
+			m.Group("/notices", func() {
+				m.Get("", admin.Notices)
+				m.Post("/delete", admin.DeleteNotices)
+				m.Get("/empty", admin.EmptyNotices)
+			})
+		}, reqAdmin)
+		// ***** END: Admin *****
 
-	m.Group("/:username", func() {
-		m.Post("/action/:action", user.Action)
-	}, reqSignIn, context.InjectParamsUser())
+		m.Group("", func() {
+			m.Group("/:username", func() {
+				m.Get("", user.Profile)
+				m.Get("/followers", user.Followers)
+				m.Get("/following", user.Following)
+				m.Get("/stars", user.Stars)
+			}, context.InjectParamsUser())
+
+			m.Get("/attachments/:uuid", func(c *context.Context) {
+				attach, err := db.GetAttachmentByUUID(c.Params(":uuid"))
+				if err != nil {
+					c.NotFoundOrError(err, "get attachment by UUID")
+					return
+				} else if !com.IsFile(attach.LocalPath()) {
+					c.NotFound()
+					return
+				}
 
-	if macaron.Env == macaron.DEV {
-		m.Get("/template/*", dev.TemplatePreview)
-	}
+				fr, err := os.Open(attach.LocalPath())
+				if err != nil {
+					c.Error(err, "open attachment file")
+					return
+				}
+				defer fr.Close()
 
-	reqRepoAdmin := context.RequireRepoAdmin()
-	reqRepoWriter := context.RequireRepoWriter()
+				c.Header().Set("Cache-Control", "public,max-age=86400")
+				c.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
 
-	webhookRoutes := func() {
-		m.Group("", func() {
-			m.Get("", repo.Webhooks)
-			m.Post("/delete", repo.DeleteWebhook)
-			m.Get("/:type/new", repo.WebhooksNew)
-			m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost)
-			m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost)
-			m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost)
-			m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost)
-			m.Get("/:id", repo.WebhooksEdit)
-			m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost)
-			m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost)
-			m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost)
-			m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost)
-		}, repo.InjectOrgRepoContext())
-	}
+				if _, err = io.Copy(c.Resp, fr); err != nil {
+					c.Error(err, "copy from file to response")
+					return
+				}
+			})
+			m.Post("/issues/attachments", repo.UploadIssueAttachment)
+			m.Post("/releases/attachments", repo.UploadReleaseAttachment)
+		}, ignSignIn)
 
-	// ***** START: Organization *****
-	m.Group("/org", func() {
-		m.Group("", func() {
-			m.Get("/create", org.Create)
-			m.Post("/create", bindIgnErr(form.CreateOrg{}), org.CreatePost)
-		}, func(c *context.Context) {
-			if !c.User.CanCreateOrganization() {
-				c.NotFound()
-			}
-		})
+		m.Group("/:username", func() {
+			m.Post("/action/:action", user.Action)
+		}, reqSignIn, context.InjectParamsUser())
 
-		m.Group("/:org", func() {
-			m.Get("/dashboard", user.Dashboard)
-			m.Get("/^:type(issues|pulls)$", user.Issues)
-			m.Get("/members", org.Members)
-			m.Get("/members/action/:action", org.MembersAction)
-
-			m.Get("/teams", org.Teams)
-		}, context.OrgAssignment(true))
-
-		m.Group("/:org", func() {
-			m.Get("/teams/:team", org.TeamMembers)
-			m.Get("/teams/:team/repositories", org.TeamRepositories)
-			m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
-			m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
-		}, context.OrgAssignment(true, false, true))
-
-		m.Group("/:org", func() {
-			m.Get("/teams/new", org.NewTeam)
-			m.Post("/teams/new", bindIgnErr(form.CreateTeam{}), org.NewTeamPost)
-			m.Get("/teams/:team/edit", org.EditTeam)
-			m.Post("/teams/:team/edit", bindIgnErr(form.CreateTeam{}), org.EditTeamPost)
-			m.Post("/teams/:team/delete", org.DeleteTeam)
+		if macaron.Env == macaron.DEV {
+			m.Get("/template/*", dev.TemplatePreview)
+		}
 
-			m.Group("/settings", func() {
-				m.Combo("").Get(org.Settings).
-					Post(bindIgnErr(form.UpdateOrgSetting{}), org.SettingsPost)
-				m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar)
-				m.Post("/avatar/delete", org.SettingsDeleteAvatar)
-				m.Group("/hooks", webhookRoutes)
-				m.Route("/delete", "GET,POST", org.SettingsDelete)
-			})
+		reqRepoAdmin := context.RequireRepoAdmin()
+		reqRepoWriter := context.RequireRepoWriter()
 
-			m.Route("/invitations/new", "GET,POST", org.Invitation)
-		}, context.OrgAssignment(true, true))
-	}, reqSignIn)
-	// ***** END: Organization *****
-
-	// ***** START: Repository *****
-	m.Group("/repo", func() {
-		m.Get("/create", repo.Create)
-		m.Post("/create", bindIgnErr(form.CreateRepo{}), repo.CreatePost)
-		m.Get("/migrate", repo.Migrate)
-		m.Post("/migrate", bindIgnErr(form.MigrateRepo{}), repo.MigratePost)
-		m.Combo("/fork/:repoid").Get(repo.Fork).
-			Post(bindIgnErr(form.CreateRepo{}), repo.ForkPost)
-	}, reqSignIn)
+		webhookRoutes := func() {
+			m.Group("", func() {
+				m.Get("", repo.Webhooks)
+				m.Post("/delete", repo.DeleteWebhook)
+				m.Get("/:type/new", repo.WebhooksNew)
+				m.Post("/gogs/new", bindIgnErr(form.NewWebhook{}), repo.WebhooksNewPost)
+				m.Post("/slack/new", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackNewPost)
+				m.Post("/discord/new", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordNewPost)
+				m.Post("/dingtalk/new", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkNewPost)
+				m.Get("/:id", repo.WebhooksEdit)
+				m.Post("/gogs/:id", bindIgnErr(form.NewWebhook{}), repo.WebhooksEditPost)
+				m.Post("/slack/:id", bindIgnErr(form.NewSlackHook{}), repo.WebhooksSlackEditPost)
+				m.Post("/discord/:id", bindIgnErr(form.NewDiscordHook{}), repo.WebhooksDiscordEditPost)
+				m.Post("/dingtalk/:id", bindIgnErr(form.NewDingtalkHook{}), repo.WebhooksDingtalkEditPost)
+			}, repo.InjectOrgRepoContext())
+		}
 
-	m.Group("/:username/:reponame", func() {
-		m.Group("/settings", func() {
-			m.Combo("").Get(repo.Settings).
-				Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost)
-			m.Combo("/avatar").Get(repo.SettingsAvatar).
-				Post(binding.MultipartForm(form.Avatar{}), repo.SettingsAvatarPost)
-			m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
-			m.Group("/collaboration", func() {
-				m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
-				m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
-				m.Post("/delete", repo.DeleteCollaboration)
-			})
-			m.Group("/branches", func() {
-				m.Get("", repo.SettingsBranches)
-				m.Post("/default_branch", repo.UpdateDefaultBranch)
-				m.Combo("/*").Get(repo.SettingsProtectedBranch).
-					Post(bindIgnErr(form.ProtectBranch{}), repo.SettingsProtectedBranchPost)
+		// ***** START: Organization *****
+		m.Group("/org", func() {
+			m.Group("", func() {
+				m.Get("/create", org.Create)
+				m.Post("/create", bindIgnErr(form.CreateOrg{}), org.CreatePost)
 			}, func(c *context.Context) {
-				if c.Repo.Repository.IsMirror {
+				if !c.User.CanCreateOrganization() {
 					c.NotFound()
-					return
 				}
 			})
 
-			m.Group("/hooks", func() {
-				webhookRoutes()
-
-				m.Group("/:id", func() {
-					m.Post("/test", repo.TestWebhook)
-					m.Post("/redelivery", repo.RedeliveryWebhook)
+			m.Group("/:org", func() {
+				m.Get("/dashboard", user.Dashboard)
+				m.Get("/^:type(issues|pulls)$", user.Issues)
+				m.Get("/members", org.Members)
+				m.Get("/members/action/:action", org.MembersAction)
+
+				m.Get("/teams", org.Teams)
+			}, context.OrgAssignment(true))
+
+			m.Group("/:org", func() {
+				m.Get("/teams/:team", org.TeamMembers)
+				m.Get("/teams/:team/repositories", org.TeamRepositories)
+				m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
+				m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
+			}, context.OrgAssignment(true, false, true))
+
+			m.Group("/:org", func() {
+				m.Get("/teams/new", org.NewTeam)
+				m.Post("/teams/new", bindIgnErr(form.CreateTeam{}), org.NewTeamPost)
+				m.Get("/teams/:team/edit", org.EditTeam)
+				m.Post("/teams/:team/edit", bindIgnErr(form.CreateTeam{}), org.EditTeamPost)
+				m.Post("/teams/:team/delete", org.DeleteTeam)
+
+				m.Group("/settings", func() {
+					m.Combo("").Get(org.Settings).
+						Post(bindIgnErr(form.UpdateOrgSetting{}), org.SettingsPost)
+					m.Post("/avatar", binding.MultipartForm(form.Avatar{}), org.SettingsAvatar)
+					m.Post("/avatar/delete", org.SettingsDeleteAvatar)
+					m.Group("/hooks", webhookRoutes)
+					m.Route("/delete", "GET,POST", org.SettingsDelete)
 				})
 
-				m.Group("/git", func() {
-					m.Get("", repo.SettingsGitHooks)
-					m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
-						Post(repo.SettingsGitHooksEditPost)
-				}, context.GitHookService())
-			})
+				m.Route("/invitations/new", "GET,POST", org.Invitation)
+			}, context.OrgAssignment(true, true))
+		}, reqSignIn)
+		// ***** END: Organization *****
+
+		// ***** START: Repository *****
+		m.Group("/repo", func() {
+			m.Get("/create", repo.Create)
+			m.Post("/create", bindIgnErr(form.CreateRepo{}), repo.CreatePost)
+			m.Get("/migrate", repo.Migrate)
+			m.Post("/migrate", bindIgnErr(form.MigrateRepo{}), repo.MigratePost)
+			m.Combo("/fork/:repoid").Get(repo.Fork).
+				Post(bindIgnErr(form.CreateRepo{}), repo.ForkPost)
+		}, reqSignIn)
+
+		m.Group("/:username/:reponame", func() {
+			m.Group("/settings", func() {
+				m.Combo("").Get(repo.Settings).
+					Post(bindIgnErr(form.RepoSetting{}), repo.SettingsPost)
+				m.Combo("/avatar").Get(repo.SettingsAvatar).
+					Post(binding.MultipartForm(form.Avatar{}), repo.SettingsAvatarPost)
+				m.Post("/avatar/delete", repo.SettingsDeleteAvatar)
+				m.Group("/collaboration", func() {
+					m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
+					m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
+					m.Post("/delete", repo.DeleteCollaboration)
+				})
+				m.Group("/branches", func() {
+					m.Get("", repo.SettingsBranches)
+					m.Post("/default_branch", repo.UpdateDefaultBranch)
+					m.Combo("/*").Get(repo.SettingsProtectedBranch).
+						Post(bindIgnErr(form.ProtectBranch{}), repo.SettingsProtectedBranchPost)
+				}, func(c *context.Context) {
+					if c.Repo.Repository.IsMirror {
+						c.NotFound()
+						return
+					}
+				})
 
-			m.Group("/keys", func() {
-				m.Combo("").Get(repo.SettingsDeployKeys).
-					Post(bindIgnErr(form.AddSSHKey{}), repo.SettingsDeployKeysPost)
-				m.Post("/delete", repo.DeleteDeployKey)
-			})
+				m.Group("/hooks", func() {
+					webhookRoutes()
 
-		}, func(c *context.Context) {
-			c.Data["PageIsSettings"] = true
-		})
-	}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
+					m.Group("/:id", func() {
+						m.Post("/test", repo.TestWebhook)
+						m.Post("/redelivery", repo.RedeliveryWebhook)
+					})
 
-	m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
-	m.Group("/:username/:reponame", func() {
-		m.Get("/issues", repo.RetrieveLabels, repo.Issues)
-		m.Get("/issues/:index", repo.ViewIssue)
-		m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
-		m.Get("/milestones", repo.Milestones)
-	}, ignSignIn, context.RepoAssignment(true))
-	m.Group("/:username/:reponame", func() {
-		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
-		// So they can apply their own enable/disable logic on routers.
-		m.Group("/issues", func() {
-			m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
-				Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
-
-			m.Group("/:index", func() {
-				m.Post("/title", repo.UpdateIssueTitle)
-				m.Post("/content", repo.UpdateIssueContent)
-				m.Combo("/comments").Post(bindIgnErr(form.CreateComment{}), repo.NewComment)
-			})
-		})
-		m.Group("/comments/:id", func() {
-			m.Post("", repo.UpdateCommentContent)
-			m.Post("/delete", repo.DeleteComment)
-		})
-	}, reqSignIn, context.RepoAssignment(true))
-	m.Group("/:username/:reponame", func() {
-		m.Group("/wiki", func() {
-			m.Get("/?:page", repo.Wiki)
-			m.Get("/_pages", repo.WikiPages)
-		}, repo.MustEnableWiki, context.RepoRef())
-	}, ignSignIn, context.RepoAssignment(false, true))
+					m.Group("/git", func() {
+						m.Get("", repo.SettingsGitHooks)
+						m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
+							Post(repo.SettingsGitHooksEditPost)
+					}, context.GitHookService())
+				})
 
-	m.Group("/:username/:reponame", func() {
-		// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
-		// So they can apply their own enable/disable logic on routers.
-		m.Group("/issues", func() {
-			m.Group("/:index", func() {
-				m.Post("/label", repo.UpdateIssueLabel)
-				m.Post("/milestone", repo.UpdateIssueMilestone)
-				m.Post("/assignee", repo.UpdateIssueAssignee)
-			}, reqRepoWriter)
-		})
-		m.Group("/labels", func() {
-			m.Post("/new", bindIgnErr(form.CreateLabel{}), repo.NewLabel)
-			m.Post("/edit", bindIgnErr(form.CreateLabel{}), repo.UpdateLabel)
-			m.Post("/delete", repo.DeleteLabel)
-			m.Post("/initialize", bindIgnErr(form.InitializeLabels{}), repo.InitializeLabels)
-		}, reqRepoWriter, context.RepoRef())
-		m.Group("/milestones", func() {
-			m.Combo("/new").Get(repo.NewMilestone).
-				Post(bindIgnErr(form.CreateMilestone{}), repo.NewMilestonePost)
-			m.Get("/:id/edit", repo.EditMilestone)
-			m.Post("/:id/edit", bindIgnErr(form.CreateMilestone{}), repo.EditMilestonePost)
-			m.Get("/:id/:action", repo.ChangeMilestonStatus)
-			m.Post("/delete", repo.DeleteMilestone)
-		}, reqRepoWriter, context.RepoRef())
-
-		m.Group("/releases", func() {
-			m.Get("/new", repo.NewRelease)
-			m.Post("/new", bindIgnErr(form.NewRelease{}), repo.NewReleasePost)
-			m.Post("/delete", repo.DeleteRelease)
-			m.Get("/edit/*", repo.EditRelease)
-			m.Post("/edit/*", bindIgnErr(form.EditRelease{}), repo.EditReleasePost)
-		}, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) {
-			c.Data["PageIsViewFiles"] = true
-		})
+				m.Group("/keys", func() {
+					m.Combo("").Get(repo.SettingsDeployKeys).
+						Post(bindIgnErr(form.AddSSHKey{}), repo.SettingsDeployKeysPost)
+					m.Post("/delete", repo.DeleteDeployKey)
+				})
 
-		// FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL
-		// for PR in same repository. After select branch on the page, the URL contains redundant head user name.
-		// e.g. /org1/test-repo/compare/master...org1:develop
-		// which should be /org1/test-repo/compare/master...develop
-		m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
-			Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost)
+			}, func(c *context.Context) {
+				c.Data["PageIsSettings"] = true
+			})
+		}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
+
+		m.Post("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
+		m.Group("/:username/:reponame", func() {
+			m.Get("/issues", repo.RetrieveLabels, repo.Issues)
+			m.Get("/issues/:index", repo.ViewIssue)
+			m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
+			m.Get("/milestones", repo.Milestones)
+		}, ignSignIn, context.RepoAssignment(true))
+		m.Group("/:username/:reponame", func() {
+			// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
+			// So they can apply their own enable/disable logic on routers.
+			m.Group("/issues", func() {
+				m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
+					Post(bindIgnErr(form.NewIssue{}), repo.NewIssuePost)
+
+				m.Group("/:index", func() {
+					m.Post("/title", repo.UpdateIssueTitle)
+					m.Post("/content", repo.UpdateIssueContent)
+					m.Combo("/comments").Post(bindIgnErr(form.CreateComment{}), repo.NewComment)
+				})
+			})
+			m.Group("/comments/:id", func() {
+				m.Post("", repo.UpdateCommentContent)
+				m.Post("/delete", repo.DeleteComment)
+			})
+		}, reqSignIn, context.RepoAssignment(true))
+		m.Group("/:username/:reponame", func() {
+			m.Group("/wiki", func() {
+				m.Get("/?:page", repo.Wiki)
+				m.Get("/_pages", repo.WikiPages)
+			}, repo.MustEnableWiki, context.RepoRef())
+		}, ignSignIn, context.RepoAssignment(false, true))
+
+		m.Group("/:username/:reponame", func() {
+			// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
+			// So they can apply their own enable/disable logic on routers.
+			m.Group("/issues", func() {
+				m.Group("/:index", func() {
+					m.Post("/label", repo.UpdateIssueLabel)
+					m.Post("/milestone", repo.UpdateIssueMilestone)
+					m.Post("/assignee", repo.UpdateIssueAssignee)
+				}, reqRepoWriter)
+			})
+			m.Group("/labels", func() {
+				m.Post("/new", bindIgnErr(form.CreateLabel{}), repo.NewLabel)
+				m.Post("/edit", bindIgnErr(form.CreateLabel{}), repo.UpdateLabel)
+				m.Post("/delete", repo.DeleteLabel)
+				m.Post("/initialize", bindIgnErr(form.InitializeLabels{}), repo.InitializeLabels)
+			}, reqRepoWriter, context.RepoRef())
+			m.Group("/milestones", func() {
+				m.Combo("/new").Get(repo.NewMilestone).
+					Post(bindIgnErr(form.CreateMilestone{}), repo.NewMilestonePost)
+				m.Get("/:id/edit", repo.EditMilestone)
+				m.Post("/:id/edit", bindIgnErr(form.CreateMilestone{}), repo.EditMilestonePost)
+				m.Get("/:id/:action", repo.ChangeMilestonStatus)
+				m.Post("/delete", repo.DeleteMilestone)
+			}, reqRepoWriter, context.RepoRef())
+
+			m.Group("/releases", func() {
+				m.Get("/new", repo.NewRelease)
+				m.Post("/new", bindIgnErr(form.NewRelease{}), repo.NewReleasePost)
+				m.Post("/delete", repo.DeleteRelease)
+				m.Get("/edit/*", repo.EditRelease)
+				m.Post("/edit/*", bindIgnErr(form.EditRelease{}), repo.EditReleasePost)
+			}, repo.MustBeNotBare, reqRepoWriter, func(c *context.Context) {
+				c.Data["PageIsViewFiles"] = true
+			})
 
-		m.Group("", func() {
-			m.Combo("/_edit/*").Get(repo.EditFile).
-				Post(bindIgnErr(form.EditRepoFile{}), repo.EditFilePost)
-			m.Combo("/_new/*").Get(repo.NewFile).
-				Post(bindIgnErr(form.EditRepoFile{}), repo.NewFilePost)
-			m.Post("/_preview/*", bindIgnErr(form.EditPreviewDiff{}), repo.DiffPreviewPost)
-			m.Combo("/_delete/*").Get(repo.DeleteFile).
-				Post(bindIgnErr(form.DeleteRepoFile{}), repo.DeleteFilePost)
+			// FIXME: Should use c.Repo.PullRequest to unify template, currently we have inconsistent URL
+			// for PR in same repository. After select branch on the page, the URL contains redundant head user name.
+			// e.g. /org1/test-repo/compare/master...org1:develop
+			// which should be /org1/test-repo/compare/master...develop
+			m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
+				Post(bindIgnErr(form.NewIssue{}), repo.CompareAndPullRequestPost)
 
 			m.Group("", func() {
-				m.Combo("/_upload/*").Get(repo.UploadFile).
-					Post(bindIgnErr(form.UploadRepoFile{}), repo.UploadFilePost)
-				m.Post("/upload-file", repo.UploadFileToServer)
-				m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer)
-			}, func(c *context.Context) {
-				if !conf.Repository.Upload.Enabled {
+				m.Combo("/_edit/*").Get(repo.EditFile).
+					Post(bindIgnErr(form.EditRepoFile{}), repo.EditFilePost)
+				m.Combo("/_new/*").Get(repo.NewFile).
+					Post(bindIgnErr(form.EditRepoFile{}), repo.NewFilePost)
+				m.Post("/_preview/*", bindIgnErr(form.EditPreviewDiff{}), repo.DiffPreviewPost)
+				m.Combo("/_delete/*").Get(repo.DeleteFile).
+					Post(bindIgnErr(form.DeleteRepoFile{}), repo.DeleteFilePost)
+
+				m.Group("", func() {
+					m.Combo("/_upload/*").Get(repo.UploadFile).
+						Post(bindIgnErr(form.UploadRepoFile{}), repo.UploadFilePost)
+					m.Post("/upload-file", repo.UploadFileToServer)
+					m.Post("/upload-remove", bindIgnErr(form.RemoveUploadFile{}), repo.RemoveUploadFileFromServer)
+				}, func(c *context.Context) {
+					if !conf.Repository.Upload.Enabled {
+						c.NotFound()
+						return
+					}
+				})
+			}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) {
+				if !c.Repo.CanEnableEditor() {
 					c.NotFound()
 					return
 				}
+
+				c.Data["PageIsViewFiles"] = true
 			})
-		}, repo.MustBeNotBare, reqRepoWriter, context.RepoRef(), func(c *context.Context) {
-			if !c.Repo.CanEnableEditor() {
-				c.NotFound()
-				return
-			}
+		}, reqSignIn, context.RepoAssignment())
 
-			c.Data["PageIsViewFiles"] = true
-		})
-	}, reqSignIn, context.RepoAssignment())
+		m.Group("/:username/:reponame", func() {
+			m.Group("", func() {
+				m.Get("/releases", repo.MustBeNotBare, repo.Releases)
+				m.Get("/pulls", repo.RetrieveLabels, repo.Pulls)
+				m.Get("/pulls/:index", repo.ViewPull)
+			}, context.RepoRef())
 
-	m.Group("/:username/:reponame", func() {
-		m.Group("", func() {
-			m.Get("/releases", repo.MustBeNotBare, repo.Releases)
-			m.Get("/pulls", repo.RetrieveLabels, repo.Pulls)
-			m.Get("/pulls/:index", repo.ViewPull)
-		}, context.RepoRef())
-
-		m.Group("/branches", func() {
-			m.Get("", repo.Branches)
-			m.Get("/all", repo.AllBranches)
-			m.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
-		}, repo.MustBeNotBare, func(c *context.Context) {
-			c.Data["PageIsViewFiles"] = true
-		})
+			m.Group("/branches", func() {
+				m.Get("", repo.Branches)
+				m.Get("/all", repo.AllBranches)
+				m.Post("/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
+			}, repo.MustBeNotBare, func(c *context.Context) {
+				c.Data["PageIsViewFiles"] = true
+			})
 
-		m.Group("/wiki", func() {
-			m.Group("", func() {
-				m.Combo("/_new").Get(repo.NewWiki).
-					Post(bindIgnErr(form.NewWiki{}), repo.NewWikiPost)
-				m.Combo("/:page/_edit").Get(repo.EditWiki).
-					Post(bindIgnErr(form.NewWiki{}), repo.EditWikiPost)
-				m.Post("/:page/delete", repo.DeleteWikiPagePost)
-			}, reqSignIn, reqRepoWriter)
-		}, repo.MustEnableWiki, context.RepoRef())
-
-		m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
-
-		m.Group("/pulls/:index", func() {
-			m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
-			m.Get("/files", context.RepoRef(), repo.ViewPullFiles)
-			m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
-		}, repo.MustAllowPulls)
+			m.Group("/wiki", func() {
+				m.Group("", func() {
+					m.Combo("/_new").Get(repo.NewWiki).
+						Post(bindIgnErr(form.NewWiki{}), repo.NewWikiPost)
+					m.Combo("/:page/_edit").Get(repo.EditWiki).
+						Post(bindIgnErr(form.NewWiki{}), repo.EditWikiPost)
+					m.Post("/:page/delete", repo.DeleteWikiPagePost)
+				}, reqSignIn, reqRepoWriter)
+			}, repo.MustEnableWiki, context.RepoRef())
 
-		m.Group("", func() {
-			m.Get("/src/*", repo.Home)
-			m.Get("/raw/*", repo.SingleDownload)
-			m.Get("/commits/*", repo.RefCommits)
-			m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff)
-			m.Get("/forks", repo.Forks)
-		}, repo.MustBeNotBare, context.RepoRef())
-		m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
-
-		m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff)
-	}, ignSignIn, context.RepoAssignment())
-	m.Group("/:username/:reponame", func() {
-		m.Get("/stars", repo.Stars)
-		m.Get("/watchers", repo.Watchers)
-	}, ignSignIn, context.RepoAssignment(), context.RepoRef())
+			m.Get("/archive/*", repo.MustBeNotBare, repo.Download)
 
-	m.Group("/:username", func() {
-		m.Get("/:reponame", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home)
+			m.Group("/pulls/:index", func() {
+				m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
+				m.Get("/files", context.RepoRef(), repo.ViewPullFiles)
+				m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
+			}, repo.MustAllowPulls)
 
-		m.Group("/:reponame", func() {
-			m.Head("/tasks/trigger", repo.TriggerTask)
-		})
-		// Use the regexp to match the repository name
-		// Duplicated route to enable different ways of accessing same set of URLs,
-		// e.g. with or without ".git" suffix.
-		m.Group("/:reponame([\\d\\w-_\\.]+\\.git$)", func() {
-			m.Get("", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home)
+			m.Group("", func() {
+				m.Get("/src/*", repo.Home)
+				m.Get("/raw/*", repo.SingleDownload)
+				m.Get("/commits/*", repo.RefCommits)
+				m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff)
+				m.Get("/forks", repo.Forks)
+			}, repo.MustBeNotBare, context.RepoRef())
+			m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.MustBeNotBare, repo.RawDiff)
+
+			m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff)
+		}, ignSignIn, context.RepoAssignment())
+		m.Group("/:username/:reponame", func() {
+			m.Get("", repo.Home)
+			m.Get("/stars", repo.Stars)
+			m.Get("/watchers", repo.Watchers)
+			m.Head("/tasks/trigger", repo.TriggerTask) // TODO: Without session and CSRF
+		}, ignSignIn, context.RepoAssignment(), context.RepoRef())
+		// ***** END: Repository *****
+
+		// **********************
+		// ----- API routes -----
+		// **********************
+
+		// TODO: Without session and CSRF
+		m.Group("/api", func() {
+			apiv1.RegisterRoutes(m)
+		}, ignSignIn)
+	},
+		session.Sessioner(session.Options{
+			Provider:       conf.Session.Provider,
+			ProviderConfig: conf.Session.ProviderConfig,
+			CookieName:     conf.Session.CookieName,
+			CookiePath:     conf.Server.Subpath,
+			Gclifetime:     conf.Session.GCInterval,
+			Maxlifetime:    conf.Session.MaxLifeTime,
+			Secure:         conf.Session.CookieSecure,
+		}),
+		csrf.Csrfer(csrf.Options{
+			Secret:         conf.Security.SecretKey,
+			Header:         "X-CSRF-Token",
+			Cookie:         conf.Session.CSRFCookieName,
+			CookieDomain:   conf.Server.URL.Hostname(),
+			CookiePath:     conf.Server.Subpath,
+			CookieHttpOnly: true,
+			SetCookie:      true,
+			Secure:         conf.Server.URL.Scheme == "https",
+		}),
+		context.Contexter(),
+	)
 
-			m.Group("/info/lfs", func() {
-				lfs.RegisterRoutes(m.Router)
-			}, ignSignInAndCsrf)
+	// ***************************
+	// ----- HTTP Git routes -----
+	// ***************************
 
-			m.Route("/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
+	m.Group("/:username/:reponame", func() {
+		m.Group("/info/lfs", func() {
+			lfs.RegisterRoutes(m.Router)
 		})
-		m.Route("/:reponame/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
-	})
-	// ***** END: Repository *****
 
-	m.Group("/api", func() {
-		apiv1.RegisterRoutes(m)
-	}, ignSignIn)
+		m.Route("/*", "GET,POST,OPTIONS", repo.HTTPContexter(), repo.HTTP)
+	})
 
 	// ***************************
 	// ----- Internal routes -----
 	// ***************************
+
 	m.Group("/-", func() {
 		m.Get("/metrics", app.MetricsFilter(), promhttp.Handler()) // "/-/metrics"
 
@@ -675,16 +673,18 @@ func runWeb(c *cli.Context) error {
 		})
 	})
 
-	// robots.txt
-	m.Get("/robots.txt", func(c *context.Context) {
+	// **********************
+	// ----- robots.txt -----
+	// **********************
+
+	m.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
 		if conf.HasRobotsTxt {
-			c.ServeFileContent(filepath.Join(conf.CustomDir(), "robots.txt"))
+			http.ServeFile(w, r, filepath.Join(conf.CustomDir(), "robots.txt"))
 		} else {
-			c.NotFound()
+			w.WriteHeader(http.StatusNotFound)
 		}
 	})
 
-	// Not found handler.
 	m.NotFound(route.NotFound)
 
 	// Flag for port number in case first time run conflict.

+ 0 - 17
internal/context/auth.go

@@ -7,14 +7,12 @@ package context
 import (
 	"net/http"
 	"net/url"
-	"strings"
 
 	"github.com/go-macaron/csrf"
 	"gopkg.in/macaron.v1"
 
 	"gogs.io/gogs/internal/auth"
 	"gogs.io/gogs/internal/conf"
-	"gogs.io/gogs/internal/tool"
 )
 
 type ToggleOptions struct {
@@ -95,18 +93,3 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 		}
 	}
 }
-
-// RequireBasicAuth verifies HTTP Basic Authentication header with given credentials.
-func (c *Context) RequireBasicAuth(username, password string) {
-	fields := strings.Fields(c.Req.Header.Get("Authorization"))
-	if len(fields) != 2 || fields[0] != "Basic" {
-		c.Status(http.StatusUnauthorized)
-		return
-	}
-
-	uname, passwd, _ := tool.BasicAuthDecode(fields[1])
-	if uname != username || passwd != password {
-		c.Status(http.StatusForbidden)
-		return
-	}
-}

+ 1 - 1
internal/db/access.go

@@ -93,7 +93,7 @@ func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (b
 }
 
 // HasAccess returns true if someone has the request access level. User can be nil!
-// Deprecated: Use Perms.HasAccess instead.
+// Deprecated: Use Perms.Authorize instead.
 func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
 	return hasAccess(x, userID, repo, testMode)
 }

+ 8 - 3
internal/route/home.go

@@ -5,7 +5,12 @@
 package route
 
 import (
+	"fmt"
+	"net/http"
+
+	"github.com/go-macaron/i18n"
 	"github.com/unknwon/paginater"
+	"gopkg.in/macaron.v1"
 
 	"gogs.io/gogs/internal/conf"
 	"gogs.io/gogs/internal/context"
@@ -157,7 +162,7 @@ func ExploreOrganizations(c *context.Context) {
 	})
 }
 
-func NotFound(c *context.Context) {
-	c.Data["Title"] = "Page Not Found"
-	c.NotFound()
+func NotFound(c *macaron.Context, l i18n.Locale) {
+	c.Data["Title"] = l.Tr("status.page_not_found")
+	c.HTML(http.StatusNotFound, fmt.Sprintf("status/%d", http.StatusNotFound))
 }

+ 4 - 4
internal/route/lfs/basic.go

@@ -12,10 +12,10 @@ import (
 	"os"
 	"strconv"
 
+	"gopkg.in/macaron.v1"
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
-	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/lfsutil"
 	"gogs.io/gogs/internal/strutil"
@@ -28,7 +28,7 @@ const (
 )
 
 // GET /{owner}/{repo}.git/info/lfs/object/basic/{oid}
-func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
+func serveBasicDownload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) {
 	object, err := db.LFS.GetObjectByOID(repo.ID, oid)
 	if err != nil {
 		if db.IsErrLFSObjectNotExist(err) {
@@ -63,7 +63,7 @@ func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID
 }
 
 // PUT /{owner}/{repo}.git/info/lfs/object/basic/{oid}
-func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
+func serveBasicUpload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) {
 	// NOTE: LFS client will retry upload the same object if there was a partial failure,
 	// therefore we would like to skip ones that already exist.
 	_, err := db.LFS.GetObjectByOID(repo.ID, oid)
@@ -91,7 +91,7 @@ func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID)
 }
 
 // POST /{owner}/{repo}.git/info/lfs/object/basic/verify
-func serveBasicVerify(c *context.Context, repo *db.Repository) {
+func serveBasicVerify(c *macaron.Context, repo *db.Repository) {
 	var request basicVerifyRequest
 	defer c.Req.Request.Body.Close()
 	err := json.NewDecoder(c.Req.Request.Body).Decode(&request)

+ 2 - 2
internal/route/lfs/batch.go

@@ -9,17 +9,17 @@ import (
 	"net/http"
 
 	jsoniter "github.com/json-iterator/go"
+	"gopkg.in/macaron.v1"
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
-	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/lfsutil"
 	"gogs.io/gogs/internal/strutil"
 )
 
 // POST /{owner}/{repo}.git/info/lfs/object/batch
-func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) {
+func serveBatch(c *macaron.Context, owner *db.User, repo *db.Repository) {
 	var request batchRequest
 	defer c.Req.Request.Body.Close()
 	err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request)

+ 6 - 7
internal/route/lfs/route.go

@@ -13,7 +13,6 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/authutil"
-	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/lfsutil"
 )
@@ -44,7 +43,7 @@ func authenticate() macaron.Handler {
 		})
 	}
 
-	return func(c *context.Context) {
+	return func(c *macaron.Context) {
 		username, password := authutil.DecodeBasic(c.Req.Header)
 		if username == "" {
 			askCredentials(c.Resp)
@@ -59,7 +58,7 @@ func authenticate() macaron.Handler {
 		}
 
 		if err == nil && user.IsEnabledTwoFactor() {
-			c.PlainText(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`)
+			c.Error(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`)
 			return
 		}
 
@@ -98,7 +97,7 @@ func authenticate() macaron.Handler {
 
 // authorize tries to authorize the user to the context repository with given access mode.
 func authorize(mode db.AccessMode) macaron.Handler {
-	return func(c *context.Context, user *db.User) {
+	return func(c *macaron.Context, user *db.User) {
 		username := c.Params(":username")
 		reponame := strings.TrimSuffix(c.Params(":reponame"), ".git")
 
@@ -137,7 +136,7 @@ func authorize(mode db.AccessMode) macaron.Handler {
 // verifyHeader checks if the HTTP header contains given value.
 // When not, response given "failCode" as status code.
 func verifyHeader(key, value string, failCode int) macaron.Handler {
-	return func(c *context.Context) {
+	return func(c *macaron.Context) {
 		if !strings.Contains(c.Req.Header.Get(key), value) {
 			c.Status(failCode)
 			return
@@ -147,10 +146,10 @@ func verifyHeader(key, value string, failCode int) macaron.Handler {
 
 // verifyOID checks if the ":oid" URL parameter is valid.
 func verifyOID() macaron.Handler {
-	return func(c *context.Context) {
+	return func(c *macaron.Context) {
 		oid := lfsutil.OID(c.Params(":oid"))
 		if !lfsutil.ValidOID(oid) {
-			c.PlainText(http.StatusBadRequest, "Invalid oid")
+			c.Error(http.StatusBadRequest, "Invalid oid")
 			return
 		}
 

+ 32 - 24
internal/route/repo/http.go

@@ -21,14 +21,13 @@ import (
 	log "unknwon.dev/clog/v2"
 
 	"gogs.io/gogs/internal/conf"
-	"gogs.io/gogs/internal/context"
 	"gogs.io/gogs/internal/db"
 	"gogs.io/gogs/internal/lazyregexp"
 	"gogs.io/gogs/internal/tool"
 )
 
 type HTTPContext struct {
-	*context.Context
+	*macaron.Context
 	OwnerName string
 	OwnerSalt string
 	RepoID    int64
@@ -37,17 +36,17 @@ type HTTPContext struct {
 }
 
 // askCredentials responses HTTP header and status which informs client to provide credentials.
-func askCredentials(c *context.Context, status int, text string) {
-	c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
-	c.PlainText(status, text)
+func askCredentials(c *macaron.Context, status int, text string) {
+	c.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
+	c.Error(status, text)
 }
 
 func HTTPContexter() macaron.Handler {
-	return func(c *context.Context) {
+	return func(c *macaron.Context) {
 		if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
 			// Set CORS headers for browser-based git clients
-			c.Resp.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
-			c.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
+			c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
+			c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
 
 			// Handle preflight OPTIONS request
 			if c.Req.Method == "OPTIONS" {
@@ -64,15 +63,25 @@ func HTTPContexter() macaron.Handler {
 			strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
 			c.Req.Method == "GET"
 
-		owner, err := db.GetUserByName(ownerName)
+		owner, err := db.Users.GetByUsername(ownerName)
 		if err != nil {
-			c.NotFoundOrError(err, "get user by name")
+			if db.IsErrUserNotExist(err) {
+				c.Status(http.StatusNotFound)
+			} else {
+				c.Status(http.StatusInternalServerError)
+				log.Error("Failed to get user [name: %s]: %v", ownerName, err)
+			}
 			return
 		}
 
-		repo, err := db.GetRepositoryByName(owner.ID, repoName)
+		repo, err := db.Repos.GetByName(owner.ID, repoName)
 		if err != nil {
-			c.NotFoundOrError(err, "get repository by name")
+			if db.IsErrRepoNotExist(err) {
+				c.Status(http.StatusNotFound)
+			} else {
+				c.Status(http.StatusInternalServerError)
+				log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, repoName, err)
+			}
 			return
 		}
 
@@ -114,7 +123,8 @@ func HTTPContexter() macaron.Handler {
 
 		authUser, err := db.Users.Authenticate(authUsername, authPassword, -1)
 		if err != nil && !db.IsErrUserNotExist(err) {
-			c.Error(err, "authenticate user")
+			c.Status(http.StatusInternalServerError)
+			log.Error("Failed to authenticate user [name: %s]: %v", authUsername, err)
 			return
 		}
 
@@ -125,7 +135,8 @@ func HTTPContexter() macaron.Handler {
 				if db.IsErrAccessTokenNotExist(err) {
 					askCredentials(c, http.StatusUnauthorized, "")
 				} else {
-					c.Error(err, "get access token by SHA")
+					c.Status(http.StatusInternalServerError)
+					log.Error("Failed to get access token [sha: %s]: %v", authUsername, err)
 				}
 				return
 			}
@@ -134,11 +145,12 @@ func HTTPContexter() macaron.Handler {
 				log.Error("Failed to update access token: %v", err)
 			}
 
-			authUser, err = db.GetUserByID(token.UserID)
+			authUser, err = db.Users.GetByID(token.UserID)
 			if err != nil {
 				// Once we found token, we're supposed to find its related user,
 				// thus any error is unexpected.
-				c.Error(err, "get user by ID")
+				c.Status(http.StatusInternalServerError)
+				log.Error("Failed to get user [id: %d]: %v", token.UserID, err)
 				return
 			}
 		} else if authUser.IsEnabledTwoFactor() {
@@ -147,23 +159,19 @@ Please create and use personal access token on user settings page`)
 			return
 		}
 
-		log.Trace("HTTPGit - Authenticated user: %s", authUser.Name)
+		log.Trace("[Git] Authenticated user: %s", authUser.Name)
 
 		mode := db.AccessModeWrite
 		if isPull {
 			mode = db.AccessModeRead
 		}
-		has, err := db.HasAccess(authUser.ID, repo, mode)
-		if err != nil {
-			c.Error(err, "check access")
-			return
-		} else if !has {
+		if !db.Perms.Authorize(authUser.ID, repo, mode) {
 			askCredentials(c, http.StatusForbidden, "User permission denied")
 			return
 		}
 
 		if !isPull && repo.IsMirror {
-			c.PlainText(http.StatusForbidden, "Mirror repository is read-only")
+			c.Error(http.StatusForbidden, "Mirror repository is read-only")
 			return
 		}
 
@@ -390,7 +398,7 @@ func HTTP(c *HTTPContext) {
 		// but we only want to output this message only if user is really trying to access
 		// Git HTTP endpoints.
 		if conf.Repository.DisableHTTPGit {
-			c.PlainText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
+			c.Error(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
 			return
 		}