From 9d08b056f307997ed587f33e12d4428a91e92900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 28 Nov 2024 06:09:34 +0100 Subject: [PATCH] Added login function --- authentication/pkg.go | 57 ++++++++++++++++++++++++++++++++++++++++--- main.go | 18 ++++++++++++-- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/authentication/pkg.go b/authentication/pkg.go index a951bf2..915fd5b 100644 --- a/authentication/pkg.go +++ b/authentication/pkg.go @@ -12,6 +12,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "io" "log/slog" "net/http" @@ -50,6 +51,7 @@ func NewManager(db *sqlx.DB, log *slog.Logger, secret string, expireDays int) (m mngr.ExpireDays = expireDays return } // }}} + func (mngr *Manager) GenerateToken(data map[string]any) (string, error) { // {{{ // Create a new token object, specifying signing method and the claims // you would like it to contain. @@ -62,6 +64,30 @@ func (mngr *Manager) GenerateToken(data map[string]any) (string, error) { // {{{ // Sign and get the complete encoded token as a string using the secret. return token.SignedString(mngr.secret) } // }}} +func (mngr *Manager) VerifyToken(tokenString string) (jwt.Claims, error) { // {{{ + // Parse takes the token string and a function for looking up the key. The latter is especially + // useful if you use multiple keys for your application. The standard is to use 'kid' in the + // head of the token to identify which key to use, but the parsed token (head and claims) is provided + // to the callback, providing flexibility. + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + // Don't forget to validate the alg is what you expect: + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + + // hmacSampleSecret is a []byte containing your secret, e.g. []byte("my_secret_key") + return mngr.secret, nil + }) + if err != nil { + mngr.log.Error("jwt", "error", err) + } + + if claims, ok := token.Claims.(jwt.MapClaims); ok { + return claims, nil + } else { + return nil, err + } +} // }}} func (mngr *Manager) AuthenticationHandler(w http.ResponseWriter, r *http.Request) { // {{{ var request struct { Username string @@ -72,22 +98,38 @@ func (mngr *Manager) AuthenticationHandler(w http.ResponseWriter, r *http.Reques err := json.Unmarshal(body, &request) if err != nil { httpError(w, err) + return } // Verify username and password against the db user table. authenticated, user, err := mngr.Authenticate(request.Username, request.Password) if err != nil { httpError(w, err) + return } if !authenticated { httpError(w, errors.New("Authentication failed")) + return + } + + // A new token is generated with the information. + var token string + data := make(map[string]any) + data["uid"] = user.ID + data["login"] = user.Username + data["name"] = user.Name + token, err = mngr.GenerateToken(data) + if err != nil { + httpError(w, err) + return } j, _ := json.Marshal(struct { - OK bool - User User - }{true, user}) + OK bool + User User + Token string + }{true, user, token}) w.Write(j) } // }}} @@ -104,6 +146,15 @@ func (mngr *Manager) Authenticate(username, password string) (authenticated bool password, ) err = row.Scan(&user.ID, &user.Username, &user.Name) + if err != nil && err.Error() == "sql: no rows in result set" { + err = nil + authenticated = false + return + } + if err != nil { + return + } + authenticated = user.ID > 0 return } // }}} diff --git a/main.go b/main.go index 601652f..8ee157e 100644 --- a/main.go +++ b/main.go @@ -99,6 +99,7 @@ func main() { // {{{ } http.HandleFunc("/", rootHandler) + http.HandleFunc("/login", pageLogin) http.HandleFunc("/authenticate", AuthManager.AuthenticationHandler) http.HandleFunc("/service_worker.js", pageServiceWorker) listen := fmt.Sprintf("%s:%d", config.Network.Address, config.Network.Port) @@ -136,9 +137,8 @@ func pageServiceWorker(w http.ResponseWriter, r *http.Request) { // {{{ return } } // }}} + func pageIndex(w http.ResponseWriter, r *http.Request) { // {{{ - // Index page is rendered since everything - // else are specifically handled. page := HTMLTemplate.SimplePage{ Layout: "main", Page: "index", @@ -151,6 +151,20 @@ func pageIndex(w http.ResponseWriter, r *http.Request) { // {{{ return } } // }}} +func pageLogin(w http.ResponseWriter, r *http.Request) { // {{{ + page := HTMLTemplate.SimplePage{ + Layout: "main", + Page: "login", + Version: VERSION, + } + + err := Webengine.Render(page, w, r) + if err != nil { + w.Write([]byte(err.Error())) + return + } +} // }}} + func sessionFilter(fn func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) { // {{{ return func(w http.ResponseWriter, r *http.Request) { fmt.Printf("Filtered ")