diff --git a/.gitignore b/.gitignore index 6d87d92..c94fcf9 100644 --- a/.gitignore +++ b/.gitignore @@ -94,4 +94,5 @@ fabric.properties .idea/ testdata/ config/ -bin/ \ No newline at end of file +bin/ +*.sqlite \ No newline at end of file diff --git a/cmd/tlm-login-server/tlm-login-server.go b/cmd/tlm-login-server/tlm-login-server.go index 4d530f5..aa5b539 100644 --- a/cmd/tlm-login-server/tlm-login-server.go +++ b/cmd/tlm-login-server/tlm-login-server.go @@ -15,9 +15,15 @@ func Run() error { //go app.CleanupLogins() //go app.CleanupLoginsCronJob() + dberr := app.InitializeDB() + if dberr != nil { + return fmt.Errorf("couldn't initialize DB: %v", dberr.Error()) + } + err := app.SetupFiber() if err != nil { return fmt.Errorf("couldn't start webserver: %v", err.Error()) } + return nil } diff --git a/go.mod b/go.mod index a9bef41..06d440b 100644 --- a/go.mod +++ b/go.mod @@ -2,21 +2,33 @@ module code.lila.network/lauralani/tlm-login-server go 1.21.4 +require ( + github.com/gofiber/fiber/v2 v2.51.0 + github.com/rs/zerolog v1.31.0 + gorm.io/gorm v1.25.5 +) + require ( github.com/andybalholm/brotli v1.0.5 // indirect - github.com/gofiber/fiber/v2 v2.51.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.10.0 // indirect github.com/google/uuid v1.4.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/compress v1.17.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rs/zerolog v1.31.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/crypto v0.16.0 // indirect golang.org/x/sys v0.15.0 // indirect - gorm.io/gorm v1.25.5 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect ) diff --git a/go.sum b/go.sum index 31cac78..0ba241e 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,12 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= +github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= @@ -10,8 +16,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= -github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -21,6 +27,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -32,12 +41,20 @@ github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +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/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/internal/app/db.go b/internal/app/db.go new file mode 100644 index 0000000..46df3e0 --- /dev/null +++ b/internal/app/db.go @@ -0,0 +1,35 @@ +package app + +import ( + "code.lila.network/lauralani/tlm-login-server/internal/models" + "github.com/glebarez/sqlite" + //"gorm.io/driver/sqlite" with CGo + "gorm.io/gorm" + "log/slog" + "os" +) + +func InitializeDB() error { + + var err error + var db *gorm.DB + var dbpath = os.Getenv("TLM_SQLITE_PATH") + + db, err = gorm.Open(sqlite.Open(dbpath), &gorm.Config{}) + + if err != nil { + slog.Error("Can't open DB") + os.Exit(1) + + } + + models.DB = db + + err = models.DB.AutoMigrate(&models.User{}) + if err != nil { + slog.Error("Can't do DB Migration", "Model", "User") + return err + } + + return nil +} diff --git a/internal/app/fiber.go b/internal/app/fiber.go index eca3aed..873256b 100644 --- a/internal/app/fiber.go +++ b/internal/app/fiber.go @@ -1,6 +1,7 @@ package app import ( + "code.lila.network/lauralani/tlm-login-server/internal/handlers" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/cors" @@ -54,6 +55,8 @@ func SetupFiber() error { fiberapp.Use(recover.New()) //fiberapp.Static("/admin/", "./web") + fiberapp.Add("POST", "/api/users/register", handlers.POSTUserRegister) + fiberapp.Add("POST", "/api/users/login", handlers.POSTUserLogin) v1 := fiberapp.Group("/api/v1") v1.Use(cors.New(cors.Config{AllowOrigins: "*"})) diff --git a/internal/handlers/user-login.go b/internal/handlers/user-login.go new file mode 100644 index 0000000..cfeae7c --- /dev/null +++ b/internal/handlers/user-login.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "code.lila.network/lauralani/tlm-login-server/internal/models" + "code.lila.network/lauralani/tlm-login-server/internal/shared" + "encoding/json" + "github.com/gofiber/fiber/v2" + "log" + "log/slog" +) + +func POSTUserLogin(c *fiber.Ctx) error { + var loginrequest models.LoginRequest + var existinguser models.User + + err := json.Unmarshal(c.Body(), &loginrequest) + if err != nil { + log.Println(err.Error()) + return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") + } + + userquery := models.DB.Where("username = ?", loginrequest.Username).First(&existinguser) + if userquery.RowsAffected == 0 { + slog.Info("Failed login for nonexistent user", "Username", loginrequest.Username, "Request-IP", c.IP()) + return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") + } + + if shared.CheckPassword([]byte(loginrequest.Password), existinguser.PasswordHash) { + c.Status(fiber.StatusOK) + return nil + } else { + return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") + } +} diff --git a/internal/handlers/user-register.go b/internal/handlers/user-register.go new file mode 100644 index 0000000..ab4c45c --- /dev/null +++ b/internal/handlers/user-register.go @@ -0,0 +1,58 @@ +package handlers + +import ( + "code.lila.network/lauralani/tlm-login-server/internal/models" + "code.lila.network/lauralani/tlm-login-server/internal/shared" + "encoding/json" + "github.com/gofiber/fiber/v2" + "log" + "log/slog" +) + +func POSTUserRegister(c *fiber.Ctx) error { + var registration models.RegistrationRequest + var user models.User + var queryuser models.User + + err := json.Unmarshal(c.Body(), ®istration) + if err != nil { + log.Println(err.Error()) + return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request") + } + + duplicatequery := models.DB.Where("username = ?", registration.Username).First(&queryuser) + if duplicatequery.RowsAffected != 0 { + c.Status(fiber.StatusBadRequest) + err = c.JSON(models.ErrorReply{ + Status: "error", + Message: "Username is already taken!", + }) + if err != nil { + log.Println(err) + } + return nil + } + + user.PasswordHash = shared.HashPassword(registration.Password) + user.Username = registration.Username + user.Email = registration.Email + + dbop := models.DB.Create(&user) + if dbop.Error != nil { + slog.Error("Error creating user", "Username", registration.Username, "Error", err.Error()) + } + + var httpreply models.RegistrationReply + + httpreply.ID = user.ID + httpreply.Username = user.Username + httpreply.CreatedAt = user.CreatedAt + + c.Status(fiber.StatusCreated) + err = c.JSON(httpreply) + if err != nil { + log.Println(err) + } + + return nil +} diff --git a/internal/models/user.go b/internal/models/database.go similarity index 72% rename from internal/models/user.go rename to internal/models/database.go index 9255f7d..213a34e 100644 --- a/internal/models/user.go +++ b/internal/models/database.go @@ -5,11 +5,12 @@ import ( "time" ) +var DB *gorm.DB + type User struct { gorm.Model Username string Email string - PasswordHash []byte - PasswordSalt string + PasswordHash []byte `gorm:"size:255"` LastLoginAt time.Time } diff --git a/internal/models/http.go b/internal/models/http.go new file mode 100644 index 0000000..e94aed3 --- /dev/null +++ b/internal/models/http.go @@ -0,0 +1,25 @@ +package models + +import "time" + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type RegistrationRequest struct { + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +type RegistrationReply struct { + ID uint `json:"id"` + Username string `json:"username"` + CreatedAt time.Time `json:"created_at"` +} + +type ErrorReply struct { + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/internal/shared/check-password.go b/internal/shared/check-password.go new file mode 100644 index 0000000..dea06a8 --- /dev/null +++ b/internal/shared/check-password.go @@ -0,0 +1,17 @@ +package shared + +import ( + "fmt" + "golang.org/x/crypto/bcrypt" +) + +// CheckPassword compares []byte password and []byte hash. +// +// Returns true if hashes match, false if not. +func CheckPassword(password []byte, hash []byte) bool { + fmt.Println(fmt.Sprintf("Hash: %v", hash)) + fmt.Println(fmt.Sprintf("Hashlength: %v", len(hash))) + err := bcrypt.CompareHashAndPassword(hash, password) + + return err == nil +} diff --git a/internal/shared/hash-password.go b/internal/shared/hash-password.go new file mode 100644 index 0000000..ce9d9a8 --- /dev/null +++ b/internal/shared/hash-password.go @@ -0,0 +1,22 @@ +package shared + +import ( + "fmt" + "golang.org/x/crypto/bcrypt" + "log/slog" +) + +func HashPassword(password string) []byte { + passwordbytes := []byte(password) + + // Hashing the password with the default cost of 10 + passwordhash, err := bcrypt.GenerateFromPassword(passwordbytes, bcrypt.DefaultCost) + if err != nil { + slog.Error("Can't hash password") + } + + fmt.Println(fmt.Sprintf("Hash: %v", passwordhash)) + fmt.Println(fmt.Sprintf("Hashlength: %v", len(passwordhash))) + + return passwordhash +}