- Refactory cookie and API session handle. Insolated y routes and controllers (api-auth.go, app-auth.go).

- Cookie lifetime with sliding expiration
- Fix bug single global expiresAt in JWT, now each token has unique ExpiresAt
This commit is contained in:
Zeni Kim 2026-09-12 21:26:01 -05:00
parent bf8e2e90ec
commit 10ded026df
10 changed files with 358 additions and 304 deletions

201
controllers/app-auth.go Normal file
View file

@ -0,0 +1,201 @@
// Copyright (c) 2026 Zeni Kim <zenik@smarteching.com>
// Use of this source code is governed by MIT-style
// license that can be found in the LICENSE file.
package controllers
import (
"errors"
"fmt"
"net/http"
"os"
"strconv"
"git.smarteching.com/goffee/core"
"git.smarteching.com/goffee/core/template/components"
"git.smarteching.com/goffee/cup/models"
"gorm.io/gorm"
)
func AppSignin(c *core.Context) *core.Response {
email := c.GetRequestParam("email")
password := c.GetRequestParam("password")
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
// Apps web only works if template is enabled in environment
if !TemplateEnable {
return c.Response.Redirect("/applogin")
}
data := map[string]interface{}{
"email": email,
"password": password,
}
rules := map[string]interface{}{
"email": "required|email",
"password": "required",
}
v := c.GetValidator().Validate(data, rules)
if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson())
return c.Response.Redirect("/applogin")
}
var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error())
return c.Response.Redirect("/applogin")
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
return c.Response.Redirect("/applogin")
}
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.Redirect("/applogin")
}
if !ok {
return c.Response.Redirect("/applogin")
}
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
"userID": user.ID,
})
if err != nil {
c.GetLogger().Error(err.Error())
return c.Response.Redirect("/applogin")
}
// cache the token
userAgent := c.GetUserAgent()
hashedCacheKey := core.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)
_ = c.GetCache().Delete(hashedSessionKey)
if err != nil {
c.GetLogger().Error(err.Error())
// TODO set error in session
return c.Response.Redirect("/applogin")
}
// create cookie
err = core.SetCookie(c.Response.HttpResponseWriter, email.(string), token)
if err != nil {
panic(fmt.Sprintf("Error write encrypted cookie: %v", err))
return c.Response.SetStatusCode(http.StatusInternalServerError)
}
// redirect to app
return c.Response.Redirect("/dashboard")
}
func AppSignout(c *core.Context) *core.Response {
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
// Apps web only works if template is enabled in environment
if !TemplateEnable {
return c.Response.Redirect("/applogin")
}
token := ""
// get cookie
usercookie, err := c.GetCookie()
if err != nil {
return c.Response.Redirect("/applogin")
}
token = usercookie.Token
if token == "" {
return c.Response.Redirect("/applogin")
}
payload, err := c.GetJWT().DecodeToken(token)
if err != nil {
return c.Response.Redirect("/applogin")
}
userAgent := c.GetUserAgent()
hashedCacheKey := core.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["userID"])), userAgent)
err = c.GetCache().Delete(hashedCacheKey)
if err != nil {
return c.Response.Redirect("/applogin")
}
return c.Response.Redirect("/applogin")
}
// Show basic app login
func AppLogin(c *core.Context) *core.Response {
// check if template engine is enable
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
if TemplateEnableStr == "" {
TemplateEnableStr = "false"
}
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
if TemplateEnable {
// initiate authority
session := c.GetSession()
// true if session is active
hassession := session.Init(c)
// only show login if no has session
if !hassession {
type templateData struct{}
tmplData := templateData{}
return c.Response.Template("login.html", tmplData)
} else {
// first, include all compoments
type templateData struct {
PageCard components.PageCard
}
// now fill data of the components
tmplData := templateData{
PageCard: components.PageCard{
CardTitle: "Golang Framework",
CardBody: "Welcome to Goffee",
},
}
return c.Response.Template("welcome.html", tmplData)
}
} else {
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "Apps web only works if template is enabled in environment",
}))
}
}
// This page is cookie protected by hook
func WelcomeToDashboard(c *core.Context) *core.Response {
type templateData struct{}
return c.Response.Template("dashboard.html", templateData{})
}