filebrowser/http/auth.go
Henrique Dias 7673678b50 🧼: types ---> lib
License: MIT
Signed-off-by: Henrique Dias <hacdias@gmail.com>
2019-01-04 08:42:42 +00:00

183 lines
4.0 KiB
Go

package http
import (
"context"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"github.com/filebrowser/filebrowser/lib"
)
func (e *Env) loginHandler(w http.ResponseWriter, r *http.Request) {
user, err := e.Auther.Auth(r)
if err == lib.ErrNoPermission {
httpErr(w, r, http.StatusForbidden, nil)
} else if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
} else {
e.printToken(w, r, user)
}
}
type signupBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
func (e *Env) signupHandler(w http.ResponseWriter, r *http.Request) {
e.RLockSettings()
defer e.RUnlockSettings()
settings := e.GetSettings()
if !settings.Signup {
httpErr(w, r, http.StatusForbidden, nil)
return
}
if r.Body == nil {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
info := &signupBody{}
err := json.NewDecoder(r.Body).Decode(info)
if err != nil {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
if info.Password == "" || info.Username == "" {
httpErr(w, r, http.StatusBadRequest, nil)
return
}
user := &lib.User{
Username: info.Username,
}
e.ApplyDefaults(user)
pwd, err := lib.HashPwd(info.Password)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
user.Password = pwd
err = e.SaveUser(user)
if err == lib.ErrExist {
httpErr(w, r, http.StatusConflict, nil)
return
} else if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
return
}
httpErr(w, r, http.StatusOK, nil)
}
type userInfo struct {
ID uint `json:"id"`
Locale string `json:"locale"`
ViewMode lib.ViewMode `json:"viewMode"`
Perm lib.Permissions `json:"perm"`
Commands []string `json:"commands"`
LockPassword bool `json:"lockPassword"`
}
type authToken struct {
User userInfo `json:"user"`
jwt.StandardClaims
}
type extractor []string
func (e extractor) ExtractToken(r *http.Request) (string, error) {
token, _ := request.AuthorizationHeaderExtractor.ExtractToken(r)
// Checks if the token isn't empty and if it contains two dots.
// The former prevents incompatibility with URLs that previously
// used basic auth.
if token != "" && strings.Count(token, ".") == 2 {
return token, nil
}
auth := r.URL.Query().Get("auth")
if auth == "" {
return "", request.ErrNoTokenInRequest
}
return auth, nil
}
func (e *Env) auth(next http.HandlerFunc) http.HandlerFunc {
keyFunc := func(token *jwt.Token) (interface{}, error) {
return e.GetSettings().Key, nil
}
nextWithUser := func(w http.ResponseWriter, r *http.Request, id uint) {
ctx := context.WithValue(r.Context(), keyUserID, id)
next(w, r.WithContext(ctx))
}
return func(w http.ResponseWriter, r *http.Request) {
var tk authToken
token, err := request.ParseFromRequestWithClaims(r, &extractor{}, &tk, keyFunc)
if err != nil || !token.Valid {
httpErr(w, r, http.StatusForbidden, nil)
return
}
if !tk.VerifyExpiresAt(time.Now().Add(time.Hour).Unix(), true) {
// TODO: chek if user info was modified use timestap
w.Header().Add("X-Renew-Token", "true")
}
nextWithUser(w, r, tk.User.ID)
}
}
func (e *Env) renew(w http.ResponseWriter, r *http.Request) {
user, ok := e.getUser(w, r)
if !ok {
return
}
e.printToken(w, r, user)
}
func (e *Env) printToken(w http.ResponseWriter, r *http.Request, user *lib.User) {
claims := &authToken{
User: userInfo{
ID: user.ID,
Locale: user.Locale,
ViewMode: user.ViewMode,
Perm: user.Perm,
LockPassword: user.LockPassword,
Commands: user.Commands,
},
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Hour * 2).Unix(),
Issuer: "File Browser",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString(e.GetSettings().Key)
if err != nil {
httpErr(w, r, http.StatusInternalServerError, err)
} else {
w.Header().Set("Content-Type", "cty")
w.Write([]byte(signed))
}
}