Feat: add dex login in apiserver (#3417)

* Feat: add dex login in apiserver

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* resolve comments

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: get dex config from secret

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* Fix: delete cache for dex config

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>

* fix ci

Signed-off-by: FogDong <dongtianxin.tx@alibaba-inc.com>
This commit is contained in:
Tianxin Dong
2022-03-16 14:37:41 +08:00
committed by GitHub
parent c91a7ac273
commit 6c5a40d768
12 changed files with 525 additions and 3 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ unit-test-core:
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... ./apis/... | grep -v apiserver)
go test $(shell go list ./references/... | grep -v apiserver)
unit-test-apiserver:
go test -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
go test -gcflags=all=-l -coverprofile=coverage.txt $(shell go list ./pkg/... ./cmd/... | grep -E 'apiserver|velaql')
# Build vela cli binary
build: fmt vet lint staticcheck vela-cli kubectl-vela
+3
View File
@@ -14,6 +14,7 @@ require (
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869
github.com/briandowns/spinner v1.11.1
github.com/containerd/containerd v1.4.13
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/coreos/prometheus-operator v0.41.1
github.com/crossplane/crossplane-runtime v0.14.1-0.20210722005935-0b469fcc77cd
github.com/davecgh/go-spew v1.1.1
@@ -214,6 +215,7 @@ require (
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
@@ -260,6 +262,7 @@ require (
gopkg.in/gorp.v1 v1.7.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.2.2 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+3
View File
@@ -353,6 +353,7 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc v2.1.0+incompatible h1:sdJrfw8akMnCuUlaZU3tE/uYXFgfqom8DBE9so9EBsM=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
@@ -1359,6 +1360,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v0.0.0-20210510181950-ab96adb96fea/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus-community/prom-label-proxy v0.1.1-0.20200616110844-0fbfa11fa8f3/go.mod h1:XdjyZg7LCbCC5FADHtpgNp6kQ0W9beXVGfmcvndMj5Y=
github.com/prometheus/alertmanager v0.18.0/go.mod h1:WcxHBl40VSPuOaqWae6l6HpnEOVRIycEJ7i9iYkadEE=
@@ -2385,6 +2387,7 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
+13 -2
View File
@@ -20,11 +20,22 @@ func init() {
RegistModel(&SystemInfo{})
}
// LoginType is the type of login
type LoginType string
const (
// LoginTypeDex is the dex login type
LoginTypeDex LoginType = "dex"
// LoginTypeLocal is the local login type
LoginTypeLocal LoginType = "local"
)
// SystemInfo systemInfo model
type SystemInfo struct {
BaseModel
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
InstallID string `json:"installID"`
EnableCollection bool `json:"enableCollection"`
LoginType LoginType `json:"loginType"`
}
// TableName return custom table name
+61
View File
@@ -0,0 +1,61 @@
/*
Copyright 2022 The KubeVela 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 model
import "strings"
func init() {
RegistModel(&User{})
}
// User is the model of user
type User struct {
BaseModel
Name string `json:"name"`
Email string `json:"email"`
Alias string `json:"alias,omitempty"`
Password string `json:"password,omitempty"`
Disabled bool `json:"disabled"`
}
// TableName return custom table name
func (u *User) TableName() string {
return tableNamePrefix + "user"
}
// PrimaryKey return custom primary key
func (u *User) PrimaryKey() string {
return verifyUserValue(u.Name)
}
// Index return custom index
func (u *User) Index() map[string]string {
index := make(map[string]string)
if u.Name != "" {
index["name"] = verifyUserValue(u.Name)
}
if u.Email != "" {
index["email"] = verifyUserValue(u.Email)
}
return index
}
func verifyUserValue(v string) string {
s := strings.ReplaceAll(v, "@", "-")
s = strings.ReplaceAll(s, " ", "-")
return s
}
+22
View File
@@ -1080,3 +1080,25 @@ type ChartVersionListResponse struct {
type SimpleResponse struct {
Status string `json:"status"`
}
// LoginResponse is the response of login request
type LoginResponse struct {
UserInfo DetailUserResponse `json:"userInfo"`
AccessToken string `json:"accessToken,omitempty"`
RefreshToken string `json:"refreshToken,omitempty"`
}
// DetailUserResponse is the detail user info for the response
type DetailUserResponse struct {
Name string `json:"name"`
Alias string `json:"alias,omitempty"`
Email string `json:"email"`
}
// DexConfigResponse is the response of dex config
type DexConfigResponse struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
RedirectURL string `json:"redirectURL"`
Issuer string `json:"issuer"`
}
@@ -0,0 +1,204 @@
/*
Copyright 2022 The KubeVela 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 usecase
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/coreos/go-oidc"
"github.com/emicklei/go-restful/v3"
"golang.org/x/oauth2"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
velatypes "github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/apiserver/clients"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/log"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
apisv1 "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
const (
secretDexConfig = "dex-config"
)
// AuthenticationUsecase is the usecase of authentication
type AuthenticationUsecase interface {
Login(ctx context.Context, req *restful.Request) (*apisv1.LoginResponse, error)
GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error)
}
type authenticationUsecaseImpl struct {
sysUsecase SystemInfoUsecase
ds datastore.DataStore
kubeClient client.Client
}
// NewAuthenticationUsecase new authentication usecase
func NewAuthenticationUsecase(ds datastore.DataStore, sysUsecase SystemInfoUsecase) AuthenticationUsecase {
kubecli, err := clients.GetKubeClient()
if err != nil {
log.Logger.Fatalf("failed to get kube client: %s", err.Error())
}
return &authenticationUsecaseImpl{
sysUsecase: sysUsecase,
ds: ds,
kubeClient: kubecli,
}
}
type authHandler interface {
login(ctx context.Context) (*apisv1.LoginResponse, error)
}
type dexHandlerImpl struct {
token *oauth2.Token
idToken *oidc.IDToken
ds datastore.DataStore
}
func (a *authenticationUsecaseImpl) newDexHandler(ctx context.Context, req *restful.Request) (*dexHandlerImpl, error) {
dexConfig, err := a.GetDexConfig(ctx)
if err != nil {
return nil, err
}
provider, err := oidc.NewProvider(ctx, dexConfig.Issuer)
if err != nil {
return nil, err
}
idTokenVerifier := provider.Verifier(&oidc.Config{ClientID: dexConfig.ClientID})
code := req.HeaderParameter("code")
oauth2Config := &oauth2.Config{
ClientID: dexConfig.ClientID,
ClientSecret: dexConfig.ClientSecret,
Endpoint: provider.Endpoint(),
RedirectURL: dexConfig.RedirectURL,
}
oidcCtx := oidc.ClientContext(ctx, http.DefaultClient)
token, err := oauth2Config.Exchange(oidcCtx, code)
if err != nil {
return nil, err
}
idToken, err := idTokenVerifier.Verify(ctx, token.Extra("id_token").(string))
if err != nil {
return nil, err
}
return &dexHandlerImpl{
token: token,
idToken: idToken,
ds: a.ds,
}, nil
}
func (a *authenticationUsecaseImpl) Login(ctx context.Context, req *restful.Request) (*apisv1.LoginResponse, error) {
var handler authHandler
var err error
sysInfo, err := a.sysUsecase.GetSystemInfo(ctx)
if err != nil {
return nil, err
}
loginType := sysInfo.LoginType
switch loginType {
case model.LoginTypeDex:
handler, err = a.newDexHandler(ctx, req)
if err != nil {
return nil, err
}
case model.LoginTypeLocal:
default:
return nil, bcode.ErrUnsupportedLoginType
}
return handler.login(ctx)
}
func (a *authenticationUsecaseImpl) GetDexConfig(ctx context.Context) (*apisv1.DexConfigResponse, error) {
secret := &v1.Secret{}
if err := a.kubeClient.Get(ctx, types.NamespacedName{
Name: secretDexConfig,
Namespace: velatypes.DefaultKubeVelaNS,
}, secret); err != nil {
log.Logger.Errorf("failed to get dex config: %s", err.Error())
return nil, err
}
var config struct {
Issuer string `json:"issuer"`
StaticClients []struct {
ID string `json:"id"`
Secret string `json:"secret"`
RedirectURIs []string `json:"redirectURIs"`
} `json:"staticClients"`
}
if err := json.Unmarshal(secret.Data[secretDexConfig], &config); err != nil {
log.Logger.Errorf("failed to unmarshal dex config: %s", err.Error())
return nil, err
}
if len(config.StaticClients) < 1 || len(config.StaticClients[0].RedirectURIs) < 1 {
return nil, fmt.Errorf("invalid dex config")
}
return &apisv1.DexConfigResponse{
Issuer: config.Issuer,
ClientID: config.StaticClients[0].ID,
ClientSecret: config.StaticClients[0].Secret,
RedirectURL: config.StaticClients[0].RedirectURIs[0],
}, nil
}
func (d *dexHandlerImpl) login(ctx context.Context) (*apisv1.LoginResponse, error) {
var claims struct {
Email string `json:"email"`
Name string `json:"name"`
}
if err := d.idToken.Claims(&claims); err != nil {
return nil, err
}
user := &model.User{Email: claims.Email}
users, err := d.ds.List(ctx, user, &datastore.ListOptions{})
if err != nil {
return nil, err
}
if len(users) > 0 {
u := users[0].(*model.User)
if u.Name != claims.Name {
u.Name = claims.Name
if err := d.ds.Put(ctx, u); err != nil {
return nil, err
}
}
} else if err := d.ds.Add(ctx, &model.User{
Email: claims.Email,
Name: claims.Name,
}); err != nil {
return nil, err
}
return &apisv1.LoginResponse{
UserInfo: apisv1.DetailUserResponse{
Name: claims.Name,
Email: claims.Email,
},
AccessToken: d.token.AccessToken,
RefreshToken: d.token.RefreshToken,
}, nil
}
@@ -0,0 +1,107 @@
/*
Copyright 2022 The KubeVela 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 usecase
import (
"context"
"encoding/json"
"reflect"
"strconv"
"time"
. "github.com/agiledragon/gomonkey/v2"
"github.com/coreos/go-oidc"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"golang.org/x/oauth2"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/datastore"
"github.com/oam-dev/kubevela/pkg/apiserver/model"
"github.com/oam-dev/kubevela/pkg/oam/util"
)
var _ = Describe("Test authentication usecase functions", func() {
var (
authUsecase *authenticationUsecaseImpl
ds datastore.DataStore
)
BeforeEach(func() {
var err error
ds, err = NewDatastore(datastore.Config{Type: "kubeapi", Database: "auth-test-" + strconv.FormatInt(time.Now().UnixNano(), 10)})
Expect(ds).ToNot(BeNil())
Expect(err).Should(BeNil())
authUsecase = &authenticationUsecaseImpl{kubeClient: k8sClient, ds: ds}
})
It("Test Dex login", func() {
testIDToken := &oidc.IDToken{}
patch := ApplyMethod(reflect.TypeOf(testIDToken), "Claims", func(_ *oidc.IDToken, v interface{}) error {
return json.Unmarshal([]byte(`{"email":"test@test.com","name":"test"}`), v)
})
defer patch.Reset()
dexHandler := dexHandlerImpl{
token: &oauth2.Token{
AccessToken: "access-token",
RefreshToken: "refresh-token",
},
idToken: testIDToken,
ds: ds,
}
resp, err := dexHandler.login(context.Background())
Expect(err).Should(BeNil())
Expect(resp.UserInfo.Email).Should(Equal("test@test.com"))
Expect(resp.UserInfo.Name).Should(Equal("test"))
Expect(resp.AccessToken).Should(Equal("access-token"))
Expect(resp.RefreshToken).Should(Equal("refresh-token"))
user := &model.User{
Name: "test",
}
err = ds.Get(context.Background(), user)
Expect(err).Should(BeNil())
Expect(user.Email).Should(Equal("test@test.com"))
})
It("Test get dex config", func() {
err := k8sClient.Create(context.Background(), &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "vela-system",
},
})
Expect(err).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
err = k8sClient.Create(context.Background(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretDexConfig,
Namespace: "vela-system",
},
StringData: map[string]string{
secretDexConfig: `{"issuer":"https://dex.oam.dev","staticClients":[{"id":"client-id","secret":"client-secret","redirectURIs":["http://localhost:8080/auth/callback"]}]}`,
},
})
Expect(err).Should(BeNil())
config, err := authUsecase.GetDexConfig(context.Background())
Expect(err).Should(BeNil())
Expect(config.Issuer).Should(Equal("https://dex.oam.dev"))
Expect(config.ClientID).Should(Equal("client-id"))
Expect(config.ClientSecret).Should(Equal("client-secret"))
Expect(config.RedirectURL).Should(Equal("http://localhost:8080/auth/callback"))
})
})
@@ -60,6 +60,7 @@ func (u systemInfoUsecaseImpl) GetSystemInfo(ctx context.Context) (*v1.SystemInf
installID := rand.String(16)
info.InstallID = installID
info.EnableCollection = true
info.LoginType = model.LoginTypeLocal
err = u.ds.Add(ctx, info)
if err != nil {
return nil, err
@@ -0,0 +1,20 @@
/*
Copyright 2021 The KubeVela 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 bcode
// ErrUnsupportedLoginType is the error of unsupported login type
var ErrUnsupportedLoginType = NewBcode(400, 12001, "the login type is not supported")
@@ -0,0 +1,86 @@
/*
Copyright 2022 The KubeVela 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 webservice
import (
restfulspec "github.com/emicklei/go-restful-openapi/v2"
"github.com/emicklei/go-restful/v3"
apis "github.com/oam-dev/kubevela/pkg/apiserver/rest/apis/v1"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/usecase"
"github.com/oam-dev/kubevela/pkg/apiserver/rest/utils/bcode"
)
type authenticationWebService struct {
authenticationUsecase usecase.AuthenticationUsecase
}
// NewAuthenticationWebService is the webservice of authentication
func NewAuthenticationWebService(authenticationUsecase usecase.AuthenticationUsecase) WebService {
return &authenticationWebService{
authenticationUsecase: authenticationUsecase,
}
}
func (c *authenticationWebService) GetWebService() *restful.WebService {
ws := new(restful.WebService)
ws.Path(versionPrefix).
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML).
Doc("api for authentication manage")
tags := []string{"authentication"}
ws.Route(ws.GET("/login").To(c.login).
Doc("handle login request").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.LoginResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.LoginResponse{}))
ws.Route(ws.GET("/dexConfig").To(c.getDexConfig).
Doc("get Dex config").
Metadata(restfulspec.KeyOpenAPITags, tags).
Returns(200, "", apis.DexConfigResponse{}).
Returns(400, "", bcode.Bcode{}).
Writes(apis.DexConfigResponse{}))
return ws
}
func (c *authenticationWebService) login(req *restful.Request, res *restful.Response) {
base, err := c.authenticationUsecase.Login(req.Request.Context(), req)
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
func (c *authenticationWebService) getDexConfig(req *restful.Request, res *restful.Response) {
base, err := c.authenticationUsecase.GetDexConfig(req.Request.Context())
if err != nil {
bcode.ReturnError(req, res, err)
return
}
if err := res.WriteEntity(base); err != nil {
bcode.ReturnError(req, res, err)
return
}
}
@@ -74,6 +74,7 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
webhookUsecase := usecase.NewWebhookUsecase(ds, applicationUsecase)
systemInfoUsecase := usecase.NewSystemInfoUsecase(ds)
helmUsecase := usecase.NewHelmUsecase()
authenticationUsecase := usecase.NewAuthenticationUsecase(ds, systemInfoUsecase)
// init for default values
@@ -97,6 +98,9 @@ func Init(ds datastore.DataStore, addonCacheTime time.Duration) {
RegisterWebService(NewVelaQLWebService(velaQLUsecase))
RegisterWebService(NewWebhookWebService(webhookUsecase, applicationUsecase))
// Authentication
RegisterWebService(NewAuthenticationWebService(authenticationUsecase))
RegisterWebService(NewSystemInfoWebService(systemInfoUsecase))
RegisterWebService(NewHelmWebService(helmUsecase))