filebrowser/backend/http/auth.go

210 lines
5.6 KiB
Go
Raw Normal View History

package http
import (
"encoding/json"
2024-11-21 00:15:30 +00:00
"fmt"
"log"
"net/http"
"os"
"strings"
2024-11-21 00:15:30 +00:00
"sync"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/golang-jwt/jwt/v4/request"
2020-05-31 23:12:36 +00:00
2023-06-15 01:08:09 +00:00
"github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/settings"
2023-06-15 01:08:09 +00:00
"github.com/gtsteffaniak/filebrowser/users"
2024-11-21 00:15:30 +00:00
"github.com/gtsteffaniak/filebrowser/utils"
)
2024-11-21 00:15:30 +00:00
var (
revokedApiKeyList map[string]bool
revokeMu sync.Mutex
)
2024-11-21 00:15:30 +00:00
// first checks for cookie
// then checks for header Authorization as Bearer token
// then checks for query parameter
func extractToken(r *http.Request) (string, error) {
hasToken := false
tokenObj, err := r.Cookie("auth")
if err == nil {
hasToken = true
token := tokenObj.Value
// 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
}
2021-04-19 12:49:40 +00:00
}
2024-11-21 00:15:30 +00:00
// Check for Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader != "" {
hasToken = true
// Split the header to get "Bearer {token}"
parts := strings.Split(authHeader, " ")
if len(parts) == 2 && parts[0] == "Bearer" {
token := parts[1]
return token, nil
}
2024-11-21 00:15:30 +00:00
}
2024-11-21 00:15:30 +00:00
auth := r.URL.Query().Get("auth")
if auth != "" {
hasToken = true
if strings.Count(auth, ".") == 2 {
return auth, nil
}
}
2024-11-21 00:15:30 +00:00
if hasToken {
return "", fmt.Errorf("invalid token provided")
}
2024-11-21 00:15:30 +00:00
return "", request.ErrNoTokenInRequest
}
2024-11-21 00:15:30 +00:00
func loginHandler(w http.ResponseWriter, r *http.Request) {
// Get the authentication method from the settings
auther, err := store.Auth.Get(config.Auth.Method)
if err != nil {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
2024-11-21 00:15:30 +00:00
// Authenticate the user based on the request
user, err := auther.Auth(r, store.Users)
if err == os.ErrPermission {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
return
} else if err != nil {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
status, err := printToken(w, r, user) // Pass the data object
if err != nil {
http.Error(w, http.StatusText(status), status)
}
}
type signupBody struct {
Username string `json:"username"`
Password string `json:"password"`
}
2024-11-21 00:15:30 +00:00
func signupHandler(w http.ResponseWriter, r *http.Request) {
if !settings.Config.Auth.Signup {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
if r.Body == nil {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
info := &signupBody{}
err := json.NewDecoder(r.Body).Decode(info)
if err != nil {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
if info.Password == "" || info.Username == "" {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return
}
2024-11-21 00:15:30 +00:00
user := settings.ApplyUserDefaults(users.User{})
2024-09-16 21:01:16 +00:00
user.Username = info.Username
user.Password = info.Password
2024-11-21 00:15:30 +00:00
userHome, err := config.MakeUserDir(user.Username, user.Scope, config.Server.Root)
if err != nil {
log.Printf("create user: failed to mkdir user home dir: [%s]", userHome)
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
user.Scope = userHome
log.Printf("new user: %s, home dir: [%s].", user.Username, userHome)
2024-11-21 00:15:30 +00:00
err = store.Users.Save(&user)
if err == errors.ErrExist {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusConflict), http.StatusConflict)
return
} else if err != nil {
2024-11-21 00:15:30 +00:00
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
}
2024-11-21 00:15:30 +00:00
func renewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
// check if x-auth header is present and token is
return printToken(w, r, d.user)
}
2024-11-21 00:15:30 +00:00
func printToken(w http.ResponseWriter, _ *http.Request, user *users.User) (int, error) {
signed, err := makeSignedTokenAPI(user, "WEB_TOKEN_"+utils.GenerateRandomHash(4), time.Hour*2, user.Perm)
if err != nil {
2024-11-21 00:15:30 +00:00
if strings.Contains(err.Error(), "key already exists with same name") {
return http.StatusConflict, err
}
return http.StatusInternalServerError, err
}
w.Header().Set("Content-Type", "text/plain")
if _, err := w.Write([]byte(signed.Key)); err != nil {
return http.StatusInternalServerError, err
}
2024-11-21 00:15:30 +00:00
return 0, nil
}
func isRevokedApiKey(key string) bool {
_, exists := revokedApiKeyList[key]
return exists
}
func revokeAPIKey(key string) {
revokeMu.Lock()
delete(revokedApiKeyList, key)
revokeMu.Unlock()
}
func makeSignedTokenAPI(user *users.User, name string, duration time.Duration, perms users.Permissions) (users.AuthToken, error) {
_, ok := user.ApiKeys[name]
if ok {
return users.AuthToken{}, fmt.Errorf("key already exists with same name %v ", name)
}
now := time.Now()
expires := now.Add(duration)
claim := users.AuthToken{
Permissions: perms,
Created: now.Unix(),
Expires: expires.Unix(),
Name: name,
BelongsTo: user.ID,
RegisteredClaims: jwt.RegisteredClaims{
2024-11-21 00:15:30 +00:00
IssuedAt: jwt.NewNumericDate(now),
ExpiresAt: jwt.NewNumericDate(expires),
Issuer: "FileBrowser Quantum",
},
}
2024-11-21 00:15:30 +00:00
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim)
tokenString, err := token.SignedString(config.Auth.Key)
if err != nil {
2024-11-21 00:15:30 +00:00
return claim, err
}
2024-11-21 00:15:30 +00:00
claim.Key = tokenString
if strings.HasPrefix(name, "WEB_TOKEN") {
// don't add to api tokens, its a short lived web token
return claim, err
2020-05-31 23:12:36 +00:00
}
2024-11-21 00:15:30 +00:00
// Perform the user update
err = store.Users.AddApiKey(user.ID, name, claim)
if err != nil {
return claim, err
}
return claim, err
}