From ee3b421566f50c758f37a200d68843c65c6dcaf9 Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Thu, 20 Aug 2020 20:20:31 +0100 Subject: [PATCH] Fixed flaw with jellyfin_login; store refresh token in cookies with jellyfin_login enabled, the username and password vals in the User struct would be "". If you disabled 'required' on the login form, blank username and password would allow you in. --- api.go | 10 +++++++++- auth.go | 30 ++++++++++++++++++------------ data/static/admin.js | 21 +++++++++++---------- main.go | 4 ++-- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/api.go b/api.go index 53b3566..6d8a753 100644 --- a/api.go +++ b/api.go @@ -610,7 +610,15 @@ func (app *appContext) ModifyConfig(gc *gin.Context) { } func (app *appContext) Logout(gc *gin.Context) { - app.invalidIds = append(app.invalidIds, gc.GetString("userId")) + cookie, err := gc.Cookie("refresh") + if err != nil { + app.debug.Printf("Couldn't get cookies: %s", err) + respond(500, "Couldn't fetch cookies", gc) + return + } + app.invalidTokens = append(app.invalidTokens, cookie) + fmt.Println("After appending", cookie, ":", app.invalidTokens) + gc.SetCookie("refresh", "invalid", -1, "/", gc.Request.URL.Hostname(), true, true) gc.JSON(200, map[string]bool{"success": true}) } diff --git a/auth.go b/auth.go index cb92ca9..4112675 100644 --- a/auth.go +++ b/auth.go @@ -87,8 +87,11 @@ func (app *appContext) GetToken(gc *gin.Context) { var userId, jfId string for _, user := range app.users { if user.Username == creds[0] && user.Password == creds[1] { - match = true - userId = user.UserID + if creds[0] != "" && creds[1] != "" { + match = true + app.debug.Println("Found existing user") + userId = user.UserID + } } } if !match { @@ -97,8 +100,17 @@ func (app *appContext) GetToken(gc *gin.Context) { respond(401, "Unauthorized", gc) return } - if creds[1] == "" { - token, err := jwt.Parse(creds[0], func(token *jwt.Token) (interface{}, error) { + cookie, err := gc.Cookie("refresh") + if err == nil && cookie != "" && creds[0] == "" && creds[1] == "" { + fmt.Println("Checking:", cookie) + for _, token := range app.invalidTokens { + if cookie == token { + app.debug.Printf("Auth denied: Refresh token in blocklist") + respond(401, "Unauthorized", gc) + return + } + } + token, err := jwt.Parse(cookie, 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"]) @@ -111,13 +123,6 @@ func (app *appContext) GetToken(gc *gin.Context) { return } claims, ok := token.Claims.(jwt.MapClaims) - for _, id := range app.invalidIds { - if claims["id"].(string) == id { - app.debug.Printf("Auth denied: Refresh token in blocklist") - respond(401, "Unauthorized", gc) - return - } - } expiryUnix, err := strconv.ParseInt(claims["exp"].(string), 10, 64) if err != nil { app.debug.Printf("Auth denied: %s", err) @@ -168,7 +173,8 @@ func (app *appContext) GetToken(gc *gin.Context) { if err != nil { respond(500, "Error generating token", gc) } - resp := map[string]string{"token": token, "refresh": refresh} + resp := map[string]string{"token": token} + gc.SetCookie("refresh", refresh, (3600 * 24), "/", gc.Request.URL.Hostname(), true, true) gc.JSON(200, resp) } diff --git a/data/static/admin.js b/data/static/admin.js index 5355c25..bb9ac75 100644 --- a/data/static/admin.js +++ b/data/static/admin.js @@ -531,7 +531,7 @@ document.getElementById('inviteForm').onsubmit = function() { return false; }; -function tryLogin(username, password, modal, button) { +function tryLogin(username, password, modal, button, callback) { let req = new XMLHttpRequest(); req.responseType = 'json'; req.onreadystatechange = function() { @@ -561,7 +561,6 @@ function tryLogin(username, password, modal, button) { } else { const data = this.response; window.token = data['token']; - document.cookie = "refresh=" + data['refresh']; generateInvites(); const interval = setInterval(function() { generateInvites(); }, 60 * 1000); let day = document.getElementById('days'); @@ -579,6 +578,9 @@ function tryLogin(username, password, modal, button) { } document.getElementById('logoutButton').setAttribute('style', ''); } + if (typeof callback === "function") { + callback(this.status); + } } }; req.open("GET", "/getToken", true); @@ -600,7 +602,7 @@ document.getElementById('loginForm').onsubmit = function() { button.innerHTML = '' + 'Loading...'; - tryLogin(details['username'], details['password'], true, button) + tryLogin(username = details['username'], password = details['password'], modal = true, button = button) return false; }; @@ -807,12 +809,12 @@ document.getElementById('openUsers').onclick = function () { generateInvites(empty = true); -let refreshToken = getCookie("refresh") -if (refreshToken != "") { - tryLogin(refreshToken, "", false) -} else { - loginModal.show(); -} +tryLogin("", "", false, callback = function(code){ + console.log(code); + if (code != 200) { + loginModal.show(); + } +}); document.getElementById('logoutButton').onclick = function () { let req = new XMLHttpRequest(); @@ -822,7 +824,6 @@ document.getElementById('logoutButton').onclick = function () { req.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { window.token = ''; - document.cookie = 'refresh=;'; location.reload(); return false; } diff --git a/main.go b/main.go index f877219..a41680f 100644 --- a/main.go +++ b/main.go @@ -42,7 +42,7 @@ type appContext struct { bsVersion int jellyfinLogin bool users []User - invalidIds []string + invalidTokens []string jf Jellyfin authJf Jellyfin datePattern string @@ -329,7 +329,7 @@ func main() { router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.local_path, "static"), false))) router.GET("/invite/:invCode", app.InviteProxy) api := router.Group("/", app.webAuth()) - api.POST("/logout", app.Logout) + router.POST("/logout", app.Logout) api.POST("/generateInvite", app.GenerateInvite) api.GET("/getInvites", app.GetInvites) api.POST("/setNotify", app.SetNotify)