From 1abc05ecd942ec259d4055e6a10bda2ea16e2e8e Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Date: Fri, 16 May 2025 14:41:16 +0330 Subject: [PATCH] Add JWT authentication for admin dashboard login --- cmd/main.go | 1 + internal/AppContext.go | 2 + internal/AppWebContext.go | 11 ----- internal/auth/jwt.go | 56 ++++++++++++++++++++++++ internal/helpers/SetupAppContext.go | 22 ++++++++++ internal/web/handlers/admin/adminDash.go | 14 +++++- 6 files changed, 93 insertions(+), 13 deletions(-) delete mode 100644 internal/AppWebContext.go diff --git a/cmd/main.go b/cmd/main.go index 31c817a..26d719a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -49,6 +49,7 @@ func main() { runFetchPosts() } }() + // Run initial fetch on startup go func() { runFetchPosts() diff --git a/internal/AppContext.go b/internal/AppContext.go index 3d2b100..1fadb99 100644 --- a/internal/AppContext.go +++ b/internal/AppContext.go @@ -1,6 +1,7 @@ package internal import ( + "CatsOfMastodonBotGo/internal/auth" "CatsOfMastodonBotGo/internal/services" "gorm.io/gorm" @@ -10,6 +11,7 @@ import ( type AppContext struct { Db *gorm.DB PostService *services.PostService + Jwt *auth.JwtTokenGenerator AdminPassword string Instance string Tag string diff --git a/internal/AppWebContext.go b/internal/AppWebContext.go deleted file mode 100644 index c529ced..0000000 --- a/internal/AppWebContext.go +++ /dev/null @@ -1,11 +0,0 @@ -package internal - -import ( - // "github.com/gin-gonic/gin" - // "gorm.io/gorm" -) - -// type AppWebContext struct { -// Db *gorm.DB -// GinEngine *gin.Engine -// } \ No newline at end of file diff --git a/internal/auth/jwt.go b/internal/auth/jwt.go index 9a72978..cc0bdf5 100644 --- a/internal/auth/jwt.go +++ b/internal/auth/jwt.go @@ -1,2 +1,58 @@ package auth +import ( + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" +) + +type JwtTokenGenerator struct { + Key string + Issuer string + Audience string +} + +func NewJwtTokenGenerator(key string, issuer string, audience string) *JwtTokenGenerator { + return &JwtTokenGenerator{ + Key: key, + Issuer: issuer, + Audience: audience, + } +} + +func (j *JwtTokenGenerator) GenerateToken(claims map[string]interface{}) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "exp": time.Now().AddDate(0, 0, 3).Unix(), + "iat": time.Now().Unix(), + "iss": j.Issuer, + "aud": j.Audience, + }) + for k, v := range claims { + token.Claims.(jwt.MapClaims)[k] = v + } + + return token.SignedString([]byte(j.Key)) +} + +// Gin middleware +func (j *JwtTokenGenerator) GinMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + token, err := c.Request.Cookie("token") + if err != nil { + c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) + return + } + _, err = jwt.Parse(token.Value, func(t *jwt.Token) (interface{}, error) { + if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, jwt.ErrSignatureInvalid + } + return []byte(j.Key), nil + }) + if err != nil { + c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) + return + } + c.Next() + } +} \ No newline at end of file diff --git a/internal/helpers/SetupAppContext.go b/internal/helpers/SetupAppContext.go index ec2188c..9861460 100644 --- a/internal/helpers/SetupAppContext.go +++ b/internal/helpers/SetupAppContext.go @@ -2,6 +2,7 @@ package helpers import ( "CatsOfMastodonBotGo/internal" + "CatsOfMastodonBotGo/internal/auth" "CatsOfMastodonBotGo/internal/services" "log" "os" @@ -27,6 +28,22 @@ func SetupAppContext() *internal.AppContext { adminPassword = "catsaregood" } + // Jwt params + secret := os.Getenv("CAOM_JWT_SECRET") + if secret == "" { + log.Fatal("No jwt secret provided, using default secret 'secret'") + } + issuer := os.Getenv("CAOM_JWT_ISSUER") + if issuer == "" { + log.Println("No jwt issuer provided, using default issuer 'CatsOfMastodonBotGo'") + issuer = "CatsOfMastodonBotGo" + } + audience := os.Getenv("CAOM_JWT_AUDIENCE") + if audience == "" { + log.Println("No jwt audience provided, using default audience 'CatsOfMastodonBotGo'") + audience = "CatsOfMastodonBotGo" + } + // Setup database db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: logger.Default.LogMode(logger.Warn)}) if err != nil { @@ -37,10 +54,15 @@ func SetupAppContext() *internal.AppContext { //Setup PostService var postService = services.NewPostService(db) + // Setup Jwt + var jwt = auth.NewJwtTokenGenerator(secret, issuer, audience) + // Inititlize AppContext var appContext = &internal.AppContext{ Db: db, PostService: postService, + Jwt: jwt, + AdminPassword: adminPassword, Instance: instance, Tag: tag, } diff --git a/internal/web/handlers/admin/adminDash.go b/internal/web/handlers/admin/adminDash.go index a8e54df..ddbf91f 100644 --- a/internal/web/handlers/admin/adminDash.go +++ b/internal/web/handlers/admin/adminDash.go @@ -47,8 +47,18 @@ func (appContext *AdminDashboardHandler) Login(c *gin.Context) { } if input.Password == appContext.AppContext.AdminPassword { - c.JSON(200, gin.H{ - "YouAreOn": "AdminDashboardHomePage", + token, err := appContext.AppContext.Jwt.GenerateToken(map[string]interface{}{"role": "admin"}) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Token generation failed"}) + return + } + + c.SetCookie("token", token, 3600, "/", "", false, true) + c.JSON(http.StatusOK, gin.H{"message": "Login successful"}) + + } else { + c.JSON(401, gin.H{ + "YouAreOn": "Unauthorized", }) }