183 lines
4.0 KiB
Go
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))
|
|
}
|
|
}
|