From 1b6f3e6103ad46c7e392bad90fa8a7143b244480 Mon Sep 17 00:00:00 2001 From: Zeni Kim Date: Mon, 30 Sep 2024 09:17:24 -0500 Subject: [PATCH] start cookie session --- controllers/authentication.go | 141 ++++++++++++++++++++++++++++------ controllers/sample.go | 27 +++++++ hooks/auth-check.go | 130 ++++++++++++++++++++++++------- storage/templates/app.html | 13 ++++ storage/templates/basic.html | 18 +++-- storage/templates/login.html | 25 ++++++ 6 files changed, 299 insertions(+), 55 deletions(-) create mode 100644 controllers/sample.go create mode 100644 storage/templates/app.html create mode 100644 storage/templates/login.html diff --git a/controllers/authentication.go b/controllers/authentication.go index 1cff51f..9001544 100644 --- a/controllers/authentication.go +++ b/controllers/authentication.go @@ -8,7 +8,10 @@ package controllers import ( "encoding/json" "errors" + "fmt" + "net/http" + "os" "strconv" "strings" "time" @@ -19,6 +22,8 @@ import ( "git.smarteching.com/goffee/cup/utils" "github.com/google/uuid" "gorm.io/gorm" + + "git.smarteching.com/goffee/core/template/components" ) func Signup(c *core.Context) *core.Response { @@ -123,6 +128,13 @@ func Signin(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, @@ -135,36 +147,61 @@ func Signin(c *core.Context) *core.Response { if v.Failed() { c.GetLogger().Error(v.GetErrorMessagesJson()) - return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(v.GetErrorMessagesJson()) + if TemplateEnable { + // TODO set error in session + return c.Response.Redirect("/applogin") + } else { + 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()) - return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{ - "message": "internal server 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", + })) + } } if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { - return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{ - "message": "invalid email or password", - })) + 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", + })) + } } ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password)) if err != nil { c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{ - "message": 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(), + })) + } } if !ok { - return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{ - "message": "invalid email or password", - })) + 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", + })) + } } token, err := c.GetJWT().GenerateToken(map[string]interface{}{ @@ -173,9 +210,14 @@ func Signin(c *core.Context) *core.Response { if err != nil { c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{ - "message": "internal server 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", + })) + } } // cache the token userAgent := c.GetUserAgent() @@ -183,14 +225,31 @@ func Signin(c *core.Context) *core.Response { err = c.GetCache().Set(hashedCacheKey, token) if err != nil { c.GetLogger().Error(err.Error()) - return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{ - "message": "internal server 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", + })) + } } - return c.Response.Json(c.MapToJson(map[string]string{ - "token": token, - })) + 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, + })) + } } func ResetPasswordRequest(c *core.Context) *core.Response { @@ -389,3 +448,41 @@ func Signout(c *core.Context) *core.Response { "message": "signed out successfully", })) } + +// Show basic app login +func AppLogin(c *core.Context) *core.Response { + + type templateData struct { + TheTitle components.Title + } + + tmplData := templateData{ + TheTitle: components.Title{ + Label: "Login form", + }, + } + + return c.Response.Template("login.html", tmplData) + +} + +// Show basic app sample +func AppSample(c *core.Context) *core.Response { + + title := "lorem" + + type templateData struct { + TheTitle components.Title + } + + tmplData := templateData{ + TheTitle: components.Title{ + Label: title, + }, + } + + //fmt.Printf("Outside cookie user is: %s", user.Email) + + return c.Response.Template("app.html", tmplData) + +} diff --git a/controllers/sample.go b/controllers/sample.go new file mode 100644 index 0000000..fca1eab --- /dev/null +++ b/controllers/sample.go @@ -0,0 +1,27 @@ +// 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 controllers + +import ( + "git.smarteching.com/goffee/core" + "git.smarteching.com/goffee/core/template/components" +) + +// Show basic template +func Sample(c *core.Context) *core.Response { + + type templateData struct { + TheTitle components.Title + } + + tmplData := templateData{ + TheTitle: components.Title{ + Label: "Lorem ipsum inside", + }, + } + + return c.Response.Template("basic.html", tmplData) + +} diff --git a/hooks/auth-check.go b/hooks/auth-check.go index f08511e..779cbda 100644 --- a/hooks/auth-check.go +++ b/hooks/auth-check.go @@ -3,6 +3,8 @@ package hooks import ( "errors" "net/http" + "os" + "strconv" "strings" "git.smarteching.com/goffee/core" @@ -11,20 +13,92 @@ import ( "gorm.io/gorm" ) -var AuthCheck core.Hook = func(c *core.Context) { - tokenRaw := c.GetHeader("Authorization") - token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1)) +var CheckSessionCookie core.Hook = func(c *core.Context) { + + pass := true + token := "" + usercookie, err := c.GetCookie() + if err != nil { + + } + token = usercookie.Token if token == "" { - c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ - "message": "unauthorized", - })).ForceSendResponse() + pass = false + } else { + payload, err := c.GetJWT().DecodeToken(token) + if err != nil { + pass = false + } else { + + userAgent := c.GetUserAgent() + hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(uint(c.CastToInt(payload["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 = ?", payload["userID"]).First(&user) + if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { + pass = false + } + } + } + } + // if have session redirect protected page + if pass { + c.Response.Redirect("/appsample").ForceSendResponse() return } + + c.Next() + +} + +var AuthCheck core.Hook = func(c *core.Context) { + + // check if template engine is enable + TemplateEnableStr := os.Getenv("TEMPLATE_ENABLE") + if TemplateEnableStr == "" { + TemplateEnableStr = "false" + } + TemplateEnable, _ := strconv.ParseBool(TemplateEnableStr) + + token := "" + + if TemplateEnable { + usercookie, err := c.GetCookie() + if err != nil { + + } + token = usercookie.Token + if token == "" { + c.Response.Redirect("/applogin").ForceSendResponse() + return + } + + } else { + tokenRaw := c.GetHeader("Authorization") + token = strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1)) + if token == "" { + c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ + "message": "unauthorized", + })).ForceSendResponse() + return + } + } + payload, err := c.GetJWT().DecodeToken(token) if err != nil { - c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ - "message": "unauthorized", - })).ForceSendResponse() + if TemplateEnable { + c.Response.Redirect("/applogin").ForceSendResponse() + } else { + c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ + "message": "unauthorized", + })).ForceSendResponse() + } return } userAgent := c.GetUserAgent() @@ -33,16 +107,24 @@ var AuthCheck core.Hook = func(c *core.Context) { cachedToken, err := c.GetCache().Get(hashedCacheKey) if err != nil { // user signed out - c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ - "message": "unauthorized", - })).ForceSendResponse() + if TemplateEnable { + c.Response.Redirect("/applogin").ForceSendResponse() + } else { + c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ + "message": "unauthorized", + })).ForceSendResponse() + } return } if cachedToken != token { // using old token replaced with new one after recent signin - c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ - "message": "unauthorized", - })).ForceSendResponse() + if TemplateEnable { + c.Response.Redirect("/applogin").ForceSendResponse() + } else { + c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ + "message": "unauthorized", + })).ForceSendResponse() + } return } @@ -51,17 +133,13 @@ var AuthCheck core.Hook = func(c *core.Context) { if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { // error with the database c.GetLogger().Error(res.Error.Error()) - c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{ - "message": "internal error", - })).ForceSendResponse() - return - } - - if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { - // user record is not found (deleted) - c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ - "message": "unauthorized", - })).ForceSendResponse() + if TemplateEnable { + c.Response.Redirect("/applogin").ForceSendResponse() + } else { + c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{ + "message": "internal error", + })).ForceSendResponse() + } return } diff --git a/storage/templates/app.html b/storage/templates/app.html new file mode 100644 index 0000000..fa6f417 --- /dev/null +++ b/storage/templates/app.html @@ -0,0 +1,13 @@ + + + {{template "head" "Goffee"}} + +
+ + {{template "title" .TheTitle}} + + +
+ + + \ No newline at end of file diff --git a/storage/templates/basic.html b/storage/templates/basic.html index e418b2c..f1114ad 100644 --- a/storage/templates/basic.html +++ b/storage/templates/basic.html @@ -1,10 +1,14 @@ - {{template "head" "Goffee"}} - - {{template "title" .TheTitle}} -
- Welcome to Goffee -
- + {{template "head" "Goffee"}} + +
+ {{template "title" .TheTitle}} +
+ Welcome to Goffee +
+ +
+ + \ No newline at end of file diff --git a/storage/templates/login.html b/storage/templates/login.html new file mode 100644 index 0000000..4bc9e4d --- /dev/null +++ b/storage/templates/login.html @@ -0,0 +1,25 @@ + + + {{template "head" "Goffee"}} + +
+ + {{template "title" .TheTitle}} +
+
+ + +
+
+ + +
+
+ +
+
+ +
+ + + \ No newline at end of file