From df1041766882f67613ed85719b818f267331cec9 Mon Sep 17 00:00:00 2001 From: armisss4 Date: Thu, 29 Dec 2022 09:42:12 +0200 Subject: [PATCH 1/8] User management More than one user with Manager or Admin permissions can be created from UI --- Dockerfile | 4 +- custom/js/helper.js | 29 ++++ handler/routes.go | 200 +++++++++++++++++++---- handler/routes_wake_on_lan.go | 2 +- handler/session.go | 32 +++- main.go | 25 ++- model/misc.go | 1 + model/user.go | 1 + router/router.go | 6 + store/jsondb/jsondb.go | 54 ++++++- store/store.go | 4 +- templates/base.html | 22 ++- templates/login.html | 6 +- templates/profile.html | 125 +++++++++------ templates/users_settings.html | 290 ++++++++++++++++++++++++++++++++++ util/config.go | 1 + util/util.go | 3 +- 17 files changed, 705 insertions(+), 100 deletions(-) create mode 100644 templates/users_settings.html diff --git a/Dockerfile b/Dockerfile index a30cefee..70223c56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ ARG BUILD_DEPENDENCIES="npm \ # Get dependencies RUN apk add --update --no-cache ${BUILD_DEPENDENCIES} +#RUN apt install ${BUILD_DEPENDENCIES} WORKDIR /build @@ -20,8 +21,7 @@ COPY package.json /build COPY yarn.lock /build # Prepare assets -RUN yarn install --pure-lockfile --production && \ - yarn cache clean +RUN yarn install --pure-lockfile --production && yarn cache clean # Move admin-lte dist RUN mkdir -p assets/dist/js assets/dist/css && \ diff --git a/custom/js/helper.js b/custom/js/helper.js index 86f6dc7e..50b1d769 100644 --- a/custom/js/helper.js +++ b/custom/js/helper.js @@ -78,6 +78,35 @@ function renderClientList(data) { }); } +function renderUserList(data) { + $.each(data, function(index, obj) { + // render client status css tag style + let clientStatusHtml = '>' + + // render client html content + let html = `
+
+
+
+ +
+
+ +
+
+ ${obj.username} + ${obj.admin? 'Administrator':'Manager'} +
+
+
` + + // add the client html elements to the list + $('#users-list').append(html); + }); +} + + function prettyDateTime(timeStr) { const dt = new Date(timeStr); const offsetMs = dt.getTimezoneOffset() * 60 * 1000; diff --git a/handler/routes.go b/handler/routes.go index 3ddbb2de..ddab14f5 100644 --- a/handler/routes.go +++ b/handler/routes.go @@ -42,39 +42,54 @@ func LoginPage() echo.HandlerFunc { // Login for signing in handler func Login(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - user := new(model.User) - c.Bind(user) + data := make(map[string]interface{}) + err := json.NewDecoder(c.Request().Body).Decode(&data) - dbuser, err := db.GetUser() + if err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) + } + + username := data["username"].(string) + password := data["password"].(string) + rememberMe := data["rememberMe"].(bool) + + dbuser, err := db.GetUserByName(username) if err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot query user from DB"}) } - userCorrect := subtle.ConstantTimeCompare([]byte(user.Username), []byte(dbuser.Username)) == 1 + userCorrect := subtle.ConstantTimeCompare([]byte(username), []byte(dbuser.Username)) == 1 var passwordCorrect bool if dbuser.PasswordHash != "" { - match, err := util.VerifyHash(dbuser.PasswordHash, user.Password) + match, err := util.VerifyHash(dbuser.PasswordHash, password) if err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot verify password"}) } passwordCorrect = match } else { - passwordCorrect = subtle.ConstantTimeCompare([]byte(user.Password), []byte(dbuser.Password)) == 1 + passwordCorrect = subtle.ConstantTimeCompare([]byte(password), []byte(dbuser.Password)) == 1 } if userCorrect && passwordCorrect { // TODO: refresh the token + ageMax := 0 + expiration := time.Now().Add(24 * time.Hour) + if rememberMe { + ageMax = 86400 + expiration.Add(144 * time.Hour) + } sess, _ := session.Get("session", c) sess.Options = &sessions.Options{ Path: util.BasePath, - MaxAge: 86400, + MaxAge: ageMax, HttpOnly: true, } // set session_token tokenUID := xid.New().String() - sess.Values["username"] = user.Username + sess.Values["username"] = dbuser.Username + sess.Values["admin"] = dbuser.Admin sess.Values["session_token"] = tokenUID sess.Save(c.Request(), c.Response()) @@ -82,7 +97,7 @@ func Login(db store.IStore) echo.HandlerFunc { cookie := new(http.Cookie) cookie.Name = "session_token" cookie.Value = tokenUID - cookie.Expires = time.Now().Add(24 * time.Hour) + cookie.Expires = expiration c.SetCookie(cookie) return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Logged in successfully"}) @@ -92,6 +107,36 @@ func Login(db store.IStore) echo.HandlerFunc { } } +// GetClients handler return a JSON list of Wireguard client data +func GetUsers(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + + clientDataList, err := db.GetUsers() + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ + false, fmt.Sprintf("Cannot get user list: %v", err), + }) + } + + return c.JSON(http.StatusOK, clientDataList) + } +} + +// GetClient handler returns a JSON object of Wireguard client data +func GetUser(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + + username := c.Param("username") + + userData, err := db.GetUserByName(username) + if err != nil { + return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, "User not found"}) + } + + return c.JSON(http.StatusOK, userData) + } +} + // Logout to log a user out func Logout() echo.HandlerFunc { return func(c echo.Context) error { @@ -103,21 +148,23 @@ func Logout() echo.HandlerFunc { // LoadProfile to load user information func LoadProfile(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { - - userInfo, err := db.GetUser() - if err != nil { - log.Error("Cannot get user information: ", err) - } - return c.Render(http.StatusOK, "profile.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c)}, - "userInfo": userInfo, + "baseData": model.BaseData{Active: "profile", CurrentUser: currentUser(c), Admin: isAdmin(c)}, + }) + } +} + +// WireGuardClients handler +func UsersSettings(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + return c.Render(http.StatusOK, "users_settings.html", map[string]interface{}{ + "baseData": model.BaseData{Active: "users-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)}, }) } } // UpdateProfile to update user information -func UpdateProfile(db store.IStore) echo.HandlerFunc { +func UpdateUser(db store.IStore) echo.HandlerFunc { return func(c echo.Context) error { data := make(map[string]interface{}) err := json.NewDecoder(c.Request().Body).Decode(&data) @@ -128,8 +175,10 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { username := data["username"].(string) password := data["password"].(string) + previousUsername := data["previous_username"].(string) + admin := data["admin"].(bool) - user, err := db.GetUser() + user, err := db.GetUserByName(previousUsername) if err != nil { return c.JSON(http.StatusNotFound, jsonHTTPResponse{false, err.Error()}) } @@ -140,6 +189,13 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { user.Username = username } + if username != previousUsername { + _, err := db.GetUserByName(username) + if err == nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"}) + } + } + if password != "" { hash, err := util.HashPassword(password) if err != nil { @@ -147,13 +203,93 @@ func UpdateProfile(db store.IStore) echo.HandlerFunc { } user.PasswordHash = hash } + user.Admin = admin + if err := db.DeleteUser(previousUsername); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } if err := db.SaveUser(user); err != nil { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) } - log.Infof("Updated admin user information successfully") + log.Infof("Updated user information successfully") + + if previousUsername == currentUser(c) { + setUser(c, user.Username, user.Admin) + } - return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated admin user information successfully"}) + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Updated user information successfully"}) + } +} + +// UpdateProfile to update user information +func CreateUser(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + data := make(map[string]interface{}) + err := json.NewDecoder(c.Request().Body).Decode(&data) + + if err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) + } + + var user model.User + username := data["username"].(string) + password := data["password"].(string) + admin := data["admin"].(bool) + + if username == "" { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Please provide a valid username"}) + } else { + user.Username = username + } + + { + _, err := db.GetUserByName(username) + if err == nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "This username is taken"}) + } + } + + hash, err := util.HashPassword(password) + if err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } + user.PasswordHash = hash + + user.Admin = admin + + if err := db.SaveUser(user); err != nil { + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()}) + } + log.Infof("Created user successfully") + + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "Created user successfully"}) + } +} + +// RemoveClient handler +func RemoveUser(db store.IStore) echo.HandlerFunc { + return func(c echo.Context) error { + data := make(map[string]interface{}) + err := json.NewDecoder(c.Request().Body).Decode(&data) + + if err != nil { + return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"}) + } + + username := data["username"].(string) + // delete client from database + + if err := db.DeleteUser(username); err != nil { + log.Error("Cannot delete user: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot delete user from database"}) + } + + log.Infof("Removed user: %s", username) + if username == currentUser(c) { + log.Infof("You removed yourself, killing session") + clearSession(c) + } + return c.JSON(http.StatusOK, jsonHTTPResponse{true, "User removed"}) } } @@ -169,7 +305,7 @@ func WireGuardClients(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "clients.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "clientDataList": clientDataList, }) } @@ -522,7 +658,7 @@ func WireGuardServer(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "server.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "wg-server", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "serverInterface": server.Interface, "serverKeyPair": server.KeyPair, }) @@ -590,7 +726,7 @@ func GlobalSettings(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "global_settings.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "global-settings", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "globalSettings": globalSettings, }) } @@ -618,7 +754,7 @@ func Status(db store.IStore) echo.HandlerFunc { wgClient, err := wgctrl.New() if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "error": err.Error(), "devices": nil, }) @@ -627,7 +763,7 @@ func Status(db store.IStore) echo.HandlerFunc { devices, err := wgClient.Devices() if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "error": err.Error(), "devices": nil, }) @@ -639,7 +775,7 @@ func Status(db store.IStore) echo.HandlerFunc { clients, err := db.GetClients(false) if err != nil { return c.Render(http.StatusInternalServerError, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "error": err.Error(), "devices": nil, }) @@ -676,7 +812,7 @@ func Status(db store.IStore) echo.HandlerFunc { } return c.Render(http.StatusOK, "status.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "status", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "devices": devicesVm, "error": "", }) @@ -790,6 +926,12 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get client config"}) } + users, err := db.GetUsers() + if err != nil { + log.Error("Cannot get users config: ", err) + return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, "Cannot get users config"}) + } + settings, err := db.GetGlobalSettings() if err != nil { log.Error("Cannot get global settings: ", err) @@ -797,7 +939,7 @@ func ApplyServerConfig(db store.IStore, tmplBox *rice.Box) echo.HandlerFunc { } // Write config file - err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings) + err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings) if err != nil { log.Error("Cannot apply server config: ", err) return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{ diff --git a/handler/routes_wake_on_lan.go b/handler/routes_wake_on_lan.go index 40cd3876..43a61863 100644 --- a/handler/routes_wake_on_lan.go +++ b/handler/routes_wake_on_lan.go @@ -37,7 +37,7 @@ func GetWakeOnLanHosts(db store.IStore) echo.HandlerFunc { } err = c.Render(http.StatusOK, "wake_on_lan_hosts.html", map[string]interface{}{ - "baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c)}, + "baseData": model.BaseData{Active: "wake_on_lan_hosts", CurrentUser: currentUser(c), Admin: isAdmin(c)}, "hosts": hosts, "error": "", }) diff --git a/handler/session.go b/handler/session.go index 9975e0d4..689d507a 100644 --- a/handler/session.go +++ b/handler/session.go @@ -14,15 +14,24 @@ func ValidSession(next echo.HandlerFunc) echo.HandlerFunc { if !isValidSession(c) { nextURL := c.Request().URL if nextURL != nil && c.Request().Method == http.MethodGet { - return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath + "/login?next=%s", c.Request().URL)) + return c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf(util.BasePath+"/login?next=%s", c.Request().URL)) } else { - return c.Redirect(http.StatusTemporaryRedirect, util.BasePath + "/login") + return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/login") } } return next(c) } } +func NeedsAdmin(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if !isAdmin(c) { + return c.Redirect(http.StatusTemporaryRedirect, util.BasePath+"/") + } + return next(c) + } +} + func isValidSession(c echo.Context) bool { if util.DisableLogin { return true @@ -46,10 +55,29 @@ func currentUser(c echo.Context) string { return username } +// currentUser to get username of logged in user +func isAdmin(c echo.Context) bool { + if util.DisableLogin { + return true + } + + sess, _ := session.Get("session", c) + admin := fmt.Sprintf("%t", sess.Values["admin"]) + return admin == "true" +} + +func setUser(c echo.Context, username string, admin bool) { + sess, _ := session.Get("session", c) + sess.Values["username"] = username + sess.Values["admin"] = admin + sess.Save(c.Request(), c.Response()) +} + // clearSession to remove current session func clearSession(c echo.Context) { sess, _ := session.Get("session", c) sess.Values["username"] = "" + sess.Values["admin"] = false sess.Values["session_token"] = "" sess.Save(c.Request(), c.Response()) } diff --git a/main.go b/main.go index 3f0cd13b..aefc0bb1 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,12 @@ func main() { app.POST(util.BasePath+"/login", handler.Login(db)) app.GET(util.BasePath+"/logout", handler.Logout(), handler.ValidSession) app.GET(util.BasePath+"/profile", handler.LoadProfile(db), handler.ValidSession) - app.POST(util.BasePath+"/profile", handler.UpdateProfile(db), handler.ValidSession) + app.GET(util.BasePath+"/users-settings", handler.UsersSettings(db), handler.ValidSession, handler.NeedsAdmin) + app.POST(util.BasePath+"/update-user", handler.UpdateUser(db), handler.ValidSession) + app.POST(util.BasePath+"/create-user", handler.CreateUser(db), handler.ValidSession, handler.NeedsAdmin) + app.POST(util.BasePath+"/remove-user", handler.RemoveUser(db), handler.ValidSession, handler.NeedsAdmin) + app.GET(util.BasePath+"/getusers", handler.GetUsers(db), handler.ValidSession, handler.NeedsAdmin) + app.GET(util.BasePath+"/api/user/:username", handler.GetUser(db), handler.ValidSession) } var sendmail emailer.Emailer @@ -154,11 +159,12 @@ func main() { app.POST(util.BasePath+"/client/set-status", handler.SetClientStatus(db), handler.ValidSession, handler.ContentTypeJson) app.POST(util.BasePath+"/remove-client", handler.RemoveClient(db), handler.ValidSession, handler.ContentTypeJson) app.GET(util.BasePath+"/download", handler.DownloadClient(db), handler.ValidSession) - app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession) - app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson) - app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson) - app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession) - app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson) + app.GET(util.BasePath+"/wg-server", handler.WireGuardServer(db), handler.ValidSession, handler.NeedsAdmin) + app.POST(util.BasePath+"/wg-server/interfaces", handler.WireGuardServerInterfaces(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.POST(util.BasePath+"/wg-server/keypair", handler.WireGuardServerKeyPair(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) + app.GET(util.BasePath+"/global-settings", handler.GlobalSettings(db), handler.ValidSession, handler.NeedsAdmin) + + app.POST(util.BasePath+"/global-settings", handler.GlobalSettingSubmit(db), handler.ValidSession, handler.ContentTypeJson, handler.NeedsAdmin) app.GET(util.BasePath+"/status", handler.Status(db), handler.ValidSession) app.GET(util.BasePath+"/api/clients", handler.GetClients(db), handler.ValidSession) app.GET(util.BasePath+"/api/client/:id", handler.GetClient(db), handler.ValidSession) @@ -197,8 +203,13 @@ func initServerConfig(db store.IStore, tmplBox *rice.Box) { log.Fatalf("Cannot get client config: ", err) } + users, err := db.GetUsers() + if err != nil { + log.Fatalf("Cannot get user config: ", err) + } + // write config file - err = util.WriteWireGuardServerConfig(tmplBox, server, clients, settings) + err = util.WriteWireGuardServerConfig(tmplBox, server, clients, users, settings) if err != nil { log.Fatalf("Cannot create server config: ", err) } diff --git a/model/misc.go b/model/misc.go index 12d6906a..d8f2cf58 100644 --- a/model/misc.go +++ b/model/misc.go @@ -10,4 +10,5 @@ type Interface struct { type BaseData struct { Active string CurrentUser string + Admin bool } diff --git a/model/user.go b/model/user.go index 711ebd1f..71f4d138 100644 --- a/model/user.go +++ b/model/user.go @@ -6,4 +6,5 @@ type User struct { Password string `json:"password"` // PasswordHash takes precedence over Password. PasswordHash string `json:"password_hash"` + Admin bool `json:"admin"` } diff --git a/router/router.go b/router/router.go index 0f9facc8..f262243a 100644 --- a/router/router.go +++ b/router/router.go @@ -83,6 +83,11 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec log.Fatal(err) } + tmplUsersSettingsString, err := tmplBox.String("users_settings.html") + if err != nil { + log.Fatal(err) + } + tmplStatusString, err := tmplBox.String("status.html") if err != nil { log.Fatal(err) @@ -103,6 +108,7 @@ func New(tmplBox *rice.Box, extraData map[string]string, secret []byte) *echo.Ec templates["clients.html"] = template.Must(template.New("clients").Funcs(funcs).Parse(tmplBaseString + tmplClientsString)) templates["server.html"] = template.Must(template.New("server").Funcs(funcs).Parse(tmplBaseString + tmplServerString)) templates["global_settings.html"] = template.Must(template.New("global_settings").Funcs(funcs).Parse(tmplBaseString + tmplGlobalSettingsString)) + templates["users_settings.html"] = template.Must(template.New("users_settings").Funcs(funcs).Parse(tmplBaseString + tmplUsersSettingsString)) templates["status.html"] = template.Must(template.New("status").Funcs(funcs).Parse(tmplBaseString + tmplStatusString)) templates["wake_on_lan_hosts.html"] = template.Must(template.New("wake_on_lan_hosts").Funcs(funcs).Parse(tmplBaseString + tmplWakeOnLanHostsString)) diff --git a/store/jsondb/jsondb.go b/store/jsondb/jsondb.go index f39a4521..61d9cc5e 100644 --- a/store/jsondb/jsondb.go +++ b/store/jsondb/jsondb.go @@ -42,7 +42,7 @@ func (o *JsonDB) Init() error { var serverInterfacePath string = path.Join(serverPath, "interfaces.json") var serverKeyPairPath string = path.Join(serverPath, "keypair.json") var globalSettingPath string = path.Join(serverPath, "global_settings.json") - var userPath string = path.Join(serverPath, "users.json") + var userPath string = path.Join(o.dbPath, "users") // create directories if they do not exist if _, err := os.Stat(clientPath); os.IsNotExist(err) { os.MkdirAll(clientPath, os.ModePerm) @@ -53,6 +53,9 @@ func (o *JsonDB) Init() error { if _, err := os.Stat(wakeOnLanHostsPath); os.IsNotExist(err) { os.MkdirAll(wakeOnLanHostsPath, os.ModePerm) } + if _, err := os.Stat(userPath); os.IsNotExist(err) { + os.MkdirAll(userPath, os.ModePerm) + } // server's interface if _, err := os.Stat(serverInterfacePath); os.IsNotExist(err) { @@ -103,9 +106,11 @@ func (o *JsonDB) Init() error { } // user info - if _, err := os.Stat(userPath); os.IsNotExist(err) { + results, err := o.conn.ReadAll("users") + if err != nil || len(results) < 1 { user := new(model.User) user.Username = util.LookupEnvOrString(util.UsernameEnvVar, util.DefaultUsername) + user.Admin = util.DefaultIsAdmin user.PasswordHash = util.LookupEnvOrString(util.PasswordHashEnvVar, "") if user.PasswordHash == "" { plaintext := util.LookupEnvOrString(util.PasswordEnvVar, util.DefaultPassword) @@ -115,7 +120,7 @@ func (o *JsonDB) Init() error { } user.PasswordHash = hash } - o.conn.Write("server", "users", user) + o.conn.Write("users", user.Username, user) } return nil @@ -127,11 +132,48 @@ func (o *JsonDB) GetUser() (model.User, error) { return user, o.conn.Read("server", "users", &user) } -// SaveUser func to user info to the database +// GetUsers func to query user info from the database +func (o *JsonDB) GetUsers() ([]model.User, error) { + var users []model.User + results, err := o.conn.ReadAll("users") + if err != nil { + return users, err + } + for _, i := range results { + user := model.User{} + + if err := json.Unmarshal([]byte(i), &user); err != nil { + return users, fmt.Errorf("cannot decode user json structure: %v", err) + } + users = append(users, user) + + } + return users, err +} + +func (o *JsonDB) GetUserByName(username string) (model.User, error) { + user := model.User{} + + if err := o.conn.Read("users", username, &user); err != nil { + return user, err + } + + return user, nil +} + func (o *JsonDB) SaveUser(user model.User) error { - return o.conn.Write("server", "users", user) + return o.conn.Write("users", user.Username, user) } +func (o *JsonDB) DeleteUser(username string) error { + return o.conn.Delete("users", username) +} + +//// SaveUser func to user info to the database +//func (o *JsonDB) SaveUser(user model.User) error { +// return o.conn.Write("server", "users", user) +//} + // GetGlobalSettings func to query global settings from the database func (o *JsonDB) GetGlobalSettings() (model.GlobalSetting, error) { settings := model.GlobalSetting{} @@ -213,7 +255,7 @@ func (o *JsonDB) GetClientByID(clientID string, qrCodeSettings model.QRCodeSetti server, _ := o.GetServer() globalSettings, _ := o.GetGlobalSettings() client := client - if !qrCodeSettings.IncludeDNS{ + if !qrCodeSettings.IncludeDNS { globalSettings.DNSServers = []string{} } if !qrCodeSettings.IncludeMTU { diff --git a/store/store.go b/store/store.go index 86d62243..166bc3da 100644 --- a/store/store.go +++ b/store/store.go @@ -6,8 +6,10 @@ import ( type IStore interface { Init() error - GetUser() (model.User, error) + GetUsers() ([]model.User, error) + GetUserByName(username string) (model.User, error) SaveUser(user model.User) error + DeleteUser(username string) error GetGlobalSettings() (model.GlobalSetting, error) GetServer() (model.Server, error) GetClients(hasQRCode bool) ([]model.ClientData, error) diff --git a/templates/base.html b/templates/base.html index fd337a7b..227e35d4 100644 --- a/templates/base.html +++ b/templates/base.html @@ -88,7 +88,13 @@
{{if .baseData.CurrentUser}} - {{.baseData.CurrentUser}} + + {{if .baseData.Admin}} + Administrator: {{.baseData.CurrentUser}} + {{else}} + Manager: {{.baseData.CurrentUser}} + {{end}} + {{else}} Administrator {{end}} @@ -107,6 +113,8 @@

+ + {{if .baseData.Admin}} + + + + {{end}} +