mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-06-26 11:27:46 +02:00
Harvey Tindall
daf190f68b
jfId was assigned too early, before checking errors. Also, handle 400 as well as 401 from jellyfin as an invalid password.
151 lines
4.0 KiB
Go
151 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/dgrijalva/jwt-go"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/lithammer/shortuuid/v3"
|
|
)
|
|
|
|
func (app *appContext) webAuth() gin.HandlerFunc {
|
|
return app.authenticate
|
|
}
|
|
|
|
func (app *appContext) authenticate(gc *gin.Context) {
|
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
|
if header[0] != "Basic" {
|
|
app.debug.Println("Invalid authentication header")
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
|
creds := strings.SplitN(string(auth), ":", 2)
|
|
token, err := jwt.Parse(creds[0], func(token *jwt.Token) (interface{}, error) {
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
app.debug.Printf("Invalid JWT signing method %s", token.Header["alg"])
|
|
return nil, fmt.Errorf("Unexpected signing method %v", token.Header["alg"])
|
|
}
|
|
return []byte(os.Getenv("JFA_SECRET")), nil
|
|
})
|
|
if err != nil {
|
|
app.debug.Printf("Auth denied: %s", err)
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
claims, ok := token.Claims.(jwt.MapClaims)
|
|
var userId string
|
|
var jfId string
|
|
if ok && token.Valid {
|
|
userId = claims["id"].(string)
|
|
jfId = claims["jfid"].(string)
|
|
} else {
|
|
app.debug.Printf("Invalid token")
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
match := false
|
|
for _, user := range app.users {
|
|
if user.UserID == userId {
|
|
match = true
|
|
}
|
|
}
|
|
if !match {
|
|
app.debug.Printf("Couldn't find user ID %s", userId)
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
gc.Set("jfId", jfId)
|
|
gc.Set("userId", userId)
|
|
app.debug.Println("Authentication successful")
|
|
gc.Next()
|
|
}
|
|
|
|
func (app *appContext) GetToken(gc *gin.Context) {
|
|
app.info.Println("Token requested (login attempt)")
|
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
|
if header[0] != "Basic" {
|
|
app.debug.Println("Invalid authentication header")
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
auth, _ := base64.StdEncoding.DecodeString(header[1])
|
|
creds := strings.SplitN(string(auth), ":", 2)
|
|
match := false
|
|
var userId string
|
|
for _, user := range app.users {
|
|
if user.Username == creds[0] && user.Password == creds[1] {
|
|
match = true
|
|
userId = user.UserID
|
|
}
|
|
}
|
|
jfId := ""
|
|
if !match {
|
|
if !app.jellyfinLogin {
|
|
app.info.Println("Auth failed: Invalid username and/or password")
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
var status int
|
|
var err error
|
|
var user map[string]interface{}
|
|
user, status, err = app.authJf.authenticate(creds[0], creds[1])
|
|
if status != 200 || err != nil {
|
|
if status == 401 || status == 400 {
|
|
app.info.Println("Auth failed: Invalid username and/or password")
|
|
respond(401, "Unauthorized", gc)
|
|
return
|
|
}
|
|
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin: Code %d", status)
|
|
respond(500, "Jellyfin error", gc)
|
|
return
|
|
} else {
|
|
jfId = user["Id"].(string)
|
|
if app.config.Section("ui").Key("admin_only").MustBool(true) {
|
|
if !user["Policy"].(map[string]interface{})["IsAdministrator"].(bool) {
|
|
app.debug.Printf("Auth failed: User \"%s\" isn't admin", creds[0])
|
|
respond(401, "Unauthorized", gc)
|
|
}
|
|
}
|
|
newuser := User{}
|
|
newuser.UserID = shortuuid.New()
|
|
userId = newuser.UserID
|
|
// uuid, nothing else identifiable!
|
|
app.debug.Printf("Token generated for user \"%s\"", creds[0])
|
|
app.users = append(app.users, newuser)
|
|
}
|
|
}
|
|
token, err := CreateToken(userId, jfId)
|
|
if err != nil {
|
|
respond(500, "Error generating token", gc)
|
|
}
|
|
resp := map[string]string{"token": token}
|
|
gc.JSON(200, resp)
|
|
}
|
|
|
|
func CreateToken(userId string, jfId string) (string, error) {
|
|
claims := jwt.MapClaims{
|
|
"valid": true,
|
|
"id": userId,
|
|
"exp": time.Now().Add(time.Minute * 20).Unix(),
|
|
"jfid": jfId,
|
|
}
|
|
|
|
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
token, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return token, nil
|
|
}
|
|
|
|
func respond(code int, message string, gc *gin.Context) {
|
|
resp := map[string]string{"error": message}
|
|
gc.JSON(code, resp)
|
|
gc.Abort()
|
|
}
|