Merge branch 'dev-ars' into dev
Some checks failed
Build and Deploy Go App / build (push) Has been cancelled
Build and Deploy Go App / deploy (push) Has been cancelled

This commit is contained in:
Iron_Felix 2025-12-04 07:33:13 +03:00
commit 61db4ff54d
3 changed files with 130 additions and 28 deletions

View file

@ -25,18 +25,18 @@ import (
var AppConfig Config var AppConfig Config
func main() { func main() {
// if len(os.Args) != 2 { if len(os.Args) != 2 {
// AppConfig.Mode = "env" AppConfig.Mode = "env"
// } else { } else {
// AppConfig.Mode = "argv" AppConfig.Mode = "argv"
// } }
// err := InitConfig() err := InitConfig()
// if err != nil { if err != nil {
// log.Fatalf("Failed to init config: %v\n", err) log.Fatalf("Failed to init config: %v\n", err)
// } }
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) pool, err := pgxpool.New(context.Background(), AppConfig.DdUrl)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err) fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
os.Exit(1) os.Exit(1)
@ -47,16 +47,11 @@ func main() {
r := gin.Default() r := gin.Default()
r.Use(middleware.CSRFMiddleware()) r.Use(middleware.CSRFMiddleware())
// jwt middle will be here r.Use(middleware.JWTAuthMiddleware(AppConfig.JwtPrivateKey))
queries := sqlc.New(pool) queries := sqlc.New(pool)
// === RabbitMQ setup === rmqConn, err := amqp091.Dial(AppConfig.rmqURL)
rmqURL := os.Getenv("RABBITMQ_URL")
if rmqURL == "" {
rmqURL = "amqp://guest:guest@rabbitmq:5672/"
}
rmqConn, err := amqp091.Dial(rmqURL)
if err != nil { if err != nil {
log.Fatalf("Failed to connect to RabbitMQ: %v", err) log.Fatalf("Failed to connect to RabbitMQ: %v", err)
} }
@ -68,7 +63,7 @@ func main() {
server := handlers.NewServer(queries, publisher, rpcClient) server := handlers.NewServer(queries, publisher, rpcClient)
r.Use(cors.New(cors.Config{ r.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"}, // allow all origins, change to specific domains in production AllowOrigins: []string{AppConfig.ServiceAddress},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
AllowHeaders: []string{"Origin", "Content-Type", "Accept"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
ExposeHeaders: []string{"Content-Length"}, ExposeHeaders: []string{"Content-Length"},
@ -78,7 +73,7 @@ func main() {
oapi.RegisterHandlers(r, oapi.NewStrictHandler( oapi.RegisterHandlers(r, oapi.NewStrictHandler(
server, server,
// сюда можно добавить middlewares, если нужно
[]oapi.StrictMiddlewareFunc{}, []oapi.StrictMiddlewareFunc{},
)) ))

View file

@ -0,0 +1,109 @@
package middleware
import (
"context"
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// ctxKey — приватный тип для ключа контекста
type ctxKey struct{}
// ginContextKey — уникальный ключ для хранения *gin.Context
var ginContextKey = &ctxKey{}
// GinContextToContext сохраняет *gin.Context в context.Context запроса
func GinContextToContext(c *gin.Context) {
ctx := context.WithValue(c.Request.Context(), ginContextKey, c)
c.Request = c.Request.WithContext(ctx)
}
// GinContextFromContext извлекает *gin.Context из context.Context
func GinContextFromContext(ctx context.Context) (*gin.Context, bool) {
ginCtx, ok := ctx.Value(ginContextKey).(*gin.Context)
return ginCtx, ok
}
func JWTAuthMiddleware(secret string) gin.HandlerFunc {
return func(c *gin.Context) {
// 1. Получаем access_token из cookie
tokenStr, err := c.Cookie("access_token")
if err != nil {
abortWithJSON(c, http.StatusUnauthorized, "missing access_token cookie")
return
}
// 2. Парсим токен с MapClaims
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
if t.Method != jwt.SigningMethodHS256 {
return nil, errors.New("unexpected signing method: " + t.Method.Alg())
}
return []byte(secret), nil // ← конвертируем string → []byte
})
if err != nil {
abortWithJSON(c, http.StatusUnauthorized, "invalid token: "+err.Error())
return
}
// 3. Проверяем валидность
if !token.Valid {
abortWithJSON(c, http.StatusUnauthorized, "token is invalid")
return
}
// 4. Извлекаем user_id из claims
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
abortWithJSON(c, http.StatusUnauthorized, "invalid claims format")
return
}
userID, ok := claims["user_id"].(string)
if !ok || userID == "" {
abortWithJSON(c, http.StatusUnauthorized, "user_id claim missing or invalid")
return
}
// 5. Сохраняем в контексте
c.Set("user_id", userID)
// 6. Для oapi-codegen — кладём gin.Context в request context
GinContextToContext(c)
c.Next()
}
}
// Вспомогательные функции (без изменений)
func UserIDFromGin(c *gin.Context) (string, bool) {
id, exists := c.Get("user_id")
if !exists {
return "", false
}
if s, ok := id.(string); ok {
return s, true
}
return "", false
}
func UserIDFromContext(ctx context.Context) (string, error) {
ginCtx, ok := GinContextFromContext(ctx)
if !ok {
return "", errors.New("gin context not found")
}
userID, ok := UserIDFromGin(ginCtx)
if !ok {
return "", errors.New("user_id not found in context")
}
return userID, nil
}
func abortWithJSON(c *gin.Context, code int, message string) {
c.AbortWithStatusJSON(code, gin.H{
"error": "unauthorized",
"message": message,
})
}

View file

@ -1,12 +1,10 @@
package main package main
type Config struct { type Config struct {
Mode string Mode string
LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"` ServiceAddress string `toml:"ServiceAddress" env:"SERVICE_ADDRESS"`
} DdUrl string `toml:"DbUrl" env:"DATABASE_URL"`
JwtPrivateKey string `toml:"JwtPrivateKey" env:"JWT_PRIVATE_KEY"`
type Item struct { LogLevel string `toml:"LogLevel" env:"LOG_LEVEL"`
ID int `json:"id"` rmqURL string `toml:"RabbitMQUrl" env:"RABBITMQ_URL"`
Title string `json:"title"`
Description string `json:"description"`
} }