feat: Add 1Panel auth middleware + add and search Website API

This commit is contained in:
KaySar12 2024-10-09 12:05:51 +07:00
parent 946e3dba59
commit ec9cc6c24e
9 changed files with 267 additions and 59 deletions

2
.gitignore vendored
View File

@ -41,6 +41,6 @@ linux-amd64-nextzenos-user-service-v1.2.4.tar.gz
dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service
dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service
dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service
.env
.vscode/launch.json
dist/metadata.json

2
.vscode/launch.json vendored
View File

@ -6,7 +6,7 @@
"type": "go",
"debugAdapter": "dlv-dap",
"request": "launch",
"port": 33107,
"port": 40131,
"host": "127.0.0.1",
"mode": "exec",
"program": "${workspaceFolder}/dist/casaos-user-service-amd64_linux_amd64_v1/build/sysroot/usr/bin/casaos-user-service"

4
go.mod
View File

@ -11,6 +11,7 @@ require (
github.com/deepmap/oapi-codegen v1.12.4
github.com/getkin/kin-openapi v0.117.0
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/sessions v1.0.1
github.com/gin-gonic/gin v1.10.0
github.com/glebarez/sqlite v1.8.0
github.com/labstack/echo/v4 v4.12.0
@ -48,7 +49,10 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/gorilla/sessions v1.2.2 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect

10
go.sum
View File

@ -34,6 +34,8 @@ github.com/getkin/kin-openapi v0.117.0 h1:QT2DyGujAL09F4NrKDHJGsUoIprlIcFVHWDVDc
github.com/getkin/kin-openapi v0.117.0/go.mod h1:l5e9PaFUo9fyLJCPGQeXI2ML8c3P8BHOEV2VaAVf/pc=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
@ -80,12 +82,20 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=

View File

@ -125,6 +125,7 @@ func main() {
apiPaths := []string{
"/v1/users",
"/v1/1panel",
route.V2APIPath,
route.V2DocPath,
"/" + jwt.JWKSPath,

View File

@ -9,6 +9,8 @@ import (
v1 "github.com/KaySar12/NextZen-UserService/route/v1"
"github.com/KaySar12/NextZen-UserService/service"
"github.com/gin-contrib/gzip"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
@ -18,12 +20,16 @@ func InitRouter() *gin.Engine {
r.Use(v1.CheckOIDCInit())
r.Use(gzip.Gzip(gzip.DefaultCompression))
store := cookie.NewStore([]byte("secret"))
sessionMiddleware := sessions.Sessions("1Panel", store)
r.Use(sessionMiddleware)
// check if environment variable is set
if ginMode, success := os.LookupEnv("GIN_MODE"); success {
gin.SetMode(ginMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
go v1.InitOIDC()
r.POST("/v1/users/register", v1.PostUserRegister)
r.POST("/v1/users/login", v1.PostUserLogin)
@ -43,8 +49,11 @@ func InitRouter() *gin.Engine {
r.GET("/v1/users/oidc/health", v1.OIDCHealthCheck)
r.GET("/v1/users/oidc/settings", v1.GetOIDCSettings)
r.POST("/v1/users/oidc/saveSettings", v1.SaveOIDCSettings)
r.GET("/v1/users/1panel/health", v1.OnePanelHealthCheck)
r.POST("/v1/users/1panel/login", v1.OnePanelLogin)
r.GET("/v1/1panel/health", v1.OnePanelHealthCheck)
// r.POST("/v1/1panel/login", v1.OnePanelLogin)
// r.POST("/v1/1panel/app/search", v1.ExternalAPIMiddleware, v1.OnePanelLogin)
// r.POST("/v1/1panel/website/search", v1.ExternalAPIMiddleware, v1.OnePanelLogin)
r.POST("/v1/1panel/website/create", v1.ExternalAPIMiddleware, v1.OnePanelCreateWebsite)
v1Group := r.Group("/v1")
v1Group.Use(jwt.JWT(
@ -54,6 +63,15 @@ func InitRouter() *gin.Engine {
},
))
{
// v1OnePanel := v1Group.Group("/1panel")
// v1OnePanel.Use()
// {
// r.GET("/health", v1.OnePanelHealthCheck)
// r.POST("/login", v1.OnePanelLogin)
// r.POST("/app/search", v1.ExternalAPIMiddleware, v1.OnePanelLogin)
// r.POST("/website/search", v1.ExternalAPIMiddleware, v1.OnePanelLogin)
// r.POST("/website/create", v1.ExternalAPIMiddleware, v1.OnePanelCreateWebsite)
// }
v1UsersGroup := v1Group.Group("/users")
v1UsersGroup.Use()
{

View File

@ -36,6 +36,7 @@ import (
"github.com/KaySar12/NextZen-UserService/service"
model2 "github.com/KaySar12/NextZen-UserService/service/model"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
uuid "github.com/satori/go.uuid"
"github.com/tidwall/gjson"
@ -45,15 +46,12 @@ import (
)
var (
//authServer = "http://10.0.0.26:9000"
authServer = "http://accessmanager.local"
clientID = "6KwKSxLCtaQ4r6HoAn3gdNMbNOAf75j3SejLIAx7"
clientSecret = "PE05fcDP4qESUmyZ1TNYpZNBxRPq70VpFI81vehsoJ6WhGz5yPXMljrFrOdMRdRhrYmF03fHWTZHgO9ZdNENrLN13BzL8CAgtEkTsyjXfgx9GvISheIjYfpSfvo219fL"
authURL = "http://accessmanager.local/application/o/nextzenos-oidc/"
//authURL = "http://10.0.0.26:9000/application/o/nextzenos-oidc/"
callbackURL = "http://nextzenos.local/v1/users/oidc/callback"
//callbackURL = "http://172.20.60.244:8080/v1/users/oidc/callback"
onePanelServer = "http://172.20.60.244:13000"
authServer = "http://accessmanager.local"
clientID = "6KwKSxLCtaQ4r6HoAn3gdNMbNOAf75j3SejLIAx7"
clientSecret = "PE05fcDP4qESUmyZ1TNYpZNBxRPq70VpFI81vehsoJ6WhGz5yPXMljrFrOdMRdRhrYmF03fHWTZHgO9ZdNENrLN13BzL8CAgtEkTsyjXfgx9GvISheIjYfpSfvo219fL"
authURL = "http://accessmanager.local/application/o/nextzenos-oidc/"
callbackURL = "http://nextzenos.local/v1/users/oidc/callback"
onePanelServer = "http://nextweb.local"
onePanelName = "nextzen"
onePanelPassword = "Smartyourlife123@*"
)
@ -117,16 +115,36 @@ func PostUserRegister(c *gin.Context) {
var limiter = rate.NewLimiter(rate.Every(time.Minute), 5)
// @Summary login
// @Produce application/json
// @Accept application/json
// @Tags user
// @Param user_name query string true "User name"
// @Param pwd query string true "password"
// @Success 200 {string} string "ok"
// @Router /user/login [post]
func OnePanelLogin(c *gin.Context) {
var cred = model2.OnePanelCredentials{
func ExternalAPIMiddleware(c *gin.Context) {
session := sessions.Default(c)
sessionId := session.Get("psession")
if sessionId == nil {
if err := OnePanelLogin(c); err != nil {
c.JSON(http.StatusUnauthorized, model.Result{
Success: common_err.SERVICE_ERROR,
Message: common_err.GetMsg(common_err.SERVICE_ERROR),
})
c.Abort()
return
}
sessionId = session.Get("psession")
if sessionId == nil {
c.JSON(http.StatusInternalServerError, model.Result{
Success: common_err.SERVICE_ERROR,
Message: common_err.GetMsg(common_err.SERVICE_ERROR),
})
c.Abort()
return
}
}
// Add sessionId to the request's Cookie header
c.Request.Header.Set("Cookie", "psession="+sessionId.(string))
c.Next()
}
func OnePanelLogin(c *gin.Context) error {
cred := model2.OnePanelCredentials{
Name: onePanelName,
Password: onePanelPassword,
IgnoreCaptcha: true,
@ -135,7 +153,94 @@ func OnePanelLogin(c *gin.Context) {
AuthMethod: "session",
Language: "en",
}
response, cookies, err := service.MyService.OnePanel().Login(cred, "http://172.20.60.244:13000")
response, cookies, err := service.MyService.OnePanel().Login(cred, onePanelServer)
fmt.Println(response)
if err != nil {
logger.Error("OnePanel login failed", zap.Error(err))
return err
}
session := sessions.Default(c)
for _, cookie := range cookies {
session.Set(cookie.Name, cookie.Value)
}
if err := session.Save(); err != nil {
logger.Error("Failed to save session", zap.Error(err))
return err
}
return nil
}
// func OnePanelLogin(c *gin.Context) {
// var cred = model2.OnePanelCredentials{
// Name: onePanelName,
// Password: onePanelPassword,
// IgnoreCaptcha: true,
// Captcha: "",
// CaptchaID: "",
// AuthMethod: "session",
// Language: "en",
// }
// response, cookies, err := service.MyService.OnePanel().Login(cred, onePanelServer)
// if err != nil {
// c.JSON(common_err.SERVICE_ERROR,
// model.Result{
// Success: common_err.SERVICE_ERROR,
// Message: common_err.GetMsg(common_err.SERVICE_ERROR),
// })
// }
// session := sessions.Default(c)
// for _, cookie := range cookies {
// session.Set(cookie.Name, cookie.Value)
// c.SetCookie(cookie.Name, cookie.Value, 3600, "/", "", false, true)
// }
// session.Save()
// c.JSON(common_err.SUCCESS,
// model.Result{
// Success: common_err.SUCCESS,
// Message: common_err.GetMsg(common_err.SUCCESS),
// Data: response,
// })
// }
func OnePanelCreateWebsite(c *gin.Context) {
json := make(map[string]string)
c.ShouldBind(&json)
domain := json["domain"]
port := json["port"]
protocol := json["protocol"]
// useSSl := json["useSSL"]
var website model2.CreateWebsiteRequest
website.PrimaryDomain = domain
website.Type = "proxy"
website.Alias = domain
website.AppType = "installed"
website.WebSiteGroupID = 2
website.Proxy = "http://127.0.0.1:" + port
portInt, err := strconv.ParseInt(port, 10, 64)
if err != nil {
log.Printf("Error converting port to integer: %v", err)
}
website.Port = portInt
website.ProxyProtocol = protocol
website.ProxyAddress = "127.0.0.1" + port
website.RuntimeType = "php"
headers := make(map[string]string)
for key, value := range c.Request.Header {
headers[key] = value[0]
}
var searchParam model2.SearchWebsiteRequest
searchParam.Name = website.PrimaryDomain
searchParam.Page = 1
searchParam.PageSize = 1
searchParam.OrderBy = "created_at"
searchParam.Order = "null"
searchParam.WebsiteGroupID = 0
search, err := service.MyService.OnePanel().SearchWebsite(searchParam, onePanelServer, headers)
if err != nil {
c.JSON(common_err.SERVICE_ERROR,
model.Result{
@ -143,14 +248,26 @@ func OnePanelLogin(c *gin.Context) {
Message: common_err.GetMsg(common_err.SERVICE_ERROR),
})
}
for _, cookie := range cookies {
c.SetCookie(cookie.Name, cookie.Value, 3600, "/", "", false, true)
if search.Data.Total == 0 {
response, err := service.MyService.OnePanel().CreateWebsite(website, onePanelServer, headers)
if err != nil {
c.JSON(common_err.SERVICE_ERROR,
model.Result{
Success: common_err.SERVICE_ERROR,
Message: common_err.GetMsg(common_err.SERVICE_ERROR),
})
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: response,
})
}
c.JSON(common_err.SUCCESS,
model.Result{
Success: common_err.SUCCESS,
Message: common_err.GetMsg(common_err.SUCCESS),
Data: response,
})
}
func PostUserLogin(c *gin.Context) {

View File

@ -16,8 +16,8 @@ type OnePanelService interface {
HealthCheck(baseURL string) (string, error)
SearchInstalledApp(p model2.InstalledAppRequest, baseURL string) (model2.InstalledAppResponse, error)
// InstallApp()
// SearchWebsite()
// CreateWebsite()
SearchWebsite(m model2.SearchWebsiteRequest, baseUrl string, headers map[string]string) (model2.SearchWebsiteResponse, error)
CreateWebsite(m model2.CreateWebsiteRequest, baseUrl string, headers map[string]string) (model2.GenericResponse, error)
// DeleteWebsite()
}
@ -28,6 +28,65 @@ var (
type onePanelService struct {
}
func (o *onePanelService) SearchWebsite(m model2.SearchWebsiteRequest, baseUrl string, headers map[string]string) (model2.SearchWebsiteResponse, error) {
path := baseUrl + "/api/v1/websites/search"
reqBody, err := json.Marshal(m)
if err != nil {
return model2.SearchWebsiteResponse{}, fmt.Errorf("error marshaling request body: %v", err)
}
req, err := http.NewRequest("POST", path, bytes.NewReader(reqBody))
if err != nil {
return model2.SearchWebsiteResponse{}, fmt.Errorf("error creating request: %v", err)
}
// Add headers to the request
for key, value := range headers {
req.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return model2.SearchWebsiteResponse{}, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return model2.SearchWebsiteResponse{}, fmt.Errorf("HTTP error: %s", resp.Status)
}
var result model2.SearchWebsiteResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return model2.SearchWebsiteResponse{}, fmt.Errorf("error decoding response: %v", err)
}
return result, nil
}
func (o *onePanelService) CreateWebsite(m model2.CreateWebsiteRequest, baseUrl string, headers map[string]string) (model2.GenericResponse, error) {
path := baseUrl + "/api/v1/websites"
reqBody, err := json.Marshal(m)
if err != nil {
return model2.GenericResponse{}, fmt.Errorf("error marshaling request body: %v", err)
}
req, err := http.NewRequest("POST", path, bytes.NewReader(reqBody))
if err != nil {
return model2.GenericResponse{}, fmt.Errorf("error creating request: %v", err)
}
// Add headers to the request
for key, value := range headers {
req.Header.Set(key, value)
}
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return model2.GenericResponse{}, fmt.Errorf("error making request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return model2.GenericResponse{}, fmt.Errorf("HTTP error: %s", resp.Status)
}
var result model2.GenericResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return model2.GenericResponse{}, fmt.Errorf("error decoding response: %v", err)
}
return result, nil
}
func (o *onePanelService) SearchInstalledApp(m model2.InstalledAppRequest, baseUrl string) (model2.InstalledAppResponse, error) {
path := baseUrl + "/api/v1/websites/search"
reqBody, err := json.Marshal(m)

View File

@ -39,37 +39,36 @@ type SearchWebsiteResponse struct {
type CreateWebsiteRequest struct {
PrimaryDomain string `json:"primaryDomain"`
Type string `json:"type"`
Alias string `json:"alias"`
Remark string `json:"remark"`
AppType string `json:"appType"`
WebSiteGroupID int `json:"webSiteGroupId"`
OtherDomains string `json:"otherDomains"`
Proxy string `json:"proxy"`
Type string `json:"type,omitempty"`
Alias string `json:"alias,omitempty"`
Remark string `json:"remark,omitempty"`
AppType string `json:"appType,omitempty"`
WebSiteGroupID int64 `json:"webSiteGroupId,omitempty"`
OtherDomains string `json:"otherDomains,omitempty"`
Proxy string `json:"proxy,omitempty"`
Appinstall struct {
AppID int `json:"appId"`
Name string `json:"name"`
AppDetailID int `json:"appDetailId"`
Params struct {
} `json:"params"`
Version string `json:"version"`
Appkey string `json:"appkey"`
Advanced bool `json:"advanced"`
CPUQuota int `json:"cpuQuota"`
MemoryLimit int `json:"memoryLimit"`
MemoryUnit string `json:"memoryUnit"`
ContainerName string `json:"containerName"`
AllowPort bool `json:"allowPort"`
} `json:"appinstall"`
IPV6 bool `json:"IPV6"`
EnableFtp bool `json:"enableFtp"`
FtpUser string `json:"ftpUser"`
FtpPassword string `json:"ftpPassword"`
ProxyType string `json:"proxyType"`
Port int `json:"port"`
ProxyProtocol string `json:"proxyProtocol"`
ProxyAddress string `json:"proxyAddress"`
RuntimeType string `json:"runtimeType"`
AppID int64 `json:"appId,omitempty"`
Name string `json:"name,omitempty"`
AppDetailID int64 `json:"appDetailId,omitempty"`
Params struct{} `json:"params,omitempty"`
Version string `json:"version,omitempty"`
Appkey string `json:"appkey,omitempty"`
Advanced bool `json:"advanced,omitempty"`
CPUQuota int64 `json:"cpuQuota,omitempty"`
MemoryLimit int64 `json:"memoryLimit,omitempty"`
MemoryUnit string `json:"memoryUnit,omitempty"`
ContainerName string `json:"containerName,omitempty"`
AllowPort bool `json:"allowPort,omitempty"`
} `json:"appinstall,omitempty"`
IPV6 bool `json:"IPV6,omitempty"`
EnableFtp bool `json:"enableFtp,omitempty"`
FtpUser string `json:"ftpUser,omitempty"`
FtpPassword string `json:"ftpPassword,omitempty"`
ProxyType string `json:"proxyType,omitempty"`
Port int64 `json:"port,omitempty"`
ProxyProtocol string `json:"proxyProtocol,omitempty"`
ProxyAddress string `json:"proxyAddress,omitempty"`
RuntimeType string `json:"runtimeType,omitempty"`
}
type DeleteWebsiteRequest struct {