diff --git a/api/auth/login.go b/api/auth/login.go index 13ed703..a3a5ccc 100644 --- a/api/auth/login.go +++ b/api/auth/login.go @@ -1,7 +1,43 @@ package auth -import "github.com/gin-gonic/gin" +import ( + "net/http" + + "git.tek.govt.hu/dowerx/szoe-pontok/config" + "git.tek.govt.hu/dowerx/szoe-pontok/database/auth" + "git.tek.govt.hu/dowerx/szoe-pontok/model" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/validator/v10" +) func Login(c *gin.Context) { + var user model.User + if c.MustBindWith(&user, binding.Form) != nil { + return + } + + val := validator.New(validator.WithRequiredStructEnabled()) + if err := val.Struct(user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "error": err.Error(), + }) + return + } + + if token, err := auth.Login(user); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "status": http.StatusBadRequest, + "error": err.Error(), + }) + return + } else { + c.SetCookie("token", token, config.GetConfig().API.TokenLife, "/", "", false, false) + c.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "token": token, + }) + } } diff --git a/api/auth/middleware.go b/api/auth/middleware.go new file mode 100644 index 0000000..e2400db --- /dev/null +++ b/api/auth/middleware.go @@ -0,0 +1,37 @@ +package auth + +import ( + "net/http" + + "git.tek.govt.hu/dowerx/szoe-pontok/database/auth" + "github.com/gin-gonic/gin" +) + +func LoggedIn(c *gin.Context) { + token, err := c.Cookie("token") + + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "status": http.StatusUnauthorized, + "error": "missing token", + }) + c.Abort() + return + } + + neptun, err := auth.LoggedIn(token) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{ + "status": http.StatusUnauthorized, + "error": "not logged in", + }) + c.Abort() + return + } + + c.Set("neptun", neptun) +} + +func IsAdmin(c *gin.Context) { + +} diff --git a/api/endpotins.go b/api/endpotins.go index ded385f..d1792dc 100644 --- a/api/endpotins.go +++ b/api/endpotins.go @@ -25,6 +25,19 @@ func Listen(address string, path string) { apiAuth.POST("register", auth.Register) apiAuth.GET("login", auth.Login) } + + apiTest := api.Group("test").Use(auth.LoggedIn) + { + apiTest.GET("logged_in", func(c *gin.Context) { + neptun, _ := c.Get("neptun") + + c.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + "message": "if you see this you are logged in", + "neptun": neptun, + }) + }) + } } server := &http.Server{ diff --git a/config/config.go b/config/config.go index c539b08..30bd44c 100644 --- a/config/config.go +++ b/config/config.go @@ -19,8 +19,9 @@ type Config struct { Redis redis.Options API struct { - Address string - Path string + Address string + Path string + TokenLife int } } @@ -42,6 +43,7 @@ func GetConfig() *Config { flag.StringVar(&config.API.Address, "api-address", ":5000", "API address") flag.StringVar(&config.API.Path, "api-path", "api", "API path root") + flag.IntVar(&config.API.TokenLife, "api-token-life", 24*60*60, "API login token lifetime in seconds") if err := envflag.Parse(); err != nil { panic(err) diff --git a/database/auth/login.go b/database/auth/login.go new file mode 100644 index 0000000..b9244aa --- /dev/null +++ b/database/auth/login.go @@ -0,0 +1,58 @@ +package auth + +import ( + "errors" + "time" + + "git.tek.govt.hu/dowerx/szoe-pontok/config" + "git.tek.govt.hu/dowerx/szoe-pontok/database" + "git.tek.govt.hu/dowerx/szoe-pontok/model" + "golang.org/x/crypto/bcrypt" + "golang.org/x/exp/rand" +) + +func generateToken(length int) string { + validRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789") + token := make([]rune, length) + for i := range token { + token[i] = validRunes[rand.Intn(len(validRunes))] + } + return string(token) +} + +func Login(user model.User) (string, error) { + db := database.GetDB() + + rows, err := db.NamedQuery(`select "password" from "user" where "neptun" = :neptun and "email" = :email`, + map[string]interface{}{ + "neptun": user.Neptun, + "email": user.Email, + }) + + if err != nil { + return "", err + } + + if !rows.Next() { + return "", errors.New("no such user") + } + + var hash string + if err = rows.Scan(&hash); err != nil { + return "", err + } + + if bcrypt.CompareHashAndPassword([]byte(hash), []byte(user.Password)) != nil { + return "", errors.New("wrong password") + } + + token := generateToken(32) + + rdb, ctx := database.GetRDB() + result := rdb.Set(ctx, token, user.Neptun, time.Duration(config.GetConfig().API.TokenLife)*time.Second) + if result.Err() != nil { + return "", result.Err() + } + + return token, nil +} diff --git a/database/auth/middleware.go b/database/auth/middleware.go new file mode 100644 index 0000000..2596d8c --- /dev/null +++ b/database/auth/middleware.go @@ -0,0 +1,23 @@ +package auth + +import ( + "git.tek.govt.hu/dowerx/szoe-pontok/database" + "github.com/redis/go-redis/v9" +) + +func LoggedIn(token string) (string, error) { + rdb, ctx := database.GetRDB() + result, err := rdb.Get(ctx, token).Result() + + if err == redis.Nil { + return "", err + } + + return result, nil +} + +func IsAdmin(neptun string) error { + // db := database.GetDB() + + return nil +} diff --git a/database/connection.go b/database/connection.go index d7eab42..d4c8e62 100644 --- a/database/connection.go +++ b/database/connection.go @@ -1,11 +1,13 @@ package database import ( + "context" "fmt" "git.tek.govt.hu/dowerx/szoe-pontok/config" "github.com/jmoiron/sqlx" _ "github.com/lib/pq" + "github.com/redis/go-redis/v9" ) var db *sqlx.DB @@ -22,3 +24,19 @@ func GetDB() *sqlx.DB { return db } + +var rdb *redis.Client +var ctx context.Context + +func GetRDB() (*redis.Client, context.Context) { + if rdb == nil { + ctx = context.Background() + rdb = redis.NewClient(&config.GetConfig().Redis) + + if err := rdb.Ping(ctx).Err(); err != nil { + panic(err) + } + } + + return rdb, ctx +} diff --git a/go.mod b/go.mod index 8d46dfc..0651b98 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.23.0 // indirect + golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index e94e9c6..33d976a 100644 --- a/go.sum +++ b/go.sum @@ -83,6 +83,8 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= +golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/model/model.go b/model/model.go index 2aef989..405cc6a 100644 --- a/model/model.go +++ b/model/model.go @@ -10,9 +10,9 @@ type User struct { type Task struct { ID int `db:"id"` - Description string `db:"description" form:"description"` - Points int `db:"points" form:"points" validate:"required"` - Recipient string `db:"recipient" form:"recipient" validate:"required,len=6"` - Issuer string `db:"issuer" form:"issuer" validate:"required,len=6"` - Date time.Time `db:"date"` + Description string `db:"description" form:"description" json:"description"` + Points int `db:"points" form:"points" json:"points" validate:"required"` + Recipient string `db:"recipient" form:"recipient" json:"recipient" validate:"required,len=6"` + Issuer string `db:"issuer" form:"issuer" json:"issuer" validate:"required,len=6"` + CreatedDate time.Time `db:"created_date" json:"created_date"` }