diff --git a/.env-dev b/.env-dev index 9157a9c..800ca98 100644 --- a/.env-dev +++ b/.env-dev @@ -29,7 +29,7 @@ COOKIE_SECRET=13d6b4dff8f84a10851021ec8608f814570d562c92fe6b5ec4c9f595bcb3234b ###### JWT ###### ####################################### JWT_SECRET=dkfTgonmgaAdlgkw -JWT_LIFESPAN_MINUTES=1440 # expires after 1 day +JWT_LIFESPAN_MINUTES=4320 # expires after 3 days ####################################### ###### DATABASE ###### diff --git a/.env-example b/.env-example index 3639247..d0780b5 100644 --- a/.env-example +++ b/.env-example @@ -28,7 +28,7 @@ CDNEnable=false ###### JWT ###### ####################################### JWT_SECRET=dkfTgonmgaAdlgkw -JWT_LIFESPAN_MINUTES=1440 # expires after 1 day +JWT_LIFESPAN_MINUTES=4320 # expires after 3 days ####################################### ###### DATABASE ###### diff --git a/config/queue.go b/config/queue.go index 0c01453..8060572 100644 --- a/config/queue.go +++ b/config/queue.go @@ -16,15 +16,5 @@ func GetQueueConfig() core.QueueConfig { // For enabling and disabling the queue system // set to true to enable it, set to false to disable EnableQueue: false, - - // Number of concurrent workers processing tasks - Concurrency: 10, - - // Queue names with priority weights (higher number = higher priority) - Queues: map[string]int{ - "critical": 6, - "default": 3, - "low": 1, - }, } } diff --git a/controllers/adminusers.go b/controllers/adminusers.go index 5870512..9ffb762 100644 --- a/controllers/adminusers.go +++ b/controllers/adminusers.go @@ -21,7 +21,7 @@ func AdminUsersList(c *core.Context) *core.Response { // initiate authority auth := new(utils.Authority) - session := c.GetSession() + var session = new(utils.SessionUser) // true if session is active hassession := session.Init(c) @@ -135,7 +135,7 @@ func AdminUsersAdd(c *core.Context) *core.Response { // initiate authority auth := new(utils.Authority) - session := c.GetSession() + var session = new(utils.SessionUser) // true if session is active hassession := session.Init(c) diff --git a/controllers/authentication.go b/controllers/authentication.go index 35ab468..9e3eff9 100644 --- a/controllers/authentication.go +++ b/controllers/authentication.go @@ -1,5 +1,5 @@ // Copyright 2023 Harran Ali . All rights reserved. -// Copyright (c) 2026 Zeni Kim +// Copyright (c) 2024 Zeni Kim // Use of this source code is governed by MIT-style // license that can be found in the LICENSE file. @@ -19,6 +19,7 @@ import ( "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/events" "git.smarteching.com/goffee/cup/models" + "git.smarteching.com/goffee/cup/utils" "github.com/google/uuid" "gorm.io/gorm" ) @@ -100,7 +101,7 @@ func Signup(c *core.Context) *core.Response { // cache the token userAgent := c.GetUserAgent() - hashedCacheKey := core.CreateAuthTokenHashedCacheKey(user.ID, userAgent) + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent) err = c.GetCache().Set(hashedCacheKey, token) if err != nil { c.GetLogger().Error(err.Error()) @@ -222,12 +223,12 @@ func Signin(c *core.Context) *core.Response { } // cache the token userAgent := c.GetUserAgent() - hashedCacheKey := core.CreateAuthTokenHashedCacheKey(user.ID, userAgent) + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent) err = c.GetCache().Set(hashedCacheKey, token) // delete data from old sessions sessionKey := fmt.Sprintf("sess_%v", userAgent) - hashedSessionKey := core.CreateAuthTokenHashedCacheKey(user.ID, sessionKey) + hashedSessionKey := utils.CreateAuthTokenHashedCacheKey(user.ID, sessionKey) _ = c.GetCache().Delete(hashedSessionKey) if err != nil { @@ -462,7 +463,7 @@ func Signout(c *core.Context) *core.Response { })) } userAgent := c.GetUserAgent() - hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) err = c.GetCache().Delete(hashedCacheKey) if err != nil { diff --git a/controllers/home.go b/controllers/home.go index ccc7ac2..0a88e29 100644 --- a/controllers/home.go +++ b/controllers/home.go @@ -47,27 +47,8 @@ func WelcomeHome(c *core.Context) *core.Response { } - +// Show dashboard func WelcomeToDashboard(c *core.Context) *core.Response { message := "{\"message\": \"Welcome to Dashboard\"}" return c.Response.Json(message) } - -// Show basic app login -func AppLogin(c *core.Context) *core.Response { - - // first, include all compoments - // first, include all compoments - type templateData struct { - PageCard components.PageCard - } - - // now fill data of the components - tmplData := templateData{ - PageCard: components.PageCard{ - CardTitle: "Card title", - CardBody: "Loerm ipsum at deim", - }, - } - return c.Response.Template("login.html", tmplData) -} diff --git a/controllers/queuesample.go b/controllers/queuesample.go deleted file mode 100644 index 0745a17..0000000 --- a/controllers/queuesample.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2025 Zeni Kim -// Use of this source code is governed by MIT-style -// license that can be found in the LICENSE file. - -package controllers - -import ( - "encoding/json" - "fmt" - "time" - - "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/cup/workers" - "github.com/hibiken/asynq" -) - -// Make samples queues -func Queuesample(c *core.Context) *core.Response { - - // Get client queue asynq - client := c.GetQueueClient() - - // Create a task with typename and payload. - payload, err := json.Marshal(workers.EmailTaskPayload{UserID: 42}) - if err != nil { - c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`) - } - - t1 := asynq.NewTask(workers.TypeWelcomeEmail, payload) - - t2 := asynq.NewTask(workers.TypeReminderEmail, payload) - - // Process the task immediately. - info, err := client.Enqueue(t1) - if err != nil { - c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`) - } - c.GetLogger().Info(fmt.Sprintf(" [*] Successfully enqueued task: %+v", info)) - - // Process 2 task 1 min later. - for i := 1; i < 3; i++ { - info, err = client.Enqueue(t2, asynq.ProcessIn(1*time.Minute)) - if err != nil { - c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(500).Json(`{"message": "internal error"}`) - } - c.GetLogger().Info(fmt.Sprintf(" [*] Successfully enqueued task: %+v", info)) - } - - message := "{\"message\": \"Task queued\"}" - return c.Response.Json(message) - -} diff --git a/hooks/auth-check.go b/hooks/auth-check.go index 1b3e1e7..779cbda 100644 --- a/hooks/auth-check.go +++ b/hooks/auth-check.go @@ -9,6 +9,7 @@ import ( "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/models" + "git.smarteching.com/goffee/cup/utils" "gorm.io/gorm" ) @@ -30,7 +31,7 @@ var CheckSessionCookie core.Hook = func(c *core.Context) { } else { userAgent := c.GetUserAgent() - hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) cachedToken, err := c.GetCache().Get(hashedCacheKey) if err != nil { @@ -101,7 +102,7 @@ var AuthCheck core.Hook = func(c *core.Context) { return } userAgent := c.GetUserAgent() - hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent) cachedToken, err := c.GetCache().Get(hashedCacheKey) if err != nil { diff --git a/register-queues.go b/register-queues.go index 169ad84..a1cf059 100644 --- a/register-queues.go +++ b/register-queues.go @@ -6,7 +6,6 @@ package main import ( "git.smarteching.com/goffee/core" - "git.smarteching.com/goffee/cup/config" "git.smarteching.com/goffee/cup/workers" ) @@ -27,5 +26,5 @@ func registerQueues() { //######################################## // Start queue server, DO NOT TOUCH //######################################## - go queque.RunQueueserver(config.GetQueueConfig()) + go queque.RunQueueserver() } diff --git a/routes.go b/routes.go index b6b1f3b..193da2d 100644 --- a/routes.go +++ b/routes.go @@ -8,7 +8,6 @@ package main import ( "git.smarteching.com/goffee/core" "git.smarteching.com/goffee/cup/controllers" - "git.smarteching.com/goffee/cup/hooks" ) // Register the app controllers @@ -20,7 +19,7 @@ func registerRoutes() { // Define your routes here... controller.Get("/", controllers.WelcomeHome) - + // Uncomment the lines below to enable authentication API controller.Post("/signup", controllers.Signup) controller.Post("/signin", controllers.Signin) @@ -28,9 +27,6 @@ func registerRoutes() { controller.Post("/reset-password", controllers.ResetPasswordRequest) controller.Post("/reset-password/code/:code", controllers.SetNewPassword) - // queue sample route - controller.Get("/queuesample", controllers.Queuesample) - // Uncomment the lines below to enable user administration controller.Get("/admin/users", controllers.AdminUsersList) controller.Post("/admin/users", controllers.AdminUsersList) @@ -41,8 +37,5 @@ func registerRoutes() { controller.Post("/admin/users/delete", controllers.AdminUsersDelete) controller.Post("/admin/users/deleteconfirm", controllers.AdminUsersDelConfirm) - controller.Get("/dashboard", controllers.WelcomeToDashboard, hooks.AuthCheck) - controller.Get("/signout", controllers.Signout) - controller.Get("/applogin", controllers.AppLogin, hooks.CheckSessionCookie) - controller.Post("/applogin", controllers.AppLogin, hooks.CheckSessionCookie) + } diff --git a/utils/helpers.go b/utils/helpers.go index 7a2e922..8ca7dd8 100644 --- a/utils/helpers.go +++ b/utils/helpers.go @@ -6,6 +6,8 @@ package utils import ( + "crypto/md5" + "fmt" "log" "time" @@ -69,7 +71,13 @@ func CreateSeedData() { } } +// generate a hashed string to be used as key for caching auth jwt token +func CreateAuthTokenHashedCacheKey(userID uint, userAgent string) string { + cacheKey := fmt.Sprintf("userid:_%v_useragent:_%v_jwt_token", userID, userAgent) + hashedCacheKey := fmt.Sprintf("%v", fmt.Sprintf("%x", md5.Sum([]byte(cacheKey)))) + return hashedCacheKey +} func FormatUnix(value int64) string { return time.Unix(value, 0).Format("2006-01-02 15:04:05") diff --git a/utils/session.go b/utils/session.go new file mode 100644 index 0000000..27e202d --- /dev/null +++ b/utils/session.go @@ -0,0 +1,170 @@ +// Copyright (c) 2024 Zeni Kim +// Use of this source code is governed by MIT-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "errors" + "fmt" + "sync" + "time" + + "encoding/json" + + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/cup/models" + "gorm.io/gorm" +) + +type SessionUser struct { + mu sync.RWMutex + context *core.Context + userID uint + hashedSessionKey string + authenticated bool + sessionStart time.Time + values map[string]interface{} +} + +// start the struct +func (s *SessionUser) Init(c *core.Context) bool { + + // check session cookie + pass := true + token := "" + s.context = c + + payload := make(map[string]interface{}) + // get cookie + usercookie, err := c.GetCookie() + if err != nil { + + } + + token = usercookie.Token + + if token == "" { + + pass = false + + } else { + + payload, err = c.GetJWT().DecodeToken(token) + + if err != nil { + + pass = false + + } else { + + userID := uint(c.CastToInt(payload["userID"])) + userAgent := c.GetUserAgent() + + // get data from redis + hashedCacheKey := CreateAuthTokenHashedCacheKey(userID, userAgent) + cachedToken, err := c.GetCache().Get(hashedCacheKey) + + if err != nil { + pass = false + } else if cachedToken != token { + pass = false + } else { + var user models.User + res := c.GetGorm().Where("id = ?", userID).First(&user) + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + pass = false + } + // if have session start the struct + if pass { + userAgent := c.GetUserAgent() + sessionKey := fmt.Sprintf("sess_%v", userAgent) + s.hashedSessionKey = CreateAuthTokenHashedCacheKey(userID, sessionKey) + + s.values = make(map[string]interface{}) + s.authenticated = true + s.userID = userID + value, _ := c.GetCache().Get(s.hashedSessionKey) + + if len(value) > 0 { + _ = json.Unmarshal([]byte(value), &s.values) + } + + return true + + } else { + + s.hashedSessionKey = "" + s.authenticated = false + s.userID = 0 + return false + } + } + } + } + + return false +} + +func (s *SessionUser) Set(key string, value interface{}) error { + s.mu.Lock() + s.values[key] = value + s.mu.Unlock() + return s.Save() +} + +func (s *SessionUser) Get(key string) (interface{}, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + val, ok := s.values[key] + return val, ok +} + +func (s *SessionUser) Delete(key string) interface{} { + s.mu.RLock() + v, ok := s.values[key] + s.mu.RUnlock() + if ok { + s.mu.Lock() + delete(s.values, key) + s.mu.Unlock() + } + s.Save() + return v +} + +func (s *SessionUser) Flush() error { + s.mu.Lock() + s.context.GetCache().Delete(s.hashedSessionKey) + s.mu.Unlock() + return nil +} + +func (s *SessionUser) Save() error { + + var value string + + s.mu.RLock() + + if len(s.values) > 0 { + buf, err := json.Marshal(&s.values) + if err != nil { + s.mu.RUnlock() + return err + } + value = string(buf) + } + + if len(value) > 0 { + s.context.GetCache().Set(s.hashedSessionKey, value) + } else { + s.context.GetCache().Delete(s.hashedSessionKey) + } + s.mu.RUnlock() + return nil +} + +func (s *SessionUser) GetUserID() uint { + + return s.userID +} diff --git a/workers/workers.go b/workers/workers.go index 6edde6f..e7a9cad 100644 --- a/workers/workers.go +++ b/workers/workers.go @@ -3,9 +3,8 @@ package workers import ( "context" "encoding/json" - "fmt" + "log" - "git.smarteching.com/goffee/core/logger" "github.com/hibiken/asynq" ) @@ -26,7 +25,7 @@ func HandleWelcomeEmailTask(ctx context.Context, t *asynq.Task) error { if err := json.Unmarshal(t.Payload(), &p); err != nil { return err } - logger.ResolveLogger().Info(fmt.Sprintf(" [*] Send Welcome Email to User %d", p.UserID)) + log.Printf(" [*] Send Welcome Email to User %d", p.UserID) return nil } @@ -35,6 +34,6 @@ func HandleReminderEmailTask(ctx context.Context, t *asynq.Task) error { if err := json.Unmarshal(t.Payload(), &p); err != nil { return err } - logger.ResolveLogger().Info(fmt.Sprintf(" [*] Send Reminder Email to User %d", p.UserID)) + log.Printf(" [*] Send Reminder Email to User %d", p.UserID) return nil }