User Auth
This commit is contained in:
parent
76ae187051
commit
e51c870c0c
19 changed files with 415 additions and 35 deletions
13
go.mod
13
go.mod
|
|
@ -4,15 +4,16 @@ go 1.20
|
|||
|
||||
require (
|
||||
github.com/containrrr/shoutrrr v0.7.1
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/spf13/viper v1.16.0
|
||||
modernc.org/sqlite v1.24.0
|
||||
golang.org/x/crypto v0.12.0
|
||||
modernc.org/sqlite v1.25.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
|
|
@ -27,17 +28,17 @@ require (
|
|||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
modernc.org/cc/v3 v3.40.0 // indirect
|
||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
||||
modernc.org/libc v1.22.5 // indirect
|
||||
modernc.org/libc v1.24.1 // indirect
|
||||
modernc.org/mathutil v1.5.0 // indirect
|
||||
modernc.org/memory v1.5.0 // indirect
|
||||
modernc.org/memory v1.6.0 // indirect
|
||||
modernc.org/opt v0.1.3 // indirect
|
||||
modernc.org/strutil v1.1.3 // indirect
|
||||
modernc.org/token v1.0.1 // indirect
|
||||
|
|
|
|||
22
go.sum
22
go.sum
|
|
@ -610,6 +610,8 @@ golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
|
@ -842,8 +844,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
|
|
@ -857,8 +859,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
@ -1181,16 +1183,16 @@ modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
|||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||
modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM=
|
||||
modernc.org/libc v1.24.1/go.mod h1:FmfO1RLrU3MHJfyi9eYYmZBfi/R+tqZ6+hQ3yQQUkak=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/memory v1.6.0 h1:i6mzavxrE9a30whzMfwf7XWVODx2r5OYXvU46cirX7o=
|
||||
modernc.org/memory v1.6.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI=
|
||||
modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||
modernc.org/sqlite v1.25.0 h1:AFweiwPNd/b3BoKnBOfFm+Y260guGMF+0UFk0savqeA=
|
||||
modernc.org/sqlite v1.25.0/go.mod h1:FL3pVXie73rg3Rii6V/u5BoHlSoyeZeIgKZEgHARyCU=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
|
||||
|
|
|
|||
35
internal/auth/auth.go
Normal file
35
internal/auth/auth.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Auth - main auth func
|
||||
func Auth(next http.HandlerFunc, conf *Conf) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if !conf.Auth || conf.User == "" || conf.Password == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
sessionToken := getTokenFromCookie(r)
|
||||
|
||||
userSession, exists := allSessions[sessionToken]
|
||||
if !exists {
|
||||
http.Redirect(w, r, "/login/", 302)
|
||||
return
|
||||
}
|
||||
if userSession.Before(time.Now()) {
|
||||
delete(allSessions, sessionToken)
|
||||
http.Redirect(w, r, "/login/", 302)
|
||||
return
|
||||
}
|
||||
|
||||
userSession = time.Now().Add(conf.Expire)
|
||||
allSessions[sessionToken] = userSession
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
22
internal/auth/cookie.go
Normal file
22
internal/auth/cookie.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func setTokenCookie(w http.ResponseWriter, token string) {
|
||||
|
||||
cookie := http.Cookie{Name: cookieName, Value: token, Path: "/"}
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
|
||||
func getTokenFromCookie(r *http.Request) string {
|
||||
|
||||
cookie, err := r.Cookie(cookieName)
|
||||
if err != nil {
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
return cookie.Value
|
||||
}
|
||||
27
internal/auth/hash.go
Normal file
27
internal/auth/hash.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// HashPassword - generate hash from password
|
||||
func HashPassword(pw string) string {
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(pw), 10)
|
||||
if err != nil {
|
||||
log.Println("PASSWORD ERROR:", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
return string(hashed)
|
||||
}
|
||||
|
||||
// MatchPasswords - check if password and hash matches
|
||||
func MatchPasswords(hash, pw string) bool {
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw))
|
||||
|
||||
return err == nil
|
||||
}
|
||||
18
internal/auth/models-vars.go
Normal file
18
internal/auth/models-vars.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Conf - auth config
|
||||
type Conf struct {
|
||||
Auth bool
|
||||
User string
|
||||
Password string
|
||||
ExpStr string
|
||||
Expire time.Duration
|
||||
}
|
||||
|
||||
var allSessions = map[string]time.Time{}
|
||||
|
||||
var cookieName = "wyl_session_token"
|
||||
33
internal/auth/session.go
Normal file
33
internal/auth/session.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// StartSession for new login
|
||||
func StartSession(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
sessionToken := uuid.NewString()
|
||||
|
||||
allSessions[sessionToken] = time.Now().Add(60 * time.Second)
|
||||
|
||||
setTokenCookie(w, sessionToken)
|
||||
|
||||
http.Redirect(w, r, "/", 302)
|
||||
|
||||
}
|
||||
|
||||
// LogOut - log out
|
||||
func LogOut(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
sessionToken := getTokenFromCookie(r)
|
||||
|
||||
delete(allSessions, sessionToken)
|
||||
|
||||
setTokenCookie(w, "")
|
||||
|
||||
http.Redirect(w, r, "/", 302)
|
||||
}
|
||||
46
internal/auth/timeparse.go
Normal file
46
internal/auth/timeparse.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func timeParse(timeout string) (time.Duration, error) {
|
||||
var err error
|
||||
var t time.Duration
|
||||
|
||||
length := len(timeout)
|
||||
if length > 1 {
|
||||
suffix := timeout[length-1]
|
||||
|
||||
switch string(suffix) {
|
||||
case "h":
|
||||
t, err = time.ParseDuration(timeout)
|
||||
case "m":
|
||||
t, err = time.ParseDuration(timeout)
|
||||
case "d": //day
|
||||
t, err = time.ParseDuration(timeout[:length-1] + "h")
|
||||
t = 24 * t
|
||||
case "M": // month
|
||||
t, err = time.ParseDuration(timeout[:length-1] + "h")
|
||||
t = 730 * t
|
||||
default:
|
||||
err = errors.New("ERROR: TimeParse: wrong time format")
|
||||
}
|
||||
} else {
|
||||
err = errors.New("ERROR: TimeParse: wrong time format")
|
||||
}
|
||||
|
||||
return t, err
|
||||
}
|
||||
|
||||
// ToTime - converts string (example: 3d) to time.Duration
|
||||
func ToTime(s string) time.Duration {
|
||||
|
||||
t, err := timeParse(s)
|
||||
if err != nil {
|
||||
t, _ = timeParse("7d")
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
|
@ -3,12 +3,14 @@ package conf
|
|||
import (
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||
)
|
||||
|
||||
// Get - get app config
|
||||
func Get(path string) (config models.Conf) {
|
||||
func Get(path string) (config models.Conf, authConf auth.Conf) {
|
||||
|
||||
viper.SetDefault("IFACE", "enp1s0")
|
||||
viper.SetDefault("DBPATH", "/data/db.sqlite")
|
||||
viper.SetDefault("GUIIP", "localhost")
|
||||
|
|
@ -19,6 +21,9 @@ func Get(path string) (config models.Conf) {
|
|||
viper.SetDefault("COLOR", "light")
|
||||
viper.SetDefault("IGNOREIP", "no")
|
||||
viper.SetDefault("LOGLEVEL", "verbose")
|
||||
viper.SetDefault("AUTH_USER", "")
|
||||
viper.SetDefault("AUTH_PASSWORD", "")
|
||||
viper.SetDefault("AUTH_EXPIRE", "7d")
|
||||
|
||||
viper.SetConfigFile(path)
|
||||
viper.SetConfigType("yaml")
|
||||
|
|
@ -37,12 +42,19 @@ func Get(path string) (config models.Conf) {
|
|||
config.Color = viper.Get("COLOR").(string)
|
||||
config.IgnoreIP = viper.Get("IGNOREIP").(string)
|
||||
config.LogLevel = viper.Get("LOGLEVEL").(string)
|
||||
authConf.Auth = viper.GetBool("AUTH")
|
||||
authConf.User, _ = viper.Get("AUTH_USER").(string)
|
||||
authConf.Password, _ = viper.Get("AUTH_PASSWORD").(string)
|
||||
authConf.ExpStr, _ = viper.Get("AUTH_EXPIRE").(string)
|
||||
|
||||
return config
|
||||
authConf.Expire = auth.ToTime(authConf.ExpStr)
|
||||
config.Auth = authConf.Auth
|
||||
|
||||
return config, authConf
|
||||
}
|
||||
|
||||
// Write - write config to file
|
||||
func Write(path string, config models.Conf) {
|
||||
func Write(path string, config models.Conf, authConf auth.Conf) {
|
||||
|
||||
viper.SetConfigFile(path)
|
||||
viper.SetConfigType("yaml")
|
||||
|
|
@ -58,6 +70,11 @@ func Write(path string, config models.Conf) {
|
|||
viper.Set("IGNOREIP", config.IgnoreIP)
|
||||
viper.Set("LOGLEVEL", config.LogLevel)
|
||||
|
||||
viper.Set("auth", authConf.Auth)
|
||||
viper.Set("auth_user", authConf.User)
|
||||
viper.Set("auth_password", authConf.Password)
|
||||
viper.Set("auth_expire", authConf.ExpStr)
|
||||
|
||||
err := viper.WriteConfig()
|
||||
check.IfError(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
||||
"github.com/aceberg/WatchYourLAN/internal/notify"
|
||||
|
|
@ -37,9 +38,10 @@ func ToYAML(path string) string {
|
|||
}
|
||||
|
||||
func migrateConfig(oldPath, newPath string) {
|
||||
var authConf auth.Conf
|
||||
|
||||
config := getOldConfig(oldPath)
|
||||
conf.Write(newPath, config)
|
||||
conf.Write(newPath, config, authConf)
|
||||
|
||||
msg := fmt.Sprintf("Config file migrated from ENV to YAML format. \nOld config file %s is no longer used and can be deleted. \nNew config file is %s, please update your settings.", oldPath, newPath)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
)
|
||||
|
||||
// Host - one host
|
||||
type Host struct {
|
||||
ID uint16
|
||||
|
|
@ -26,6 +30,7 @@ type Conf struct {
|
|||
LogLevel string
|
||||
NodePath string
|
||||
Icon string
|
||||
Auth bool
|
||||
}
|
||||
|
||||
// GuiData - all data sent to html page
|
||||
|
|
@ -34,4 +39,5 @@ type GuiData struct {
|
|||
Hosts []Host
|
||||
Themes []string
|
||||
Version string
|
||||
Auth auth.Conf
|
||||
}
|
||||
|
|
|
|||
52
internal/web/auth-conf.go
Normal file
52
internal/web/auth-conf.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||
)
|
||||
|
||||
func authConfHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var guiData models.GuiData
|
||||
|
||||
guiData.Config = AppConfig
|
||||
guiData.Auth.Auth = authConf.Auth
|
||||
guiData.Auth.User = authConf.User
|
||||
guiData.Auth.ExpStr = authConf.ExpStr
|
||||
|
||||
execTemplate(w, "auth", guiData)
|
||||
}
|
||||
|
||||
func saveAuthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
authConf.User = r.FormValue("user")
|
||||
authConf.ExpStr = r.FormValue("expire")
|
||||
|
||||
authStr := r.FormValue("auth")
|
||||
pw := r.FormValue("password")
|
||||
|
||||
if authStr == "on" {
|
||||
authConf.Auth = true
|
||||
} else {
|
||||
authConf.Auth = false
|
||||
}
|
||||
AppConfig.Auth = authConf.Auth
|
||||
|
||||
if pw != "" {
|
||||
authConf.Password = auth.HashPassword(pw)
|
||||
}
|
||||
|
||||
authConf.Expire = auth.ToTime(authConf.ExpStr)
|
||||
|
||||
if authConf.Auth && (authConf.User == "" || authConf.Password == "") {
|
||||
log.Println("WARNING: Auth won't work with empty login or password.")
|
||||
authConf.Auth = false
|
||||
}
|
||||
|
||||
conf.Write(ConfigPath, AppConfig, authConf)
|
||||
|
||||
http.Redirect(w, r, r.Header.Get("Referer"), 302)
|
||||
}
|
||||
|
|
@ -48,7 +48,7 @@ func saveConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
close(QuitScan)
|
||||
|
||||
conf.Write(ConfigPath, AppConfig)
|
||||
conf.Write(ConfigPath, AppConfig, authConf)
|
||||
|
||||
log.Println("INFO: writing new config")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ package web
|
|||
import (
|
||||
"embed"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||
)
|
||||
|
||||
var (
|
||||
authConf auth.Conf
|
||||
|
||||
// AppConfig - app config
|
||||
AppConfig models.Conf
|
||||
|
||||
|
|
|
|||
31
internal/web/login.go
Normal file
31
internal/web/login.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/models"
|
||||
)
|
||||
|
||||
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var guiData models.GuiData
|
||||
guiData.Config = AppConfig
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
logout := r.FormValue("logout")
|
||||
|
||||
if username == authConf.User && auth.MatchPasswords(authConf.Password, password) {
|
||||
|
||||
log.Println("INFO: user '"+username+"' logged in. Session expire time", authConf.Expire)
|
||||
|
||||
auth.StartSession(w, r)
|
||||
}
|
||||
if logout == "yes" {
|
||||
|
||||
auth.LogOut(w, r)
|
||||
}
|
||||
|
||||
execTemplate(w, "login", guiData)
|
||||
}
|
||||
53
internal/web/templates/auth.html
Normal file
53
internal/web/templates/auth.html
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
{{ define "auth" }}
|
||||
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table">
|
||||
<form action="/auth_save/" method="post">
|
||||
<tr>
|
||||
<td>Enable</td>
|
||||
<td>
|
||||
<div class="form-check form-switch">
|
||||
{{ if .Auth.Auth }}
|
||||
<input class="form-check-input" type="checkbox" name="auth" checked>
|
||||
{{ else }}
|
||||
<input class="form-check-input" type="checkbox" name="auth">
|
||||
{{ end }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Expire after</td>
|
||||
<td><input name="expire" type="text" class="form-control" value="{{ .Auth.ExpStr }}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Login</td>
|
||||
<td><input name="user" type="text" class="form-control" value="{{ .Auth.User }}"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New password</td>
|
||||
<td><input name="password" type="password" class="form-control"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><button type="submit" class="btn btn-primary">Save</button></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</form>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="alert alert-info" role="alert">
|
||||
<p>● <b>WARNING!</b> Authentication is only safe when used over <b>https</b> and with a strong password.</p>
|
||||
<p>● <b>Expire after</b> - when user session expires. Can be set with suffixes <b>m</b>(minute), <b>h</b>(hour), <b>d</b>(day) or <b>M</b>(month). Default: <b>7d</b>.</p>
|
||||
<p>● <b>Login</b>. For Auth to work, login and password must not be empty.</p>
|
||||
<p>● <b>Password</b> is stored encrypted in config file. If you forgot your password, just set <b>auth_password:</b> field to empty there.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{{ template "footer" }}
|
||||
{{ end }}
|
||||
|
|
@ -31,6 +31,14 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/config/">Config</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/auth_conf/">Auth</a>
|
||||
</li>
|
||||
{{ if .Config.Auth }}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="/login/?logout=yes">Log Out</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<form action="/search_hosts/" method="post">
|
||||
|
|
|
|||
19
internal/web/templates/login.html
Normal file
19
internal/web/templates/login.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{{ define "login" }}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<br>
|
||||
<br>
|
||||
<div class="row text-center">
|
||||
<div class="d-flex align-items-center justify-content-center">
|
||||
<form action="/login/" method="post">
|
||||
<input name="username" type="text" placeholder="Login" class="form-control p-2 m-2">
|
||||
<input name="password" type="password" placeholder="Password" class="form-control p-2 m-2">
|
||||
<button type="submit" class="btn btn-primary p-2 m-2">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ template "footer" }}
|
||||
{{ end }}
|
||||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/aceberg/WatchYourLAN/internal/auth"
|
||||
"github.com/aceberg/WatchYourLAN/internal/check"
|
||||
"github.com/aceberg/WatchYourLAN/internal/conf"
|
||||
"github.com/aceberg/WatchYourLAN/internal/db"
|
||||
|
|
@ -15,7 +16,7 @@ import (
|
|||
func Gui(configPath, nodePath string) {
|
||||
|
||||
ConfigPath = migrate.ToYAML(configPath)
|
||||
AppConfig = conf.Get(ConfigPath)
|
||||
AppConfig, authConf = conf.Get(ConfigPath)
|
||||
AppConfig.NodePath = nodePath
|
||||
AppConfig.Icon = Icon
|
||||
|
||||
|
|
@ -33,19 +34,23 @@ func Gui(configPath, nodePath string) {
|
|||
log.Printf("Web GUI at http://%s", address)
|
||||
log.Println("=================================== ")
|
||||
|
||||
http.HandleFunc("/", indexHandler)
|
||||
http.HandleFunc("/clear/", clearHandler)
|
||||
http.HandleFunc("/config/", configHandler)
|
||||
http.HandleFunc("/del_host/", delHandler)
|
||||
http.HandleFunc("/home/", homeHandler)
|
||||
http.HandleFunc("/host/", hostHandler)
|
||||
http.HandleFunc("/line/", lineHandler)
|
||||
http.HandleFunc("/port_scan/", portHandler)
|
||||
http.HandleFunc("/save_config/", saveConfigHandler)
|
||||
http.HandleFunc("/search_hosts/", searchHandler)
|
||||
http.HandleFunc("/sort_hosts/", sortHandler)
|
||||
http.HandleFunc("/test_notify/", testNotifyHandler)
|
||||
http.HandleFunc("/update_host/", updateHandler)
|
||||
http.HandleFunc("/login/", loginHandler)
|
||||
|
||||
http.HandleFunc("/", auth.Auth(indexHandler, &authConf))
|
||||
http.HandleFunc("/auth_conf/", auth.Auth(authConfHandler, &authConf))
|
||||
http.HandleFunc("/auth_save/", auth.Auth(saveAuthHandler, &authConf))
|
||||
http.HandleFunc("/clear/", auth.Auth(clearHandler, &authConf))
|
||||
http.HandleFunc("/config/", auth.Auth(configHandler, &authConf))
|
||||
http.HandleFunc("/del_host/", auth.Auth(delHandler, &authConf))
|
||||
http.HandleFunc("/home/", auth.Auth(homeHandler, &authConf))
|
||||
http.HandleFunc("/host/", auth.Auth(hostHandler, &authConf))
|
||||
http.HandleFunc("/line/", auth.Auth(lineHandler, &authConf))
|
||||
http.HandleFunc("/port_scan/", auth.Auth(portHandler, &authConf))
|
||||
http.HandleFunc("/save_config/", auth.Auth(saveConfigHandler, &authConf))
|
||||
http.HandleFunc("/search_hosts/", auth.Auth(searchHandler, &authConf))
|
||||
http.HandleFunc("/sort_hosts/", auth.Auth(sortHandler, &authConf))
|
||||
http.HandleFunc("/test_notify/", auth.Auth(testNotifyHandler, &authConf))
|
||||
http.HandleFunc("/update_host/", auth.Auth(updateHandler, &authConf))
|
||||
err := http.ListenAndServe(address, nil)
|
||||
check.IfError(err)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue