web.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  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. "net/http"
  18. "strings"
  19. "github.com/go-chi/render"
  20. "github.com/unrolled/secure"
  21. "github.com/drakkan/sftpgo/v2/internal/util"
  22. "github.com/drakkan/sftpgo/v2/internal/version"
  23. )
  24. const (
  25. pageMFATitle = "Two-factor authentication"
  26. pageTwoFactorTitle = "Two-Factor authentication"
  27. pageTwoFactorRecoveryTitle = "Two-Factor recovery"
  28. webDateTimeFormat = "2006-01-02 15:04:05" // YYYY-MM-DD HH:MM:SS
  29. redactedSecret = "[**redacted**]"
  30. csrfFormToken = "_form_token"
  31. csrfHeaderToken = "X-CSRF-TOKEN"
  32. templateCommonDir = "common"
  33. templateTwoFactor = "twofactor.html"
  34. templateTwoFactorRecovery = "twofactor-recovery.html"
  35. templateForgotPassword = "forgot-password.html"
  36. templateResetPassword = "reset-password.html"
  37. templateChangePwd = "changepassword.html"
  38. templateMessage = "message.html"
  39. templateCommonBase = "base.html"
  40. templateCommonBaseLogin = "baselogin.html"
  41. templateCommonLogin = "login.html"
  42. )
  43. var (
  44. errInvalidTokenClaims = errors.New("invalid token claims")
  45. )
  46. type commonBasePage struct {
  47. CSPNonce string
  48. StaticURL string
  49. Version string
  50. }
  51. type loginPage struct {
  52. commonBasePage
  53. CurrentURL string
  54. Error *util.I18nError
  55. CSRFToken string
  56. AltLoginURL string
  57. AltLoginName string
  58. ForgotPwdURL string
  59. OpenIDLoginURL string
  60. Title string
  61. Branding UIBranding
  62. FormDisabled bool
  63. CheckRedirect bool
  64. }
  65. type twoFactorPage struct {
  66. commonBasePage
  67. CurrentURL string
  68. Error *util.I18nError
  69. CSRFToken string
  70. RecoveryURL string
  71. Title string
  72. Branding UIBranding
  73. CheckRedirect bool
  74. }
  75. type forgotPwdPage struct {
  76. commonBasePage
  77. CurrentURL string
  78. Error *util.I18nError
  79. CSRFToken string
  80. LoginURL string
  81. Title string
  82. Branding UIBranding
  83. CheckRedirect bool
  84. }
  85. type resetPwdPage struct {
  86. commonBasePage
  87. CurrentURL string
  88. Error *util.I18nError
  89. CSRFToken string
  90. LoginURL string
  91. Title string
  92. Branding UIBranding
  93. CheckRedirect bool
  94. }
  95. func getSliceFromDelimitedValues(values, delimiter string) []string {
  96. result := []string{}
  97. for _, v := range strings.Split(values, delimiter) {
  98. cleaned := strings.TrimSpace(v)
  99. if cleaned != "" {
  100. result = append(result, cleaned)
  101. }
  102. }
  103. return result
  104. }
  105. func hasPrefixAndSuffix(key, prefix, suffix string) bool {
  106. return strings.HasPrefix(key, prefix) && strings.HasSuffix(key, suffix)
  107. }
  108. func getCommonBasePage(r *http.Request) commonBasePage {
  109. return commonBasePage{
  110. CSPNonce: secure.CSPNonce(r.Context()),
  111. StaticURL: webStaticFilesPath,
  112. Version: version.GetServerVersion(" ", true),
  113. }
  114. }
  115. func i18nListDirMsg(status int) string {
  116. if status == http.StatusForbidden {
  117. return util.I18nErrorDirList403
  118. }
  119. return util.I18nErrorDirListGeneric
  120. }
  121. func i18nFsMsg(status int) string {
  122. if status == http.StatusForbidden {
  123. return util.I18nError403Message
  124. }
  125. return util.I18nErrorFsGeneric
  126. }
  127. func getI18NErrorString(err error, fallback string) string {
  128. var errI18n *util.I18nError
  129. if errors.As(err, &errI18n) {
  130. return errI18n.Message
  131. }
  132. return fallback
  133. }
  134. func getI18nError(err error) *util.I18nError {
  135. var errI18n *util.I18nError
  136. if err != nil {
  137. errI18n = util.NewI18nError(err, util.I18nError500Message)
  138. }
  139. return errI18n
  140. }
  141. func handlePingRequest(w http.ResponseWriter, r *http.Request) {
  142. r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
  143. render.PlainText(w, r, "PONG")
  144. }