From 0e69e269417333c47b10187132167f0f8fca78a7 Mon Sep 17 00:00:00 2001 From: aunefyren Date: Sun, 22 Oct 2023 14:25:41 +0200 Subject: [PATCH] Changes Create share links without Plex Auth Delete old share links once a day Pull friendly name for requester from Tautulli --- files/link.go | 75 +++++++++++++++++++++++++++++++++++++++----- go.mod | 2 ++ go.sum | 8 +++++ main.go | 18 ++++++++++- routes/no_auth.go | 41 ++++++++++++++---------- routes/statistics.go | 13 +++++++- routes/user_auth.go | 69 ++++++++++++++++++++-------------------- web/js/get_stats.js | 22 ++++++++++--- 8 files changed, 183 insertions(+), 65 deletions(-) diff --git a/files/link.go b/files/link.go index 41fb15c..7a600df 100644 --- a/files/link.go +++ b/files/link.go @@ -9,12 +9,14 @@ import ( "os" "path/filepath" "strconv" + "strings" + "time" ) var link_path, _ = filepath.Abs("./config/links") // Save new link object to the correct path -func SaveLink(link_object *models.WrapperrShareLink) error { +func SaveLink(link_object *models.WrapperrShareLink, plexAuth bool) error { // Check if the link folder exists err := CheckLinkDir() @@ -27,7 +29,12 @@ func SaveLink(link_object *models.WrapperrShareLink) error { return err } - var link_object_path, _ = filepath.Abs(link_path + "/" + strconv.Itoa(link_object.UserID) + ".json") + var link_object_path string + if plexAuth { + link_object_path, _ = filepath.Abs(link_path + "/" + strconv.Itoa(link_object.UserID) + ".json") + } else { + link_object_path, _ = filepath.Abs(link_path + "/" + link_object.Hash + ".json") + } err = ioutil.WriteFile(link_object_path, file, 0644) if err != nil { @@ -64,8 +71,22 @@ func CheckLinkDir() error { return nil } +func DeleteLink(fileName string) error { + linkpath, err := filepath.Abs(link_path + "/" + fileName + ".json") + if err != nil { + log.Println("Failed to create path. Error: " + err.Error()) + return errors.New("Failed to create path.") + } + err = os.Remove(linkpath) + if err != nil { + log.Println("Failed to delete file. Error: " + err.Error()) + return errors.New("Failed to delete file.") + } + return nil +} + // Read -func GetLink(UserID string) (*models.WrapperrShareLink, error) { +func GetLink(fileName string) (*models.WrapperrShareLink, error) { // Check if the link folder exists err := CheckLinkDir() if err != nil { @@ -74,13 +95,14 @@ func GetLink(UserID string) (*models.WrapperrShareLink, error) { link_object := models.WrapperrShareLink{} - share_link_path, err := filepath.Abs(link_path + "/" + UserID + ".json") + share_link_path, err := filepath.Abs(link_path + "/" + fileName + ".json") if err != nil { return nil, err } _, err = os.Stat(share_link_path) if err != nil { + log.Println("Failed to load file from path. Returning error. Error: " + err.Error()) return nil, errors.New("Invalid share link.") } @@ -94,10 +116,47 @@ func GetLink(UserID string) (*models.WrapperrShareLink, error) { return nil, err } - if link_object.Expired { - return nil, errors.New("This Wrapped link has expired.") - } - // Return config object return &link_object, nil } + +func CleanOldShareableLinks() { + // Check if the link folder exists + err := CheckLinkDir() + if err != nil { + log.Println("Failed to verify link directory exists. Error: " + err.Error()) + return + } + + items, _ := ioutil.ReadDir(link_path) + + for _, item := range items { + if item.IsDir() { + continue + } else { + if strings.HasSuffix(strings.ToLower(item.Name()), ".json") { + fileNameParts := strings.Split(item.Name(), ".") + if len(fileNameParts) != 2 { + continue + } + linkObject, err := GetLink(fileNameParts[0]) + if err != nil { + log.Println("Failed to load " + item.Name() + ". Error: " + err.Error()) + } + now := time.Now() + linkTime, err := time.Parse("2006-01-02", linkObject.Date) + if err != nil { + log.Println("Failed to parse " + item.Name() + " datetime. Error: " + err.Error()) + } + linkTime = linkTime.Add(7 * 24 * time.Hour) + if linkTime.Before(now) { + log.Println("Deleting " + item.Name() + ".") + err = DeleteLink(fileNameParts[0]) + if err != nil { + log.Println("Failed to delete " + item.Name() + ". Error: " + err.Error()) + } + } + } + } + } +} diff --git a/go.mod b/go.mod index 2fc48c3..38064da 100644 --- a/go.mod +++ b/go.mod @@ -11,3 +11,5 @@ require ( github.com/patrickmn/sortutil v0.0.0-20120526081524-abeda66eb583 golang.org/x/crypto v0.14.0 ) + +require github.com/procyon-projects/chrono v1.1.2 // indirect diff --git a/go.sum b/go.sum index 53044bd..e19e5f5 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang-jwt/jwt/v5 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v5 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= @@ -14,7 +15,14 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/patrickmn/sortutil v0.0.0-20120526081524-abeda66eb583 h1:+gFSK6FP5Ky3BPFrxQjHz92uRsj0DsrBL+xoIbiWRco= github.com/patrickmn/sortutil v0.0.0-20120526081524-abeda66eb583/go.mod h1:DyFPU22sg+Or/eRPmpwVVp0fUw+aQSYddY0DzzNjSN4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/procyon-projects/chrono v1.1.2 h1:Uw7V96Ckl/pOeMBNvaEki7k6Ssgd9OX8b9PY0gpXmoU= +github.com/procyon-projects/chrono v1.1.2/go.mod h1:RwQ27W7hRaq+QUWN2yXU3BDG2FUyEQiKds8/M1FI5C8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 33fb847..ef573cc 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "aunefyren/wrapperr/files" "aunefyren/wrapperr/routes" "aunefyren/wrapperr/utilities" + "context" "flag" "fmt" "log" @@ -13,6 +14,7 @@ import ( "time" "github.com/gorilla/mux" + "github.com/procyon-projects/chrono" _ "time/tzdata" ) @@ -116,9 +118,11 @@ func main() { router.HandleFunc(root+"/api/get/login-url", routes.ApiGetLoginURL) router.HandleFunc(root+"/api/login/plex-auth", routes.ApiLoginPlexAuth) router.HandleFunc(root+"/api/validate/plex-auth", routes.ApiValidatePlexAuth) - router.HandleFunc(root+"/api/create/share-link", routes.ApiCreateShareLink) router.HandleFunc(root+"/api/get/user-share-link", routes.ApiGetUserShareLink) + + // Depends on config router.HandleFunc(root+"/api/delete/user-share-link", routes.ApiDeleteUserShareLink) + router.HandleFunc(root+"/api/create/share-link", routes.ApiCreateShareLink) // Get stats route router.HandleFunc(root+"/api/get/statistics", routes.ApiWrapperGetStatistics) @@ -149,6 +153,18 @@ func main() { http.ServeFile(w, r, "./web/txt/robots.txt") }) + // Create task scheduler for sunday reminders + taskScheduler := chrono.NewDefaultTaskScheduler() + + _, err = taskScheduler.ScheduleWithCron(func(ctx context.Context) { + log.Println("Shareable link cleaner starting.") + files.CleanOldShareableLinks() + }, "0 0 0 * * *") + + if err != nil { + log.Println("Shareable link cleaner was not scheduled successfully. Error: " + err.Error()) + } + // Start web-server log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), router)) } diff --git a/routes/no_auth.go b/routes/no_auth.go index 14341cb..bac3400 100644 --- a/routes/no_auth.go +++ b/routes/no_auth.go @@ -350,12 +350,6 @@ func ApiGetShareLink(w http.ResponseWriter, r *http.Request) { return } - if !config.PlexAuth { - log.Println("Plex Auth is not enabled in the Wrapperr configuration.") - utilities.RespondDefaultError(w, r, errors.New("Plex Auth is not enabled in the Wrapperr configuration."), 400) - return - } - if !config.CreateShareLinks { log.Println("Shareable links are not enabled in the Wrapperr configuration.") utilities.RespondDefaultError(w, r, errors.New("Shareable links are not enabled in the Wrapperr configuration."), 400) @@ -374,6 +368,9 @@ func ApiGetShareLink(w http.ResponseWriter, r *http.Request) { var link_payload models.WrapperrShareLinkGetRequest json.Unmarshal(reqBody, &link_payload) + var fileName string + var hash string + hash_array := strings.Split(link_payload.Hash, "-") if len(hash_array) < 2 { @@ -384,17 +381,29 @@ func ApiGetShareLink(w http.ResponseWriter, r *http.Request) { } - user_id := hash_array[0] - hash := "" + if config.PlexAuth { - for j := 1; j < len(hash_array); j++ { - if j != 1 { - hash = hash + "-" + for j := 1; j < len(hash_array); j++ { + if j != 1 { + hash = hash + "-" + } + hash = hash + hash_array[j] } - hash = hash + hash_array[j] + + fileName = hash_array[0] + } else { + + for j := 1; j < len(hash_array); j++ { + if j != 1 { + hash = hash + "-" + } + hash = hash + hash_array[j] + } + + fileName = hash } - share_link_object, err := files.GetLink(user_id) + share_link_object, err := files.GetLink(fileName) if err != nil { log.Println(err) @@ -415,14 +424,14 @@ func ApiGetShareLink(w http.ResponseWriter, r *http.Request) { linkTime = linkTime.Add(7 * 24 * time.Hour) - if !linkTime.Before(currentTime) && share_link_object.Hash == hash { + if !linkTime.Before(currentTime) && share_link_object.Hash == hash && !share_link_object.Expired { share_link_object.Message = "Shared link retrieved." share_link_object.Error = false ip_string := utilities.GetOriginIPString(w, r) - log.Println("Retrieved Wrapperr share link made by User ID: " + user_id + "." + ip_string) + log.Println("Retrieved Wrapperr share link made by User ID/with hash: " + fileName + "." + ip_string) utilities.RespondWithJSON(w, http.StatusOK, share_link_object) return @@ -434,7 +443,7 @@ func ApiGetShareLink(w http.ResponseWriter, r *http.Request) { if linkTime.Before(currentTime) { share_link_object.Expired = true - err = files.SaveLink(share_link_object) + err = files.SaveLink(share_link_object, config.PlexAuth) if err != nil { log.Println(err) } diff --git a/routes/statistics.go b/routes/statistics.go index 5e456fd..cd3a176 100644 --- a/routes/statistics.go +++ b/routes/statistics.go @@ -94,8 +94,20 @@ func ApiWrapperGetStatistics(w http.ResponseWriter, r *http.Request) { return } + // Set user details from Plex login user_name = plex_object.Username user_id = plex_object.ID + + // Check for friendly name using Tautulli + for i := 0; i < len(config.TautulliConfig); i++ { + _, new_username, err := modules.TautulliGetUserId(config.TautulliConfig[i].TautulliPort, config.TautulliConfig[i].TautulliIP, config.TautulliConfig[i].TautulliHttps, config.TautulliConfig[i].TautulliRoot, config.TautulliConfig[i].TautulliApiKey, user_name) + + if err == nil { + user_name = new_username + } + break + } + } log.Println("3. Auth check passed." + ip_string) @@ -114,7 +126,6 @@ func ApiWrapperGetStatistics(w http.ResponseWriter, r *http.Request) { // If no auth has been passed, caching mode is false, and user is not admin, search for the Plex details using Tautulli and PlexIdentity if !auth_passed && !wrapperr_request.CachingMode && !admin { - UserNameFound := false for i := 0; i < len(config.TautulliConfig); i++ { diff --git a/routes/user_auth.go b/routes/user_auth.go index 3dad3dd..dcf6eae 100644 --- a/routes/user_auth.go +++ b/routes/user_auth.go @@ -244,39 +244,35 @@ func ApiCreateShareLink(w http.ResponseWriter, r *http.Request) { return } - if !config.PlexAuth { - log.Println("Plex Auth is not enabled in the Wrapperr configuration.") - utilities.RespondDefaultError(w, r, errors.New("Plex Auth is not enabled in the Wrapperr configuration."), 400) - return - } - if !config.CreateShareLinks { log.Panicln("Shareable links are not enabled in the Wrapperr configuration.") utilities.RespondDefaultError(w, r, errors.New("Shareable links are not enabled in the Wrapperr configuration."), 400) return } - // Try to authorize bearer token from header - payload, err := modules.AuthorizeToken(w, r, false) - var user_name string var user_id int - if err != nil || payload.Admin { - log.Println(err) - log.Println(payload.Admin) - utilities.RespondDefaultError(w, r, errors.New("Failed to authorize request."), 401) - return - } else { - plex_object, err := modules.PlexAuthValidateToken(payload.AuthToken, config.ClientKey, config.WrapperrVersion) - if err != nil { - log.Println(err) - utilities.RespondDefaultError(w, r, errors.New("Could not validate Plex Auth login."), 500) - return - } + // Try to authorize bearer token from header + if config.PlexAuth { + payload, err := modules.AuthorizeToken(w, r, false) - user_name = plex_object.Username - user_id = plex_object.ID + if err != nil || payload.Admin { + log.Println(err) + log.Println(payload.Admin) + utilities.RespondDefaultError(w, r, errors.New("Failed to authorize request."), 401) + return + } else { + plex_object, err := modules.PlexAuthValidateToken(payload.AuthToken, config.ClientKey, config.WrapperrVersion) + if err != nil { + log.Println(err) + utilities.RespondDefaultError(w, r, errors.New("Could not validate Plex Auth login."), 500) + return + } + + user_name = plex_object.Username + user_id = plex_object.ID + } } // Read payload from Post input @@ -292,6 +288,11 @@ func ApiCreateShareLink(w http.ResponseWriter, r *http.Request) { currentTime := time.Now() hash_value := uuid.New().String() + if !config.PlexAuth { + user_name = link_payload.Data.User.Name + user_id = 0 + } + link_object := models.WrapperrShareLink{ Content: link_payload, UserID: user_id, @@ -301,7 +302,7 @@ func ApiCreateShareLink(w http.ResponseWriter, r *http.Request) { Expired: false, } - err = files.SaveLink(&link_object) + err = files.SaveLink(&link_object, config.PlexAuth) if err != nil { log.Println(err) utilities.RespondDefaultError(w, r, errors.New("Failed to save new link."), 500) @@ -477,30 +478,30 @@ func ApiDeleteUserShareLink(w http.ResponseWriter, r *http.Request) { return } - if !config.PlexAuth { - log.Println("Plex Auth is not enabled in the Wrapperr configuration.") - utilities.RespondDefaultError(w, r, errors.New("Plex Auth is not enabled in the Wrapperr configuration."), 400) - return - } - if !config.CreateShareLinks { log.Println("Shareable links are not enabled in the Wrapperr configuration.") utilities.RespondDefaultError(w, r, errors.New("Shareable links are not enabled in the Wrapperr configuration."), 400) return } - // Try to authorize bearer token from header - payload, err := modules.AuthorizeToken(w, r, false) + if !config.PlexAuth { + log.Println("Shareable links are are only deleteable if made through Plex Auth login.") + utilities.RespondDefaultError(w, r, errors.New("Shareable links are are only deleteable if made through Plex Auth login."), 400) + return + } var user_name string var user_id int + // Try to authorize bearer token from header + payload, err := modules.AuthorizeToken(w, r, false) + if err != nil || payload.Admin { log.Println(err) log.Println(payload.Admin) utilities.RespondDefaultError(w, r, errors.New("Failed to authorize request."), 401) return - } else { + } else if config.PlexAuth { plex_object, err := modules.PlexAuthValidateToken(payload.AuthToken, config.ClientKey, config.WrapperrVersion) if err != nil { log.Println(err) @@ -523,7 +524,7 @@ func ApiDeleteUserShareLink(w http.ResponseWriter, r *http.Request) { share_link_object.Date = "1970-01-01" - err = files.SaveLink(share_link_object) + err = files.SaveLink(share_link_object, config.PlexAuth) if err != nil { log.Println(err) diff --git a/web/js/get_stats.js b/web/js/get_stats.js index dd5b0bd..582a360 100644 --- a/web/js/get_stats.js +++ b/web/js/get_stats.js @@ -975,7 +975,7 @@ function load_outro() { text += "
"; - if(!link_mode && functions.create_share_links && functions.plex_auth) { + if(!link_mode && functions.create_share_links) { text += "
"; text += "
"; text += "
"; } @@ -1027,6 +1031,15 @@ function create_wrapped_link() { var wrapped_data = JSON.stringify(wrapped_form); + // Debug lines + // console.log(wrapped_data); + // return; + + var bearerToken = "" + if(functions.plex_auth) { + bearerToken = "Bearer " + cookie + } + var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && (this.status == 200 || this.status == 400 || this.status == 500)) { @@ -1046,7 +1059,6 @@ function create_wrapped_link() { document.getElementById("share_wrapped_button").style.opacity = '1'; } else { - document.getElementById('share_wrapped_results_url').innerHTML = window.location.href.split('?')[0] + '?hash=' + result.data; document.getElementById('share_wrapped_results_title_div').style.display = 'block'; document.getElementById('share_wrapped_results_div').style.display = 'flex'; @@ -1059,7 +1071,7 @@ function create_wrapped_link() { xhttp.withCredentials = true; xhttp.open("post", api_url + "create/share-link"); xhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - xhttp.setRequestHeader("Authorization", "Bearer " + cookie); + xhttp.setRequestHeader("Authorization", bearerToken); xhttp.send(wrapped_data); }