Compare commits

..

No commits in common. "ca5b14fc13c79a5aa224f60ec751a807354ffc7d" and "e845d88b6c9ec6389a5cb71357b8126c048a8939" have entirely different histories.

8 changed files with 17 additions and 355 deletions

View File

@ -2,9 +2,8 @@ package main
import (
"os"
"playback-device-server/data"
"playback-device-server/management"
"playback-device-server/server"
"playback-device-server/users"
"sync"
"github.com/rs/zerolog"
@ -21,7 +20,7 @@ func main() {
os.Exit(1)
}
userDatabase := data.UserDatabase{}
userDatabase := users.UserDatabase{}
userDatabase.SetDirectory(configuration.DatabaseDirectory)
err = userDatabase.Initialize()
if err != nil {
@ -30,7 +29,7 @@ func main() {
}
defer userDatabase.Close()
userManager := management.UserManager{}
userManager := users.UserManager{}
err = userManager.Initialize(&userDatabase)
if err != nil {
log.Error().Err(err).Msg("failed to initialize user manager")
@ -42,14 +41,6 @@ func main() {
webServer.Initialize()
defer webServer.Close()
authenticator := server.Authenticator{}
authenticator.SetUserManager(&userManager)
userApiHandler := server.UsersApiHandler{}
userApiHandler.SetUserManager(&userManager)
userApiHandler.SetRouter(webServer.Router())
userApiHandler.Initialize(&authenticator)
var wg sync.WaitGroup
wg.Add(1)
go func() {

View File

@ -1,66 +0,0 @@
package server
import (
"fmt"
d "playback-device-server/data"
m "playback-device-server/management"
"strings"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
)
type AuthContext struct {
echo.Context
User *d.User
Session *d.Session
}
type Authenticator struct {
userManager *m.UserManager
}
func (r *Authenticator) SetUserManager(userManager *m.UserManager) {
r.userManager = userManager
}
func (r *Authenticator) Authenticate(path string, exceptions []string) func(next echo.HandlerFunc) echo.HandlerFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(context echo.Context) error {
requestURI := context.Request().RequestURI
if !strings.HasPrefix(requestURI, path) {
return next(context)
}
for _, exception := range exceptions {
if strings.HasPrefix(requestURI, exception) {
return next(context)
}
}
cookie, err := context.Cookie("token")
if err != nil {
SendError(401, context, "no session token found")
return err
}
session, error := r.userManager.GetSession(cookie.Value)
if error != nil {
SendError(401, context, fmt.Sprintf("session not found: %s", cookie.Value))
return fmt.Errorf("session not found: %s", cookie.Value)
}
user, error := r.userManager.GetUserById(session.UserID)
if error != nil {
log.Error().Err(error).Msg("error getting user by id")
SendError(401, context, "no user found for given session")
return error
}
if user == nil {
SendError(401, context, "no user found for given session")
return fmt.Errorf("no user found for session '%s'", cookie.Value)
}
authContext := AuthContext{Context: context, User: user, Session: session}
return next(authContext)
}
}
}

View File

@ -1,262 +0,0 @@
package server
import (
"fmt"
"net/http"
d "playback-device-server/data"
m "playback-device-server/management"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
)
type UsersApiHandler struct {
router *echo.Echo
userManager *m.UserManager
}
func (r *UsersApiHandler) Initialize(authenticator *Authenticator) {
r.router.Use(authenticator.Authenticate("/api/users", []string{"/api/users/login"}))
usersApi := r.router.Group("/api/users")
usersApi.POST("/login", r.handleLogin)
usersApi.POST("/logout", r.handleLogout)
usersApi.GET("/session/info", r.handleGetSessionInfo)
usersApi.POST("/newpassword", r.handleChangePassword)
usersApi.PUT("/:id", r.handleUpdateConfig)
usersApi.GET("", r.handleGetUsers)
usersApi.POST("", r.handleCreateUser)
usersApi.DELETE("/:id", r.handleDeleteUser)
}
func (r UsersApiHandler) handleLogin(context echo.Context) error {
var loginData struct {
Username string `json:"username"`
Password string `json:"password"`
}
var errorResponse struct {
Error string `json:"error"`
}
error := context.Bind(&loginData)
if error != nil {
log.Info().Msgf("failed to bind login data: %s", error)
errorResponse.Error = fmt.Sprintf("%s", error)
context.JSON(400, errorResponse)
return error
}
token, error := r.userManager.Login(loginData.Username, loginData.Password)
if error != nil {
log.Info().Msgf("failed to login: %s", error)
errorResponse.Error = fmt.Sprintf("%s", error)
context.JSON(400, errorResponse)
return error
}
thirdyDays := 30 * 24 * 60 * 60
cookie := &http.Cookie{
Name: "token",
Value: token,
Path: "/",
HttpOnly: true,
MaxAge: thirdyDays,
}
context.SetCookie(cookie)
return context.JSON(200, "")
}
func (r UsersApiHandler) handleLogout(context echo.Context) error {
r.removeTokenCookie(&context)
context.JSON(200, "")
authContext := context.(AuthContext)
session := authContext.Session
r.userManager.DeleteSession(session.Token)
return nil
}
func (r UsersApiHandler) handleChangePassword(context echo.Context) error {
authContext := context.(AuthContext)
user := authContext.User
var data struct {
CurrentPassword string `json:"currentPassword"`
NewPassword string `json:"newPassword"`
}
error := context.Bind(&data)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to change password: %s", error))
return error
}
error = r.userManager.UpdatePassword(data.CurrentPassword, data.NewPassword, user)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to change password: %s", error))
}
return error
}
func (r UsersApiHandler) handleGetSessionInfo(context echo.Context) error {
authContext := context.(AuthContext)
user := authContext.User
response := struct {
UserId string `json:"user_id"`
Username string `json:"username"`
IsAdmin bool `json:"is_admin"`
}{
UserId: user.ID,
Username: user.Username,
IsAdmin: user.IsAdmin,
}
return context.JSON(200, response)
}
func (r UsersApiHandler) handleGetUsers(context echo.Context) error {
users, error := r.userManager.GetUsers()
if error != nil {
SendError(500, context, fmt.Sprintf("failed to get user list: %s", error))
}
return context.JSON(200, users)
}
func (r UsersApiHandler) handleCreateUser(context echo.Context) error {
authContext := context.(AuthContext)
user := authContext.User
if !user.IsAdmin {
SendError(403, context, "permission denied")
log.Info().Msg("failed to delete user: permission denied")
return fmt.Errorf("permission denied")
}
var newUser d.User
error := context.Bind(&newUser)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to create user: %s", error))
return error
}
id, error := r.userManager.CreateUser(&newUser)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to create user: %s", error))
return error
}
user.ID = id
return context.JSON(200, user)
}
func (r UsersApiHandler) handleDeleteUser(context echo.Context) error {
authContext := context.(AuthContext)
user := authContext.User
if !user.IsAdmin {
SendError(403, context, "permission denied")
log.Info().Msg("failed to delete user: permission denied")
return fmt.Errorf("permission denied")
}
id := context.Param("id")
exists, error := r.userManager.UserIdExists(id)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to delete user: %s", error))
return error
}
if !exists {
SendError(404, context, fmt.Sprintf("Failed to delete user: Could not find user id %s", id))
return fmt.Errorf("not found")
}
error = r.userManager.DeleteUser(id)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to delete user: %s", error))
return error
}
return context.JSON(200, "")
}
func (r *UsersApiHandler) removeTokenCookie(context *echo.Context) {
cookie := &http.Cookie{
Name: "token",
Path: "/",
HttpOnly: true,
MaxAge: -1,
}
(*context).SetCookie(cookie)
}
func (r *UsersApiHandler) handleUpdateConfig(context echo.Context) error {
authContext := context.(AuthContext)
user := authContext.User
if !user.IsAdmin {
SendError(403, context, "permission denied")
log.Info().Msg("failed to delete user: permission denied")
return fmt.Errorf("permission denied")
}
id := context.Param("id")
data := struct {
IsAdmin int `json:"is_admin"`
}{
IsAdmin: -1,
}
error := context.Bind(&data)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to update user config: %s", error))
return error
}
updatingUser, error := r.userManager.GetUserById(id)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to update user config: %s", error))
return error
}
if user.ID != id && data.IsAdmin != -1 {
updatingUser.IsAdmin = data.IsAdmin == 1
}
error = r.userManager.UpdateUser(updatingUser)
if error != nil {
SendError(500, context, fmt.Sprintf("Failed to update user config: %s", error))
return error
}
return context.JSON(200, updatingUser)
}
func (r *UsersApiHandler) SetRouter(router *echo.Echo) {
r.router = router
}
func (r *UsersApiHandler) SetUserManager(userManager *m.UserManager) {
r.userManager = userManager
}

View File

@ -69,7 +69,7 @@ func (r WebServer) Router() *echo.Echo {
return r.router
}
func SendError(statusCode int, context echo.Context, message string) {
func sendError(statusCode int, context echo.Context, message string) {
log.Info().Msg(message)
responseData := struct {
Error string `json:"error"`

View File

@ -1,4 +1,4 @@
package data
package users
import "time"

View File

@ -1,4 +1,4 @@
package data
package users
type User struct {
ID string `json:"id"`

View File

@ -1,4 +1,4 @@
package data
package users
import (
"database/sql"

View File

@ -1,10 +1,9 @@
package management
package users
import (
"crypto/rand"
"encoding/base64"
"fmt"
d "playback-device-server/data"
"time"
"github.com/rs/zerolog/log"
@ -14,10 +13,10 @@ const DEFAULT_USERNAME = "admin"
const MIN_PASSWORD_LENGTH = 6
type UserManager struct {
userDatabase *d.UserDatabase
userDatabase *UserDatabase
}
func (um *UserManager) Initialize(userDatabase *d.UserDatabase) error {
func (um *UserManager) Initialize(userDatabase *UserDatabase) error {
um.userDatabase = userDatabase
exists, error := um.UsernameExists(DEFAULT_USERNAME)
@ -33,7 +32,7 @@ func (um *UserManager) Initialize(userDatabase *d.UserDatabase) error {
log.Info().Str("username", DEFAULT_USERNAME).Str("password", password).Msg("creating default admin user")
user := d.User{Username: DEFAULT_USERNAME, Password: password, IsAdmin: true}
user := User{Username: DEFAULT_USERNAME, Password: password, IsAdmin: true}
_, error = um.CreateUser(&user)
if error != nil {
return error
@ -43,7 +42,7 @@ func (um *UserManager) Initialize(userDatabase *d.UserDatabase) error {
return nil
}
func (um *UserManager) CreateUser(user *d.User) (string, error) {
func (um *UserManager) CreateUser(user *User) (string, error) {
exists, error := um.UsernameExists(user.Username)
if error != nil {
return "", error
@ -110,7 +109,7 @@ func (um *UserManager) Login(username, password string) (string, error) {
return token, nil
}
func (um *UserManager) GetSession(sessionToken string) (*d.Session, error) {
func (um *UserManager) GetSession(sessionToken string) (*Session, error) {
session, error := um.userDatabase.GetSession(sessionToken)
if error != nil {
return nil, error
@ -118,7 +117,7 @@ func (um *UserManager) GetSession(sessionToken string) (*d.Session, error) {
return session, nil
}
func (um *UserManager) GetUserById(id string) (*d.User, error) {
func (um *UserManager) GetUserById(id string) (*User, error) {
user, error := um.userDatabase.GetUserById(id)
if error != nil {
return nil, error
@ -126,12 +125,12 @@ func (um *UserManager) GetUserById(id string) (*d.User, error) {
return user, nil
}
func (um *UserManager) UpdateUser(user *d.User) error {
func (um *UserManager) UpdateUser(user *User) error {
error := um.userDatabase.UpdateUser(user)
return error
}
func (um *UserManager) UpdatePassword(currentPassword string, newPassword string, user *d.User) error {
func (um *UserManager) UpdatePassword(currentPassword string, newPassword string, user *User) error {
correct, error := um.userDatabase.CheckCredentials(user.Username, currentPassword)
if error != nil {
return error
@ -156,7 +155,7 @@ func (um *UserManager) DeleteSession(token string) error {
return nil
}
func (um *UserManager) GetUsers() (*[]d.User, error) {
func (um *UserManager) GetUsers() (*[]User, error) {
users, error := um.userDatabase.GetUsers()
return users, error