diff --git a/handlers.go b/handlers.go index 86bd4f9..9bcf6e6 100644 --- a/handlers.go +++ b/handlers.go @@ -31,6 +31,7 @@ type Response struct { Error string User string + IsAdmin bool Section string // Paging @@ -54,16 +55,28 @@ type Response struct { Youtubes []youtube.Video } +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + func NewResponse(r *http.Request, ps httprouter.Params) *Response { diskInfo, err := NewDiskInfo(datadir) if err != nil { panic(err) } + user, _, _ := r.BasicAuth() + isAdmin := stringInSlice(user, httpAdminUsers) return &Response{ Config: config.Get(), Request: r, Params: &ps, User: ps.ByName("user"), + IsAdmin: isAdmin, HTTPHost: httpHost, Version: version, Backlink: backlink, diff --git a/main.go b/main.go index ce0ed27..d7b2e28 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,9 @@ var ( debug bool httpAddr string httpAdmins arrayFlags + httpAdminUsers []string + httpReadOnlys arrayFlags + httpReadOnlyUsers []string httpHost string httpPrefix string letsencrypt bool @@ -61,7 +64,8 @@ func init() { cli.StringVar(&datadir, "data-dir", "/data", "data directory") cli.BoolVar(&debug, "debug", false, "debug mode") cli.StringVar(&httpAddr, "http-addr", ":80", "listen address") - cli.Var(&httpAdmins, "http-admin", "HTTP basic auth user/password for admin.") + cli.Var(&httpAdmins, "http-admin", "HTTP basic auth user/password for admins.") + cli.Var(&httpReadOnlys, "http-read-only", "HTTP basic auth user/password for read only users.") cli.StringVar(&httpHost, "http-host", "", "HTTP host") cli.StringVar(&httpPrefix, "http-prefix", "/streamlist", "HTTP URL prefix (not actually supported yet!)") cli.BoolVar(&letsencrypt, "letsencrypt", false, "enable TLS using Let's Encrypt") @@ -74,6 +78,17 @@ func main() { cli.Parse(os.Args[1:]) + for _, httpUser := range httpAdmins { + split := strings.Split(httpUser, ":") + httpUsername := split[0] + httpAdminUsers = append(httpAdminUsers, httpUsername) + } + for _, httpUser := range httpReadOnlys { + split := strings.Split(httpUser, ":") + httpUsername := split[0] + httpReadOnlyUsers = append(httpReadOnlyUsers, httpUsername) + } + // logtailer logtail, err = logtailer.NewLogtailer(200 * 1024) if err != nil { @@ -159,55 +174,55 @@ func main() { r.HandleMethodNotAllowed = false // Handlers - r.GET("/", Log(Auth(index, false))) - r.GET(Prefix("/logs"), Log(Auth(logs, false))) - r.GET(Prefix("/"), Log(Auth(home, false))) + r.GET("/", Log(auth(index, "readonly"))) + r.GET(Prefix("/logs"), Log(auth(logs, "admin"))) + r.GET(Prefix("/"), Log(auth(home, "readonly"))) // Library - r.GET(Prefix("/library"), Log(Auth(library, false))) + r.GET(Prefix("/library"), Log(auth(library, "readonly"))) // Media - r.GET(Prefix("/media/thumbnail/:media"), Log(Auth(thumbnailMedia, false))) - r.GET(Prefix("/media/view/:media"), Log(Auth(viewMedia, false))) - r.GET(Prefix("/media/delete/:media"), Log(Auth(deleteMedia, false))) - r.GET(Prefix("/media/access/:filename"), Auth(streamMedia, false)) - r.GET(Prefix("/media/download/:filename"), Auth(downloadMedia, false)) + r.GET(Prefix("/media/thumbnail/:media"), Log(auth(thumbnailMedia, "readonly"))) + r.GET(Prefix("/media/view/:media"), Log(auth(viewMedia, "readonly"))) + r.GET(Prefix("/media/delete/:media"), Log(auth(deleteMedia, "admin"))) + r.GET(Prefix("/media/access/:filename"), auth(streamMedia, "readonly")) + r.GET(Prefix("/media/download/:filename"), auth(downloadMedia, "readonly")) // Publicly accessible streaming (using playlist id as "auth") - r.GET(Prefix("/stream/:list/:filename"), Auth(streamMedia, true)) + r.GET(Prefix("/stream/:list/:filename"), auth(streamMedia, "none")) // Import - r.GET(Prefix("/import"), Log(Auth(importHandler, false))) + r.GET(Prefix("/import"), Log(auth(importHandler, "admin"))) // Archiver - r.GET(Prefix("/archiver/jobs"), Auth(archiverJobs, false)) - r.POST(Prefix("/archiver/save/:id"), Log(Auth(archiverSave, false))) - r.GET(Prefix("/archiver/cancel/:id"), Log(Auth(archiverCancel, false))) + r.GET(Prefix("/archiver/jobs"), auth(archiverJobs, "admin")) + r.POST(Prefix("/archiver/save/:id"), Log(auth(archiverSave, "admin"))) + r.GET(Prefix("/archiver/cancel/:id"), Log(auth(archiverCancel, "admin"))) // List - r.GET(Prefix("/create"), Log(Auth(createList, false))) - r.POST(Prefix("/create"), Log(Auth(createList, false))) - r.POST(Prefix("/add/:list/:media"), Log(Auth(addMediaList, false))) - r.POST(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, false))) - r.GET(Prefix("/remove/:list/:media"), Log(Auth(removeMediaList, false))) + r.GET(Prefix("/create"), Log(auth(createList, "admin"))) + r.POST(Prefix("/create"), Log(auth(createList, "admin"))) + r.POST(Prefix("/add/:list/:media"), Log(auth(addMediaList, "admin"))) + r.POST(Prefix("/remove/:list/:media"), Log(auth(removeMediaList, "admin"))) + r.GET(Prefix("/remove/:list/:media"), Log(auth(removeMediaList, "admin"))) - r.GET(Prefix("/edit/:id"), Log(Auth(editList, false))) - r.POST(Prefix("/edit/:id"), Log(Auth(editList, false))) - r.GET(Prefix("/shuffle/:id"), Log(Auth(shuffleList, false))) - r.GET(Prefix("/play/:id"), Log(Auth(playList, true))) - r.GET(Prefix("/m3u/:id"), Log(Auth(m3uList, true))) - r.GET(Prefix("/podcast/:id"), Log(Auth(podcastList, true))) + r.GET(Prefix("/edit/:id"), Log(auth(editList, "admin"))) + r.POST(Prefix("/edit/:id"), Log(auth(editList, "admin"))) + r.GET(Prefix("/shuffle/:id"), Log(auth(shuffleList, "admin"))) + r.GET(Prefix("/play/:id"), Log(auth(playList, "none"))) + r.GET(Prefix("/m3u/:id"), Log(auth(m3uList, "none"))) + r.GET(Prefix("/podcast/:id"), Log(auth(podcastList, "none"))) - r.POST(Prefix("/config"), Log(Auth(configHandler, false))) + r.POST(Prefix("/config"), Log(auth(configHandler, "admin"))) - r.GET(Prefix("/delete/:id"), Log(Auth(deleteList, false))) + r.GET(Prefix("/delete/:id"), Log(auth(deleteList, "admin"))) // API - r.GET(Prefix("/v1/status"), Log(Auth(v1status, true))) + r.GET(Prefix("/v1/status"), Log(auth(v1status, "none"))) // Assets - r.GET(Prefix("/static/*path"), Auth(staticAsset, true)) // TODO: Auth() but by checking Origin/Referer for a valid playlist ID? - r.GET(Prefix("/logo.png"), Log(Auth(logo, true))) + r.GET(Prefix("/static/*path"), auth(staticAsset, "none")) + r.GET(Prefix("/logo.png"), Log(auth(logo, "none"))) // // Server diff --git a/templates/header.html b/templates/header.html index 38dd8b4..cb57c67 100644 --- a/templates/header.html +++ b/templates/header.html @@ -18,7 +18,6 @@ - {{if $.User}} {{end}} diff --git a/templates/home.html b/templates/home.html index a10f5b2..65703eb 100644 --- a/templates/home.html +++ b/templates/home.html @@ -1,7 +1,9 @@ {{template "header.html" .}}
- New Playlist + {{if $.IsAdmin}} + New Playlist + {{end}}

Playlists

@@ -22,8 +24,10 @@

Playlists

{{with $tl := $list.TotalLength}} {{duration $tl}} {{end}} -    - + {{if $.IsAdmin}} +    + + {{end}} {{end}} diff --git a/templates/library.html b/templates/library.html index db89c8c..8bb0bc6 100644 --- a/templates/library.html +++ b/templates/library.html @@ -35,15 +35,17 @@

Library

{{range $list := $.Lists}}
{{$hasmedia := $list.HasMedia $media}} - - + +
{{end}} {{duration $media.Length}} -    - + {{if $.IsAdmin}} +    + + {{end}} {{end}} diff --git a/web.go b/web.go index db81389..adb7aca 100644 --- a/web.go +++ b/web.go @@ -117,14 +117,27 @@ func Log(h httprouter.Handle) httprouter.Handle { } } -func Auth(h httprouter.Handle, optional bool) httprouter.Handle { +func auth(h httprouter.Handle, role string) httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { user := "" + // none role is no auth required + if role == "none" { + h(w, r, ps) + return + } + // Method: Basic Auth (if we're not behind a reverse proxy, use basic auth) if httpAdmins != nil { - for _, httpAdmin := range httpAdmins { - split := strings.Split(httpAdmin, ":") + var userList []string + // Admin are always OK + userList = append(userList, httpAdmins...) + // If role readonly, we add readonly users + if role == "readonly" { + userList = append(userList, httpReadOnlys...) + } + for _, httpUser := range userList { + split := strings.Split(httpUser, ":") httpUsername := split[0] httpPassword := split[1] user, password, _ := r.BasicAuth() @@ -134,10 +147,6 @@ func Auth(h httprouter.Handle, optional bool) httprouter.Handle { return } } - if optional { - h(w, r, ps) - return - } w.Header().Set("WWW-Authenticate", `Basic realm="Sign-in Required"`) http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) return @@ -155,7 +164,8 @@ func Auth(h httprouter.Handle, optional bool) httprouter.Handle { user = r.Header.Get(reverseProxyAuthHeader) } - if user == "" && !optional { + //if user == "" && !optional { + if user == "" { logger.Errorf("auth failed: client %q", clientIP) if backlink != "" { http.Redirect(w, r, backlink, http.StatusFound)