develop #4

Merged
zeni merged 11 commits from develop into main 2024-10-16 00:00:21 -04:00
6 changed files with 299 additions and 55 deletions
Showing only changes of commit 1b6f3e6103 - Show all commits

View file

@ -8,7 +8,10 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"os"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -19,6 +22,8 @@ import (
"git.smarteching.com/goffee/cup/utils" "git.smarteching.com/goffee/cup/utils"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
"git.smarteching.com/goffee/core/template/components"
) )
func Signup(c *core.Context) *core.Response { func Signup(c *core.Context) *core.Response {
@ -123,6 +128,13 @@ func Signin(c *core.Context) *core.Response {
email := c.GetRequestParam("email") email := c.GetRequestParam("email")
password := c.GetRequestParam("password") 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{}{ data := map[string]interface{}{
"email": email, "email": email,
"password": password, "password": password,
@ -135,37 +147,62 @@ func Signin(c *core.Context) *core.Response {
if v.Failed() { if v.Failed() {
c.GetLogger().Error(v.GetErrorMessagesJson()) 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 var user models.User
res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user) res := c.GetGorm().Where("email = ?", c.CastToString(email)).First(&user)
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
c.GetLogger().Error(res.Error.Error()) 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{ return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error", "message": "internal server error",
})) }))
} }
}
if res.Error != nil && errors.Is(res.Error, gorm.ErrRecordNotFound) { 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{ return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password", "message": "invalid email or password",
})) }))
} }
}
ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password)) ok, err := c.GetHashing().CheckPasswordHash(user.Password, c.CastToString(password))
if err != nil { if err != nil {
c.GetLogger().Error(err.Error()) 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{ return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": err.Error(), "message": err.Error(),
})) }))
} }
}
if !ok { 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{ return c.Response.SetStatusCode(http.StatusUnprocessableEntity).Json(c.MapToJson(map[string]string{
"message": "invalid email or password", "message": "invalid email or password",
})) }))
} }
}
token, err := c.GetJWT().GenerateToken(map[string]interface{}{ token, err := c.GetJWT().GenerateToken(map[string]interface{}{
"userID": user.ID, "userID": user.ID,
@ -173,24 +210,46 @@ func Signin(c *core.Context) *core.Response {
if err != nil { if err != nil {
c.GetLogger().Error(err.Error()) 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{ return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]string{
"message": "internal server error", "message": "internal server error",
})) }))
} }
}
// cache the token // cache the token
userAgent := c.GetUserAgent() userAgent := c.GetUserAgent()
hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent) hashedCacheKey := utils.CreateAuthTokenHashedCacheKey(user.ID, userAgent)
err = c.GetCache().Set(hashedCacheKey, token) err = c.GetCache().Set(hashedCacheKey, token)
if err != nil { if err != nil {
c.GetLogger().Error(err.Error()) 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{}{ return c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal server error", "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{ return c.Response.Json(c.MapToJson(map[string]string{
"token": token, "token": token,
})) }))
}
} }
func ResetPasswordRequest(c *core.Context) *core.Response { func ResetPasswordRequest(c *core.Context) *core.Response {
@ -389,3 +448,41 @@ func Signout(c *core.Context) *core.Response {
"message": "signed out successfully", "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)
}

27
controllers/sample.go Normal file
View file

@ -0,0 +1,27 @@
// Copyright (c) 2024 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 (
"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)
}

View file

@ -3,6 +3,8 @@ package hooks
import ( import (
"errors" "errors"
"net/http" "net/http"
"os"
"strconv"
"strings" "strings"
"git.smarteching.com/goffee/core" "git.smarteching.com/goffee/core"
@ -11,20 +13,92 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
var CheckSessionCookie core.Hook = func(c *core.Context) {
pass := true
token := ""
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 {
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) { 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") tokenRaw := c.GetHeader("Authorization")
token := strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1)) token = strings.TrimSpace(strings.Replace(tokenRaw, "Bearer", "", 1))
if token == "" { if token == "" {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized", "message": "unauthorized",
})).ForceSendResponse() })).ForceSendResponse()
return return
} }
}
payload, err := c.GetJWT().DecodeToken(token) payload, err := c.GetJWT().DecodeToken(token)
if err != nil { if err != nil {
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized", "message": "unauthorized",
})).ForceSendResponse() })).ForceSendResponse()
}
return return
} }
userAgent := c.GetUserAgent() userAgent := c.GetUserAgent()
@ -33,16 +107,24 @@ var AuthCheck core.Hook = func(c *core.Context) {
cachedToken, err := c.GetCache().Get(hashedCacheKey) cachedToken, err := c.GetCache().Get(hashedCacheKey)
if err != nil { if err != nil {
// user signed out // user signed out
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized", "message": "unauthorized",
})).ForceSendResponse() })).ForceSendResponse()
}
return return
} }
if cachedToken != token { if cachedToken != token {
// using old token replaced with new one after recent signin // using old token replaced with new one after recent signin
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{ c.Response.SetStatusCode(http.StatusUnauthorized).Json(c.MapToJson(map[string]interface{}{
"message": "unauthorized", "message": "unauthorized",
})).ForceSendResponse() })).ForceSendResponse()
}
return return
} }
@ -51,17 +133,13 @@ var AuthCheck core.Hook = func(c *core.Context) {
if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) { if res.Error != nil && !errors.Is(res.Error, gorm.ErrRecordNotFound) {
// error with the database // error with the database
c.GetLogger().Error(res.Error.Error()) c.GetLogger().Error(res.Error.Error())
if TemplateEnable {
c.Response.Redirect("/applogin").ForceSendResponse()
} else {
c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{ c.Response.SetStatusCode(http.StatusInternalServerError).Json(c.MapToJson(map[string]interface{}{
"message": "internal error", "message": "internal error",
})).ForceSendResponse() })).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()
return return
} }

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
{{template "head" "Goffee"}}
<body>
<main>
{{template "title" .TheTitle}}
</main>
<script src="/public/app.js"></script>
</body>
</html>

View file

@ -2,9 +2,13 @@
<html lang="en"> <html lang="en">
{{template "head" "Goffee"}} {{template "head" "Goffee"}}
<body> <body>
<main>
{{template "title" .TheTitle}} {{template "title" .TheTitle}}
<div class="section"> <div class="section">
Welcome to Goffee Welcome to Goffee
</div> </div>
<img class="goffeelogo"src="/public/img/goffee.png" alt="Goffee logo" />
</main>
<script src="/public/app.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
{{template "head" "Goffee"}}
<body>
<main>
{{template "title" .TheTitle}}
<form method="POST" action="/signin">
<div>
<label for="email">Email:</label>
<input type="text" id="email" name="email" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
</div>
<div>
<button type="submit" name="login">Login</button>
</div>
</form>
</main>
<script src="/public/app.js"></script>
</body>
</html>