Switch from cookie to Bearer token auth and add CORS support - Semi finished

This commit is contained in:
2025-05-16 16:20:30 +03:30
parent 2e4b97e4bc
commit cb5149b7bc
8 changed files with 61 additions and 27 deletions

1
go.mod
View File

@@ -9,6 +9,7 @@ require (
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/cors v1.7.5 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/gin-gonic/gin v1.10.0 // indirect github.com/gin-gonic/gin v1.10.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect

2
go.sum
View File

@@ -13,6 +13,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.5 h1:cXC9SmofOrRg0w9PigwGlHG3ztswH6bqq4vJVXnvYMk=
github.com/gin-contrib/cors v1.7.5/go.mod h1:4q3yi7xBEDDWKapjT2o1V7mScKDDr8k+jZ0fSquGoy0=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=

View File

@@ -1,6 +1,7 @@
package auth package auth
import ( import (
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -38,26 +39,31 @@ func (j *JwtTokenGenerator) GenerateToken(claims map[string]interface{}) (string
// Gin middleware // Gin middleware
func (j *JwtTokenGenerator) GinMiddleware() gin.HandlerFunc { func (j *JwtTokenGenerator) GinMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
token, err := c.Request.Cookie("token") authHeader := c.GetHeader("Authorization")
if err != nil { if authHeader == "" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return return
} }
t, err := jwt.Parse(token.Value, func(t *jwt.Token) (interface{}, error) {
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
t, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, jwt.ErrSignatureInvalid return nil, jwt.ErrSignatureInvalid
} }
return []byte(j.Key), nil return []byte(j.Key), nil
}) })
if err != nil { if err != nil {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return return
} }
claims, ok := t.Claims.(jwt.MapClaims) claims, ok := t.Claims.(jwt.MapClaims)
if !ok || claims["role"] != "admin" { // Check role, only if its admin let it go if !ok || claims["role"] != "admin" {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
return return
} }
c.Next() c.Next()
} }
} }

View File

@@ -7,7 +7,7 @@ import (
) )
func AddMigrations(db *gorm.DB) { func AddMigrations(db *gorm.DB) {
db.AutoMigrate(&models.Post{}, &models.MediaAttachment{}, &models.Account{}, models.ComUser{}) db.AutoMigrate(&models.Post{}, &models.MediaAttachment{}, &models.Account{})
} }

View File

@@ -1,10 +0,0 @@
package models
// Funny you are
type ComUser struct {
Id string
Username string
Password string
Email string
IsVerified bool
}

View File

@@ -5,12 +5,19 @@ import (
handlers_admin "CatsOfMastodonBotGo/internal/web/handlers/admin" handlers_admin "CatsOfMastodonBotGo/internal/web/handlers/admin"
handlers_api "CatsOfMastodonBotGo/internal/web/handlers/api" handlers_api "CatsOfMastodonBotGo/internal/web/handlers/api"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func SetupRouter(appContext *internal.AppContext) *gin.Engine { func SetupRouter(appContext *internal.AppContext) *gin.Engine {
r := gin.Default() r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://extra-mama-chiz.surge.sh"}, // Just for test
AllowMethods: []string{"POST", "GET", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
AllowCredentials: true,
}))
adminDashboardHandler := handlers_admin.NewAdminDashboardHandler(appContext) adminDashboardHandler := handlers_admin.NewAdminDashboardHandler(appContext)
apiHandler := handlers_api.NewApiEndpointHandler(appContext) apiHandler := handlers_api.NewApiEndpointHandler(appContext)
@@ -19,8 +26,9 @@ func SetupRouter(appContext *internal.AppContext) *gin.Engine {
// My man, this is done way more efficient and fast in .NET, specially the authentication part // My man, this is done way more efficient and fast in .NET, specially the authentication part
admin.POST("/login", adminDashboardHandler.Login) admin.POST("/login", adminDashboardHandler.Login)
admin.POST("/approve", appContext.Jwt.GinMiddleware() ,adminDashboardHandler.ApproveMedia) admin.GET("/getmedia", appContext.Jwt.GinMiddleware(), adminDashboardHandler.GetMedia)
admin.POST("/reject" ,appContext.Jwt.GinMiddleware() , adminDashboardHandler.RejectMedia) admin.POST("/approve", appContext.Jwt.GinMiddleware(), adminDashboardHandler.ApproveMedia)
admin.POST("/reject", appContext.Jwt.GinMiddleware(), adminDashboardHandler.RejectMedia)
api := r.Group("/api") api := r.Group("/api")

View File

@@ -60,11 +60,22 @@ func (ps *PostService) GetExistingAccountIds() []string {
ps.db.Model(&models.Account{}).Pluck("acc_id", &existingAccountIds) ps.db.Model(&models.Account{}).Pluck("acc_id", &existingAccountIds)
return existingAccountIds return existingAccountIds
} }
func (*PostService) GetNewPosts(existingPostIds []string, posts []models.Post) []models.Post { func (*PostService) GetNewPosts(existingPostIds []string, posts []models.Post) []models.Post {
var newPosts []models.Post = nil var newPosts []models.Post = nil
for _, post := range posts { for _, post := range posts {
if !arrayContains(existingPostIds, post.ID) && len(post.Attachments) > 0 && !post.Account.IsBot { if !arrayContains(existingPostIds, post.ID) && len(post.Attachments) > 0 && !post.Account.IsBot {
newPosts = append(newPosts, post) var allImageMedia = true
for _, attachment := range post.Attachments {
if attachment.Type != "image" {
allImageMedia = false
break
}
} // Inefficient but anyways
if allImageMedia {
newPosts = append(newPosts, post)
}
} }
} }
return newPosts return newPosts
@@ -94,9 +105,12 @@ func (ps *PostService) GetRandomPost() models.Post {
var post models.Post var post models.Post
ps.db. ps.db.
Preload("Account"). Preload("Account").
Preload("Attachments"). Preload("Attachments", "approved = ?", true).
Order("RANDOM()"). Order("RANDOM()").
First(&post) First(&post)
if len(post.Attachments) > 0 {
post.Attachments = []models.MediaAttachment{post.Attachments[0]}
}
return post return post
} }
@@ -112,6 +126,18 @@ func (ps *PostService) RejectMedia(mediaId string) bool {
Update("rejected", true).RowsAffected > 0 Update("rejected", true).RowsAffected > 0
} }
// Get a post which approve and rejet are false (For admin panel)
func (ps *PostService) GetMedia() models.MediaAttachment {
var media models.MediaAttachment
ps.db.Model(&models.MediaAttachment{}).
Where("approved = ?", false).
Where("rejected = ?", false).
Order("RANDOM()").
First(&media)
return media
}
func arrayContains(arr []string, str string) bool { func arrayContains(arr []string, str string) bool {
for _, a := range arr { for _, a := range arr {
if a == str { if a == str {

View File

@@ -45,10 +45,15 @@ func (appContext *AdminDashboardHandler) RejectMedia(c *gin.Context) {
} }
} }
func (appContext *AdminDashboardHandler) GetMedia(c *gin.Context) {
media := appContext.AppContext.PostService.GetMedia()
c.JSON(http.StatusOK, media)
}
func (appContext *AdminDashboardHandler) Login(c *gin.Context) { func (appContext *AdminDashboardHandler) Login(c *gin.Context) {
var input requestmodels.LoginInput var input requestmodels.LoginInput
// Validate data // Validate data
if err := c.ShouldBindJSON(&input); err != nil { if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
@@ -62,17 +67,13 @@ func (appContext *AdminDashboardHandler) Login(c *gin.Context) {
return return
} }
c.SetCookie("token", token, 3600, "/", "", false, true) c.JSON(http.StatusOK, gin.H{"message": "Login successful", "token": token})
c.JSON(http.StatusOK, gin.H{"message": "Login successful"})
} else { } else {
c.JSON(401, gin.H{ c.JSON(401, gin.H{
"YouAreOn": "Unauthorized", "error": "wrong password",
}) })
return return
} }
c.JSON(200, gin.H{
"YouAreOn": "Login",
})
} }