login.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package ac
  11. import (
  12. "encoding/json"
  13. "fmt"
  14. "net/http"
  15. "net/url"
  16. "time"
  17. "devt.de/krotik/common/datautil"
  18. "devt.de/krotik/common/errorutil"
  19. "devt.de/krotik/common/httputil"
  20. "devt.de/krotik/common/httputil/auth"
  21. "devt.de/krotik/eliasdb/api"
  22. )
  23. /*
  24. EndpointLogin is the login endpoint definition (rooted). Handles login/
  25. */
  26. const EndpointLogin = api.APIRoot + "/login/"
  27. /*
  28. DebounceTime default debounce time for each failed logins
  29. */
  30. var DebounceTime = 5 * time.Second
  31. /*
  32. LoginEndpointInst creates a new endpoint handler. Requires a CookieAuthHandleFuncWrapper
  33. object to verify login requests.
  34. */
  35. func LoginEndpointInst() api.RestEndpointHandler {
  36. errorutil.AssertTrue(AuthHandler != nil, "AuthHandler not initialized")
  37. return &loginEndpoint{
  38. &api.DefaultEndpointHandler{},
  39. AuthHandler,
  40. 3,
  41. 20,
  42. datautil.NewMapCache(0, int64(20)),
  43. datautil.NewMapCache(0, int64(20)),
  44. }
  45. }
  46. /*
  47. Handler object for cookie based login operations.
  48. */
  49. type loginEndpoint struct {
  50. *api.DefaultEndpointHandler
  51. authHandler *auth.CookieAuthHandleFuncWrapper // AuthHandler object to verify login requests
  52. allowedRetries int // Number of retries a user has to enter the correct password
  53. bruteForceDebounce int // Time in seconds a user has to wait after too many failed attempts
  54. failedLogins *datautil.MapCache // Map of failed login attempts per user
  55. debounceUsers *datautil.MapCache // Map of users which have to wait after too many failed attempts
  56. }
  57. /*
  58. HandlePOST tries to log a user in.
  59. */
  60. func (le *loginEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
  61. restClient := false
  62. data := make(map[string]interface{})
  63. ct := r.Header.Get("Content-Type")
  64. // Decode body either as application/json or application/x-www-form-urlencoded
  65. // This endpoint can be used by REST clients as well as pages using form submissions
  66. if ct == "application/json" {
  67. // The client is a REST client
  68. restClient = true
  69. dec := json.NewDecoder(r.Body)
  70. if err := dec.Decode(&data); err != nil {
  71. http.Error(w, fmt.Sprintf("Could not decode request body: %v",
  72. err.Error()), http.StatusBadRequest)
  73. return
  74. }
  75. } else if err := r.ParseForm(); err == nil {
  76. // Json decoding did not work out try normal form data decoding
  77. data["user"] = r.FormValue("user")
  78. data["pass"] = r.FormValue("pass")
  79. data["redirect_ok"] = r.FormValue("redirect_ok")
  80. data["redirect_notok"] = r.FormValue("redirect_notok")
  81. }
  82. // Handle query and ast requests
  83. user, ok1 := data["user"]
  84. pass, ok2 := data["pass"]
  85. redirectOk, ok3 := data["redirect_ok"]
  86. if !ok3 || redirectOk == "" {
  87. redirectOk = "/"
  88. }
  89. redirectNotOk, ok4 := data["redirect_notok"]
  90. if !ok4 || redirectNotOk == "" {
  91. redirectNotOk = "/"
  92. if u, err := url.Parse(r.Referer()); err == nil {
  93. redirectNotOk = u.Path
  94. }
  95. }
  96. if ok1 && ok2 && user != "" {
  97. redirect := redirectNotOk
  98. if aid := le.authHandler.AuthUser(fmt.Sprint(user), fmt.Sprint(pass), false); aid != "" {
  99. redirect = redirectOk
  100. le.authHandler.SetAuthCookie(aid, w)
  101. } else {
  102. LogAccess("Authentication for user ", user, " failed")
  103. // Add a time delay for negative answers to make dictionary attacks
  104. // more tedious
  105. time.Sleep(DebounceTime)
  106. }
  107. if !restClient {
  108. // Redirect if the other end is not a REST client
  109. redirectString := fmt.Sprint(redirect)
  110. // Make sure ok/notok redirect are local!
  111. if err := httputil.CheckLocalRedirect(redirectString); err != nil {
  112. http.Error(w, err.Error(), http.StatusBadRequest)
  113. return
  114. }
  115. http.Redirect(w, r, redirectString, http.StatusFound)
  116. } else if redirect == redirectNotOk {
  117. // The other end is a REST client and failed the authentication
  118. http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
  119. }
  120. // REST clients will just get a 200 with the cookie
  121. return
  122. }
  123. http.Error(w, "Invalid authentication request", http.StatusBadRequest)
  124. }
  125. /*
  126. SwaggerDefs is used to describe the endpoint in swagger.
  127. */
  128. func (le *loginEndpoint) SwaggerDefs(s map[string]interface{}) {
  129. s["paths"].(map[string]interface{})["/login"] = map[string]interface{}{
  130. "post": map[string]interface{}{
  131. "summary": "Login as a user and create a session.",
  132. "description": "The login endpoint can be used to log in and create a new user session.",
  133. "consumes": []string{
  134. "application/x-www-form-urlencoded",
  135. "application/json",
  136. },
  137. "produces": []string{
  138. "text/plain",
  139. },
  140. "parameters": []map[string]interface{}{
  141. map[string]interface{}{
  142. "name": "user",
  143. "in": "formData",
  144. "description": "Username to log in.",
  145. "required": true,
  146. "type": "string",
  147. },
  148. map[string]interface{}{
  149. "name": "pass",
  150. "in": "formData",
  151. "description": "Cleartext password of the username.",
  152. "required": true,
  153. "type": "string",
  154. },
  155. map[string]interface{}{
  156. "name": "redirect_ok",
  157. "in": "formData",
  158. "description": "Redirect URL if the log in is successful.",
  159. "required": false,
  160. "type": "string",
  161. },
  162. map[string]interface{}{
  163. "name": "redirect_notok",
  164. "in": "formData",
  165. "description": "Redirect URL if the log in is not successful.",
  166. "required": false,
  167. "type": "string",
  168. },
  169. },
  170. "responses": map[string]interface{}{
  171. "302": map[string]interface{}{
  172. "description": "Redirect depending on the log in result.",
  173. },
  174. "default": map[string]interface{}{
  175. "description": "Error response",
  176. "schema": map[string]interface{}{
  177. "$ref": "#/definitions/Error",
  178. },
  179. },
  180. },
  181. },
  182. }
  183. // Add generic error object to definition
  184. s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
  185. "description": "A human readable error mesage.",
  186. "type": "string",
  187. }
  188. }