]> code.octet-stream.net Git - broadcaster/blobdiff - server/main.go
More UI improvements
[broadcaster] / server / main.go
index 5f0a7487c3aff8e0082588386f0f32abc745ae3b..d1d47d329afe6029632e073b35d5f1af0fcc800d 100644 (file)
@@ -5,6 +5,7 @@ import (
        "embed"
        "flag"
        "fmt"
        "embed"
        "flag"
        "fmt"
+       "golang.org/x/crypto/bcrypt"
        "golang.org/x/net/websocket"
        "html/template"
        "io"
        "golang.org/x/net/websocket"
        "html/template"
        "io"
@@ -76,7 +77,7 @@ func main() {
        // Public routes
 
        http.HandleFunc("/login", logInPage)
        // Public routes
 
        http.HandleFunc("/login", logInPage)
-       http.Handle("/file-downloads/", http.StripPrefix("/file-downloads/", http.FileServer(http.Dir(config.AudioFilesPath))))
+       http.Handle("/file-downloads/", applyDisposition(http.StripPrefix("/file-downloads/", http.FileServer(http.Dir(config.AudioFilesPath)))))
 
        // Authenticated routes
 
 
        // Authenticated routes
 
@@ -92,7 +93,7 @@ func main() {
 
        // Admin routes
 
 
        // Admin routes
 
-       // TODO: user management
+       http.Handle("/users/", requireAdmin(userSection))
 
        // Websocket routes, which perform their own auth
 
 
        // Websocket routes, which perform their own auth
 
@@ -105,6 +106,24 @@ func main() {
        }
 }
 
        }
 }
 
+type DispositionMiddleware struct {
+       handler http.Handler
+}
+
+func (m DispositionMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+       log.Println("path", r.URL.Path)
+       if r.URL.Path != "/file-downloads/" {
+               w.Header().Add("Content-Disposition", "attachment")
+       }
+       m.handler.ServeHTTP(w, r)
+}
+
+func applyDisposition(handler http.Handler) DispositionMiddleware {
+       return DispositionMiddleware{
+               handler: handler,
+       }
+}
+
 type authenticatedHandler func(http.ResponseWriter, *http.Request, User)
 
 type AuthMiddleware struct {
 type authenticatedHandler func(http.ResponseWriter, *http.Request, User)
 
 type AuthMiddleware struct {
@@ -137,14 +156,16 @@ func requireAdmin(handler authenticatedHandler) AuthMiddleware {
 
 type HeaderData struct {
        SelectedMenu string
 
 type HeaderData struct {
        SelectedMenu string
-       Username     string
+       User         User
+       Version      string
 }
 
 }
 
-func renderHeader(w http.ResponseWriter, selectedMenu string) {
+func renderHeader(w http.ResponseWriter, selectedMenu string, user User) {
        tmpl := template.Must(template.ParseFS(content, "templates/header.html"))
        data := HeaderData{
                SelectedMenu: selectedMenu,
        tmpl := template.Must(template.ParseFS(content, "templates/header.html"))
        data := HeaderData{
                SelectedMenu: selectedMenu,
-               Username:     "username",
+               User:         user,
+               Version:      version,
        }
        err := tmpl.Execute(w, data)
        if err != nil {
        }
        err := tmpl.Execute(w, data)
        if err != nil {
@@ -166,7 +187,7 @@ type HomeData struct {
 }
 
 func homePage(w http.ResponseWriter, r *http.Request, user User) {
 }
 
 func homePage(w http.ResponseWriter, r *http.Request, user User) {
-       renderHeader(w, "status")
+       renderHeader(w, "status", user)
        tmpl := template.Must(template.ParseFS(content, "templates/index.html"))
        data := HomeData{
                LoggedIn: true,
        tmpl := template.Must(template.ParseFS(content, "templates/index.html"))
        data := HomeData{
                LoggedIn: true,
@@ -199,7 +220,7 @@ func logInPage(w http.ResponseWriter, r *http.Request) {
        data := LogInData{
                Error: errText,
        }
        data := LogInData{
                Error: errText,
        }
-       renderHeader(w, "")
+       renderHeader(w, "", User{})
        tmpl := template.Must(template.ParseFS(content, "templates/login.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
        tmpl := template.Must(template.ParseFS(content, "templates/login.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
@@ -212,20 +233,20 @@ func playlistSection(w http.ResponseWriter, r *http.Request, user User) {
                return
        }
        if path[2] == "new" {
                return
        }
        if path[2] == "new" {
-               editPlaylistPage(w, r, 0)
+               editPlaylistPage(w, r, 0, user)
        } else if path[2] == "submit" && r.Method == "POST" {
                submitPlaylist(w, r)
        } else if path[2] == "delete" && r.Method == "POST" {
                deletePlaylist(w, r)
        } else if path[2] == "" {
        } else if path[2] == "submit" && r.Method == "POST" {
                submitPlaylist(w, r)
        } else if path[2] == "delete" && r.Method == "POST" {
                deletePlaylist(w, r)
        } else if path[2] == "" {
-               playlistsPage(w, r)
+               playlistsPage(w, r, user)
        } else {
                id, err := strconv.Atoi(path[2])
                if err != nil {
                        http.NotFound(w, r)
                        return
                }
        } else {
                id, err := strconv.Atoi(path[2])
                if err != nil {
                        http.NotFound(w, r)
                        return
                }
-               editPlaylistPage(w, r, id)
+               editPlaylistPage(w, r, id, user)
        }
 }
 
        }
 }
 
@@ -240,7 +261,7 @@ func fileSection(w http.ResponseWriter, r *http.Request, user User) {
        } else if path[2] == "delete" && r.Method == "POST" {
                deleteFile(w, r)
        } else if path[2] == "" {
        } else if path[2] == "delete" && r.Method == "POST" {
                deleteFile(w, r)
        } else if path[2] == "" {
-               filesPage(w, r)
+               filesPage(w, r, user)
        } else {
                http.NotFound(w, r)
                return
        } else {
                http.NotFound(w, r)
                return
@@ -254,21 +275,138 @@ func radioSection(w http.ResponseWriter, r *http.Request, user User) {
                return
        }
        if path[2] == "new" {
                return
        }
        if path[2] == "new" {
-               editRadioPage(w, r, 0)
+               editRadioPage(w, r, 0, user)
        } else if path[2] == "submit" && r.Method == "POST" {
                submitRadio(w, r)
        } else if path[2] == "delete" && r.Method == "POST" {
                deleteRadio(w, r)
        } else if path[2] == "" {
        } else if path[2] == "submit" && r.Method == "POST" {
                submitRadio(w, r)
        } else if path[2] == "delete" && r.Method == "POST" {
                deleteRadio(w, r)
        } else if path[2] == "" {
-               radiosPage(w, r)
+               radiosPage(w, r, user)
+       } else {
+               id, err := strconv.Atoi(path[2])
+               if err != nil {
+                       http.NotFound(w, r)
+                       return
+               }
+               editRadioPage(w, r, id, user)
+       }
+}
+
+func userSection(w http.ResponseWriter, r *http.Request, user User) {
+       path := strings.Split(r.URL.Path, "/")
+       if len(path) != 3 {
+               http.NotFound(w, r)
+               return
+       }
+       if path[2] == "new" {
+               editUserPage(w, r, 0, user)
+       } else if path[2] == "submit" && r.Method == "POST" {
+               submitUser(w, r)
+       } else if path[2] == "delete" && r.Method == "POST" {
+               deleteUser(w, r)
+       } else if path[2] == "reset-password" && r.Method == "POST" {
+               resetUserPassword(w, r)
+       } else if path[2] == "" {
+               usersPage(w, r, user)
        } else {
                id, err := strconv.Atoi(path[2])
                if err != nil {
                        http.NotFound(w, r)
                        return
                }
        } else {
                id, err := strconv.Atoi(path[2])
                if err != nil {
                        http.NotFound(w, r)
                        return
                }
-               editRadioPage(w, r, id)
+               editUserPage(w, r, id, user)
+       }
+}
+
+type EditUserPageData struct {
+       User User
+}
+
+func editUserPage(w http.ResponseWriter, r *http.Request, id int, user User) {
+       var data EditUserPageData
+       if id != 0 {
+               user, err := db.GetUserById(id)
+               if err != nil {
+                       http.NotFound(w, r)
+                       return
+               }
+               data.User = user
        }
        }
+       renderHeader(w, "users", user)
+       tmpl := template.Must(template.ParseFS(content, "templates/user.html"))
+       tmpl.Execute(w, data)
+       renderFooter(w)
+}
+
+func submitUser(w http.ResponseWriter, r *http.Request) {
+       err := r.ParseForm()
+       if err == nil {
+               id, err := strconv.Atoi(r.Form.Get("userId"))
+               if err != nil {
+                       return
+               }
+               if id == 0 {
+                       newPassword := r.Form.Get("password")
+                       hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
+                       if err != nil {
+                               return
+                       }
+                       user := User{
+                               Id:           0,
+                               Username:     r.Form.Get("username"),
+                               IsAdmin:      r.Form.Get("isAdmin") == "1",
+                               PasswordHash: string(hashed),
+                       }
+                       db.CreateUser(user)
+               } else {
+                       user, err := db.GetUserById(id)
+                       if err != nil {
+                               http.NotFound(w, r)
+                               return
+                       }
+                       db.SetUserIsAdmin(user.Username, r.Form.Get("isAdmin") == "1")
+               }
+       }
+       http.Redirect(w, r, "/users/", http.StatusFound)
+}
+
+func deleteUser(w http.ResponseWriter, r *http.Request) {
+       err := r.ParseForm()
+       if err == nil {
+               id, err := strconv.Atoi(r.Form.Get("userId"))
+               if err != nil {
+                       return
+               }
+               user, err := db.GetUserById(id)
+               if err != nil {
+                       http.NotFound(w, r)
+                       return
+               }
+               db.DeleteUser(user.Username)
+       }
+       http.Redirect(w, r, "/users/", http.StatusFound)
+}
+
+func resetUserPassword(w http.ResponseWriter, r *http.Request) {
+       err := r.ParseForm()
+       if err == nil {
+               id, err := strconv.Atoi(r.Form.Get("userId"))
+               if err != nil {
+                       return
+               }
+               user, err := db.GetUserById(id)
+               if err != nil {
+                       http.NotFound(w, r)
+                       return
+               }
+               newPassword := r.Form.Get("newPassword")
+               hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
+               if err != nil {
+                       return
+               }
+               db.SetUserPassword(user.Username, string(hashed))
+       }
+       http.Redirect(w, r, "/users/", http.StatusFound)
 }
 
 type ChangePasswordPageData struct {
 }
 
 type ChangePasswordPageData struct {
@@ -295,7 +433,7 @@ func changePasswordPage(w http.ResponseWriter, r *http.Request, user User) {
                        data.ShowForm = false
                        cookie, err := r.Cookie("broadcast_session")
                        if err == nil {
                        data.ShowForm = false
                        cookie, err := r.Cookie("broadcast_session")
                        if err == nil {
-                               log.Println("clearing other sessions for username", user.Username, "token", cookie.Value)
+                               log.Println("Clearing other sessions for username", user.Username, "token", cookie.Value)
                                db.ClearOtherSessions(user.Username, cookie.Value)
                        }
                }
                                db.ClearOtherSessions(user.Username, cookie.Value)
                        }
                }
@@ -303,7 +441,7 @@ func changePasswordPage(w http.ResponseWriter, r *http.Request, user User) {
                data.Message = ""
                data.ShowForm = true
        }
                data.Message = ""
                data.ShowForm = true
        }
-       renderHeader(w, "change-password")
+       renderHeader(w, "change-password", user)
        tmpl := template.Must(template.ParseFS(content, "templates/change_password.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
        tmpl := template.Must(template.ParseFS(content, "templates/change_password.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
@@ -312,15 +450,35 @@ func changePasswordPage(w http.ResponseWriter, r *http.Request, user User) {
        renderFooter(w)
 }
 
        renderFooter(w)
 }
 
+type UsersPageData struct {
+       Users []User
+}
+
+func usersPage(w http.ResponseWriter, _ *http.Request, user User) {
+       renderHeader(w, "users", user)
+       data := UsersPageData{
+               Users: db.GetUsers(),
+       }
+       tmpl := template.Must(template.ParseFS(content, "templates/users.html"))
+       err := tmpl.Execute(w, data)
+       if err != nil {
+               log.Fatal(err)
+       }
+       renderFooter(w)
+}
+
 type PlaylistsPageData struct {
        Playlists []Playlist
 }
 
 type PlaylistsPageData struct {
        Playlists []Playlist
 }
 
-func playlistsPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "playlists")
+func playlistsPage(w http.ResponseWriter, _ *http.Request, user User) {
+       renderHeader(w, "playlists", user)
        data := PlaylistsPageData{
                Playlists: db.GetPlaylists(),
        }
        data := PlaylistsPageData{
                Playlists: db.GetPlaylists(),
        }
+       for i := range data.Playlists {
+               data.Playlists[i].StartTime = strings.Replace(data.Playlists[i].StartTime, "T", " ", -1)
+       }
        tmpl := template.Must(template.ParseFS(content, "templates/playlists.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
        tmpl := template.Must(template.ParseFS(content, "templates/playlists.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
@@ -333,8 +491,8 @@ type RadiosPageData struct {
        Radios []Radio
 }
 
        Radios []Radio
 }
 
-func radiosPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "radios")
+func radiosPage(w http.ResponseWriter, _ *http.Request, user User) {
+       renderHeader(w, "radios", user)
        data := RadiosPageData{
                Radios: db.GetRadios(),
        }
        data := RadiosPageData{
                Radios: db.GetRadios(),
        }
@@ -352,7 +510,7 @@ type EditPlaylistPageData struct {
        Files    []string
 }
 
        Files    []string
 }
 
-func editPlaylistPage(w http.ResponseWriter, r *http.Request, id int) {
+func editPlaylistPage(w http.ResponseWriter, r *http.Request, id int, user User) {
        var data EditPlaylistPageData
        for _, f := range files.Files() {
                data.Files = append(data.Files, f.Name)
        var data EditPlaylistPageData
        for _, f := range files.Files() {
                data.Files = append(data.Files, f.Name)
@@ -371,7 +529,7 @@ func editPlaylistPage(w http.ResponseWriter, r *http.Request, id int) {
                data.Playlist = playlist
                data.Entries = db.GetEntriesForPlaylist(id)
        }
                data.Playlist = playlist
                data.Entries = db.GetEntriesForPlaylist(id)
        }
-       renderHeader(w, "radios")
+       renderHeader(w, "playlists", user)
        tmpl := template.Must(template.ParseFS(content, "templates/playlist.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
        tmpl := template.Must(template.ParseFS(content, "templates/playlist.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
@@ -447,7 +605,7 @@ type EditRadioPageData struct {
        Radio Radio
 }
 
        Radio Radio
 }
 
-func editRadioPage(w http.ResponseWriter, r *http.Request, id int) {
+func editRadioPage(w http.ResponseWriter, r *http.Request, id int, user User) {
        var data EditRadioPageData
        if id == 0 {
                data.Radio.Name = "New Radio"
        var data EditRadioPageData
        if id == 0 {
                data.Radio.Name = "New Radio"
@@ -460,7 +618,7 @@ func editRadioPage(w http.ResponseWriter, r *http.Request, id int) {
                }
                data.Radio = radio
        }
                }
                data.Radio = radio
        }
-       renderHeader(w, "radios")
+       renderHeader(w, "radios", user)
        tmpl := template.Must(template.ParseFS(content, "templates/radio.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
        tmpl := template.Must(template.ParseFS(content, "templates/radio.html"))
        tmpl.Execute(w, data)
        renderFooter(w)
@@ -502,12 +660,11 @@ type FilesPageData struct {
        Files []FileSpec
 }
 
        Files []FileSpec
 }
 
-func filesPage(w http.ResponseWriter, _ *http.Request) {
-       renderHeader(w, "files")
+func filesPage(w http.ResponseWriter, _ *http.Request, user User) {
+       renderHeader(w, "files", user)
        data := FilesPageData{
                Files: files.Files(),
        }
        data := FilesPageData{
                Files: files.Files(),
        }
-       log.Println("file page data", data)
        tmpl := template.Must(template.ParseFS(content, "templates/files.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
        tmpl := template.Must(template.ParseFS(content, "templates/files.html"))
        err := tmpl.Execute(w, data)
        if err != nil {
@@ -536,15 +693,19 @@ func uploadFile(w http.ResponseWriter, r *http.Request) {
                f, _ := os.Create(path)
                defer f.Close()
                io.Copy(f, file)
                f, _ := os.Create(path)
                defer f.Close()
                io.Copy(f, file)
-               log.Println("uploaded file to", path)
+               log.Println("Uploaded file to", path)
                files.Refresh()
        }
        http.Redirect(w, r, "/files/", http.StatusFound)
 }
 
 func logOutPage(w http.ResponseWriter, r *http.Request, user User) {
                files.Refresh()
        }
        http.Redirect(w, r, "/files/", http.StatusFound)
 }
 
 func logOutPage(w http.ResponseWriter, r *http.Request, user User) {
+       cookie, err := r.Cookie("broadcast_session")
+       if err == nil {
+               db.ClearSession(user.Username, cookie.Value)
+       }
        clearSessionCookie(w)
        clearSessionCookie(w)
-       renderHeader(w, "")
+       renderHeader(w, "", user)
        tmpl := template.Must(template.ParseFS(content, "templates/logout.html"))
        tmpl.Execute(w, nil)
        renderFooter(w)
        tmpl := template.Must(template.ParseFS(content, "templates/logout.html"))
        tmpl.Execute(w, nil)
        renderFooter(w)