auth_utils.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package httpd
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/go-chi/jwtauth/v5"
  8. "github.com/lestrrat-go/jwx/jwt"
  9. "github.com/rs/xid"
  10. "github.com/drakkan/sftpgo/v2/dataprovider"
  11. "github.com/drakkan/sftpgo/v2/logger"
  12. "github.com/drakkan/sftpgo/v2/util"
  13. )
  14. type tokenAudience = string
  15. const (
  16. tokenAudienceWebAdmin tokenAudience = "WebAdmin"
  17. tokenAudienceWebClient tokenAudience = "WebClient"
  18. tokenAudienceWebAdminPartial tokenAudience = "WebAdminPartial"
  19. tokenAudienceWebClientPartial tokenAudience = "WebClientPartial"
  20. tokenAudienceAPI tokenAudience = "API"
  21. tokenAudienceAPIUser tokenAudience = "APIUser"
  22. tokenAudienceCSRF tokenAudience = "CSRF"
  23. )
  24. const (
  25. claimUsernameKey = "username"
  26. claimPermissionsKey = "permissions"
  27. claimAPIKey = "api_key"
  28. basicRealm = "Basic realm=\"SFTPGo\""
  29. )
  30. var (
  31. tokenDuration = 20 * time.Minute
  32. // csrf token duration is greater than normal token duration to reduce issues
  33. // with the login form
  34. csrfTokenDuration = 6 * time.Hour
  35. tokenRefreshThreshold = 10 * time.Minute
  36. )
  37. type jwtTokenClaims struct {
  38. Username string
  39. Permissions []string
  40. Signature string
  41. Audience string
  42. APIKeyID string
  43. }
  44. func (c *jwtTokenClaims) hasUserAudience() bool {
  45. if c.Audience == tokenAudienceWebClient || c.Audience == tokenAudienceAPIUser {
  46. return true
  47. }
  48. return false
  49. }
  50. func (c *jwtTokenClaims) asMap() map[string]interface{} {
  51. claims := make(map[string]interface{})
  52. claims[claimUsernameKey] = c.Username
  53. claims[claimPermissionsKey] = c.Permissions
  54. if c.APIKeyID != "" {
  55. claims[claimAPIKey] = c.APIKeyID
  56. }
  57. claims[jwt.SubjectKey] = c.Signature
  58. return claims
  59. }
  60. func (c *jwtTokenClaims) Decode(token map[string]interface{}) {
  61. c.Permissions = nil
  62. username := token[claimUsernameKey]
  63. switch v := username.(type) {
  64. case string:
  65. c.Username = v
  66. }
  67. signature := token[jwt.SubjectKey]
  68. switch v := signature.(type) {
  69. case string:
  70. c.Signature = v
  71. }
  72. audience := token[jwt.AudienceKey]
  73. switch v := audience.(type) {
  74. case []string:
  75. if len(v) > 0 {
  76. c.Audience = v[0]
  77. }
  78. }
  79. if val, ok := token[claimAPIKey]; ok {
  80. switch v := val.(type) {
  81. case string:
  82. c.APIKeyID = v
  83. }
  84. }
  85. permissions := token[claimPermissionsKey]
  86. switch v := permissions.(type) {
  87. case []interface{}:
  88. for _, elem := range v {
  89. switch elemValue := elem.(type) {
  90. case string:
  91. c.Permissions = append(c.Permissions, elemValue)
  92. }
  93. }
  94. }
  95. }
  96. func (c *jwtTokenClaims) isCriticalPermRemoved(permissions []string) bool {
  97. if util.IsStringInSlice(dataprovider.PermAdminAny, permissions) {
  98. return false
  99. }
  100. if (util.IsStringInSlice(dataprovider.PermAdminManageAdmins, c.Permissions) ||
  101. util.IsStringInSlice(dataprovider.PermAdminAny, c.Permissions)) &&
  102. !util.IsStringInSlice(dataprovider.PermAdminManageAdmins, permissions) &&
  103. !util.IsStringInSlice(dataprovider.PermAdminAny, permissions) {
  104. return true
  105. }
  106. return false
  107. }
  108. func (c *jwtTokenClaims) hasPerm(perm string) bool {
  109. if util.IsStringInSlice(dataprovider.PermAdminAny, c.Permissions) {
  110. return true
  111. }
  112. return util.IsStringInSlice(perm, c.Permissions)
  113. }
  114. func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (map[string]interface{}, error) {
  115. claims := c.asMap()
  116. now := time.Now().UTC()
  117. claims[jwt.JwtIDKey] = xid.New().String()
  118. claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second)
  119. claims[jwt.ExpirationKey] = now.Add(tokenDuration)
  120. claims[jwt.AudienceKey] = audience
  121. token, tokenString, err := tokenAuth.Encode(claims)
  122. if err != nil {
  123. return nil, err
  124. }
  125. response := make(map[string]interface{})
  126. response["access_token"] = tokenString
  127. response["expires_at"] = token.Expiration().Format(time.RFC3339)
  128. return response, nil
  129. }
  130. func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth, audience tokenAudience) error {
  131. resp, err := c.createTokenResponse(tokenAuth, audience)
  132. if err != nil {
  133. return err
  134. }
  135. var basePath string
  136. if audience == tokenAudienceWebAdmin || audience == tokenAudienceWebAdminPartial {
  137. basePath = webBaseAdminPath
  138. } else {
  139. basePath = webBaseClientPath
  140. }
  141. http.SetCookie(w, &http.Cookie{
  142. Name: "jwt",
  143. Value: resp["access_token"].(string),
  144. Path: basePath,
  145. Expires: time.Now().Add(tokenDuration),
  146. MaxAge: int(tokenDuration / time.Second),
  147. HttpOnly: true,
  148. Secure: isTLS(r),
  149. SameSite: http.SameSiteStrictMode,
  150. })
  151. return nil
  152. }
  153. func (c *jwtTokenClaims) removeCookie(w http.ResponseWriter, r *http.Request, cookiePath string) {
  154. http.SetCookie(w, &http.Cookie{
  155. Name: "jwt",
  156. Value: "",
  157. Path: cookiePath,
  158. Expires: time.Unix(0, 0),
  159. MaxAge: -1,
  160. HttpOnly: true,
  161. Secure: isTLS(r),
  162. SameSite: http.SameSiteStrictMode,
  163. })
  164. invalidateToken(r)
  165. }
  166. func isTLS(r *http.Request) bool {
  167. if r.TLS != nil {
  168. return true
  169. }
  170. if proto, ok := r.Context().Value(forwardedProtoKey).(string); ok {
  171. return proto == "https"
  172. }
  173. return false
  174. }
  175. func isTokenInvalidated(r *http.Request) bool {
  176. isTokenFound := false
  177. token := jwtauth.TokenFromHeader(r)
  178. if token != "" {
  179. isTokenFound = true
  180. if _, ok := invalidatedJWTTokens.Load(token); ok {
  181. return true
  182. }
  183. }
  184. token = jwtauth.TokenFromCookie(r)
  185. if token != "" {
  186. isTokenFound = true
  187. if _, ok := invalidatedJWTTokens.Load(token); ok {
  188. return true
  189. }
  190. }
  191. return !isTokenFound
  192. }
  193. func invalidateToken(r *http.Request) {
  194. tokenString := jwtauth.TokenFromHeader(r)
  195. if tokenString != "" {
  196. invalidatedJWTTokens.Store(tokenString, time.Now().Add(tokenDuration).UTC())
  197. }
  198. tokenString = jwtauth.TokenFromCookie(r)
  199. if tokenString != "" {
  200. invalidatedJWTTokens.Store(tokenString, time.Now().Add(tokenDuration).UTC())
  201. }
  202. }
  203. func getUserFromToken(r *http.Request) *dataprovider.User {
  204. user := &dataprovider.User{}
  205. _, claims, err := jwtauth.FromContext(r.Context())
  206. if err != nil {
  207. return user
  208. }
  209. tokenClaims := jwtTokenClaims{}
  210. tokenClaims.Decode(claims)
  211. user.Username = tokenClaims.Username
  212. user.Filters.WebClient = tokenClaims.Permissions
  213. return user
  214. }
  215. func getAdminFromToken(r *http.Request) *dataprovider.Admin {
  216. admin := &dataprovider.Admin{}
  217. _, claims, err := jwtauth.FromContext(r.Context())
  218. if err != nil {
  219. return admin
  220. }
  221. tokenClaims := jwtTokenClaims{}
  222. tokenClaims.Decode(claims)
  223. admin.Username = tokenClaims.Username
  224. admin.Permissions = tokenClaims.Permissions
  225. return admin
  226. }
  227. func createCSRFToken() string {
  228. claims := make(map[string]interface{})
  229. now := time.Now().UTC()
  230. claims[jwt.JwtIDKey] = xid.New().String()
  231. claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second)
  232. claims[jwt.ExpirationKey] = now.Add(csrfTokenDuration)
  233. claims[jwt.AudienceKey] = tokenAudienceCSRF
  234. _, tokenString, err := csrfTokenAuth.Encode(claims)
  235. if err != nil {
  236. logger.Debug(logSender, "", "unable to create CSRF token: %v", err)
  237. return ""
  238. }
  239. return tokenString
  240. }
  241. func verifyCSRFToken(tokenString string) error {
  242. token, err := jwtauth.VerifyToken(csrfTokenAuth, tokenString)
  243. if err != nil || token == nil {
  244. logger.Debug(logSender, "", "error validating CSRF token %#v: %v", tokenString, err)
  245. return fmt.Errorf("unable to verify form token: %v", err)
  246. }
  247. if !util.IsStringInSlice(tokenAudienceCSRF, token.Audience()) {
  248. logger.Debug(logSender, "", "error validating CSRF token audience")
  249. return errors.New("the form token is not valid")
  250. }
  251. return nil
  252. }