123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package ac
- import (
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
- "time"
- "devt.de/krotik/common/datautil"
- "devt.de/krotik/common/errorutil"
- "devt.de/krotik/common/httputil"
- "devt.de/krotik/common/httputil/auth"
- "devt.de/krotik/eliasdb/api"
- )
- /*
- EndpointLogin is the login endpoint definition (rooted). Handles login/
- */
- const EndpointLogin = api.APIRoot + "/login/"
- /*
- DebounceTime default debounce time for each failed logins
- */
- var DebounceTime = 5 * time.Second
- /*
- LoginEndpointInst creates a new endpoint handler. Requires a CookieAuthHandleFuncWrapper
- object to verify login requests.
- */
- func LoginEndpointInst() api.RestEndpointHandler {
- errorutil.AssertTrue(AuthHandler != nil, "AuthHandler not initialized")
- return &loginEndpoint{
- &api.DefaultEndpointHandler{},
- AuthHandler,
- 3,
- 20,
- datautil.NewMapCache(0, int64(20)),
- datautil.NewMapCache(0, int64(20)),
- }
- }
- /*
- Handler object for cookie based login operations.
- */
- type loginEndpoint struct {
- *api.DefaultEndpointHandler
- authHandler *auth.CookieAuthHandleFuncWrapper // AuthHandler object to verify login requests
- allowedRetries int // Number of retries a user has to enter the correct password
- bruteForceDebounce int // Time in seconds a user has to wait after too many failed attempts
- failedLogins *datautil.MapCache // Map of failed login attempts per user
- debounceUsers *datautil.MapCache // Map of users which have to wait after too many failed attempts
- }
- /*
- HandlePOST tries to log a user in.
- */
- func (le *loginEndpoint) HandlePOST(w http.ResponseWriter, r *http.Request, resources []string) {
- restClient := false
- data := make(map[string]interface{})
- ct := r.Header.Get("Content-Type")
- // Decode body either as application/json or application/x-www-form-urlencoded
- // This endpoint can be used by REST clients as well as pages using form submissions
- if ct == "application/json" {
- // The client is a REST client
- restClient = true
- dec := json.NewDecoder(r.Body)
- if err := dec.Decode(&data); err != nil {
- http.Error(w, fmt.Sprintf("Could not decode request body: %v",
- err.Error()), http.StatusBadRequest)
- return
- }
- } else if err := r.ParseForm(); err == nil {
- // Json decoding did not work out try normal form data decoding
- data["user"] = r.FormValue("user")
- data["pass"] = r.FormValue("pass")
- data["redirect_ok"] = r.FormValue("redirect_ok")
- data["redirect_notok"] = r.FormValue("redirect_notok")
- }
- // Handle query and ast requests
- user, ok1 := data["user"]
- pass, ok2 := data["pass"]
- redirectOk, ok3 := data["redirect_ok"]
- if !ok3 || redirectOk == "" {
- redirectOk = "/"
- }
- redirectNotOk, ok4 := data["redirect_notok"]
- if !ok4 || redirectNotOk == "" {
- redirectNotOk = "/"
- if u, err := url.Parse(r.Referer()); err == nil {
- redirectNotOk = u.Path
- }
- }
- if ok1 && ok2 && user != "" {
- redirect := redirectNotOk
- if aid := le.authHandler.AuthUser(fmt.Sprint(user), fmt.Sprint(pass), false); aid != "" {
- redirect = redirectOk
- le.authHandler.SetAuthCookie(aid, w)
- } else {
- LogAccess("Authentication for user ", user, " failed")
- // Add a time delay for negative answers to make dictionary attacks
- // more tedious
- time.Sleep(DebounceTime)
- }
- if !restClient {
- // Redirect if the other end is not a REST client
- redirectString := fmt.Sprint(redirect)
- // Make sure ok/notok redirect are local!
- if err := httputil.CheckLocalRedirect(redirectString); err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
- http.Redirect(w, r, redirectString, http.StatusFound)
- } else if redirect == redirectNotOk {
- // The other end is a REST client and failed the authentication
- http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
- }
- // REST clients will just get a 200 with the cookie
- return
- }
- http.Error(w, "Invalid authentication request", http.StatusBadRequest)
- }
- /*
- SwaggerDefs is used to describe the endpoint in swagger.
- */
- func (le *loginEndpoint) SwaggerDefs(s map[string]interface{}) {
- s["paths"].(map[string]interface{})["/login"] = map[string]interface{}{
- "post": map[string]interface{}{
- "summary": "Login as a user and create a session.",
- "description": "The login endpoint can be used to log in and create a new user session.",
- "consumes": []string{
- "application/x-www-form-urlencoded",
- "application/json",
- },
- "produces": []string{
- "text/plain",
- },
- "parameters": []map[string]interface{}{
- map[string]interface{}{
- "name": "user",
- "in": "formData",
- "description": "Username to log in.",
- "required": true,
- "type": "string",
- },
- map[string]interface{}{
- "name": "pass",
- "in": "formData",
- "description": "Cleartext password of the username.",
- "required": true,
- "type": "string",
- },
- map[string]interface{}{
- "name": "redirect_ok",
- "in": "formData",
- "description": "Redirect URL if the log in is successful.",
- "required": false,
- "type": "string",
- },
- map[string]interface{}{
- "name": "redirect_notok",
- "in": "formData",
- "description": "Redirect URL if the log in is not successful.",
- "required": false,
- "type": "string",
- },
- },
- "responses": map[string]interface{}{
- "302": map[string]interface{}{
- "description": "Redirect depending on the log in result.",
- },
- "default": map[string]interface{}{
- "description": "Error response",
- "schema": map[string]interface{}{
- "$ref": "#/definitions/Error",
- },
- },
- },
- },
- }
- // Add generic error object to definition
- s["definitions"].(map[string]interface{})["Error"] = map[string]interface{}{
- "description": "A human readable error mesage.",
- "type": "string",
- }
- }
|