Implement session management with login and logout functionality

This commit is contained in:
BENEDEK László 2025-06-01 17:17:37 +02:00
parent d201bd6636
commit eb24084e24
7 changed files with 214 additions and 7 deletions

View File

@ -3,19 +3,32 @@ package api
import (
"net/http"
"git.tek.govt.hu/dowerx/chat/server/config"
"git.tek.govt.hu/dowerx/chat/server/controller"
"github.com/gin-gonic/gin"
)
func isLoggedIn(c *gin.Context) {
const SESSION_COOKIE string = "session"
func isLoggedIn(c *gin.Context) {
token, err := c.Cookie(SESSION_COOKIE)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "missing token",
})
c.Abort()
return
}
c.Set(SESSION_COOKIE, token)
c.Next()
}
func register(c *gin.Context) {
type registerTransaction struct {
Username string `form:"username"`
Password string `form:"password"`
RepeatPassword string `form:"repeatPassword"`
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
RepeatPassword string `form:"repeatPassword" json:"repeatPassword"`
}
transaction := registerTransaction{}
@ -49,9 +62,68 @@ func register(c *gin.Context) {
}
func login(c *gin.Context) {
type loginTransaction struct {
Username string `form:"username" json:"username"`
Password string `form:"password" json:"password"`
}
transaction := loginTransaction{}
if err := c.Bind(&transaction); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
userController, err := controller.MakeUserController()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
token, ok, err := userController.Login(transaction.Username, transaction.Password)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{
"error": "bad credentials",
})
return
}
c.SetCookie(SESSION_COOKIE, token, config.GetConfig().API.TokenLife, "", "", false, false)
c.JSON(http.StatusOK, gin.H{
"message": "sucessful login",
"session": token,
})
}
func logout(c *gin.Context) {
userController, err := controller.MakeUserController()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
token, _ := c.Get(SESSION_COOKIE) // must exist after isLoggedIn
err = userController.Logout(token.(string))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
c.SetCookie(SESSION_COOKIE, "", 0, "", "", false, false)
c.JSON(http.StatusOK, gin.H{
"message": "sucessful logout",
})
}

View File

@ -1,6 +1,8 @@
package controller
import (
"crypto/rand"
"encoding/base64"
"errors"
"git.tek.govt.hu/dowerx/chat/server/dao"
@ -9,19 +11,31 @@ import (
)
type UserController struct {
userDAO dao.IUserDAO
userDAO dao.IUserDAO
sessionDAO dao.ISessionDAO
}
const (
MIN_USERNAME_LENGTH int = 3
MIN_PASSWORD_LENGTH int = 6
HASH_COST int = bcrypt.DefaultCost
TOKEN_LENGTH int = 32
)
func (c *UserController) init() error {
userDAO, err := dao.MakeUserDAO()
c.userDAO = userDAO
return err
if err != nil {
return err
}
sessionDao, err := dao.MakeSessionDAO()
c.sessionDAO = sessionDao
if err != nil {
return err
}
return nil
}
func (c UserController) Register(username string, password string, repeatPassword string) error {
@ -47,3 +61,40 @@ func (c UserController) Register(username string, password string, repeatPasswor
PasswordHash: string(hash),
})
}
func generateToken(length int) (string, error) {
b := make([]byte, length)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
func (c UserController) Login(username string, password string) (string, bool, error) {
user, err := c.userDAO.Read(model.User{Username: username})
if err != nil {
return "", false, err
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password)); err != nil {
return "", false, errors.New("wrong password")
}
token, err := generateToken(TOKEN_LENGTH)
if err != nil {
return "", false, err
}
err = c.sessionDAO.Set(token, user.ID)
if err != nil {
return "", false, err
}
return token, true, nil
}
func (c UserController) Logout(token string) error {
return c.sessionDAO.Delete(token)
}

View File

@ -1,9 +1,18 @@
package dao
import "git.tek.govt.hu/dowerx/chat/server/dao/postgres"
import (
"git.tek.govt.hu/dowerx/chat/server/dao/postgres"
"git.tek.govt.hu/dowerx/chat/server/dao/valkey"
)
func MakeUserDAO() (IUserDAO, error) {
dao := &postgres.UserDAOPG{}
err := dao.Init()
return dao, err
}
func MakeSessionDAO() (ISessionDAO, error) {
dao := &valkey.SessionDAOVK{}
err := dao.Init()
return dao, err
}

View File

@ -3,4 +3,6 @@ package dao
type ISessionDAO interface {
Set(token string, id int) error
Get(token string) (int, error)
Delete(token string) error
Bump(token string, time int) error
}

18
dao/valkey/Connection.go Normal file
View File

@ -0,0 +1,18 @@
package valkey
import (
"git.tek.govt.hu/dowerx/chat/server/config"
"github.com/valkey-io/valkey-go"
)
var vk valkey.Client = nil
func getClient() (*valkey.Client, error) {
if vk == nil {
client, err := valkey.NewClient(config.GetConfig().Valkey)
vk = client
return &vk, err
}
return &vk, nil
}

40
dao/valkey/SessionDAO.go Normal file
View File

@ -0,0 +1,40 @@
package valkey
import (
"context"
"strconv"
"git.tek.govt.hu/dowerx/chat/server/config"
)
const SESSION_PREFIX string = "session:"
type SessionDAOVK struct {
vkDAO
}
func (d SessionDAOVK) Set(token string, id int) error {
cmd := (*d.vk).B().Set().Key(SESSION_PREFIX + token).Value(strconv.Itoa(id)).ExSeconds(int64(config.GetConfig().API.TokenLife)).Build()
return (*d.vk).Do(context.Background(), cmd).Error()
}
func (d SessionDAOVK) Get(token string) (int, error) {
cmd := (*d.vk).B().Get().Key(SESSION_PREFIX + token).Build()
result := (*d.vk).Do(context.Background(), cmd)
if err := result.Error(); err != nil {
return 0, err
}
id, err := result.AsInt64()
return int(id), err
}
func (d SessionDAOVK) Delete(token string) error {
cmd := (*d.vk).B().Del().Key(SESSION_PREFIX + token).Build()
return (*d.vk).Do(context.Background(), cmd).Error()
}
func (d SessionDAOVK) Bump(token string, time int) error {
cmd := (*d.vk).B().Expire().Key(SESSION_PREFIX + token).Seconds(int64(time)).Build()
return (*d.vk).Do(context.Background(), cmd).Error()
}

15
dao/valkey/vkDAO.go Normal file
View File

@ -0,0 +1,15 @@
package valkey
import (
"github.com/valkey-io/valkey-go"
)
type vkDAO struct {
vk *valkey.Client
}
func (d *vkDAO) Init() error {
client, err := getClient()
d.vk = client
return err
}