From d00e57945896876bdd0f8defb06b76144333a2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AE=B7=E5=8B=87?= Date: Fri, 8 Sep 2023 10:38:59 +0800 Subject: [PATCH] add api middleware --- server/adminserver/api/middleware.go | 64 ++++++++++++++++++++++++++++ server/adminserver/api/server.go | 21 +++++---- server/adminserver/api/user.go | 6 ++- server/adminserver/token/maker.go | 10 +++++ server/adminserver/token/payload.go | 37 ++++++++++++++++ 5 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 server/adminserver/api/middleware.go create mode 100644 server/adminserver/token/maker.go create mode 100644 server/adminserver/token/payload.go diff --git a/server/adminserver/api/middleware.go b/server/adminserver/api/middleware.go new file mode 100644 index 00000000..006a3d20 --- /dev/null +++ b/server/adminserver/api/middleware.go @@ -0,0 +1,64 @@ +package api + +import ( + "errors" + "fmt" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "main/token" +) + +const ( + authorizationHeaderKey = "authorization" + authorizationTypeBearer = "bearer" + authorizationTypeBasic = "basic" + authorizationPayloadKey = "authorization_payload" +) + +func authMiddleware(tokenMaker token.Maker) gin.HandlerFunc { + return func(ctx *gin.Context) { + authorizationHeader := ctx.GetHeader(authorizationHeaderKey) + if len(authorizationHeader) == 0 { + err := errors.New("authorization header is not provided") + ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + fields := strings.Fields(authorizationHeader) + if len(fields) < 2 { + err := errors.New("invalid authorization header format") + ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + // Headers key: Authorization + // Headers value: Basic cm9vdDoxMjM0NTY= + authorizationType := strings.ToLower(fields[0]) + if authorizationType == authorizationTypeBasic { + + user, password, hasAuth := ctx.Request.BasicAuth() + if hasAuth && user == "root" && password == "123456" { + ctx.Next() + return + } + } + + if authorizationType != authorizationTypeBearer { + err := fmt.Errorf("unsupported authorization type %s", authorizationType) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + accessToken := fields[1] + payload, err := tokenMaker.VerifyToken(accessToken) + if err != nil { + ctx.AbortWithStatusJSON(http.StatusUnauthorized, errorResponse(err)) + return + } + + ctx.Set(authorizationPayloadKey, payload) + ctx.Next() + } +} diff --git a/server/adminserver/api/server.go b/server/adminserver/api/server.go index 3f51dda6..5e2d5ee0 100644 --- a/server/adminserver/api/server.go +++ b/server/adminserver/api/server.go @@ -3,12 +3,14 @@ package api import ( "github.com/gin-gonic/gin" "github.com/jmoiron/sqlx" + "main/token" ) // Server serves HTTP requests for our banking service. type Server struct { - store *sqlx.DB - router *gin.Engine + store *sqlx.DB + tokenMaker token.Maker + router *gin.Engine } func NewServer(store *sqlx.DB) (*Server, error) { @@ -22,14 +24,17 @@ func NewServer(store *sqlx.DB) (*Server, error) { func (server *Server) setupRouter() { router := gin.Default() + // Default routers + router.GET("/welcome", server.welcome) + router.POST("/login", server.loginUser) - router.GET("/guilds", server.listGuilds) - router.GET("/guilds/:guild_id", server.getGuild) - router.POST("/guilds", server.createGuild) - router.DELETE("/guilds/:guild_id", server.deleteGuild) - - authRoutes := router.Group("/") + // Authentication routers + authRoutes := router.Group("/").Use(authMiddleware(server.tokenMaker)) authRoutes.GET("/accounts", server.listAccounts) + authRoutes.GET("/guilds", server.listGuilds) + authRoutes.GET("/guilds/:guild_id", server.getGuild) + authRoutes.POST("/guilds", server.createGuild) + authRoutes.DELETE("/guilds/:guild_id", server.deleteGuild) server.router = router } diff --git a/server/adminserver/api/user.go b/server/adminserver/api/user.go index daa8e9fa..c6c38c7a 100644 --- a/server/adminserver/api/user.go +++ b/server/adminserver/api/user.go @@ -63,8 +63,12 @@ type Account struct { Currency string `json:"currency"` } -func (server *Server) loginUser(ctx *gin.Context) { +func (server *Server) welcome(ctx *gin.Context) { + ctx.JSON(http.StatusOK, "welcome") +} +func (server *Server) loginUser(ctx *gin.Context) { + ctx.JSON(http.StatusOK, "loginUser") } func (server *Server) createAccount(ctx *gin.Context) { diff --git a/server/adminserver/token/maker.go b/server/adminserver/token/maker.go new file mode 100644 index 00000000..8546730b --- /dev/null +++ b/server/adminserver/token/maker.go @@ -0,0 +1,10 @@ +package token + +import ( + "time" +) + +type Maker interface { + CreateToken(username string, duration time.Duration) (string, *Payload, error) + VerifyToken(token string) (*Payload, error) +} diff --git a/server/adminserver/token/payload.go b/server/adminserver/token/payload.go new file mode 100644 index 00000000..72896e0b --- /dev/null +++ b/server/adminserver/token/payload.go @@ -0,0 +1,37 @@ +package token + +import ( + "errors" + "f5" + "time" +) + +var ( + ErrInvalidToken = errors.New("token is invalid") + ErrExpiredToken = errors.New("token has expired") +) + +type Payload struct { + ID int64 `json:"id"` + Username string `json:"username"` + IssuedAt time.Time `json:"issued_at"` + ExpiredAt time.Time `json:"expired_at"` +} + +func NewPayload(username string, duration time.Duration) (*Payload, error) { + tokenID := f5.GetApp().NewUuid() + payload := &Payload{ + ID: tokenID, + Username: username, + IssuedAt: time.Now(), + ExpiredAt: time.Now().Add(duration), + } + return payload, nil +} + +func (payload *Payload) Valid() error { + if time.Now().After(payload.ExpiredAt) { + return ErrExpiredToken + } + return nil +}