Create share links without Plex Auth
Delete old share links once a day
Pull friendly name for requester from Tautulli
This commit is contained in:
aunefyren 2023-10-22 14:25:41 +02:00
parent 07a12ade2b
commit 0e69e26941
8 changed files with 183 additions and 65 deletions

View file

@ -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())
}
}
}
}
}
}

2
go.mod
View file

@ -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

8
go.sum
View file

@ -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=

18
main.go
View file

@ -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))
}

View file

@ -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)
}

View file

@ -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++ {

View file

@ -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)

View file

@ -975,7 +975,7 @@ function load_outro() {
text += "<div class='boks2' style='margin-top:5em;'>";
if(!link_mode && functions.create_share_links && functions.plex_auth) {
if(!link_mode && functions.create_share_links) {
text += "<div class='form-group' id='share_wrapped_div' style=''>";
text += "<button class='form-control btn' name='share_wrapped_button' id='share_wrapped_button' onclick='create_wrapped_link()'>";
@ -992,8 +992,12 @@ function load_outro() {
text += "<div class='form-control btn' id='share_wrapped_delete_button' style='background-color: var(--white); cursor: default;'>";
text += "<span id='share_wrapped_results_url' class='share_wrapped_url' style=''></span>";
text += "<img id='share_wrapped_copy' src='assets/share.svg' style='' title='Click to copy the URL' onclick='copy_link_user();'>";
text += "<img id='share_wrapped_delete' src='assets/trash.svg' style='' title='Click to delete this URL' onclick='delete_new_link_user();'>";
text += "</div>";
if(functions.plex_auth) {
text += "<img id='share_wrapped_delete' src='assets/trash.svg' style='' title='Click to delete this URL' onclick='delete_new_link_user();'>";
}
text += "</div>";
text += "</div>";
}
@ -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);
}