Added login function

This commit is contained in:
Magnus Åhall 2024-11-28 06:09:34 +01:00
parent a1a928e7cb
commit 9d08b056f3
2 changed files with 70 additions and 5 deletions

View File

@ -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
} // }}}

18
main.go
View File

@ -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 ")