add application scope

This commit is contained in:
ivamp 2024-11-07 20:00:06 +08:00
parent f2efea4ed1
commit 40074417c7
13 changed files with 325 additions and 45 deletions

View File

@ -22,6 +22,7 @@ import (
"leafdev.top/Ecosystem/recommender/internal/handler/grpc/documents" "leafdev.top/Ecosystem/recommender/internal/handler/grpc/documents"
"leafdev.top/Ecosystem/recommender/internal/handler/grpc/interceptor" "leafdev.top/Ecosystem/recommender/internal/handler/grpc/interceptor"
"leafdev.top/Ecosystem/recommender/internal/handler/http" "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/controller/v1"
"leafdev.top/Ecosystem/recommender/internal/handler/http/middleware" "leafdev.top/Ecosystem/recommender/internal/handler/http/middleware"
"leafdev.top/Ecosystem/recommender/internal/router" "leafdev.top/Ecosystem/recommender/internal/router"
@ -37,18 +38,20 @@ func CreateApp() (*base.Application, error) {
loggerLogger := logger.NewZapLogger() loggerLogger := logger.NewZapLogger()
config := conf.ProviderConfig(loggerLogger) config := conf.ProviderConfig(loggerLogger)
jwksJWKS := jwks.NewJWKS(config, loggerLogger) jwksJWKS := jwks.NewJWKS(config, loggerLogger)
authService := auth.NewAuthService(config, jwksJWKS, loggerLogger)
db := orm.NewGORM(config, loggerLogger) db := orm.NewGORM(config, loggerLogger)
query := dao.NewQuery(db) query := dao.NewQuery(db)
authService := auth.NewAuthService(config, jwksJWKS, loggerLogger, query)
applicationService := application.NewService(query) applicationService := application.NewService(query)
applicationController := v1.NewApplicationController(authService, applicationService) 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) api := router.NewApiRoute(handlers)
swaggerRouter := router.NewSwaggerRoute() swaggerRouter := router.NewSwaggerRoute()
ginLoggerMiddleware := middleware.NewGinLoggerMiddleware(loggerLogger) ginLoggerMiddleware := middleware.NewGinLoggerMiddleware(loggerLogger)
authMiddleware := middleware.NewAuthMiddleware(authService) authMiddleware := middleware.NewAuthMiddleware(authService)
jsonResponseMiddleware := middleware.NewJSONResponseMiddleware() 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) httpServer := server.NewHTTPServer(config, api, swaggerRouter, httpMiddleware)
documentService := documents.NewDocumentService(query) documentService := documents.NewDocumentService(query)
interceptorAuth := interceptor.NewAuth(authService) interceptorAuth := interceptor.NewAuth(authService)

View File

@ -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": { "definitions": {

View File

@ -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": { "definitions": {

View File

@ -176,6 +176,32 @@ paths:
summary: 创建并保存一个应用程序 summary: 创建并保存一个应用程序
tags: tags:
- applications - 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: securityDefinitions:
ApiKeyAuth: ApiKeyAuth:
in: header in: header

View File

@ -73,7 +73,6 @@ func (hs *HttpServer) BizRouter() *gin.Engine {
apiV1 := rootGroup.Group("/api/v1") apiV1 := rootGroup.Group("/api/v1")
{ {
//apiV1.Use(corsMiddleWare)
apiV1.Use(hs.middleware.JSONResponse.ContentTypeJSON) apiV1.Use(hs.middleware.JSONResponse.ContentTypeJSON)
apiV1.Use(hs.middleware.Auth.RequireJWTIDToken) apiV1.Use(hs.middleware.Auth.RequireJWTIDToken)
hs.apiRouter.InitApiRouter(apiV1) hs.apiRouter.InitApiRouter(apiV1)
@ -85,6 +84,13 @@ func (hs *HttpServer) BizRouter() *gin.Engine {
hs.apiRouter.InitNoAuthApiRouter(apiV1NoAuth) 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) { hs.Gin.NoRoute(func(ctx *gin.Context) {
response.Ctx(ctx).Status(http.StatusNotFound).Error(consts.ErrPageNotFound).Send() response.Ctx(ctx).Status(http.StatusNotFound).Error(consts.ErrPageNotFound).Send()
}) })

View File

@ -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
}

View File

@ -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()
}

View File

@ -29,7 +29,8 @@ func (a AuthMiddleware) RequireJWTIDToken(c *gin.Context) {
return return
} }
c.Set(consts.AuthMiddlewareKey, user) c.Set(consts.AuthMiddlewareKeyUser.String(), user)
c.Next() c.Next()
} }
@ -41,6 +42,7 @@ func (a AuthMiddleware) RequireJWTAccessToken(c *gin.Context) {
return return
} }
c.Set(consts.AuthMiddlewareKey, user) c.Set(consts.AuthMiddlewareKeyUser.String(), user)
c.Next() c.Next()
} }

View File

@ -2,6 +2,7 @@ package http
import ( import (
"github.com/google/wire" "github.com/google/wire"
"leafdev.top/Ecosystem/recommender/internal/handler/http/controller/application_v1"
v1 "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/v1" v1 "leafdev.top/Ecosystem/recommender/internal/handler/http/controller/v1"
"leafdev.top/Ecosystem/recommender/internal/handler/http/middleware" "leafdev.top/Ecosystem/recommender/internal/handler/http/middleware"
) )
@ -10,14 +11,17 @@ var ProviderSet = wire.NewSet(
middleware.NewAuthMiddleware, middleware.NewAuthMiddleware,
middleware.NewGinLoggerMiddleware, middleware.NewGinLoggerMiddleware,
middleware.NewJSONResponseMiddleware, middleware.NewJSONResponseMiddleware,
middleware.NewApplicationAuthMiddleware,
NewMiddleware, NewMiddleware,
v1.NewApplicationController, v1.NewApplicationController,
application_v1.NewApplicationController,
NewHandler, NewHandler,
) )
type Middleware struct { type Middleware struct {
GinLogger *middleware.GinLoggerMiddleware GinLogger *middleware.GinLoggerMiddleware
Auth *middleware.AuthMiddleware Auth *middleware.AuthMiddleware
ApplicationAuth *middleware.ApplicationAuthMiddleware
JSONResponse *middleware.JSONResponseMiddleware JSONResponse *middleware.JSONResponseMiddleware
} }
@ -25,22 +29,27 @@ func NewMiddleware(
GinLogger *middleware.GinLoggerMiddleware, GinLogger *middleware.GinLoggerMiddleware,
Auth *middleware.AuthMiddleware, Auth *middleware.AuthMiddleware,
JSONResponse *middleware.JSONResponseMiddleware, JSONResponse *middleware.JSONResponseMiddleware,
ApplicationAuth *middleware.ApplicationAuthMiddleware,
) *Middleware { ) *Middleware {
return &Middleware{ return &Middleware{
Auth: Auth, Auth: Auth,
GinLogger: GinLogger, GinLogger: GinLogger,
JSONResponse: JSONResponse, JSONResponse: JSONResponse,
ApplicationAuth: ApplicationAuth,
} }
} }
type Handlers struct { type Handlers struct {
Application *v1.ApplicationController ApplicationController *v1.ApplicationController
ApplicationApi *application_v1.ApplicationController
} }
func NewHandler( func NewHandler(
application *v1.ApplicationController, applicationController *v1.ApplicationController,
applicationApiController *application_v1.ApplicationController,
) *Handlers { ) *Handlers {
return &Handlers{ return &Handlers{
Application: application, ApplicationController: applicationController,
ApplicationApi: applicationApiController,
} }
} }

View File

@ -23,13 +23,17 @@ func NewApiRoute(
} }
func (a *Api) InitApiRouter(r *gin.RouterGroup) { func (a *Api) InitApiRouter(r *gin.RouterGroup) {
r.GET("/applications", a.h.Application.List) r.GET("/applications", a.h.ApplicationController.List)
r.POST("/applications", a.h.Application.Save) r.POST("/applications", a.h.ApplicationController.Save)
r.GET("/application/:application_id/tokens", a.h.Application.ListToken) r.GET("/application/:application_id/tokens", a.h.ApplicationController.ListToken)
r.POST("/application/:application_id/tokens", a.h.Application.SaveToken) r.POST("/application/:application_id/tokens", a.h.ApplicationController.SaveToken)
} }
func (a *Api) InitNoAuthApiRouter(r *gin.RouterGroup) { func (a *Api) InitNoAuthApiRouter(r *gin.RouterGroup) {
} }
func (a *Api) InitApplicationApi(r *gin.RouterGroup) {
r.GET("/info", a.h.ApplicationApi.Info)
}

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"leafdev.top/Ecosystem/recommender/internal/base/conf" "leafdev.top/Ecosystem/recommender/internal/base/conf"
"leafdev.top/Ecosystem/recommender/internal/base/logger" "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/schema"
"leafdev.top/Ecosystem/recommender/internal/service/jwks" "leafdev.top/Ecosystem/recommender/internal/service/jwks"
"leafdev.top/Ecosystem/recommender/pkg/consts" "leafdev.top/Ecosystem/recommender/pkg/consts"
@ -17,37 +19,49 @@ type Service struct {
config *conf.Config config *conf.Config
jwks *jwks.JWKS jwks *jwks.JWKS
logger *logger.Logger 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{ return &Service{
config: config, config: config,
jwks: jwks, jwks: jwks,
logger: logger, 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) { func (a *Service) GinMiddlewareAuth(tokenType schema.JWTTokenTypes, c *gin.Context) (*schema.User, error) {
if a.config.Debug.Enabled { if a.config.Debug.Enabled {
return a.parseUserJWT(tokenType, "") return a.parseUserJWT(tokenType, "")
} }
authorization := c.Request.Header.Get(consts.AuthHeader) token, err := a.GetBearerToken(c)
if authorization == "" { if err != nil {
return nil, consts.ErrJWTFormatError return nil, err
} }
authSplit := strings.Split(authorization, " ") return a.parseUserJWT(tokenType, token)
if len(authSplit) != 2 {
return nil, consts.ErrJWTFormatError
}
if authSplit[0] != consts.AuthPrefix {
return nil, consts.ErrNotBearerType
}
return a.parseUserJWT(tokenType, authSplit[1])
} }
func (a *Service) AuthFromToken(tokenType schema.JWTTokenTypes, token string) (*schema.User, error) { 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 { func (a *Service) GinUser(c *gin.Context) *schema.User {
user, _ := c.Get(consts.AuthMiddlewareKey) user, _ := c.Get(consts.AuthMiddlewareKeyUser.String())
return user.(*schema.User) 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 { 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 := user.(*schema.User)
user2.ID = user2.Token.Sub 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 { func (a *Service) SetUser(ctx context.Context, user *schema.User) context.Context {
context.WithValue(ctx, consts.AuthMiddlewareKey, user) return context.WithValue(ctx, consts.AuthMiddlewareKeyUser.String(), user)
return context.WithValue(ctx, consts.AuthMiddlewareKey, user)
} }
func (a *Service) parseUserJWT(tokenType schema.JWTTokenTypes, jwtToken string) (*schema.User, error) { 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 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)
}

View File

@ -0,0 +1,7 @@
package consts
import "errors"
var (
ErrApplicationNotFound = errors.New("application not found")
)

View File

@ -5,6 +5,8 @@ import (
"leafdev.top/Ecosystem/recommender/internal/schema" "leafdev.top/Ecosystem/recommender/internal/schema"
) )
type AuthMiddlewareKey string
const ( const (
AuthHeader = "Authorization" AuthHeader = "Authorization"
AuthPrefix = "Bearer" AuthPrefix = "Bearer"
@ -12,11 +14,16 @@ const (
//AnonymousUser schema.UserId = 1 //AnonymousUser schema.UserId = 1
AnonymousUser schema.UserId = "anonymous" AnonymousUser schema.UserId = "anonymous"
AuthMiddlewareKey = "auth.user" AuthMiddlewareKeyUser AuthMiddlewareKey = "auth.user"
AuthAssistantShareMiddlewareKey = "auth.assistant.share" AuthMiddlewareKeyApplication AuthMiddlewareKey = "auth.application"
) )
func (a AuthMiddlewareKey) String() string {
return string(a)
}
var ( var (
ErrBearerTokenNotFound = errors.New("bearer 令牌未找到")
ErrNotValidToken = errors.New("无效的 JWT 令牌") ErrNotValidToken = errors.New("无效的 JWT 令牌")
ErrJWTFormatError = errors.New("JWT 格式错误") ErrJWTFormatError = errors.New("JWT 格式错误")
ErrNotBearerType = errors.New("不是 Bearer 类型") ErrNotBearerType = errors.New("不是 Bearer 类型")