- 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:
parent
bf8e2e90ec
commit
10ded026df
10 changed files with 358 additions and 304 deletions
|
|
@ -11,7 +11,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -23,7 +22,7 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Signup(c *core.Context) *core.Response {
|
||||
func APISignup(c *core.Context) *core.Response {
|
||||
name := c.GetRequestParam("name")
|
||||
fullname := c.GetRequestParam("fullname")
|
||||
email := c.GetRequestParam("email")
|
||||
|
|
@ -125,17 +124,10 @@ func Signup(c *core.Context) *core.Response {
|
|||
}))
|
||||
}
|
||||
|
||||
func Signin(c *core.Context) *core.Response {
|
||||
func APISignin(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)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"email": email,
|
||||
"password": password,
|
||||
|
|
@ -148,61 +140,36 @@ func Signin(c *core.Context) *core.Response {
|
|||
|
||||
if v.Failed() {
|
||||
c.GetLogger().Error(v.GetErrorMessagesJson())
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson())
|
||||
}
|
||||
|
||||
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())
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) {
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
|
||||
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": err.Error(),
|
||||
}))
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": err.Error(),
|
||||
}))
|
||||
}
|
||||
|
||||
if !ok {
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
|
||||
"message": "invalid email or password",
|
||||
}))
|
||||
}
|
||||
|
||||
token, err := c.GetJWT().GenerateToken(map[string]interface{}{
|
||||
|
|
@ -211,14 +178,9 @@ func Signin(c *core.Context) *core.Response {
|
|||
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
// TODO set error in session
|
||||
if TemplateEnable {
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
// cache the token
|
||||
userAgent := c.GetUserAgent()
|
||||
|
|
@ -232,36 +194,20 @@ func Signin(c *core.Context) *core.Response {
|
|||
|
||||
if err != nil {
|
||||
c.GetLogger().Error(err.Error())
|
||||
if TemplateEnable {
|
||||
// TODO set error in session
|
||||
return c.Response.Redirect("/applogin")
|
||||
} else {
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
if TemplateEnable {
|
||||
// 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)
|
||||
}
|
||||
|
||||
// redirecto to app
|
||||
return c.Response.Redirect("/appsample")
|
||||
} else {
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"token": token,
|
||||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
"token": token,
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
func ResetPasswordRequest(c *core.Context) *core.Response {
|
||||
func APIRequestPasswordRequest(c *core.Context) *core.Response {
|
||||
email := c.GetRequestParam("email")
|
||||
|
||||
loggr := c.GetLogger()
|
||||
// validation data
|
||||
data := map[string]interface{}{
|
||||
"email": email,
|
||||
|
|
@ -309,6 +255,8 @@ func ResetPasswordRequest(c *core.Context) *core.Response {
|
|||
return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
|
||||
"message": "internal server error",
|
||||
}))
|
||||
} else {
|
||||
loggr.Debug(code)
|
||||
}
|
||||
|
||||
return c.Response.Json(c.MapToJson(map[string]string{
|
||||
|
|
@ -316,7 +264,7 @@ func ResetPasswordRequest(c *core.Context) *core.Response {
|
|||
}))
|
||||
}
|
||||
|
||||
func SetNewPassword(c *core.Context) *core.Response {
|
||||
func APISetNewPassword(c *core.Context) *core.Response {
|
||||
urlCode := c.CastToString(c.GetPathParam("code"))
|
||||
linkCodeDataStr, err := c.GetCache().Get(urlCode)
|
||||
if err != nil {
|
||||
|
|
@ -427,28 +375,10 @@ func SetNewPassword(c *core.Context) *core.Response {
|
|||
}))
|
||||
}
|
||||
|
||||
func Signout(c *core.Context) *core.Response {
|
||||
func APISignout(c *core.Context) *core.Response {
|
||||
|
||||
// check if template engine is enable
|
||||
TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE")
|
||||
if TemplateEnableStr == "" {
|
||||
TemplateEnableStr = "false"
|
||||
}
|
||||
TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr)
|
||||
|
||||
token := ""
|
||||
|
||||
if TemplateEnable {
|
||||
// get cookie
|
||||
usercookie, err := c.GetCookie()
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
token = usercookie.Token
|
||||
} else {
|
||||
tokenRaw := c.GetHeader("Authorization")
|
||||
token = strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
|
||||
}
|
||||
tokenRaw := c.GetHeader("Authorization")
|
||||
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
|
||||
|
||||
if token == "" {
|
||||
return c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
|
||||
|
|
@ -475,3 +405,10 @@ func Signout(c *core.Context) *core.Response {
|
|||
"message": "signed out successfully",
|
||||
}))
|
||||
}
|
||||
|
||||
// This route is token protected by hook
|
||||
func APIPing(c *core.Context) *core.Response {
|
||||
return c.Response.SetStatusCode(http.StatusOK).Json(c.MapToJson(map[string]interface{}{
|
||||
"message": "Pong",
|
||||
}))
|
||||
}
|
||||
201
controllers/app-auth.go
Normal file
201
controllers/app-auth.go
Normal 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{})
|
||||
|
||||
}
|
||||
|
|
@ -46,28 +46,3 @@ func WelcomeHome(c *core.Context) *core.Response {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue