Implement session management with login and logout functionality
This commit is contained in:
parent
d201bd6636
commit
eb24084e24
82
api/auth.go
82
api/auth.go
@ -3,19 +3,32 @@ package api
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.tek.govt.hu/dowerx/chat/server/config"
|
||||||
"git.tek.govt.hu/dowerx/chat/server/controller"
|
"git.tek.govt.hu/dowerx/chat/server/controller"
|
||||||
"github.com/gin-gonic/gin"
|
"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) {
|
func register(c *gin.Context) {
|
||||||
type registerTransaction struct {
|
type registerTransaction struct {
|
||||||
Username string `form:"username"`
|
Username string `form:"username" json:"username"`
|
||||||
Password string `form:"password"`
|
Password string `form:"password" json:"password"`
|
||||||
RepeatPassword string `form:"repeatPassword"`
|
RepeatPassword string `form:"repeatPassword" json:"repeatPassword"`
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction := registerTransaction{}
|
transaction := registerTransaction{}
|
||||||
@ -49,9 +62,68 @@ func register(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func login(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) {
|
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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.tek.govt.hu/dowerx/chat/server/dao"
|
"git.tek.govt.hu/dowerx/chat/server/dao"
|
||||||
@ -10,20 +12,32 @@ import (
|
|||||||
|
|
||||||
type UserController struct {
|
type UserController struct {
|
||||||
userDAO dao.IUserDAO
|
userDAO dao.IUserDAO
|
||||||
|
sessionDAO dao.ISessionDAO
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MIN_USERNAME_LENGTH int = 3
|
MIN_USERNAME_LENGTH int = 3
|
||||||
MIN_PASSWORD_LENGTH int = 6
|
MIN_PASSWORD_LENGTH int = 6
|
||||||
HASH_COST int = bcrypt.DefaultCost
|
HASH_COST int = bcrypt.DefaultCost
|
||||||
|
TOKEN_LENGTH int = 32
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *UserController) init() error {
|
func (c *UserController) init() error {
|
||||||
userDAO, err := dao.MakeUserDAO()
|
userDAO, err := dao.MakeUserDAO()
|
||||||
c.userDAO = userDAO
|
c.userDAO = userDAO
|
||||||
|
if err != nil {
|
||||||
return err
|
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 {
|
func (c UserController) Register(username string, password string, repeatPassword string) error {
|
||||||
if len(username) < MIN_USERNAME_LENGTH {
|
if len(username) < MIN_USERNAME_LENGTH {
|
||||||
return errors.New("username too short")
|
return errors.New("username too short")
|
||||||
@ -47,3 +61,40 @@ func (c UserController) Register(username string, password string, repeatPasswor
|
|||||||
PasswordHash: string(hash),
|
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
|
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) {
|
func MakeUserDAO() (IUserDAO, error) {
|
||||||
dao := &postgres.UserDAOPG{}
|
dao := &postgres.UserDAOPG{}
|
||||||
err := dao.Init()
|
err := dao.Init()
|
||||||
return dao, err
|
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 {
|
type ISessionDAO interface {
|
||||||
Set(token string, id int) error
|
Set(token string, id int) error
|
||||||
Get(token string) (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