Implement session management with login and logout functionality
This commit is contained in:
parent
d201bd6636
commit
eb24084e24
80
api/auth.go
80
api/auth.go
@ -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",
|
||||
})
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
18
dao/valkey/Connection.go
Normal 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
40
dao/valkey/SessionDAO.go
Normal 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
15
dao/valkey/vkDAO.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user