web.go 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. package httpd
  15. import (
  16. "errors"
  17. "fmt"
  18. "net/http"
  19. "strings"
  20. "github.com/go-chi/render"
  21. "github.com/unrolled/secure"
  22. "github.com/drakkan/sftpgo/v2/internal/util"
  23. "github.com/drakkan/sftpgo/v2/internal/version"
  24. )
  25. const (
  26. pageMFATitle = "Two-factor authentication"
  27. page400Title = "Bad request"
  28. page403Title = "Forbidden"
  29. page404Title = "Not found"
  30. page404Body = "The page you are looking for does not exist."
  31. page500Title = "Internal Server Error"
  32. page500Body = "The server is unable to fulfill your request."
  33. pageTwoFactorTitle = "Two-Factor authentication"
  34. pageTwoFactorRecoveryTitle = "Two-Factor recovery"
  35. webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
  36. redactedSecret = "[**redacted**]"
  37. csrfFormToken = "_form_token"
  38. csrfHeaderToken = "X-CSRF-TOKEN"
  39. templateCommonDir = "common"
  40. templateTwoFactor = "twofactor.html"
  41. templateTwoFactorRecovery = "twofactor-recovery.html"
  42. templateForgotPassword = "forgot-password.html"
  43. templateResetPassword = "reset-password.html"
  44. templateCommonCSS = "sftpgo.css"
  45. templateCommonBase = "base.html"
  46. templateCommonBaseLogin = "baselogin.html"
  47. templateCommonLogin = "login.html"
  48. )
  49. var (
  50. errInvalidTokenClaims = errors.New("invalid token claims")
  51. )
  52. type commonBasePage struct {
  53. CSPNonce string
  54. StaticURL string
  55. Version string
  56. }
  57. type loginPage struct {
  58. commonBasePage
  59. CurrentURL string
  60. Error *util.I18nError
  61. CSRFToken string
  62. AltLoginURL string
  63. AltLoginName string
  64. ForgotPwdURL string
  65. OpenIDLoginURL string
  66. Title string
  67. Branding UIBranding
  68. FormDisabled bool
  69. }
  70. type twoFactorPage struct {
  71. commonBasePage
  72. CurrentURL string
  73. Error *util.I18nError
  74. CSRFToken string
  75. RecoveryURL string
  76. Title string
  77. Branding UIBranding
  78. }
  79. type forgotPwdPage struct {
  80. commonBasePage
  81. CurrentURL string
  82. Error *util.I18nError
  83. CSRFToken string
  84. LoginURL string
  85. Title string
  86. Branding UIBranding
  87. }
  88. type resetPwdPage struct {
  89. commonBasePage
  90. CurrentURL string
  91. Error *util.I18nError
  92. CSRFToken string
  93. LoginURL string
  94. Title string
  95. Branding UIBranding
  96. }
  97. func getSliceFromDelimitedValues(values, delimiter string) []string {
  98. result := []string{}
  99. for _, v := range strings.Split(values, delimiter) {
  100. cleaned := strings.TrimSpace(v)
  101. if cleaned != "" {
  102. result = append(result, cleaned)
  103. }
  104. }
  105. return result
  106. }
  107. func hasPrefixAndSuffix(key, prefix, suffix string) bool {
  108. return strings.HasPrefix(key, prefix) && strings.HasSuffix(key, suffix)
  109. }
  110. func getCommonBasePage(r *http.Request) commonBasePage {
  111. v := version.Get()
  112. return commonBasePage{
  113. CSPNonce: secure.CSPNonce(r.Context()),
  114. StaticURL: webStaticFilesPath,
  115. Version: fmt.Sprintf("v%v-%v", v.Version, v.CommitHash),
  116. }
  117. }
  118. func i18nListDirMsg(status int) string {
  119. if status == http.StatusForbidden {
  120. return util.I18nErrorDirList403
  121. }
  122. return util.I18nErrorDirListGeneric
  123. }
  124. func i18nFsMsg(status int) string {
  125. if status == http.StatusForbidden {
  126. return util.I18nError403Message
  127. }
  128. return util.I18nErrorFsGeneric
  129. }
  130. func getI18NErrorString(err error, fallback string) string {
  131. var errI18n *util.I18nError
  132. if errors.As(err, &errI18n) {
  133. return errI18n.Message
  134. }
  135. return fallback
  136. }
  137. func getI18nError(err error) *util.I18nError {
  138. var errI18n *util.I18nError
  139. if err != nil {
  140. errI18n = util.NewI18nError(err, util.I18nError500Message)
  141. }
  142. return errI18n
  143. }
  144. func handlePingRequest(w http.ResponseWriter, r *http.Request) {
  145. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  146. render.PlainText(w, r, "PONG")
  147. }