feat: middlewares for authorization and automatic request parsing;
feat: roles enum
This commit is contained in:
@@ -7,6 +7,7 @@ require (
|
|||||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||||
github.com/jackc/pgx/v5 v5.7.5
|
github.com/jackc/pgx/v5 v5.7.5
|
||||||
github.com/spf13/viper v1.20.1
|
github.com/spf13/viper v1.20.1
|
||||||
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
github.com/swaggo/swag v1.16.4
|
github.com/swaggo/swag v1.16.4
|
||||||
@@ -19,6 +20,7 @@ require (
|
|||||||
github.com/bytedance/sonic v1.13.3 // indirect
|
github.com/bytedance/sonic v1.13.3 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
@@ -43,6 +45,7 @@ require (
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/spf13/afero v1.14.0 // indirect
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
// Copyright (c) 2025 Nikolai Papin
|
// Copyright (c) 2025 Nikolai Papin
|
||||||
//
|
//
|
||||||
// This file is part of Easywish
|
// This file is part of Easywish
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
// the GNU General Public License for more details.
|
// the GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"easywish/internal/middleware"
|
||||||
"easywish/internal/models"
|
"easywish/internal/models"
|
||||||
"easywish/internal/services"
|
"easywish/internal/services"
|
||||||
|
"easywish/internal/utils/enums"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -122,7 +124,7 @@ func (a *authControllerImpl) RegistrationComplete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *authControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
func (a *authControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
||||||
group.POST("/registrationBegin", a.RegistrationBegin)
|
group.POST("/registrationBegin", middleware.RequestMiddleware[models.RegistrationBeginRequest](enums.GuestRole), a.RegistrationBegin)
|
||||||
group.POST("/registrationComplete", a.RegistrationComplete)
|
group.POST("/registrationComplete", a.RegistrationComplete)
|
||||||
group.POST("/login", a.Login)
|
group.POST("/login", a.Login)
|
||||||
group.POST("/refresh", a.Refresh)
|
group.POST("/refresh", a.Refresh)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
package controllers
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/internal/middleware"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -92,11 +91,4 @@ func (p *profileControllerImpl) UpdatePrivacySettings(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *profileControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
func (p *profileControllerImpl) RegisterRoutes(group *gin.RouterGroup) {
|
||||||
protected := group.Group("")
|
|
||||||
protected.Use(middleware.JWTAuthMiddleware())
|
|
||||||
{
|
|
||||||
protected.GET("/me", p.GetOwnProfile)
|
|
||||||
protected.GET("/:username", p.GetProfile)
|
|
||||||
protected.GET("/privacy", p.GetPrivacySettings)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
// Copyright (c) 2025 Nikolai Papin
|
// Copyright (c) 2025 Nikolai Papin
|
||||||
//
|
//
|
||||||
// This file is part of Easywish
|
// This file is part of Easywish
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
// the GNU General Public License for more details.
|
// the GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/config"
|
"easywish/config"
|
||||||
|
"easywish/internal/utils/enums"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -28,16 +29,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Claims struct {
|
type Claims struct {
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
Role enums.Role `json:"role"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
func JWTAuthMiddleware() gin.HandlerFunc {
|
func AuthMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
cfg := config.GetConfig()
|
cfg := config.GetConfig()
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
|
|
||||||
if authHeader == "" {
|
if authHeader == "" {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header required"})
|
c.Set("username", nil)
|
||||||
|
c.Set("role", enums.GuestRole)
|
||||||
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +69,8 @@ func JWTAuthMiddleware() gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
|
||||||
c.Set("userID", claims.Username)
|
c.Set("username", claims.Username)
|
||||||
|
c.Set("role", claims.Role)
|
||||||
c.Next()
|
c.Next()
|
||||||
} else {
|
} else {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid claims"})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid claims"})
|
||||||
|
|||||||
86
backend/internal/middleware/request.go
Normal file
86
backend/internal/middleware/request.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright (c) 2025 Nikolai Papin
|
||||||
|
//
|
||||||
|
// This file is part of Easywish
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
|
// the GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"easywish/internal/utils/enums"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserInfo struct {
|
||||||
|
Username string
|
||||||
|
Role enums.Role
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request[T any] struct {
|
||||||
|
User UserInfo
|
||||||
|
Body T
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestKey = "request"
|
||||||
|
|
||||||
|
func UserInfoFromContext(c *gin.Context) (*UserInfo, bool) {
|
||||||
|
|
||||||
|
var username any
|
||||||
|
var role any
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
username, ok = c.Get("username") ; if !ok {
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
role, ok = c.Get("role"); if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return &UserInfo{Username: username.(string), Role: role.(enums.Role)}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequestFromContext[T any](c *gin.Context) Request[T] {
|
||||||
|
return c.Value(requestKey).(Request[T])
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequestMiddleware[T any](role enums.Role) gin.HandlerFunc {
|
||||||
|
return gin.HandlerFunc(func(c *gin.Context) {
|
||||||
|
|
||||||
|
userInfo, ok := UserInfoFromContext(c)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
c.Status(http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var body T
|
||||||
|
if err := c.ShouldBindJSON(&body); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, err)
|
||||||
|
|
||||||
|
// TODO: implement automatic validation here
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
request := Request[T]{
|
||||||
|
User: *userInfo,
|
||||||
|
Body: body,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set(requestKey, request)
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
// Copyright (c) 2025 Nikolai Papin
|
// Copyright (c) 2025 Nikolai Papin
|
||||||
//
|
//
|
||||||
// This file is part of Easywish
|
// This file is part of Easywish
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
// (at your option) any later version.
|
// (at your option) any later version.
|
||||||
//
|
//
|
||||||
// This program is distributed in the hope that it will be useful,
|
// This program is distributed in the hope that it will be useful,
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
||||||
// the GNU General Public License for more details.
|
// the GNU General Public License for more details.
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
@@ -19,12 +19,14 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"easywish/internal/controllers"
|
"easywish/internal/controllers"
|
||||||
|
"easywish/internal/middleware"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRouter(engine *gin.Engine, groups []RouteGroup) *gin.Engine {
|
func NewRouter(engine *gin.Engine, groups []RouteGroup) *gin.Engine {
|
||||||
apiGroup := engine.Group("/api")
|
apiGroup := engine.Group("/api")
|
||||||
|
apiGroup.Use(middleware.AuthMiddleware())
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
subgroup := apiGroup.Group(group.BasePath)
|
subgroup := apiGroup.Group(group.BasePath)
|
||||||
subgroup.Use(group.Middleware...)
|
subgroup.Use(group.Middleware...)
|
||||||
|
|||||||
@@ -18,8 +18,14 @@
|
|||||||
package enums
|
package enums
|
||||||
|
|
||||||
type ConfirmationCodeType int32
|
type ConfirmationCodeType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RegistrationCodeType ConfirmationCodeType = iota
|
RegistrationCodeType ConfirmationCodeType = iota
|
||||||
PasswordResetCodeType
|
PasswordResetCodeType
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Role int32
|
||||||
|
const (
|
||||||
|
GuestRole Role = iota
|
||||||
|
UserRole
|
||||||
|
AdminRole
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user