diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index f52c697..2d8ef8f 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -22,6 +22,7 @@ import ( "leafdev.top/Ecosystem/recommender/internal/handler/grpc/documents" "leafdev.top/Ecosystem/recommender/internal/handler/grpc/interceptor" "leafdev.top/Ecosystem/recommender/internal/handler/http" + "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/application_v1" "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/v1" "leafdev.top/Ecosystem/recommender/internal/handler/http/middleware" "leafdev.top/Ecosystem/recommender/internal/router" @@ -37,18 +38,20 @@ func CreateApp() (*base.Application, error) { loggerLogger := logger.NewZapLogger() config := conf.ProviderConfig(loggerLogger) jwksJWKS := jwks.NewJWKS(config, loggerLogger) - authService := auth.NewAuthService(config, jwksJWKS, loggerLogger) db := orm.NewGORM(config, loggerLogger) query := dao.NewQuery(db) + authService := auth.NewAuthService(config, jwksJWKS, loggerLogger, query) applicationService := application.NewService(query) applicationController := v1.NewApplicationController(authService, applicationService) - handlers := http.NewHandler(applicationController) + application_v1ApplicationController := application_v1.NewApplicationController(authService, applicationService) + handlers := http.NewHandler(applicationController, application_v1ApplicationController) api := router.NewApiRoute(handlers) swaggerRouter := router.NewSwaggerRoute() ginLoggerMiddleware := middleware.NewGinLoggerMiddleware(loggerLogger) authMiddleware := middleware.NewAuthMiddleware(authService) jsonResponseMiddleware := middleware.NewJSONResponseMiddleware() - httpMiddleware := http.NewMiddleware(ginLoggerMiddleware, authMiddleware, jsonResponseMiddleware) + applicationAuthMiddleware := middleware.NewApplicationAuthMiddleware(authService) + httpMiddleware := http.NewMiddleware(ginLoggerMiddleware, authMiddleware, jsonResponseMiddleware, applicationAuthMiddleware) httpServer := server.NewHTTPServer(config, api, swaggerRouter, httpMiddleware) documentService := documents.NewDocumentService(query) interceptorAuth := interceptor.NewAuth(authService) diff --git a/docs/docs.go b/docs/docs.go index 3e8f243..4100c80 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -237,6 +237,52 @@ const docTemplate = `{ } } } + }, + "/applications/v1/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "当前的应用程序信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "application_api" + ], + "summary": "Current Application Info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/schema.ResponseBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Application" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/schema.ResponseBody" + } + } + } + } } }, "definitions": { diff --git a/docs/swagger.json b/docs/swagger.json index 9dcb690..a0667bb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -228,6 +228,52 @@ } } } + }, + "/applications/v1/info": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "当前的应用程序信息", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "application_api" + ], + "summary": "Current Application Info", + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/schema.ResponseBody" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/entity.Application" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/schema.ResponseBody" + } + } + } + } } }, "definitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 885b0ac..6ac965a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -176,6 +176,32 @@ paths: summary: 创建并保存一个应用程序 tags: - applications + /applications/v1/info: + get: + consumes: + - application/json + description: 当前的应用程序信息 + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/schema.ResponseBody' + - properties: + data: + $ref: '#/definitions/entity.Application' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/schema.ResponseBody' + security: + - ApiKeyAuth: [] + summary: Current Application Info + tags: + - application_api securityDefinitions: ApiKeyAuth: in: header diff --git a/internal/base/server/gin.go b/internal/base/server/gin.go index 0995752..d2e092f 100644 --- a/internal/base/server/gin.go +++ b/internal/base/server/gin.go @@ -73,7 +73,6 @@ func (hs *HttpServer) BizRouter() *gin.Engine { apiV1 := rootGroup.Group("/api/v1") { - //apiV1.Use(corsMiddleWare) apiV1.Use(hs.middleware.JSONResponse.ContentTypeJSON) apiV1.Use(hs.middleware.Auth.RequireJWTIDToken) hs.apiRouter.InitApiRouter(apiV1) @@ -85,6 +84,13 @@ func (hs *HttpServer) BizRouter() *gin.Engine { hs.apiRouter.InitNoAuthApiRouter(apiV1NoAuth) } + applicationApiV1 := rootGroup.Group("/applications/v1") + { + applicationApiV1.Use(hs.middleware.JSONResponse.ContentTypeJSON) + applicationApiV1.Use(hs.middleware.ApplicationAuth.RequireApplicationAuth) + hs.apiRouter.InitApplicationApi(applicationApiV1) + } + hs.Gin.NoRoute(func(ctx *gin.Context) { response.Ctx(ctx).Status(http.StatusNotFound).Error(consts.ErrPageNotFound).Send() }) diff --git a/internal/handler/http/controller/application_v1/content.go b/internal/handler/http/controller/application_v1/content.go new file mode 100644 index 0000000..9d7cd63 --- /dev/null +++ b/internal/handler/http/controller/application_v1/content.go @@ -0,0 +1,42 @@ +package application_v1 + +import ( + "github.com/gin-gonic/gin" + "leafdev.top/Ecosystem/recommender/internal/handler/http/response" + "leafdev.top/Ecosystem/recommender/internal/service/application" + "leafdev.top/Ecosystem/recommender/internal/service/auth" + "net/http" +) + +type ApplicationController struct { + authService *auth.Service + applicationService *application.Service +} + +func NewApplicationController(authService *auth.Service, applicationService *application.Service) *ApplicationController { + return &ApplicationController{ + authService, + applicationService, + } +} + +// Info godoc +// @Summary Current Application Info +// @Description 当前的应用程序信息 +// @Tags application_api +// @Accept json +// @Produce json +// @Security ApiKeyAuth +// @Success 200 {object} schema.ResponseBody{data=entity.Application} +// @Failure 400 {object} schema.ResponseBody +// @Router /applications/v1/info [get] +func (ac *ApplicationController) Info(c *gin.Context) { + app, err := ac.authService.GetApplication(c) + if err != nil { + response.Ctx(c).Error(err).Status(http.StatusBadRequest).Send() + return + } + + response.Ctx(c).Data(app).Send() + return +} diff --git a/internal/handler/http/middleware/application_auth.go b/internal/handler/http/middleware/application_auth.go new file mode 100644 index 0000000..d79d01d --- /dev/null +++ b/internal/handler/http/middleware/application_auth.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "leafdev.top/Ecosystem/recommender/internal/handler/http/response" + "leafdev.top/Ecosystem/recommender/internal/service/auth" + "net/http" +) + +type ApplicationAuthMiddleware struct { + authService *auth.Service +} + +func NewApplicationAuthMiddleware(authService *auth.Service) *ApplicationAuthMiddleware { + return &ApplicationAuthMiddleware{ + authService, + } +} + +func (a *ApplicationAuthMiddleware) RequireApplicationAuth(c *gin.Context) { + token, err := a.authService.GetBearerToken(c) + if err != nil { + response.Ctx(c).Status(http.StatusForbidden).Error(err).Send() + c.Abort() + return + } + + applicationEntity, err := a.authService.GetAppByToken(c, token) + if err != nil { + response.Ctx(c).Status(http.StatusForbidden).Error(err).Send() + c.Abort() + return + } + + a.authService.SetApplicationScope(c, applicationEntity) + c.Next() +} diff --git a/internal/handler/http/middleware/auth.go b/internal/handler/http/middleware/auth.go index cc64e18..eb28626 100644 --- a/internal/handler/http/middleware/auth.go +++ b/internal/handler/http/middleware/auth.go @@ -29,7 +29,8 @@ func (a AuthMiddleware) RequireJWTIDToken(c *gin.Context) { return } - c.Set(consts.AuthMiddlewareKey, user) + c.Set(consts.AuthMiddlewareKeyUser.String(), user) + c.Next() } @@ -41,6 +42,7 @@ func (a AuthMiddleware) RequireJWTAccessToken(c *gin.Context) { return } - c.Set(consts.AuthMiddlewareKey, user) + c.Set(consts.AuthMiddlewareKeyUser.String(), user) + c.Next() } diff --git a/internal/handler/http/provider.go b/internal/handler/http/provider.go index 7cbc2e3..011cb54 100644 --- a/internal/handler/http/provider.go +++ b/internal/handler/http/provider.go @@ -2,6 +2,7 @@ package http import ( "github.com/google/wire" + "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/application_v1" v1 "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/v1" "leafdev.top/Ecosystem/recommender/internal/handler/http/middleware" ) @@ -10,37 +11,45 @@ var ProviderSet = wire.NewSet( middleware.NewAuthMiddleware, middleware.NewGinLoggerMiddleware, middleware.NewJSONResponseMiddleware, + middleware.NewApplicationAuthMiddleware, NewMiddleware, v1.NewApplicationController, + application_v1.NewApplicationController, NewHandler, ) type Middleware struct { - GinLogger *middleware.GinLoggerMiddleware - Auth *middleware.AuthMiddleware - JSONResponse *middleware.JSONResponseMiddleware + GinLogger *middleware.GinLoggerMiddleware + Auth *middleware.AuthMiddleware + ApplicationAuth *middleware.ApplicationAuthMiddleware + JSONResponse *middleware.JSONResponseMiddleware } func NewMiddleware( GinLogger *middleware.GinLoggerMiddleware, Auth *middleware.AuthMiddleware, JSONResponse *middleware.JSONResponseMiddleware, + ApplicationAuth *middleware.ApplicationAuthMiddleware, ) *Middleware { return &Middleware{ - Auth: Auth, - GinLogger: GinLogger, - JSONResponse: JSONResponse, + Auth: Auth, + GinLogger: GinLogger, + JSONResponse: JSONResponse, + ApplicationAuth: ApplicationAuth, } } type Handlers struct { - Application *v1.ApplicationController + ApplicationController *v1.ApplicationController + ApplicationApi *application_v1.ApplicationController } func NewHandler( - application *v1.ApplicationController, + applicationController *v1.ApplicationController, + applicationApiController *application_v1.ApplicationController, ) *Handlers { return &Handlers{ - Application: application, + ApplicationController: applicationController, + ApplicationApi: applicationApiController, } } diff --git a/internal/router/api.go b/internal/router/api.go index 7e18ff1..e11fd86 100644 --- a/internal/router/api.go +++ b/internal/router/api.go @@ -23,13 +23,17 @@ func NewApiRoute( } func (a *Api) InitApiRouter(r *gin.RouterGroup) { - r.GET("/applications", a.h.Application.List) - r.POST("/applications", a.h.Application.Save) - r.GET("/application/:application_id/tokens", a.h.Application.ListToken) - r.POST("/application/:application_id/tokens", a.h.Application.SaveToken) + r.GET("/applications", a.h.ApplicationController.List) + r.POST("/applications", a.h.ApplicationController.Save) + r.GET("/application/:application_id/tokens", a.h.ApplicationController.ListToken) + r.POST("/application/:application_id/tokens", a.h.ApplicationController.SaveToken) } func (a *Api) InitNoAuthApiRouter(r *gin.RouterGroup) { } + +func (a *Api) InitApplicationApi(r *gin.RouterGroup) { + r.GET("/info", a.h.ApplicationApi.Info) +} diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go index 5045a39..66351ac 100644 --- a/internal/service/auth/auth.go +++ b/internal/service/auth/auth.go @@ -4,6 +4,8 @@ import ( "context" "leafdev.top/Ecosystem/recommender/internal/base/conf" "leafdev.top/Ecosystem/recommender/internal/base/logger" + "leafdev.top/Ecosystem/recommender/internal/dao" + "leafdev.top/Ecosystem/recommender/internal/entity" "leafdev.top/Ecosystem/recommender/internal/schema" "leafdev.top/Ecosystem/recommender/internal/service/jwks" "leafdev.top/Ecosystem/recommender/pkg/consts" @@ -17,37 +19,49 @@ type Service struct { config *conf.Config jwks *jwks.JWKS logger *logger.Logger + q *dao.Query } -func NewAuthService(config *conf.Config, jwks *jwks.JWKS, logger *logger.Logger) *Service { +func NewAuthService(config *conf.Config, jwks *jwks.JWKS, logger *logger.Logger, q *dao.Query) *Service { return &Service{ config: config, jwks: jwks, logger: logger, + q: q, } } +func (a *Service) GetBearerToken(c *gin.Context) (string, error) { + authorization := c.Request.Header.Get(consts.AuthHeader) + + if authorization == "" { + return "", consts.ErrBearerTokenNotFound + } + + authSplit := strings.Split(authorization, " ") + if len(authSplit) != 2 { + return "", consts.ErrNotBearerType + } + + if authSplit[0] != consts.AuthPrefix { + return "", consts.ErrNotBearerType + } + + return authSplit[1], nil +} + func (a *Service) GinMiddlewareAuth(tokenType schema.JWTTokenTypes, c *gin.Context) (*schema.User, error) { if a.config.Debug.Enabled { return a.parseUserJWT(tokenType, "") } - authorization := c.Request.Header.Get(consts.AuthHeader) + token, err := a.GetBearerToken(c) - if authorization == "" { - return nil, consts.ErrJWTFormatError + if err != nil { + return nil, err } - authSplit := strings.Split(authorization, " ") - if len(authSplit) != 2 { - return nil, consts.ErrJWTFormatError - } - - if authSplit[0] != consts.AuthPrefix { - return nil, consts.ErrNotBearerType - } - - return a.parseUserJWT(tokenType, authSplit[1]) + return a.parseUserJWT(tokenType, token) } func (a *Service) AuthFromToken(tokenType schema.JWTTokenTypes, token string) (*schema.User, error) { @@ -63,7 +77,7 @@ func (a *Service) GetUserFromIdToken(idToken string) (*schema.User, error) { } func (a *Service) GinUser(c *gin.Context) *schema.User { - user, _ := c.Get(consts.AuthMiddlewareKey) + user, _ := c.Get(consts.AuthMiddlewareKeyUser.String()) return user.(*schema.User) } @@ -74,7 +88,7 @@ func (a *Service) GetUserId(ctx context.Context) schema.UserId { } func (a *Service) GetUser(ctx context.Context) *schema.User { - user := ctx.Value(consts.AuthMiddlewareKey) + user := ctx.Value(consts.AuthMiddlewareKeyUser.String()) user2 := user.(*schema.User) user2.ID = user2.Token.Sub @@ -83,8 +97,7 @@ func (a *Service) GetUser(ctx context.Context) *schema.User { } func (a *Service) SetUser(ctx context.Context, user *schema.User) context.Context { - context.WithValue(ctx, consts.AuthMiddlewareKey, user) - return context.WithValue(ctx, consts.AuthMiddlewareKey, user) + return context.WithValue(ctx, consts.AuthMiddlewareKeyUser.String(), user) } func (a *Service) parseUserJWT(tokenType schema.JWTTokenTypes, jwtToken string) (*schema.User, error) { @@ -137,3 +150,35 @@ func (a *Service) parseUserJWT(tokenType schema.JWTTokenTypes, jwtToken string) return jwtIdToken, nil } + +func (a *Service) GetAppByToken(ctx context.Context, token string) (*entity.Application, error) { + r, err := a.q.ApplicationToken.WithContext(ctx).Where(a.q.ApplicationToken.Token.Eq(token)). + Preload(a.q.ApplicationToken.Application).First() + + if err != nil { + return nil, err + } + + if r.Application == nil { + return nil, consts.ErrApplicationNotFound + } + + return r.Application, nil +} + +func (a *Service) GetApplication(ctx context.Context) (*entity.Application, error) { + app := ctx.Value(consts.AuthMiddlewareKeyApplication.String()) + + app2, ok := app.(*entity.Application) + + if !ok { + return nil, consts.ErrApplicationNotFound + } + + return app2, nil +} + +func (a *Service) SetApplicationScope(c *gin.Context, application *entity.Application) context.Context { + c.Set(consts.AuthMiddlewareKeyApplication.String(), application) + return context.WithValue(c, consts.AuthMiddlewareKeyApplication, application) +} diff --git a/pkg/consts/application.go b/pkg/consts/application.go new file mode 100644 index 0000000..44aa044 --- /dev/null +++ b/pkg/consts/application.go @@ -0,0 +1,7 @@ +package consts + +import "errors" + +var ( + ErrApplicationNotFound = errors.New("application not found") +) diff --git a/pkg/consts/auth.go b/pkg/consts/auth.go index 48714db..a4045e0 100644 --- a/pkg/consts/auth.go +++ b/pkg/consts/auth.go @@ -5,6 +5,8 @@ import ( "leafdev.top/Ecosystem/recommender/internal/schema" ) +type AuthMiddlewareKey string + const ( AuthHeader = "Authorization" AuthPrefix = "Bearer" @@ -12,17 +14,22 @@ const ( //AnonymousUser schema.UserId = 1 AnonymousUser schema.UserId = "anonymous" - AuthMiddlewareKey = "auth.user" - AuthAssistantShareMiddlewareKey = "auth.assistant.share" + AuthMiddlewareKeyUser AuthMiddlewareKey = "auth.user" + AuthMiddlewareKeyApplication AuthMiddlewareKey = "auth.application" ) +func (a AuthMiddlewareKey) String() string { + return string(a) +} + var ( - ErrNotValidToken = errors.New("无效的 JWT 令牌") - ErrJWTFormatError = errors.New("JWT 格式错误") - ErrNotBearerType = errors.New("不是 Bearer 类型") - ErrEmptyResponse = errors.New("我们的服务器返回了空请求,可能某些环节出了问题") - ErrTokenError = errors.New("token 类型错误") - ErrBearerToken = errors.New("无效的 Bearer 令牌") + ErrBearerTokenNotFound = errors.New("bearer 令牌未找到") + ErrNotValidToken = errors.New("无效的 JWT 令牌") + ErrJWTFormatError = errors.New("JWT 格式错误") + ErrNotBearerType = errors.New("不是 Bearer 类型") + ErrEmptyResponse = errors.New("我们的服务器返回了空请求,可能某些环节出了问题") + ErrTokenError = errors.New("token 类型错误") + ErrBearerToken = errors.New("无效的 Bearer 令牌") ErrNotYourResource = errors.New("你不能修改这个资源,因为它不是你创建的。") ErrPermissionDenied = errors.New("没有权限访问此资源")