add login method and api endpoint

This commit is contained in:
Adora Laura Kalb 2023-04-27 10:37:42 +02:00
parent d7c7140981
commit 224edb3ce5
Signed by: adoralaura
GPG key ID: 7A4552166FC8C056
8 changed files with 201 additions and 0 deletions

62
internal/api/users.go Normal file
View file

@ -0,0 +1,62 @@
package api
import (
"codeberg.org/lauralani/go-urlsh/internal/misc"
"codeberg.org/lauralani/go-urlsh/models"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/gofiber/fiber/v2"
"log"
"time"
)
func HandleUserPost(c *fiber.Ctx) error {
var newuser models.LoginRequest
err := json.Unmarshal(c.Body(), &newuser)
if err != nil {
log.Println(err.Error())
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
}
usercount, err := models.DB.NewSelect().Model((*models.User)(nil)).Count(context.Background())
if err != nil {
log.Printf("[POST /api/v1/users] Error querying database for users: %v\n", err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
}
if usercount != 0 {
log.Printf("[POST /api/v1/users] someone trying to create user but user already exists\n")
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
} else {
salt := misc.RandomString(15)
created := time.Now()
hashbytes := sha256.Sum256([]byte(salt + newuser.Password))
fmt.Printf("%x\n", hashbytes)
hash := hex.EncodeToString(hashbytes[:])
user := new(models.User)
user.UserName = newuser.Username
user.PasswordSalt = salt
user.PasswordHash = hash
user.Created = created
_, err = models.DB.NewInsert().Model(user).Exec(context.Background())
if err != nil {
log.Printf("[POST /api/v1/users] Error adding user %v to database : %v\n", newuser.Username, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
}
userresponse := models.UserResponse{UserName: newuser.Username, Created: created}
c.Status(fiber.StatusCreated)
err = c.JSON(userresponse)
if err != nil {
log.Println(err)
}
return nil
}
}

View file

@ -2,6 +2,7 @@ package app
import (
"codeberg.org/lauralani/go-urlsh/internal/api"
"codeberg.org/lauralani/go-urlsh/internal/web"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/compress"
"github.com/gofiber/fiber/v2/middleware/cors"
@ -47,6 +48,8 @@ func SetupFiber() error {
fiberapp.Static("/admin/", "./web")
fiberapp.Post("/admin/login", web.HandleLogin)
v1 := fiberapp.Group("/api/v1")
v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
@ -60,6 +63,8 @@ func SetupFiber() error {
v1.Post("/apikeys", api.HandleApiKeysPost)
v1.Delete("/apikeys/:id<guid>", api.HandleApiKeysPost)
v1.Post("/users", api.HandleUserPost)
listenerr := fiberapp.Listen(ip + ":" + port)
if listenerr != nil {
return listenerr

View file

@ -19,5 +19,20 @@ func InitializeDB() error {
if err != nil {
return fmt.Errorf("couldn't create database: [%w]", err)
}
_, err = models.DB.NewCreateTable().IfNotExists().Model((*models.User)(nil)).Exec(context.Background())
if err != nil {
return fmt.Errorf("couldn't create database: [%w]", err)
}
_, err = models.DB.NewCreateTable().IfNotExists().Model((*models.Login)(nil)).Exec(context.Background())
if err != nil {
return fmt.Errorf("couldn't create database: [%w]", err)
}
_, err = models.DB.NewCreateTable().IfNotExists().Model((*models.ApiKey)(nil)).Exec(context.Background())
if err != nil {
return fmt.Errorf("couldn't create database: [%w]", err)
}
return nil
}

67
internal/web/login.go Normal file
View file

@ -0,0 +1,67 @@
package web
import (
"codeberg.org/lauralani/go-urlsh/models"
"context"
"crypto/sha256"
"encoding/hex"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"log"
"time"
)
func HandleLogin(c *fiber.Ctx) error {
var login models.LoginRequest
var user models.User
if err := c.BodyParser(&login); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "400 Bad Request")
}
err := models.DB.NewSelect().Model(&user).Where("username = ?", login.Username).Scan(context.Background())
if err != nil {
log.Printf("Error authenticating User %v: User doesn't exist by IP %v\n", login.Username, c.IP())
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
}
passwordsumbytes := sha256.Sum256([]byte(user.PasswordSalt + login.Password))
passwordsum := hex.EncodeToString(passwordsumbytes[:])
if passwordsum == user.PasswordHash {
// Passwords match
expires := time.Now().Add(30 * 24 * time.Hour)
key := uuid.New().String()
login := new(models.Login)
cookie := new(fiber.Cookie)
cookie.Name = "gourlsh_auth"
cookie.Value = key
cookie.Expires = expires
login.Expires = expires
login.Cookie = key
login.UserName = user.UserName
_, err = models.DB.NewInsert().Model(login).Exec(context.Background())
if err != nil {
log.Printf("DB Error inserting login cookie information for user %v: %v\n", login.UserName, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
}
user.LastLogin = time.Now()
_, err = models.DB.NewUpdate().Model(&user).WherePK().Exec(context.Background())
if err != nil {
log.Printf("DB Error updating last login information for user %v: %v\n", login.UserName, err.Error())
return fiber.NewError(fiber.StatusInternalServerError, "500 Internal Server Error")
}
c.Cookie(cookie)
c.Status(fiber.StatusSeeOther)
c.Location("/admin/")
return nil
} else {
log.Printf("Error authenticating User %v: password mismatch by IP %v\n", login.Username, c.IP())
return fiber.NewError(fiber.StatusUnauthorized, "401 Unauthorized")
}
}

13
models/apikey.go Normal file
View file

@ -0,0 +1,13 @@
package models
import (
"github.com/uptrace/bun"
"time"
)
type ApiKey struct {
bun.BaseModel `bun:"table:apikeys"`
Key string `bun:"key,pk,type:uuid,default:gen_random_uuid()" json:"key,omitempty"`
UserName string `bun:"username,notnull" json:"username"`
Created time.Time `bun:"created,default:now()" json:"created"`
}

19
models/login.go Normal file
View file

@ -0,0 +1,19 @@
package models
import (
"github.com/uptrace/bun"
"time"
)
type Login struct {
bun.BaseModel `bun:"table:logins"`
ID int `bun:"id,pk,autoincrement"`
UserName string `bun:"username,notnull"`
Cookie string `bun:"cookie,notnull"`
Expires time.Time `bun:"expires,notnull"`
}
type LoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
}

20
models/user.go Normal file
View file

@ -0,0 +1,20 @@
package models
import (
"github.com/uptrace/bun"
"time"
)
type User struct {
bun.BaseModel `bun:"table:users"`
UserName string `bun:"username,pk" json:"username"`
Created time.Time `bun:"created,notnull,default:now()" json:"created"`
LastLogin time.Time `bun:"lastlogin" json:"last_login"`
PasswordSalt string `bun:"salt" json:"password_salt"`
PasswordHash string `bun:"password" json:"password_hash"`
}
type UserResponse struct {
UserName string `json:"username"`
Created time.Time `json:"created"`
}