add login method and api endpoint
This commit is contained in:
parent
d7c7140981
commit
224edb3ce5
8 changed files with 201 additions and 0 deletions
62
internal/api/users.go
Normal file
62
internal/api/users.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"codeberg.org/lauralani/go-urlsh/internal/api"
|
"codeberg.org/lauralani/go-urlsh/internal/api"
|
||||||
|
"codeberg.org/lauralani/go-urlsh/internal/web"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/compress"
|
"github.com/gofiber/fiber/v2/middleware/compress"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
|
@ -47,6 +48,8 @@ func SetupFiber() error {
|
||||||
|
|
||||||
fiberapp.Static("/admin/", "./web")
|
fiberapp.Static("/admin/", "./web")
|
||||||
|
|
||||||
|
fiberapp.Post("/admin/login", web.HandleLogin)
|
||||||
|
|
||||||
v1 := fiberapp.Group("/api/v1")
|
v1 := fiberapp.Group("/api/v1")
|
||||||
v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
|
v1.Use(cors.New(cors.Config{AllowOrigins: "*"}))
|
||||||
|
|
||||||
|
@ -60,6 +63,8 @@ func SetupFiber() error {
|
||||||
v1.Post("/apikeys", api.HandleApiKeysPost)
|
v1.Post("/apikeys", api.HandleApiKeysPost)
|
||||||
v1.Delete("/apikeys/:id<guid>", api.HandleApiKeysPost)
|
v1.Delete("/apikeys/:id<guid>", api.HandleApiKeysPost)
|
||||||
|
|
||||||
|
v1.Post("/users", api.HandleUserPost)
|
||||||
|
|
||||||
listenerr := fiberapp.Listen(ip + ":" + port)
|
listenerr := fiberapp.Listen(ip + ":" + port)
|
||||||
if listenerr != nil {
|
if listenerr != nil {
|
||||||
return listenerr
|
return listenerr
|
||||||
|
|
|
@ -19,5 +19,20 @@ func InitializeDB() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't create database: [%w]", err)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
67
internal/web/login.go
Normal file
67
internal/web/login.go
Normal 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
13
models/apikey.go
Normal 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
19
models/login.go
Normal 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
20
models/user.go
Normal 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"`
|
||||||
|
}
|
Loading…
Reference in a new issue