feat: add personal access token middleware to open api (#2590)
Signed-off-by: Gaius <gaius.qi@gmail.com>
This commit is contained in:
parent
58f486a4ed
commit
b7231818e9
|
|
@ -148,7 +148,7 @@ func New(cfg *config.Config, d dfpath.Dfpath) (*Server, error) {
|
||||||
|
|
||||||
// Initialize REST server
|
// Initialize REST server
|
||||||
restService := service.New(cfg, db, cache, job, enforcer, objectStorage)
|
restService := service.New(cfg, db, cache, job, enforcer, objectStorage)
|
||||||
router, err := router.Init(cfg, d.LogDir(), restService, enforcer, EmbedFolder(assets, assetsTargetPath))
|
router, err := router.Init(cfg, d.LogDir(), restService, db, enforcer, EmbedFolder(assets, assetsTargetPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2023 The Dragonfly Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package middlewares
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-http-utils/headers"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
|
"d7y.io/dragonfly/v2/manager/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PersonalAccessToken(gdb *gorm.DB) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// Get bearer token from Authorization header.
|
||||||
|
authorization := c.GetHeader(headers.Authorization)
|
||||||
|
tokenFields := strings.Fields(authorization)
|
||||||
|
if len(tokenFields) != 2 || tokenFields[0] != "Bearer" {
|
||||||
|
c.JSON(http.StatusUnauthorized, ErrorResponse{
|
||||||
|
Message: http.StatusText(http.StatusUnauthorized),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the personal access token is valid.
|
||||||
|
personalAccessToken := tokenFields[1]
|
||||||
|
if err := gdb.WithContext(c).Where("token = ?", personalAccessToken).First(&models.PersonalAccessToken{}).Error; err != nil {
|
||||||
|
c.JSON(http.StatusUnauthorized, ErrorResponse{
|
||||||
|
Message: http.StatusText(http.StatusUnauthorized),
|
||||||
|
})
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ type PersonalAccessToken struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
Name string `gorm:"column:name;type:varchar(256);index:uk_personal_access_token_name,unique;not null;comment:name" json:"name"`
|
Name string `gorm:"column:name;type:varchar(256);index:uk_personal_access_token_name,unique;not null;comment:name" json:"name"`
|
||||||
BIO string `gorm:"column:bio;type:varchar(1024);comment:biography" json:"bio"`
|
BIO string `gorm:"column:bio;type:varchar(1024);comment:biography" json:"bio"`
|
||||||
Token string `gorm:"column:token;type:varchar(256);not null;comment:access token" json:"token"`
|
Token string `gorm:"column:token;type:varchar(256);index:uk_personal_access_token,unique;not null;comment:access token" json:"token"`
|
||||||
Scopes Array `gorm:"column:scopes;not null;comment:scopes flags" json:"scopes"`
|
Scopes Array `gorm:"column:scopes;not null;comment:scopes flags" json:"scopes"`
|
||||||
State string `gorm:"column:state;type:varchar(256);default:'inactive';comment:service state" json:"state"`
|
State string `gorm:"column:state;type:varchar(256);default:'inactive';comment:service state" json:"state"`
|
||||||
ExpiredAt time.Time `gorm:"column:expired_at;type:timestamp;default:current_timestamp;not null;comment:expired at" json:"expired_at"`
|
ExpiredAt time.Time `gorm:"column:expired_at;type:timestamp;default:current_timestamp;not null;comment:expired at" json:"expired_at"`
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
logger "d7y.io/dragonfly/v2/internal/dflog"
|
logger "d7y.io/dragonfly/v2/internal/dflog"
|
||||||
"d7y.io/dragonfly/v2/manager/config"
|
"d7y.io/dragonfly/v2/manager/config"
|
||||||
|
"d7y.io/dragonfly/v2/manager/database"
|
||||||
"d7y.io/dragonfly/v2/manager/handlers"
|
"d7y.io/dragonfly/v2/manager/handlers"
|
||||||
"d7y.io/dragonfly/v2/manager/middlewares"
|
"d7y.io/dragonfly/v2/manager/middlewares"
|
||||||
"d7y.io/dragonfly/v2/manager/service"
|
"d7y.io/dragonfly/v2/manager/service"
|
||||||
|
|
@ -41,7 +42,7 @@ const (
|
||||||
OtelServiceName = "dragonfly-manager"
|
OtelServiceName = "dragonfly-manager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(cfg *config.Config, logDir string, service service.Service, enforcer *casbin.Enforcer, assets static.ServeFileSystem) (*gin.Engine, error) {
|
func Init(cfg *config.Config, logDir string, service service.Service, database *database.Database, enforcer *casbin.Enforcer, assets static.ServeFileSystem) (*gin.Engine, error) {
|
||||||
// Set mode.
|
// Set mode.
|
||||||
if !cfg.Verbose {
|
if !cfg.Verbose {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
|
@ -65,23 +66,31 @@ func Init(cfg *config.Config, logDir string, service service.Service, enforcer *
|
||||||
r.Use(otelgin.Middleware(OtelServiceName))
|
r.Use(otelgin.Middleware(OtelServiceName))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middleware.
|
// Gin middleware.
|
||||||
r.Use(gin.Recovery())
|
r.Use(gin.Recovery())
|
||||||
r.Use(ginzap.Ginzap(logger.GinLogger.Desugar(), time.RFC3339, true))
|
r.Use(ginzap.Ginzap(logger.GinLogger.Desugar(), time.RFC3339, true))
|
||||||
r.Use(ginzap.RecoveryWithZap(logger.GinLogger.Desugar(), true))
|
r.Use(ginzap.RecoveryWithZap(logger.GinLogger.Desugar(), true))
|
||||||
|
|
||||||
|
// Error middleware.
|
||||||
r.Use(middlewares.Error())
|
r.Use(middlewares.Error())
|
||||||
|
|
||||||
|
// CORS middleware.
|
||||||
r.Use(middlewares.CORS())
|
r.Use(middlewares.CORS())
|
||||||
|
|
||||||
|
// RBAC middleware.
|
||||||
rbac := middlewares.RBAC(enforcer)
|
rbac := middlewares.RBAC(enforcer)
|
||||||
jwt, err := middlewares.Jwt(cfg.Auth.JWT, service)
|
jwt, err := middlewares.Jwt(cfg.Auth.JWT, service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Personal access token middleware.
|
||||||
|
personalAccessToken := middlewares.PersonalAccessToken(database.DB)
|
||||||
|
|
||||||
// Manager view.
|
// Manager view.
|
||||||
r.Use(static.Serve("/", assets))
|
r.Use(static.Serve("/", assets))
|
||||||
|
|
||||||
// Router.
|
// API router.
|
||||||
apiv1 := r.Group("/api/v1")
|
apiv1 := r.Group("/api/v1")
|
||||||
|
|
||||||
// User.
|
// User.
|
||||||
|
|
@ -179,6 +188,7 @@ func Init(cfg *config.Config, logDir string, service service.Service, enforcer *
|
||||||
config.GET(":id", jwt.MiddlewareFunc(), rbac, h.GetConfig)
|
config.GET(":id", jwt.MiddlewareFunc(), rbac, h.GetConfig)
|
||||||
config.GET("", h.GetConfigs)
|
config.GET("", h.GetConfigs)
|
||||||
|
|
||||||
|
// TODO Add auth to the following routes and fix the tests.
|
||||||
// Job.
|
// Job.
|
||||||
job := apiv1.Group("/jobs")
|
job := apiv1.Group("/jobs")
|
||||||
job.POST("", h.CreateJob)
|
job.POST("", h.CreateJob)
|
||||||
|
|
@ -187,12 +197,6 @@ func Init(cfg *config.Config, logDir string, service service.Service, enforcer *
|
||||||
job.GET(":id", h.GetJob)
|
job.GET(":id", h.GetJob)
|
||||||
job.GET("", h.GetJobs)
|
job.GET("", h.GetJobs)
|
||||||
|
|
||||||
// Compatible with the V1 preheat.
|
|
||||||
pv1 := r.Group("/preheats")
|
|
||||||
r.GET("_ping", h.GetHealth)
|
|
||||||
pv1.POST("", h.CreateV1Preheat)
|
|
||||||
pv1.GET(":id", h.GetV1Preheat)
|
|
||||||
|
|
||||||
// Application.
|
// Application.
|
||||||
cs := apiv1.Group("/applications", jwt.MiddlewareFunc(), rbac)
|
cs := apiv1.Group("/applications", jwt.MiddlewareFunc(), rbac)
|
||||||
cs.POST("", h.CreateApplication)
|
cs.POST("", h.CreateApplication)
|
||||||
|
|
@ -216,6 +220,24 @@ func Init(cfg *config.Config, logDir string, service service.Service, enforcer *
|
||||||
pat.GET(":id", h.GetPersonalAccessToken)
|
pat.GET(":id", h.GetPersonalAccessToken)
|
||||||
pat.GET("", h.GetPersonalAccessTokens)
|
pat.GET("", h.GetPersonalAccessTokens)
|
||||||
|
|
||||||
|
// Open API router.
|
||||||
|
oapiv1 := r.Group("/oapi/v1")
|
||||||
|
|
||||||
|
// Job.
|
||||||
|
ojob := oapiv1.Group("/jobs", personalAccessToken)
|
||||||
|
ojob.POST("", h.CreateJob)
|
||||||
|
ojob.DELETE(":id", h.DestroyJob)
|
||||||
|
ojob.PATCH(":id", h.UpdateJob)
|
||||||
|
ojob.GET(":id", h.GetJob)
|
||||||
|
ojob.GET("", h.GetJobs)
|
||||||
|
|
||||||
|
// TODO Remove this api.
|
||||||
|
// Compatible with the V1 preheat.
|
||||||
|
pv1 := r.Group("/preheats")
|
||||||
|
r.GET("_ping", h.GetHealth)
|
||||||
|
pv1.POST("", h.CreateV1Preheat)
|
||||||
|
pv1.GET(":id", h.GetV1Preheat)
|
||||||
|
|
||||||
// Health Check.
|
// Health Check.
|
||||||
r.GET("/healthy", h.GetHealth)
|
r.GET("/healthy", h.GetHealth)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue