mirror of https://github.com/artifacthub/hub.git
Allow customizing colors, site name and logo (#1362)
Closes #1259 Signed-off-by: Sergio Castaño Arteaga <tegioz@icloud.com> Signed-off-by: Cintia Sanchez Garcia <cynthiasg@icloud.com> Co-authored-by: Sergio Castaño Arteaga <tegioz@icloud.com> Co-authored-by: Cintia Sanchez Garcia <cynthiasg@icloud.com>
This commit is contained in:
parent
30fc83ace6
commit
012a983b9b
|
|
@ -2,7 +2,7 @@ apiVersion: v2
|
|||
name: artifact-hub
|
||||
description: Artifact Hub is a web-based application that enables finding, installing, and publishing Kubernetes packages.
|
||||
type: application
|
||||
version: 0.19.5
|
||||
version: 0.19.6
|
||||
appVersion: 0.19.0
|
||||
kubeVersion: ">= 1.14.0-0"
|
||||
home: https://artifacthub.io
|
||||
|
|
|
|||
|
|
@ -73,3 +73,14 @@ stringData:
|
|||
xffIndex: {{ .Values.hub.server.xffIndex }}
|
||||
analytics:
|
||||
gaTrackingID: {{ .Values.hub.analytics.gaTrackingID }}
|
||||
theme:
|
||||
colors:
|
||||
primary: {{ .Values.hub.theme.colors.primary }}
|
||||
secondary: {{ .Values.hub.theme.colors.secondary }}
|
||||
images:
|
||||
appleTouchIcon192: {{ .Values.hub.theme.images.appleTouchIcon192 }}
|
||||
appleTouchIcon512: {{ .Values.hub.theme.images.appleTouchIcon512 }}
|
||||
openGraphImage: {{ .Values.hub.theme.images.openGraphImage }}
|
||||
shortcutIcon: {{ .Values.hub.theme.images.shortcutIcon }}
|
||||
websiteLogo: {{ .Values.hub.theme.images.websiteLogo }}
|
||||
siteName: {{ .Values.hub.theme.siteName }}
|
||||
|
|
|
|||
|
|
@ -507,9 +507,77 @@
|
|||
}
|
||||
},
|
||||
"required": ["port", "type"]
|
||||
},
|
||||
"theme": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colors": {
|
||||
"title": "Colors used in the website",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"primary": {
|
||||
"title": "Primary color",
|
||||
"description": "Primary color used in the website. For an optimal experience, it's better to use colors that play well with white fonts.",
|
||||
"type": "string",
|
||||
"default": "#417598"
|
||||
},
|
||||
"secondary": {
|
||||
"title": "Secondary color",
|
||||
"description": "Secondary color used in the website, usually a darker version of the primary color. For an optimal experience, it's better to use colors that play well with white fonts.",
|
||||
"type": "string",
|
||||
"default": "#2D4857"
|
||||
}
|
||||
},
|
||||
"required": ["primary", "secondary"]
|
||||
},
|
||||
"images": {
|
||||
"title": "Images used in the website",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"appleTouchIcon192": {
|
||||
"title": "Apple touch icon (192x192)",
|
||||
"description": "URL of the image used for the Apple touch icon (192x192).",
|
||||
"type": "string",
|
||||
"default": "/static/media/logo192_v2.png"
|
||||
},
|
||||
"appleTouchIcon512": {
|
||||
"title": "Apple touch icon (512x512)",
|
||||
"description": "URL of the image used for the Apple touch icon (512x512).",
|
||||
"type": "string",
|
||||
"default": "/static/media/logo512_v2.png"
|
||||
},
|
||||
"openGraphImage": {
|
||||
"title": "Open Graph image",
|
||||
"description": "URL of the image used in the og:image tag. This image is displayed when an Artifact Hub link is shared in Twitter or Slack, for example. The URL must use `https`.",
|
||||
"type": "string",
|
||||
"default": "/static/media/artifactHub_v2.png"
|
||||
},
|
||||
"shortcutIcon": {
|
||||
"title": "Shortcut icon",
|
||||
"description": "URL of the image used for the shortcut icon (also known as favicon).",
|
||||
"type": "string",
|
||||
"default": "/static/media/logo_v2.png"
|
||||
},
|
||||
"websiteLogo": {
|
||||
"title": "Website logo",
|
||||
"description": "URL of the logo used in the website header. For an optimal experience, it's better to use a white logo with transparent background, with no margin around it. It'll be displayed using a maximum height of 20px and a maximum width of 185px.",
|
||||
"type": "string",
|
||||
"default": "/static/media/logo/artifacthub-brand-white.svg"
|
||||
}
|
||||
},
|
||||
"required": ["appleTouchIcon192", "appleTouchIcon512", "openGraphImage", "shortcutIcon", "websiteLogo"]
|
||||
},
|
||||
"siteName": {
|
||||
"title": "Name of the site",
|
||||
"description": "This name is displayed in some places in the website and email templates. When a different value than the default one (Artifact Hub) is provided, the site enters `white label` mode. In this mode, some sections of the website are displayed in a more generic way, omitting certain parts that are unique to Artifact Hub.",
|
||||
"type": "string",
|
||||
"default": "Artifact Hub"
|
||||
}
|
||||
},
|
||||
"required": ["colors", "images", "siteName"]
|
||||
}
|
||||
},
|
||||
"required": ["ingress", "service", "deploy", "server"]
|
||||
"required": ["ingress", "service", "deploy", "server", "theme"]
|
||||
},
|
||||
"imagePullSecrets": {
|
||||
"type": "array",
|
||||
|
|
|
|||
|
|
@ -110,6 +110,17 @@ hub:
|
|||
xffIndex: 0
|
||||
analytics:
|
||||
gaTrackingID: ""
|
||||
theme:
|
||||
colors:
|
||||
primary: "#417598"
|
||||
secondary: "#2D4857"
|
||||
images:
|
||||
appleTouchIcon192: "/static/media/logo192_v2.png"
|
||||
appleTouchIcon512: "/static/media/logo512_v2.png"
|
||||
openGraphImage: "/static/media/artifactHub_v2.png"
|
||||
shortcutIcon: "/static/media/logo_v2.png"
|
||||
websiteLogo: "/static/media/logo/artifacthub-brand-white.svg"
|
||||
siteName: "Artifact hub"
|
||||
|
||||
scanner:
|
||||
cronjob:
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ func main() {
|
|||
// Setup and launch http server
|
||||
ctx, stop := context.WithCancel(context.Background())
|
||||
hSvc := &handlers.Services{
|
||||
OrganizationManager: org.NewManager(db, es, az),
|
||||
UserManager: user.NewManager(db, es),
|
||||
OrganizationManager: org.NewManager(cfg, db, es, az),
|
||||
UserManager: user.NewManager(cfg, db, es),
|
||||
RepositoryManager: repo.NewManager(cfg, db, az, hc),
|
||||
PackageManager: pkg.NewManager(db),
|
||||
SubscriptionManager: subscription.NewManager(db),
|
||||
|
|
@ -114,6 +114,7 @@ func main() {
|
|||
|
||||
// Setup and launch notifications dispatcher
|
||||
nSvc := ¬ification.Services{
|
||||
Cfg: cfg,
|
||||
DB: db,
|
||||
ES: es,
|
||||
NotificationManager: notification.NewManager(),
|
||||
|
|
@ -122,7 +123,7 @@ func main() {
|
|||
PackageManager: pkg.NewManager(db),
|
||||
HTTPClient: hc,
|
||||
}
|
||||
notificationsDispatcher := notification.NewDispatcher(cfg, nSvc)
|
||||
notificationsDispatcher := notification.NewDispatcher(nSvc)
|
||||
wg.Add(1)
|
||||
go notificationsDispatcher.Run(ctx, &wg)
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@
|
|||
}
|
||||
|
||||
.line {
|
||||
border-top: 7px solid #417598;
|
||||
border-top: 7px solid {{ .Theme.PrimaryColor }};
|
||||
}
|
||||
|
||||
.line-danger {
|
||||
|
|
@ -113,17 +113,17 @@
|
|||
}
|
||||
|
||||
.AHlink {
|
||||
color: #2d4857;
|
||||
color: {{ .Theme.SecondaryColor }};
|
||||
}
|
||||
|
||||
.AHbtn {
|
||||
background-color: #2d4857;
|
||||
border: solid 1px #2d4857;
|
||||
background-color: {{ .Theme.SecondaryColor }};
|
||||
border: solid 1px {{ .Theme.SecondaryColor }};
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.hr {
|
||||
border-top: 1px solid #417598;
|
||||
border-top: 1px solid {{ .Theme.PrimaryColor }};
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ func Setup(ctx context.Context, cfg *viper.Viper, svc *Services) (*Handlers, err
|
|||
|
||||
Organizations: org.NewHandlers(svc.OrganizationManager, svc.Authorizer, cfg),
|
||||
Users: userHandlers,
|
||||
Repositories: repo.NewHandlers(svc.RepositoryManager),
|
||||
Repositories: repo.NewHandlers(cfg, svc.RepositoryManager),
|
||||
Packages: pkg.NewHandlers(svc.PackageManager, svc.RepositoryManager, cfg, svc.HTTPClient),
|
||||
Subscriptions: subscription.NewHandlers(svc.SubscriptionManager),
|
||||
Webhooks: webhook.NewHandlers(svc.WebhookManager, svc.HTTPClient),
|
||||
|
|
@ -142,7 +142,7 @@ func (h *Handlers) setupRouter() {
|
|||
if h.cfg.GetBool("server.basicAuth.enabled") {
|
||||
r.Use(h.Users.BasicAuth)
|
||||
}
|
||||
r.NotFound(h.Static.ServeIndex)
|
||||
r.NotFound(h.Static.Index)
|
||||
|
||||
// API
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
|
|
@ -384,8 +384,8 @@ func (h *Handlers) setupRouter() {
|
|||
// Index special entry points
|
||||
r.Route("/packages", func(r chi.Router) {
|
||||
r.Route("/{^helm$|^falco$|^opa$|^olm|^tbaction|^krew|^helm-plugin|^tekton-task|^keda-scaler|^coredns$}/{repoName}/{packageName}", func(r chi.Router) {
|
||||
r.With(h.Packages.InjectIndexMeta).Get("/{version}", h.Static.ServeIndex)
|
||||
r.With(h.Packages.InjectIndexMeta).Get("/", h.Static.ServeIndex)
|
||||
r.With(h.Packages.InjectIndexMeta).Get("/{version}", h.Static.Index)
|
||||
r.With(h.Packages.InjectIndexMeta).Get("/", h.Static.Index)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -408,7 +408,7 @@ func (h *Handlers) setupRouter() {
|
|||
w.Header().Set("Cache-Control", helpers.BuildCacheControlHeader(5*time.Minute))
|
||||
http.ServeFile(w, r, path.Join(widgetBuildPath, "static/js/artifacthub-widget.js"))
|
||||
})
|
||||
r.Get("/", h.Static.ServeIndex)
|
||||
r.Get("/", h.Static.Index)
|
||||
|
||||
h.Router = r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ package repo
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/artifacthub/hub/internal/handlers/helpers"
|
||||
"github.com/artifacthub/hub/internal/hub"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -18,13 +20,15 @@ const (
|
|||
// Handlers represents a group of http handlers in charge of handling
|
||||
// repositories operations.
|
||||
type Handlers struct {
|
||||
cfg *viper.Viper
|
||||
repoManager hub.RepositoryManager
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
// NewHandlers creates a new Handlers instance.
|
||||
func NewHandlers(repoManager hub.RepositoryManager) *Handlers {
|
||||
func NewHandlers(cfg *viper.Viper, repoManager hub.RepositoryManager) *Handlers {
|
||||
return &Handlers{
|
||||
cfg: cfg,
|
||||
repoManager: repoManager,
|
||||
logger: log.With().Str("handlers", "repo").Logger(),
|
||||
}
|
||||
|
|
@ -51,9 +55,9 @@ func (h *Handlers) Add(w http.ResponseWriter, r *http.Request) {
|
|||
// repository badge.
|
||||
func (h *Handlers) Badge(w http.ResponseWriter, r *http.Request) {
|
||||
data := map[string]interface{}{
|
||||
"color": "2d4857",
|
||||
"label": "Artifact Hub",
|
||||
"labelColor": "417598",
|
||||
"color": strings.TrimPrefix(h.cfg.GetString("theme.colors.secondary"), "#"),
|
||||
"label": h.cfg.GetString("theme.siteName"),
|
||||
"labelColor": strings.TrimPrefix(h.cfg.GetString("theme.colors.primary"), "#"),
|
||||
"logoSvg": logoSVG,
|
||||
"logoWidth": 18,
|
||||
"message": chi.URLParam(r, "repoName"),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/artifacthub/hub/internal/tests"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
|
@ -156,7 +157,7 @@ func TestBadge(t *testing.T) {
|
|||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, "application/json", h.Get("Content-Type"))
|
||||
assert.Equal(t, helpers.BuildCacheControlHeader(helpers.DefaultAPICacheMaxAge), h.Get("Cache-Control"))
|
||||
assert.Equal(t, []byte(`{"color":"2d4857","label":"Artifact Hub","labelColor":"417598","logoSvg":"\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-hexagon\"\u003e\u003cpath d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"\u003e\u003c/path\u003e\u003c/svg\u003e","logoWidth":18,"message":"artifact-hub","schemaVersion":1,"style":"flat"}`), data)
|
||||
assert.Equal(t, []byte(`{"color":"2D4857","label":"Artifact Hub","labelColor":"417598","logoSvg":"\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#ffffff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-hexagon\"\u003e\u003cpath d=\"M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z\"\u003e\u003c/path\u003e\u003c/svg\u003e","logoWidth":18,"message":"artifact-hub","schemaVersion":1,"style":"flat"}`), data)
|
||||
hw.rm.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
|
|
@ -763,15 +764,21 @@ func TestUpdate(t *testing.T) {
|
|||
}
|
||||
|
||||
type handlersWrapper struct {
|
||||
rm *repo.ManagerMock
|
||||
h *Handlers
|
||||
cfg *viper.Viper
|
||||
rm *repo.ManagerMock
|
||||
h *Handlers
|
||||
}
|
||||
|
||||
func newHandlersWrapper() *handlersWrapper {
|
||||
cfg := viper.New()
|
||||
cfg.Set("theme.colors.primary", "#417598")
|
||||
cfg.Set("theme.colors.secondary", "#2D4857")
|
||||
cfg.Set("theme.siteName", "Artifact Hub")
|
||||
rm := &repo.ManagerMock{}
|
||||
|
||||
return &handlersWrapper{
|
||||
rm: rm,
|
||||
h: NewHandlers(rm),
|
||||
cfg: cfg,
|
||||
rm: rm,
|
||||
h: NewHandlers(cfg, rm),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,16 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
cspPolicy = `
|
||||
default-src 'none';
|
||||
connect-src 'self' https://play.openpolicyagent.org https://www.google-analytics.com https://kubernetesjsonschema.dev;
|
||||
font-src 'self';
|
||||
img-src 'self' data: https:;
|
||||
manifest-src 'self';
|
||||
script-src 'self' https://www.google-analytics.com;
|
||||
style-src 'self' 'unsafe-inline'
|
||||
`
|
||||
|
||||
indexCacheMaxAge = 5 * time.Minute
|
||||
|
||||
// DocsCacheMaxAge is the cache max age used when serving the docs.
|
||||
|
|
@ -113,6 +123,50 @@ func (h *Handlers) Image(w http.ResponseWriter, r *http.Request) {
|
|||
_, _ = w.Write(data)
|
||||
}
|
||||
|
||||
// Index is an http handler that serves the index.html file.
|
||||
func (h *Handlers) Index(w http.ResponseWriter, r *http.Request) {
|
||||
// Set headers
|
||||
w.Header().Set("Cache-Control", helpers.BuildCacheControlHeader(indexCacheMaxAge))
|
||||
w.Header().Set("Content-Security-Policy", cspPolicy)
|
||||
|
||||
// Execute index template
|
||||
title, _ := r.Context().Value(hub.IndexMetaTitleKey).(string)
|
||||
if title == "" {
|
||||
title = h.cfg.GetString("theme.siteName")
|
||||
}
|
||||
description, _ := r.Context().Value(hub.IndexMetaDescriptionKey).(string)
|
||||
if description == "" {
|
||||
description = "Find, install and publish Kubernetes packages"
|
||||
}
|
||||
openGraphImage := h.cfg.GetString("theme.images.openGraphImage")
|
||||
if !strings.HasPrefix(openGraphImage, "http") {
|
||||
openGraphImage = h.cfg.GetString("server.baseURL") + openGraphImage
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"allowPrivateRepositories": h.cfg.GetBool("server.allowPrivateRepositories"),
|
||||
"appleTouchIcon192": h.cfg.GetString("theme.images.appleTouchIcon192"),
|
||||
"appleTouchIcon512": h.cfg.GetString("theme.images.appleTouchIcon512"),
|
||||
"description": description,
|
||||
"gaTrackingID": h.cfg.GetString("analytics.gaTrackingID"),
|
||||
"githubAuth": h.cfg.IsSet("server.oauth.github"),
|
||||
"googleAuth": h.cfg.IsSet("server.oauth.google"),
|
||||
"motd": h.cfg.GetString("server.motd"),
|
||||
"motdSeverity": h.cfg.GetString("server.motdSeverity"),
|
||||
"oidcAuth": h.cfg.IsSet("server.oauth.oidc"),
|
||||
"openGraphImage": openGraphImage,
|
||||
"primaryColor": h.cfg.GetString("theme.colors.primary"),
|
||||
"secondaryColor": h.cfg.GetString("theme.colors.secondary"),
|
||||
"shortcutIcon": h.cfg.GetString("theme.images.shortcutIcon"),
|
||||
"siteName": h.cfg.GetString("theme.siteName"),
|
||||
"title": title,
|
||||
"websiteLogo": h.cfg.GetString("theme.images.websiteLogo"),
|
||||
}
|
||||
if err := h.indexTmpl.Execute(w, data); err != nil {
|
||||
h.logger.Error().Err(err).Msg("error executing index template")
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveImage is an http handler that stores the provided image returning its id.
|
||||
func (h *Handlers) SaveImage(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
|
|
@ -131,45 +185,6 @@ func (h *Handlers) SaveImage(w http.ResponseWriter, r *http.Request) {
|
|||
helpers.RenderJSON(w, dataJSON, 0, http.StatusOK)
|
||||
}
|
||||
|
||||
// ServeIndex is an http handler that serves the index.html file.
|
||||
func (h *Handlers) ServeIndex(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Cache-Control", helpers.BuildCacheControlHeader(indexCacheMaxAge))
|
||||
w.Header().Set("Content-Security-Policy", `
|
||||
default-src 'none';
|
||||
connect-src 'self' https://play.openpolicyagent.org https://www.google-analytics.com https://kubernetesjsonschema.dev;
|
||||
font-src 'self';
|
||||
img-src 'self' data: https:;
|
||||
manifest-src 'self';
|
||||
script-src 'self' https://www.google-analytics.com;
|
||||
style-src 'self' 'unsafe-inline'
|
||||
`)
|
||||
|
||||
// Execute index template
|
||||
title, _ := r.Context().Value(hub.IndexMetaTitleKey).(string)
|
||||
if title == "" {
|
||||
title = "Artifact Hub"
|
||||
}
|
||||
description, _ := r.Context().Value(hub.IndexMetaDescriptionKey).(string)
|
||||
if description == "" {
|
||||
description = "Find, install and publish Kubernetes packages"
|
||||
}
|
||||
data := map[string]interface{}{
|
||||
"baseURL": h.cfg.GetString("server.baseURL"),
|
||||
"title": title,
|
||||
"description": description,
|
||||
"gaTrackingID": h.cfg.GetString("analytics.gaTrackingID"),
|
||||
"allowPrivateRepositories": h.cfg.GetBool("server.allowPrivateRepositories"),
|
||||
"githubAuth": h.cfg.IsSet("server.oauth.github"),
|
||||
"googleAuth": h.cfg.IsSet("server.oauth.google"),
|
||||
"oidcAuth": h.cfg.IsSet("server.oauth.oidc"),
|
||||
"motd": h.cfg.GetString("server.motd"),
|
||||
"motdSeverity": h.cfg.GetString("server.motdSeverity"),
|
||||
}
|
||||
if err := h.indexTmpl.Execute(w, data); err != nil {
|
||||
h.logger.Error().Err(err).Msg("Error executing index template")
|
||||
}
|
||||
}
|
||||
|
||||
// FileServer sets up a http.FileServer handler to serve static files.
|
||||
func FileServer(r chi.Router, public, static string, cacheMaxAge time.Duration) {
|
||||
if strings.ContainsAny(public, "{}*") {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,23 @@ func TestImage(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
hw := newHandlersWrapper()
|
||||
hw.h.Index(w, r)
|
||||
resp := w.Result()
|
||||
defer resp.Body.Close()
|
||||
h := resp.Header
|
||||
data, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, helpers.BuildCacheControlHeader(indexCacheMaxAge), h.Get("Cache-Control"))
|
||||
assert.Equal(t, []byte("title:Artifact Hub\ndescription:Find, install and publish Kubernetes packages\ngaTrackingID:1234\n"), data)
|
||||
}
|
||||
|
||||
func TestSaveImage(t *testing.T) {
|
||||
fakeSaveImageError := errors.New("fake save image error")
|
||||
|
||||
|
|
@ -143,23 +160,6 @@ func TestSaveImage(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestServeIndex(t *testing.T) {
|
||||
t.Parallel()
|
||||
w := httptest.NewRecorder()
|
||||
r, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
hw := newHandlersWrapper()
|
||||
hw.h.ServeIndex(w, r)
|
||||
resp := w.Result()
|
||||
defer resp.Body.Close()
|
||||
h := resp.Header
|
||||
data, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
assert.Equal(t, helpers.BuildCacheControlHeader(indexCacheMaxAge), h.Get("Cache-Control"))
|
||||
assert.Equal(t, []byte("title:Artifact Hub\ndescription:Find, install and publish Kubernetes packages\ngaTrackingID:1234\n"), data)
|
||||
}
|
||||
|
||||
func TestServeStaticFile(t *testing.T) {
|
||||
hw := newHandlersWrapper()
|
||||
r := chi.NewRouter()
|
||||
|
|
@ -201,6 +201,7 @@ func newHandlersWrapper() *handlersWrapper {
|
|||
cfg := viper.New()
|
||||
cfg.Set("server.webBuildPath", "testdata")
|
||||
cfg.Set("analytics.gaTrackingID", "1234")
|
||||
cfg.Set("theme.siteName", "Artifact Hub")
|
||||
is := &img.StoreMock{}
|
||||
|
||||
return &handlersWrapper{
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ func (h *Handlers) ApproveSession(w http.ResponseWriter, r *http.Request) {
|
|||
func (h *Handlers) BasicAuth(next http.Handler) http.Handler {
|
||||
validUser := []byte(h.cfg.GetString("server.basicAuth.username"))
|
||||
validPass := []byte(h.cfg.GetString("server.basicAuth.password"))
|
||||
realm := "Artifact Hub"
|
||||
realm := h.cfg.GetString("theme.siteName")
|
||||
|
||||
areCredentialsValid := func(user, pass []byte) bool {
|
||||
if subtle.ConstantTimeCompare(user, validUser) != 1 {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type PackageNotificationTemplateData struct {
|
|||
BaseURL string `json:"base_url"`
|
||||
Event map[string]interface{} `json:"event"`
|
||||
Package map[string]interface{} `json:"package"`
|
||||
Theme map[string]string `json:"theme"`
|
||||
}
|
||||
|
||||
// RepositoryNotificationTemplateData represents some details of a notification
|
||||
|
|
@ -42,4 +43,5 @@ type RepositoryNotificationTemplateData struct {
|
|||
BaseURL string `json:"base_url"`
|
||||
Event map[string]interface{} `json:"event"`
|
||||
Repository map[string]interface{} `json:"repository"`
|
||||
Theme map[string]string `json:"theme"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ var (
|
|||
// Services is a wrapper around several internal services used to handle
|
||||
// notifications deliveries.
|
||||
type Services struct {
|
||||
Cfg *viper.Viper
|
||||
DB hub.DB
|
||||
ES hub.EmailSender
|
||||
NotificationManager hub.NotificationManager
|
||||
|
|
@ -66,7 +67,7 @@ type Dispatcher struct {
|
|||
}
|
||||
|
||||
// NewDispatcher creates a new Dispatcher instance.
|
||||
func NewDispatcher(cfg *viper.Viper, svc *Services, opts ...func(d *Dispatcher)) *Dispatcher {
|
||||
func NewDispatcher(svc *Services, opts ...func(d *Dispatcher)) *Dispatcher {
|
||||
// Setup dispatcher
|
||||
d := &Dispatcher{
|
||||
numWorkers: defaultNumWorkers,
|
||||
|
|
@ -86,7 +87,7 @@ func NewDispatcher(cfg *viper.Viper, svc *Services, opts ...func(d *Dispatcher))
|
|||
|
||||
// Setup and launch workers
|
||||
c := cache.New(cacheDefaultExpiration, cacheCleanupInterval)
|
||||
baseURL := cfg.GetString("server.baseURL")
|
||||
baseURL := svc.Cfg.GetString("server.baseURL")
|
||||
d.workers = make([]*Worker, 0, d.numWorkers)
|
||||
for i := 0; i < d.numWorkers; i++ {
|
||||
d.workers = append(d.workers, NewWorker(svc, c, baseURL, tmpl))
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ func TestDispatcher(t *testing.T) {
|
|||
// Setup dispatcher
|
||||
cfg := viper.New()
|
||||
cfg.Set("server.baseURL", "http://localhost:8000")
|
||||
d := NewDispatcher(cfg, &Services{}, WithNumWorkers(0))
|
||||
d := NewDispatcher(&Services{Cfg: cfg}, WithNumWorkers(0))
|
||||
|
||||
// Run it
|
||||
ctx, stopDispatcher := context.WithCancel(context.Background())
|
||||
|
|
|
|||
|
|
@ -121,12 +121,12 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 10px; text-align: center;">
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't subscribe to Artifact Hub notifications for {{ .Package.Name }} package? You can unsubscribe <a href="{{ .BaseURL }}/control-panel/settings/subscriptions" target="_blank" class="text-muted" style="text-decoration: underline;">here</a>.</p>
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't subscribe to {{ .Theme.SiteName }} notifications for {{ .Package.Name }} package? You can unsubscribe <a href="{{ .BaseURL }}/control-panel/settings/subscriptions" target="_blank" class="text-muted" style="text-decoration: underline;">here</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@
|
|||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
|
||||
We encountered some errors while scanning the packages in repository <strong>{{ .Repository.Name }}</strong> for security vulnerabilities.
|
||||
</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">
|
||||
If you find something in them that doesn't make sense, or there is anything you need help with, please file an issue <a href="https://github.com/artifacthub/hub/issues" class="AHlink" style="text-decoration: none;">here</a>.
|
||||
</p>
|
||||
<h4 style="color: #921e12; font-family: sans-serif; margin: 0; Margin-bottom: 15px;">Errors log</h4>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="Margin-bottom: 30px; border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box; background: #1D1F21; border-radius: 3px;">
|
||||
<tbody>
|
||||
|
|
@ -70,7 +67,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<h4 class="subtitle" style="font-family: sans-serif; margin: 0; Margin-bottom: 15px;">{{ .Package.repository.publisher }} </h4>
|
||||
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px; text-align: left;">
|
||||
We found one or more potential security vulnerabilities in the images of the <b>{{ .Package.Name }}</b> package version <b>{{ .Package.Version }}</b>. For more information, please see the package's security report in Artifact Hub.
|
||||
We found one or more potential security vulnerabilities in the images of the <b>{{ .Package.Name }}</b> package version <b>{{ .Package.Version }}</b>. For more information, please see the package's security report in {{ .Theme.SiteName }}.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<p class="text-muted" style="font-size: 11px; text-decoration: none; Margin-bottom: 30px;">Or you can copy-paste this link: <span class="copy-link">{{ .Package.URL }}?modal=security-report</span></p>
|
||||
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px; text-align: left;">
|
||||
Please note that security alerts only consider vulnerabilities of <b>high</b> and <b>critical</b> severity. Any time a new potential security vulnerability is detected you’ll be notified again.
|
||||
Please note that security alerts only consider vulnerabilities of <b>high</b> and <b>critical</b> severity. Any time a new potential security vulnerability is detected you'll be notified again.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -66,12 +66,12 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 10px; text-align: center;">
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't subscribe to Artifact Hub notifications for {{ .Package.Name }} package? You can unsubscribe <a href="{{ .BaseURL }}/control-panel/settings/subscriptions" target="_blank" class="text-muted" style="text-decoration: underline;">here</a>.</p>
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't subscribe to {{ .Theme.SiteName }} notifications for {{ .Package.Name }} package? You can unsubscribe <a href="{{ .BaseURL }}/control-panel/settings/subscriptions" target="_blank" class="text-muted" style="text-decoration: underline;">here</a>.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,7 @@
|
|||
</p>
|
||||
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
|
||||
Some or all of these errors may be just warnings, and it's possible that your packages have been still indexed properly. However, it'd be great if you can take a look at them just in case there is something missing or failing in your repository that may affect how your content is displayed on Artifact Hub.
|
||||
</p>
|
||||
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">
|
||||
If you find something in them that doesn't make sense, or there is anything you need help with, please file an issue <a href="https://github.com/artifacthub/hub/issues" style="color: #2d4857; text-decoration: none;">here</a>.
|
||||
Some or all of these errors may be just warnings, and it's possible that your packages have been still indexed properly. However, it'd be great if you can take a look at them just in case there is something missing or failing in your repository that may affect how your content is displayed on {{ .Theme.SiteName }}.
|
||||
</p>
|
||||
|
||||
<h4 style="color: #921e12; font-family: sans-serif; margin: 0; Margin-bottom: 15px;">Errors log</h4>
|
||||
|
|
@ -50,7 +46,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="width: 100%; border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top;"><div style="text-align: center;"> <a href="{{ .BaseURL }}/control-panel/repositories?modal=tracking&user-alias={{ .Repository.UserAlias }}&org-name={{ .Repository.OrganizationName }}&repo-name={{ .Repository.Name }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px;">View in Artifact Hub</a> </div></td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top;"><div style="text-align: center;"> <a href="{{ .BaseURL }}/control-panel/repositories?modal=tracking&user-alias={{ .Repository.UserAlias }}&org-name={{ .Repository.OrganizationName }}&repo-name={{ .Repository.Name }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px;">View in {{ .Theme.SiteName }}</a> </div></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -82,7 +78,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<a href="{{ .BaseURL }}" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -308,6 +308,11 @@ func (w *Worker) preparePkgNotificationTemplateData(
|
|||
"Publisher": publisher,
|
||||
},
|
||||
},
|
||||
Theme: map[string]string{
|
||||
"PrimaryColor": w.svc.Cfg.GetString("theme.colors.primary"),
|
||||
"SecondaryColor": w.svc.Cfg.GetString("theme.colors.secondary"),
|
||||
"SiteName": w.svc.Cfg.GetString("theme.siteName"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -357,6 +362,11 @@ func (w *Worker) prepareRepoNotificationTemplateData(
|
|||
"LastScanningErrors": strings.Split(r.LastScanningErrors, "\n"),
|
||||
"LastTrackingErrors": strings.Split(r.LastTrackingErrors, "\n"),
|
||||
},
|
||||
Theme: map[string]string{
|
||||
"PrimaryColor": w.svc.Cfg.GetString("theme.colors.primary"),
|
||||
"SecondaryColor": w.svc.Cfg.GetString("theme.colors.secondary"),
|
||||
"SiteName": w.svc.Cfg.GetString("theme.siteName"),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/artifacthub/hub/internal/subscription"
|
||||
"github.com/artifacthub/hub/internal/tests"
|
||||
"github.com/patrickmn/go-cache"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
|
@ -349,6 +350,7 @@ type servicesWrapper struct {
|
|||
ctx context.Context
|
||||
stopWorker context.CancelFunc
|
||||
wg *sync.WaitGroup
|
||||
cfg *viper.Viper
|
||||
db *tests.DBMock
|
||||
tx *tests.TXMock
|
||||
es *email.SenderMock
|
||||
|
|
@ -367,6 +369,7 @@ func newServicesWrapper() *servicesWrapper {
|
|||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
cfg := viper.New()
|
||||
db := &tests.DBMock{}
|
||||
tx := &tests.TXMock{}
|
||||
es := &email.SenderMock{}
|
||||
|
|
@ -381,6 +384,7 @@ func newServicesWrapper() *servicesWrapper {
|
|||
ctx: ctx,
|
||||
stopWorker: stopWorker,
|
||||
wg: &wg,
|
||||
cfg: cfg,
|
||||
db: db,
|
||||
tx: tx,
|
||||
es: es,
|
||||
|
|
@ -391,6 +395,7 @@ func newServicesWrapper() *servicesWrapper {
|
|||
cache: cache,
|
||||
hc: hc,
|
||||
svc: &Services{
|
||||
Cfg: cfg,
|
||||
DB: db,
|
||||
ES: es,
|
||||
NotificationManager: nm,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/artifacthub/hub/internal/util"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
"github.com/satori/uuid"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -52,6 +53,7 @@ var organizationNameRE = regexp.MustCompile(`^[a-z0-9-]+$`)
|
|||
|
||||
// Manager provides an API to manage organizations.
|
||||
type Manager struct {
|
||||
cfg *viper.Viper
|
||||
db hub.DB
|
||||
es hub.EmailSender
|
||||
az hub.Authorizer
|
||||
|
|
@ -59,11 +61,12 @@ type Manager struct {
|
|||
}
|
||||
|
||||
// NewManager creates a new Manager instance.
|
||||
func NewManager(db hub.DB, es hub.EmailSender, az hub.Authorizer) *Manager {
|
||||
func NewManager(cfg *viper.Viper, db hub.DB, es hub.EmailSender, az hub.Authorizer) *Manager {
|
||||
return &Manager{
|
||||
db: db,
|
||||
es: es,
|
||||
az: az,
|
||||
cfg: cfg,
|
||||
db: db,
|
||||
es: es,
|
||||
az: az,
|
||||
tmpl: map[templateID]*template.Template{
|
||||
invitationEmail: template.Must(template.New("").Parse(email.BaseTmpl + invitationEmailTmpl)),
|
||||
},
|
||||
|
|
@ -131,9 +134,14 @@ func (m *Manager) AddMember(ctx context.Context, orgName, userAlias, baseURL str
|
|||
if err := m.db.QueryRow(ctx, getUserEmailDBQ, userAlias).Scan(&userEmail); err != nil {
|
||||
return err
|
||||
}
|
||||
templateData := map[string]string{
|
||||
"link": fmt.Sprintf("%s/accept-invitation?org=%s", baseURL, orgName),
|
||||
"orgName": orgName,
|
||||
templateData := map[string]interface{}{
|
||||
"Link": fmt.Sprintf("%s/accept-invitation?org=%s", baseURL, orgName),
|
||||
"OrgName": orgName,
|
||||
"Theme": map[string]string{
|
||||
"PrimaryColor": m.cfg.GetString("theme.colors.primary"),
|
||||
"SecondaryColor": m.cfg.GetString("theme.colors.secondary"),
|
||||
"SiteName": m.cfg.GetString("theme.siteName"),
|
||||
},
|
||||
}
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[invitationEmail].Execute(&emailBody, templateData); err != nil {
|
||||
|
|
|
|||
|
|
@ -11,16 +11,19 @@ import (
|
|||
"github.com/artifacthub/hub/internal/hub"
|
||||
"github.com/artifacthub/hub/internal/tests"
|
||||
"github.com/artifacthub/hub/internal/util"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
var cfg = viper.New()
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), hub.UserIDKey, "userID")
|
||||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.Add(context.Background(), &hub.Organization{})
|
||||
})
|
||||
|
|
@ -61,7 +64,7 @@ func TestAdd(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.Add(ctx, tc.org)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -73,7 +76,7 @@ func TestAdd(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, addOrgDBQ, "userID", mock.Anything).Return(nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.Add(ctx, &hub.Organization{Name: "org1"})
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -84,7 +87,7 @@ func TestAdd(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, addOrgDBQ, "userID", mock.Anything).Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.Add(ctx, &hub.Organization{Name: "org1"})
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -97,7 +100,7 @@ func TestAddMember(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.AddMember(context.Background(), "orgName", "userAlias", "")
|
||||
})
|
||||
|
|
@ -139,7 +142,7 @@ func TestAddMember(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.AddMember(ctx, tc.orgName, tc.userAlias, tc.baseURL)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -155,7 +158,7 @@ func TestAddMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.AddOrganizationMember,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(nil, nil, az)
|
||||
m := NewManager(cfg, nil, nil, az)
|
||||
|
||||
err := m.AddMember(ctx, "orgName", "userAlias", "http://baseurl.com")
|
||||
assert.Equal(t, tests.ErrFake, err)
|
||||
|
|
@ -191,7 +194,7 @@ func TestAddMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.AddOrganizationMember,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, es, az)
|
||||
m := NewManager(cfg, db, es, az)
|
||||
|
||||
err := m.AddMember(ctx, "orgName", "userAlias", "http://baseurl.com")
|
||||
assert.Equal(t, tc.emailSenderResponse, err)
|
||||
|
|
@ -228,7 +231,7 @@ func TestAddMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.AddOrganizationMember,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.AddMember(ctx, "orgName", "userAlias", "http://baseurl.com")
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
|
@ -263,7 +266,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
_, err := m.CheckAvailability(context.Background(), tc.resourceKind, tc.value)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -290,7 +293,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
tc.dbQuery = fmt.Sprintf("select not exists (%s)", tc.dbQuery)
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, tc.dbQuery, "value").Return(tc.available, nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
available, err := m.CheckAvailability(ctx, tc.resourceKind, "value")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -305,7 +308,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
dbQuery := fmt.Sprintf(`select not exists (%s)`, checkOrgNameAvailDBQ)
|
||||
db.On("QueryRow", ctx, dbQuery, "value").Return(false, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
available, err := m.CheckAvailability(context.Background(), "organizationName", "value")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -319,7 +322,7 @@ func TestConfirmMembership(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.ConfirmMembership(context.Background(), "orgName")
|
||||
})
|
||||
|
|
@ -327,7 +330,7 @@ func TestConfirmMembership(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.ConfirmMembership(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -336,7 +339,7 @@ func TestConfirmMembership(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, confirmMembershipDBQ, "userID", "orgName").Return(nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.ConfirmMembership(ctx, "orgName")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -347,7 +350,7 @@ func TestConfirmMembership(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, confirmMembershipDBQ, "userID", "orgName").Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.ConfirmMembership(ctx, "orgName")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -360,7 +363,7 @@ func TestDelete(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.Add(context.Background(), &hub.Organization{})
|
||||
})
|
||||
|
|
@ -368,7 +371,7 @@ func TestDelete(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.Delete(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), "name not provided")
|
||||
|
|
@ -383,7 +386,7 @@ func TestDelete(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganization,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.Delete(ctx, "org1")
|
||||
assert.Equal(t, tests.ErrFake, err)
|
||||
|
|
@ -400,7 +403,7 @@ func TestDelete(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganization,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.Delete(ctx, "org1")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -417,7 +420,7 @@ func TestDelete(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganization,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.Delete(ctx, "org1")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -430,7 +433,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.DeleteMember(context.Background(), "orgName", "userAlias")
|
||||
})
|
||||
|
|
@ -457,7 +460,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.DeleteMember(ctx, tc.orgName, tc.userAlias)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -469,7 +472,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserAliasDBQ, "userID").Return("", tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.DeleteMember(ctx, "orgName", "userAlias")
|
||||
assert.Error(t, err)
|
||||
|
|
@ -486,7 +489,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganizationMember,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.DeleteMember(ctx, "orgName", "userAlias")
|
||||
assert.Equal(t, tests.ErrFake, err)
|
||||
|
|
@ -504,7 +507,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganizationMember,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.DeleteMember(ctx, "orgName", "userAlias")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -517,7 +520,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserAliasDBQ, "userID").Return("userAlias", nil)
|
||||
db.On("Exec", ctx, deleteOrgMemberDBQ, "userID", "orgName", "userAlias").Return(nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
err := m.DeleteMember(ctx, "orgName", "userAlias")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -551,7 +554,7 @@ func TestDeleteMember(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.DeleteOrganizationMember,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.DeleteMember(ctx, "orgName", "userAlias")
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
|
@ -567,7 +570,7 @@ func TestGetAuthorizationPolicyJSON(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.GetAuthorizationPolicyJSON(context.Background(), "org1")
|
||||
})
|
||||
|
|
@ -575,7 +578,7 @@ func TestGetAuthorizationPolicyJSON(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
_, err := m.GetAuthorizationPolicyJSON(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -588,7 +591,7 @@ func TestGetAuthorizationPolicyJSON(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.GetAuthorizationPolicy,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(nil, nil, az)
|
||||
m := NewManager(cfg, nil, nil, az)
|
||||
|
||||
dataJSON, err := m.GetAuthorizationPolicyJSON(ctx, "org1")
|
||||
assert.Equal(t, tests.ErrFake, err)
|
||||
|
|
@ -606,7 +609,7 @@ func TestGetAuthorizationPolicyJSON(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.GetAuthorizationPolicy,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
dataJSON, err := m.GetAuthorizationPolicyJSON(ctx, "org1")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -641,7 +644,7 @@ func TestGetAuthorizationPolicyJSON(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.GetAuthorizationPolicy,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
dataJSON, err := m.GetAuthorizationPolicyJSON(ctx, "org1")
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
|
@ -658,7 +661,7 @@ func TestGetByUserJSON(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.GetByUserJSON(context.Background())
|
||||
})
|
||||
|
|
@ -668,7 +671,7 @@ func TestGetByUserJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserOrgsDBQ, "userID").Return([]byte("dataJSON"), nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetByUserJSON(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -680,7 +683,7 @@ func TestGetByUserJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserOrgsDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetByUserJSON(ctx)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -694,7 +697,7 @@ func TestGetJSON(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
_, err := m.GetJSON(context.Background(), "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -703,7 +706,7 @@ func TestGetJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getOrgDBQ, "orgName").Return([]byte("dataJSON"), nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetJSON(ctx, "orgName")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -715,7 +718,7 @@ func TestGetJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getOrgDBQ, "orgName").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetJSON(ctx, "orgName")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -729,7 +732,7 @@ func TestGetMembersJSON(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.GetMembersJSON(context.Background(), "orgName")
|
||||
})
|
||||
|
|
@ -737,7 +740,7 @@ func TestGetMembersJSON(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
_, err := m.GetMembersJSON(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -746,7 +749,7 @@ func TestGetMembersJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getOrgMembersDBQ, "userID", "orgName").Return([]byte("dataJSON"), nil)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetMembersJSON(ctx, "orgName")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -774,7 +777,7 @@ func TestGetMembersJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getOrgMembersDBQ, "userID", "orgName").Return(nil, tc.dbErr)
|
||||
m := NewManager(db, nil, nil)
|
||||
m := NewManager(cfg, db, nil, nil)
|
||||
|
||||
dataJSON, err := m.GetMembersJSON(ctx, "orgName")
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
|
@ -790,7 +793,7 @@ func TestUpdate(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.Update(context.Background(), "org1", &hub.Organization{})
|
||||
})
|
||||
|
|
@ -831,7 +834,7 @@ func TestUpdate(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
err := m.Update(ctx, "org1", tc.org)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -847,7 +850,7 @@ func TestUpdate(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateOrganization,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(nil, nil, az)
|
||||
m := NewManager(cfg, nil, nil, az)
|
||||
|
||||
err := m.Update(ctx, "org1", &hub.Organization{
|
||||
Name: "org1",
|
||||
|
|
@ -867,7 +870,7 @@ func TestUpdate(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateOrganization,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.Update(ctx, "org1", &hub.Organization{
|
||||
Name: "org1",
|
||||
|
|
@ -904,7 +907,7 @@ func TestUpdate(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateOrganization,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.Update(ctx, "org1", &hub.Organization{
|
||||
Name: "org1",
|
||||
|
|
@ -928,7 +931,7 @@ func TestUpdateAuthorizationPolicy(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil, nil)
|
||||
m := NewManager(cfg, nil, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.UpdateAuthorizationPolicy(context.Background(), "org1", &hub.AuthorizationPolicy{})
|
||||
})
|
||||
|
|
@ -1032,7 +1035,7 @@ func TestUpdateAuthorizationPolicy(t *testing.T) {
|
|||
t.Parallel()
|
||||
az := &authz.AuthorizerMock{}
|
||||
az.On("WillUserBeLockedOut", ctx, tc.policy, "userID").Return(true, nil).Maybe()
|
||||
m := NewManager(nil, nil, az)
|
||||
m := NewManager(cfg, nil, nil, az)
|
||||
err := m.UpdateAuthorizationPolicy(ctx, tc.orgName, tc.policy)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -1049,7 +1052,7 @@ func TestUpdateAuthorizationPolicy(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateAuthorizationPolicy,
|
||||
}).Return(tests.ErrFake)
|
||||
m := NewManager(nil, nil, az)
|
||||
m := NewManager(cfg, nil, nil, az)
|
||||
|
||||
err := m.UpdateAuthorizationPolicy(ctx, "org1", validPolicy)
|
||||
assert.Equal(t, tests.ErrFake, err)
|
||||
|
|
@ -1067,7 +1070,7 @@ func TestUpdateAuthorizationPolicy(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateAuthorizationPolicy,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.UpdateAuthorizationPolicy(ctx, "org1", validPolicy)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -1102,7 +1105,7 @@ func TestUpdateAuthorizationPolicy(t *testing.T) {
|
|||
UserID: "userID",
|
||||
Action: hub.UpdateAuthorizationPolicy,
|
||||
}).Return(nil)
|
||||
m := NewManager(db, nil, az)
|
||||
m := NewManager(cfg, db, nil, az)
|
||||
|
||||
err := m.UpdateAuthorizationPolicy(ctx, "org1", validPolicy)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
{{ define "content" }}
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Invitation to {{ .orgName }} organization on Artifact Hub</span>
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Invitation to {{ .OrgName }} organization on {{ .Theme.SiteName }}</span>
|
||||
<table class="main line" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-radius: 3px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">You have been invited to join <b>{{ .orgName }}</b> organization on Artifact Hub.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">You have been invited to join <b>{{ .OrgName }}</b> organization on {{ .Theme.SiteName }}.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
<tr>
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .link }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Accept invitation</a> </td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .Link }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Accept invitation</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; font-size: 11px; padding-bottom: 30px; padding-top: 10px;">
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">You can also accept the invitation by visiting the page directly at <span class="copy-link">{{ .link }}</span></p>
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">You can also accept the invitation by visiting the page directly at <span class="copy-link">{{ .Link }}</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -57,7 +57,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/satori/uuid"
|
||||
"github.com/spf13/viper"
|
||||
pwvalidator "github.com/wagslane/go-password-validator"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
|
@ -110,16 +111,18 @@ var (
|
|||
|
||||
// Manager provides an API to manage users.
|
||||
type Manager struct {
|
||||
cfg *viper.Viper
|
||||
db hub.DB
|
||||
es hub.EmailSender
|
||||
tmpl map[templateID]*template.Template
|
||||
}
|
||||
|
||||
// NewManager creates a new Manager instance.
|
||||
func NewManager(db hub.DB, es hub.EmailSender) *Manager {
|
||||
func NewManager(cfg *viper.Viper, db hub.DB, es hub.EmailSender) *Manager {
|
||||
return &Manager{
|
||||
db: db,
|
||||
es: es,
|
||||
cfg: cfg,
|
||||
db: db,
|
||||
es: es,
|
||||
tmpl: map[templateID]*template.Template{
|
||||
passwordResetEmail: template.Must(template.New("").Parse(email.BaseTmpl + passwordResetEmailTmpl)),
|
||||
passwordResetSuccessEmail: template.Must(template.New("").Parse(email.BaseTmpl + passwordResetSuccessEmailTmpl)),
|
||||
|
|
@ -334,7 +337,7 @@ func (m *Manager) DisableTFA(ctx context.Context, passcode string) error {
|
|||
return err
|
||||
}
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[tfaDisabledEmail].Execute(&emailBody, nil); err != nil {
|
||||
if err := m.tmpl[tfaDisabledEmail].Execute(&emailBody, baseTemplateData(m.cfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
emailData := &email.Data{
|
||||
|
|
@ -387,7 +390,7 @@ func (m *Manager) EnableTFA(ctx context.Context, passcode string) error {
|
|||
return err
|
||||
}
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[tfaEnabledEmail].Execute(&emailBody, nil); err != nil {
|
||||
if err := m.tmpl[tfaEnabledEmail].Execute(&emailBody, baseTemplateData(m.cfg)); err != nil {
|
||||
return err
|
||||
}
|
||||
emailData := &email.Data{
|
||||
|
|
@ -472,9 +475,8 @@ func (m *Manager) RegisterPasswordResetCode(ctx context.Context, userEmail, base
|
|||
|
||||
// Send password reset email
|
||||
if m.es != nil {
|
||||
templateData := map[string]string{
|
||||
"link": fmt.Sprintf("%s/reset-password?code=%s", baseURL, code),
|
||||
}
|
||||
templateData := baseTemplateData(m.cfg)
|
||||
templateData["Link"] = fmt.Sprintf("%s/reset-password?code=%s", baseURL, code)
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[passwordResetEmail].Execute(&emailBody, templateData); err != nil {
|
||||
return err
|
||||
|
|
@ -575,9 +577,8 @@ func (m *Manager) RegisterUser(ctx context.Context, user *hub.User, baseURL stri
|
|||
|
||||
// Send email verification code
|
||||
if code != nil && m.es != nil {
|
||||
templateData := map[string]string{
|
||||
"link": fmt.Sprintf("%s/verify-email?code=%s", baseURL, *code),
|
||||
}
|
||||
templateData := baseTemplateData(m.cfg)
|
||||
templateData["Link"] = fmt.Sprintf("%s/verify-email?code=%s", baseURL, *code)
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[verificationEmail].Execute(&emailBody, templateData); err != nil {
|
||||
return err
|
||||
|
|
@ -632,9 +633,8 @@ func (m *Manager) ResetPassword(ctx context.Context, code, newPassword, baseURL
|
|||
|
||||
// Send password reset success email
|
||||
if m.es != nil {
|
||||
templateData := map[string]string{
|
||||
"baseURL": baseURL,
|
||||
}
|
||||
templateData := baseTemplateData(m.cfg)
|
||||
templateData["BaseURL"] = baseURL
|
||||
var emailBody bytes.Buffer
|
||||
if err := m.tmpl[passwordResetSuccessEmail].Execute(&emailBody, templateData); err != nil {
|
||||
return err
|
||||
|
|
@ -667,7 +667,7 @@ func (m *Manager) SetupTFA(ctx context.Context) ([]byte, error) {
|
|||
|
||||
// Generate TOTP key
|
||||
opts := totp.GenerateOpts{
|
||||
Issuer: "Artifact Hub",
|
||||
Issuer: m.cfg.GetString("theme.siteName"),
|
||||
AccountName: userEmail,
|
||||
}
|
||||
key, err := totp.Generate(opts)
|
||||
|
|
@ -807,3 +807,15 @@ func isValidRecoveryCode(recoveryCodes []string, code string) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// baseTemplateData creates a new base template data from the configuration
|
||||
// provided.
|
||||
func baseTemplateData(cfg *viper.Viper) map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"Theme": map[string]string{
|
||||
"PrimaryColor": cfg.GetString("theme.colors.primary"),
|
||||
"SecondaryColor": cfg.GetString("theme.colors.secondary"),
|
||||
"SiteName": cfg.GetString("theme.siteName"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,20 @@ import (
|
|||
"github.com/jackc/pgx/v4"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/satori/uuid"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var cfg *viper.Viper
|
||||
|
||||
func init() {
|
||||
cfg = viper.New()
|
||||
cfg.Set("theme.siteName", "Artifact Hub")
|
||||
}
|
||||
|
||||
func TestApproveSession(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sessionID := "sessionID"
|
||||
|
|
@ -58,7 +66,7 @@ func TestApproveSession(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.ApproveSession(ctx, tc.sessionID, tc.passcode)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -70,7 +78,7 @@ func TestApproveSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserIDFromSessionIDDBQ, hashedSessionID).Return("", tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.ApproveSession(ctx, sessionID, "123456")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -82,7 +90,7 @@ func TestApproveSession(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserIDFromSessionIDDBQ, hash(sessionID)).Return("userID", nil)
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.ApproveSession(ctx, sessionID, "123456")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -94,7 +102,7 @@ func TestApproveSession(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserIDFromSessionIDDBQ, hash(sessionID)).Return("userID", nil)
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.ApproveSession(ctx, sessionID, "123456")
|
||||
assert.Equal(t, errInvalidTFAPasscode, err)
|
||||
|
|
@ -107,7 +115,7 @@ func TestApproveSession(t *testing.T) {
|
|||
db.On("QueryRow", ctx, getUserIDFromSessionIDDBQ, hash(sessionID)).Return("userID", nil)
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, approveSessionDBQ, hashedSessionID, "").Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.ApproveSession(ctx, sessionID, passcode)
|
||||
|
|
@ -121,7 +129,7 @@ func TestApproveSession(t *testing.T) {
|
|||
db.On("QueryRow", ctx, getUserIDFromSessionIDDBQ, hash(sessionID)).Return("userID", nil)
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, approveSessionDBQ, hashedSessionID, code1).Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.ApproveSession(ctx, sessionID, code1)
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -153,7 +161,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
_, err := m.CheckAvailability(ctx, tc.resourceKind, tc.value)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -180,7 +188,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
tc.dbQuery = fmt.Sprintf("select not exists (%s)", tc.dbQuery)
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, tc.dbQuery, "value").Return(tc.available, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
available, err := m.CheckAvailability(ctx, tc.resourceKind, "value")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -195,7 +203,7 @@ func TestCheckAvailability(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
dbQuery := fmt.Sprintf(`select not exists (%s)`, checkUserAliasAvailDBQ)
|
||||
db.On("QueryRow", ctx, dbQuery, "value").Return(false, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
available, err := m.CheckAvailability(ctx, "userAlias", "value")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -228,7 +236,7 @@ func TestCheckCredentials(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
_, err := m.CheckCredentials(ctx, tc.email, tc.password)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -240,7 +248,7 @@ func TestCheckCredentials(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, checkUserCredsDBQ, "email").Return(nil, pgx.ErrNoRows)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckCredentials(ctx, "email", "pass")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -253,7 +261,7 @@ func TestCheckCredentials(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, checkUserCredsDBQ, "email").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckCredentials(ctx, "email", "pass")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -266,7 +274,7 @@ func TestCheckCredentials(t *testing.T) {
|
|||
pw, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.DefaultCost)
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, checkUserCredsDBQ, "email").Return([]interface{}{"userID", string(pw)}, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckCredentials(ctx, "email", "pass2")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -280,7 +288,7 @@ func TestCheckCredentials(t *testing.T) {
|
|||
pw, _ := bcrypt.GenerateFromPassword([]byte("pass"), bcrypt.DefaultCost)
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, checkUserCredsDBQ, "email").Return([]interface{}{"userID", string(pw)}, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckCredentials(ctx, "email", "pass")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -316,7 +324,7 @@ func TestCheckSession(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
_, err := m.CheckSession(ctx, tc.sessionID, tc.duration)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -328,7 +336,7 @@ func TestCheckSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getSessionDBQ, hashedSessionID).Return(nil, pgx.ErrNoRows)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckSession(ctx, sessionID, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -341,7 +349,7 @@ func TestCheckSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getSessionDBQ, hashedSessionID).Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckSession(ctx, sessionID, 1*time.Hour)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -357,7 +365,7 @@ func TestCheckSession(t *testing.T) {
|
|||
int64(1),
|
||||
true,
|
||||
}, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckSession(ctx, sessionID, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -374,7 +382,7 @@ func TestCheckSession(t *testing.T) {
|
|||
time.Now().Unix(),
|
||||
false,
|
||||
}, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckSession(ctx, sessionID, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -391,7 +399,7 @@ func TestCheckSession(t *testing.T) {
|
|||
time.Now().Unix(),
|
||||
true,
|
||||
}, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
output, err := m.CheckSession(ctx, sessionID, 1*time.Hour)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -422,7 +430,7 @@ func TestDeleteSession(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.DeleteSession(ctx, tc.sessionID)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -450,7 +458,7 @@ func TestDeleteSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, deleteSessionDBQ, hashedSessionID).Return(tc.dbResponse)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.DeleteSession(ctx, sessionID)
|
||||
assert.Equal(t, tc.dbResponse, err)
|
||||
|
|
@ -477,7 +485,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.DisableTFA(ctx, "123456")
|
||||
})
|
||||
|
|
@ -485,7 +493,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.DisableTFA(ctx, "")
|
||||
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
|
|
@ -495,7 +503,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.DisableTFA(ctx, "123456")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -506,7 +514,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.DisableTFA(ctx, "123456")
|
||||
assert.Equal(t, errInvalidTFAPasscode, err)
|
||||
|
|
@ -518,7 +526,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, disableTFADBQ, "userID").Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.DisableTFA(ctx, passcode)
|
||||
|
|
@ -531,7 +539,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, disableTFADBQ, "userID").Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.DisableTFA(ctx, passcode)
|
||||
|
|
@ -544,7 +552,7 @@ func TestDisableTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, disableTFADBQ, "userID").Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.DisableTFA(ctx, code1)
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -567,7 +575,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.EnableTFA(ctx, "123456")
|
||||
})
|
||||
|
|
@ -575,7 +583,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.EnableTFA(ctx, "")
|
||||
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
|
|
@ -585,7 +593,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.EnableTFA(ctx, "123456")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -596,7 +604,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.EnableTFA(ctx, "123456")
|
||||
assert.Equal(t, errInvalidTFAPasscode, err)
|
||||
|
|
@ -608,7 +616,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getTFAConfigDBQ, "userID").Return(tfaConfigJSON, nil)
|
||||
db.On("Exec", ctx, enableTFADBQ, "userID").Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.EnableTFA(ctx, passcode)
|
||||
|
|
@ -624,7 +632,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
db.On("QueryRow", ctx, getUserEmailDBQ, "userID").Return("email", nil)
|
||||
es := &email.SenderMock{}
|
||||
es.On("SendEmail", mock.Anything).Return(email.ErrFakeSenderFailure)
|
||||
m := NewManager(db, es)
|
||||
m := NewManager(cfg, db, es)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.EnableTFA(ctx, passcode)
|
||||
|
|
@ -640,7 +648,7 @@ func TestEnableTFA(t *testing.T) {
|
|||
db.On("QueryRow", ctx, getUserEmailDBQ, "userID").Return("email", nil)
|
||||
es := &email.SenderMock{}
|
||||
es.On("SendEmail", mock.Anything).Return(nil)
|
||||
m := NewManager(db, es)
|
||||
m := NewManager(cfg, db, es)
|
||||
|
||||
passcode, _ := totp.GenerateCode(key.Secret(), time.Now())
|
||||
err := m.EnableTFA(ctx, passcode)
|
||||
|
|
@ -654,7 +662,7 @@ func TestGetProfile(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.GetProfile(context.Background())
|
||||
})
|
||||
|
|
@ -664,7 +672,7 @@ func TestGetProfile(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserProfileDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
profile, err := m.GetProfile(ctx)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -696,7 +704,7 @@ func TestGetProfile(t *testing.T) {
|
|||
"tfa_enabled": true
|
||||
}
|
||||
`), nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
profile, err := m.GetProfile(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -710,7 +718,7 @@ func TestGetProfileJSON(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.GetProfileJSON(context.Background())
|
||||
})
|
||||
|
|
@ -720,7 +728,7 @@ func TestGetProfileJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserProfileDBQ, "userID").Return([]byte("dataJSON"), nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
data, err := m.GetProfileJSON(ctx)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -732,7 +740,7 @@ func TestGetProfileJSON(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserProfileDBQ, "userID").Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
data, err := m.GetProfileJSON(ctx)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -746,7 +754,7 @@ func TestGetUserID(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
_, err := m.GetUserID(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -755,7 +763,7 @@ func TestGetUserID(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserIDFromEmailDBQ, "email").Return("userID", nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
userID, err := m.GetUserID(ctx, "email")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -767,7 +775,7 @@ func TestGetUserID(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserIDFromEmailDBQ, "email").Return("", tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
userID, err := m.GetUserID(ctx, "email")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -798,7 +806,7 @@ func TestRegisterSession(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
s := &hub.Session{UserID: tc.userID}
|
||||
_, err := m.RegisterSession(ctx, s)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
|
|
@ -811,7 +819,7 @@ func TestRegisterSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, registerSessionDBQ, mock.Anything).Return(nil, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
sIN := &hub.Session{UserID: userID}
|
||||
sOUT, err := m.RegisterSession(ctx, sIN)
|
||||
|
|
@ -824,7 +832,7 @@ func TestRegisterSession(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, registerSessionDBQ, mock.Anything).Return(true, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
sIN := &hub.Session{
|
||||
UserID: userID,
|
||||
|
|
@ -865,7 +873,7 @@ func TestRegisterPasswordResetCode(t *testing.T) {
|
|||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
es := &email.SenderMock{}
|
||||
m := NewManager(nil, es)
|
||||
m := NewManager(cfg, nil, es)
|
||||
|
||||
err := m.RegisterPasswordResetCode(ctx, tc.userEmail, tc.baseURL)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
|
|
@ -896,7 +904,7 @@ func TestRegisterPasswordResetCode(t *testing.T) {
|
|||
db.On("Exec", ctx, registerPasswordResetCodeDBQ, "email@email.com", mock.Anything).Return(nil)
|
||||
es := &email.SenderMock{}
|
||||
es.On("SendEmail", mock.Anything).Return(tc.emailSenderResponse)
|
||||
m := NewManager(db, es)
|
||||
m := NewManager(cfg, db, es)
|
||||
|
||||
err := m.RegisterPasswordResetCode(ctx, "email@email.com", "http://baseurl.com")
|
||||
assert.Equal(t, tc.emailSenderResponse, err)
|
||||
|
|
@ -910,7 +918,7 @@ func TestRegisterPasswordResetCode(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, registerPasswordResetCodeDBQ, "email@email.com", mock.Anything).Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.RegisterPasswordResetCode(ctx, "email@email.com", "http://baseurl.com")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -959,7 +967,7 @@ func TestRegisterUser(t *testing.T) {
|
|||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
es := &email.SenderMock{}
|
||||
m := NewManager(nil, es)
|
||||
m := NewManager(cfg, nil, es)
|
||||
|
||||
err := m.RegisterUser(ctx, tc.user, tc.baseURL)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
|
|
@ -991,7 +999,7 @@ func TestRegisterUser(t *testing.T) {
|
|||
db.On("QueryRow", ctx, registerUserDBQ, mock.Anything).Return(&code, nil)
|
||||
es := &email.SenderMock{}
|
||||
es.On("SendEmail", mock.Anything).Return(tc.emailSenderResponse)
|
||||
m := NewManager(db, es)
|
||||
m := NewManager(cfg, db, es)
|
||||
|
||||
u := &hub.User{
|
||||
Alias: "alias",
|
||||
|
|
@ -1014,7 +1022,7 @@ func TestRegisterUser(t *testing.T) {
|
|||
code := ""
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, registerUserDBQ, mock.Anything).Return(&code, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
u := &hub.User{
|
||||
Alias: "alias",
|
||||
|
|
@ -1071,7 +1079,7 @@ func TestResetPassword(t *testing.T) {
|
|||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
es := &email.SenderMock{}
|
||||
m := NewManager(nil, es)
|
||||
m := NewManager(cfg, nil, es)
|
||||
err := m.ResetPassword(ctx, tc.code, tc.newPassword, tc.baseURL)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -1099,7 +1107,7 @@ func TestResetPassword(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, resetUserPasswordDBQ, codeHashed, mock.Anything).Return("", tc.dbErr)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.ResetPassword(ctx, code, newPassword, baseURL)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
|
|
@ -1130,7 +1138,7 @@ func TestResetPassword(t *testing.T) {
|
|||
db.On("QueryRow", ctx, resetUserPasswordDBQ, codeHashed, mock.Anything).Return("email", nil)
|
||||
es := &email.SenderMock{}
|
||||
es.On("SendEmail", mock.Anything).Return(tc.emailSenderResponse)
|
||||
m := NewManager(db, es)
|
||||
m := NewManager(cfg, db, es)
|
||||
|
||||
err := m.ResetPassword(ctx, code, newPassword, baseURL)
|
||||
assert.Equal(t, tc.emailSenderResponse, err)
|
||||
|
|
@ -1146,7 +1154,7 @@ func TestSetupTFA(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_, _ = m.SetupTFA(context.Background())
|
||||
})
|
||||
|
|
@ -1156,7 +1164,7 @@ func TestSetupTFA(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserEmailDBQ, "userID").Return("", tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
dataJSON, err := m.SetupTFA(ctx)
|
||||
assert.Nil(t, dataJSON)
|
||||
|
|
@ -1169,7 +1177,7 @@ func TestSetupTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserEmailDBQ, "userID").Return("email", nil)
|
||||
db.On("Exec", ctx, updateTFAInfoDBQ, "userID", mock.Anything, mock.Anything).Return(tests.ErrFake)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
dataJSON, err := m.SetupTFA(ctx)
|
||||
assert.Nil(t, dataJSON)
|
||||
|
|
@ -1182,7 +1190,7 @@ func TestSetupTFA(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserEmailDBQ, "userID").Return("email", nil)
|
||||
db.On("Exec", ctx, updateTFAInfoDBQ, "userID", mock.Anything, mock.Anything).Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
dataJSON, err := m.SetupTFA(ctx)
|
||||
assert.NotNil(t, dataJSON)
|
||||
|
|
@ -1207,7 +1215,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.UpdatePassword(context.Background(), "old", "new")
|
||||
})
|
||||
|
|
@ -1239,7 +1247,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.UpdatePassword(ctx, tc.old, tc.new)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -1251,7 +1259,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserPasswordDBQ, "userID").Return("", tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdatePassword(ctx, "old", new)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -1262,7 +1270,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserPasswordDBQ, "userID").Return(string(oldHashed), nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdatePassword(ctx, "old2", new)
|
||||
assert.Error(t, err)
|
||||
|
|
@ -1275,7 +1283,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
db.On("QueryRow", ctx, getUserPasswordDBQ, "userID").Return(string(oldHashed), nil)
|
||||
db.On("Exec", ctx, updateUserPasswordDBQ, "userID", mock.Anything, mock.Anything).
|
||||
Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdatePassword(ctx, "old", new)
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -1287,7 +1295,7 @@ func TestUpdatePassword(t *testing.T) {
|
|||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, getUserPasswordDBQ, "userID").Return(string(oldHashed), nil)
|
||||
db.On("Exec", ctx, updateUserPasswordDBQ, "userID", mock.Anything, mock.Anything).Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdatePassword(ctx, "old", new)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -1300,7 +1308,7 @@ func TestUpdateProfile(t *testing.T) {
|
|||
|
||||
t.Run("user id not found in ctx", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
assert.Panics(t, func() {
|
||||
_ = m.UpdateProfile(context.Background(), &hub.User{})
|
||||
})
|
||||
|
|
@ -1324,7 +1332,7 @@ func TestUpdateProfile(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.UpdateProfile(ctx, tc.user)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -1336,7 +1344,7 @@ func TestUpdateProfile(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, updateUserProfileDBQ, "userID", mock.Anything).Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdateProfile(ctx, &hub.User{Alias: "user1"})
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -1347,7 +1355,7 @@ func TestUpdateProfile(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, updateUserProfileDBQ, "userID", mock.Anything).Return(tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.UpdateProfile(ctx, &hub.User{Alias: "user1"})
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -1360,7 +1368,7 @@ func TestVerifyEmail(t *testing.T) {
|
|||
|
||||
t.Run("invalid input", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
_, err := m.VerifyEmail(ctx, "")
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
})
|
||||
|
|
@ -1369,7 +1377,7 @@ func TestVerifyEmail(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, verifyEmailDBQ, "emailVerificationCode").Return(true, nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
verified, err := m.VerifyEmail(ctx, "emailVerificationCode")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -1381,7 +1389,7 @@ func TestVerifyEmail(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("QueryRow", ctx, verifyEmailDBQ, "emailVerificationCode").Return(false, tests.ErrFakeDB)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
verified, err := m.VerifyEmail(ctx, "emailVerificationCode")
|
||||
assert.Equal(t, tests.ErrFakeDB, err)
|
||||
|
|
@ -1409,7 +1417,7 @@ func TestVerifyPasswordResetCode(t *testing.T) {
|
|||
tc := tc
|
||||
t.Run(tc.errMsg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := NewManager(nil, nil)
|
||||
m := NewManager(cfg, nil, nil)
|
||||
err := m.VerifyPasswordResetCode(ctx, tc.code)
|
||||
assert.True(t, errors.Is(err, hub.ErrInvalidInput))
|
||||
assert.Contains(t, err.Error(), tc.errMsg)
|
||||
|
|
@ -1437,7 +1445,7 @@ func TestVerifyPasswordResetCode(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, verifyPasswordResetCodeDBQ, codeHashed).Return(tc.dbErr)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.VerifyPasswordResetCode(ctx, code)
|
||||
assert.Equal(t, tc.expectedErr, err)
|
||||
|
|
@ -1450,7 +1458,7 @@ func TestVerifyPasswordResetCode(t *testing.T) {
|
|||
t.Parallel()
|
||||
db := &tests.DBMock{}
|
||||
db.On("Exec", ctx, verifyPasswordResetCodeDBQ, codeHashed).Return(nil)
|
||||
m := NewManager(db, nil)
|
||||
m := NewManager(cfg, db, nil)
|
||||
|
||||
err := m.VerifyPasswordResetCode(ctx, code)
|
||||
assert.Equal(t, nil, err)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;"> We got a request to reset your <span class="AHlink" style="font-weight: bold;">Artifact Hub</span> password.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;"> We got a request to reset your <span class="AHlink" style="font-weight: bold;">{{ .Theme.SiteName }}</span> password.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">If you did not perform this request, you can safely ignore this email. Otherwise, click the link below to complete the process.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Please note that the password reset link <span style="font-weight: bold;">will only be valid for 15 minutes</span>. If you haven't completed the process by then, you'll need to get a new password reset link.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .link }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Reset password</a> </td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .Link }}" class="AHbtn" target="_blank" style="display: inline-block; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Reset password</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; font-size: 11px; padding-bottom: 30px; padding-top: 10px;">
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .link }}</span></p>
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .Link }}</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
{{ define "title" }} Your Artifact Hub password has been reset {{ end }}
|
||||
{{ define "title" }} Your {{ .Theme.SiteName }} password has been reset {{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Your Artifact Hub password has been reset</span>
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Your {{ .Theme.SiteName }} password has been reset</span>
|
||||
<table class="main line" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-radius: 3px;">
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
<tr>
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Your <span class="AHlink" style="font-weight: bold;">Artifact Hub</span> password has been reset. You can now use your new password to log in to your account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Your <span class="AHlink" style="font-weight: bold;">{{ .Theme.SiteName }}</span> password has been reset. You can now use your new password to log in to your account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">If this wasn't you, please reset your password to secure your account.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; font-size: 11px; padding-bottom: 30px; padding-top: 10px;">
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .baseURL }}/?modal=login</span></p>
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .BaseURL }}/?modal=login</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
|
@ -49,7 +49,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@
|
|||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">
|
||||
Two-factor authentication has been successfully disabled for your <span class="AHlink" style="font-weight: bold;">Artifact Hub</span> account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Please, remember that two-factor authentication is an additional layer of security designed to prevent unauthorised access to your account and protect all your data in Artifact Hub.</p>
|
||||
Two-factor authentication has been successfully disabled for your <span class="AHlink" style="font-weight: bold;">{{ .Theme.SiteName }}</span> account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Please, remember that two-factor authentication is an additional layer of security designed to prevent unauthorised access to your account and protect all your data in {{ .Theme.SiteName }}.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Two-factor authentication has been successfully enabled for your <span class="AHlink" style="font-weight: bold;">Artifact Hub</span> account. Please don't forget to print the recovery codes provided during the setup process.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Two-factor authentication has been successfully enabled for your <span class="AHlink" style="font-weight: bold;">{{ .Theme.SiteName }}</span> account. Please don't forget to print the recovery codes provided during the setup process.</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
{{ define "content" }}
|
||||
<div class="content" style="box-sizing: border-box; display: block; Margin: 0 auto; max-width: 580px; padding: 10px;">
|
||||
<!-- START CENTERED WHITE CONTAINER -->
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Welcome to Artifact Hub!</span>
|
||||
<span class="preheader" style="color: transparent; display: none; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">Welcome to {{ .Theme.SiteName }}!</span>
|
||||
<table class="main line" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-radius: 3px;">
|
||||
|
||||
<!-- START MAIN CONTENT AREA -->
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; vertical-align: top;">
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Hi!</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Welcome to Artifact Hub! You are only one step from being able to sign in on our site. Please simply click on the link below to confirm your account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Welcome to {{ .Theme.SiteName }}! You are only one step from being able to sign in on our site. Please simply click on the link below to confirm your account.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 30px;">Please note that the verification code <span style="font-weight: bold;">is only valid for 24 hours</span>. If you haven't verified your account by then you'll need to sign up again.</p>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="btn btn-primary" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; box-sizing: border-box;">
|
||||
<tbody>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: auto;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .link }}" class="AHbtn" target="_blank" style="display: inline-block; color: #ffffff; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Confirm your account</a> </td>
|
||||
<td style="font-family: sans-serif; font-size: 14px; border-radius: 5px; vertical-align: top; text-align: center;"> <a href="{{ .Link }}" class="AHbtn" target="_blank" style="display: inline-block; color: #ffffff; border-radius: 5px; box-sizing: border-box; cursor: pointer; text-decoration: none; font-size: 14px; font-weight: bold; margin: 0; padding: 12px 25px; text-transform: capitalize;">Confirm your account</a> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -33,12 +33,12 @@
|
|||
<tbody>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; font-size: 11px; padding-bottom: 30px; padding-top: 10px;">
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .link }}</span></p>
|
||||
<p class="text-muted" style="font-size: 11px; text-decoration: none;">Or you can copy-paste this link: <span class="copy-link">{{ .Link }}</span></p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">After activation you may sign in to Artifact Hub using your credentials.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">After activation you may sign in to {{ .Theme.SiteName }} using your credentials.</p>
|
||||
<p style="font-family: sans-serif; font-size: 14px; font-weight: normal; margin: 0; Margin-bottom: 15px;">Thanks for creating an account.</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
|
@ -54,12 +54,12 @@
|
|||
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%;">
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 10px; text-align: center;">
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't create an Artifact Hub account? I's likely someone just typed in your email address by accident.<br>Feel free to ignore this email.</p>
|
||||
<p class="text-muted" style="font-size: 10px; text-align: center; text-decoration: none;">Didn't create an {{ .Theme.SiteName }} account? I's likely someone just typed in your email address by accident.<br>Feel free to ignore this email.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; color: #2d4857; text-align: center;">
|
||||
<a href="https://artifacthub.io" class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© Artifact Hub</a>
|
||||
<td class="content-block powered-by" style="font-family: sans-serif; vertical-align: top; padding-bottom: 10px; padding-top: 10px; font-size: 12px; text-align: center;">
|
||||
<span class="AHlink" style="font-size: 12px; text-align: center; text-decoration: none;">© {{ .Theme.SiteName }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
"regexify-string": "^1.0.5",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"semver": "^7.3.2",
|
||||
"tinycolor2": "^1.4.2",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -2,30 +2,34 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" type="image/png" href="{{ .baseURL }}/static/media/logo_v2.png" />
|
||||
<link rel="shortcut icon" type="image/png" href="{{ .shortcutIcon }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="apple-touch-icon" href="{{ .baseURL }}/static/media/logo192_v2.png" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="{{ .baseURL }}/static/media/logo512_v2.png" />
|
||||
<link rel="manifest" href="{{ .baseURL }}/manifest.json" />
|
||||
<link rel="apple-touch-icon" href="{{ .appleTouchIcon192 }}" />
|
||||
<link rel="apple-touch-icon" sizes="512x512" href="{{ .appleTouchIcon512 }}" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>{{ .title }}</title>
|
||||
<meta name="description" content="{{ .description }}" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="{{ .title }}" />
|
||||
<meta property="og:description" content="{{ .description }}" />
|
||||
<meta property="og:image" content="{{ .baseURL }}/static/media/artifactHub_v2.png" />
|
||||
<meta property="og:image" content="{{ .openGraphImage }}" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="{{ .title }}" />
|
||||
<meta name="twitter:description" content="{{ .description }}" />
|
||||
<meta name="twitter:image:src" content="{{ .baseURL }}/static/media/artifactHub_v2.png" />
|
||||
<meta name="twitter:image:src" content="{{ .openGraphImage }}" />
|
||||
<meta name="artifacthub:allowPrivateRepositories" content="{{ .allowPrivateRepositories }}" />
|
||||
<meta name="artifacthub:gaTrackingID" content="{{ .gaTrackingID }}" />
|
||||
<meta name="artifacthub:githubAuth" content="{{ .githubAuth }}" />
|
||||
<meta name="artifacthub:googleAuth" content="{{ .googleAuth }}" />
|
||||
<meta name="artifacthub:oidcAuth" content="{{ .oidcAuth }}" />
|
||||
<meta name="artifacthub:motd" content="{{ .motd }}" />
|
||||
<meta name="artifacthub:motdSeverity" content="{{ .motdSeverity }}" />
|
||||
<meta name="artifacthub:gaTrackingID" content="{{ .gaTrackingID }}" />
|
||||
<script type="text/javascript" src="{{ .baseURL }}/static/js/fixFirefoxNightMode.js" async></script>
|
||||
<meta name="artifacthub:primaryColor" content="{{ .primaryColor }}" />
|
||||
<meta name="artifacthub:secondaryColor" content="{{ .secondaryColor }}" />
|
||||
<meta name="artifacthub:siteName" content="{{ .siteName }}" />
|
||||
<meta name="artifacthub:websiteLogo" content="{{ .websiteLogo }}" />
|
||||
<script type="text/javascript" src="/static/js/fixFirefoxNightMode.js" async></script>
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import googleAnalytics from '@analytics/google-analytics';
|
|||
import Analytics from 'analytics';
|
||||
import { isNull } from 'lodash';
|
||||
|
||||
import getMetaTag from '../utils/getMetaTag';
|
||||
|
||||
const getPlugins = (): object[] => {
|
||||
let plugins: object[] = [];
|
||||
const analyticsConfig: string | null = document.querySelector(`meta[name='artifacthub:gaTrackingID']`)
|
||||
? document.querySelector(`meta[name='artifacthub:gaTrackingID']`)!.getAttribute('content')
|
||||
: null;
|
||||
const analyticsConfig: string | null = getMetaTag('gaTrackingID');
|
||||
|
||||
if (!isNull(analyticsConfig) && analyticsConfig !== '' && analyticsConfig !== '{{ .gaTrackingID }}') {
|
||||
plugins.push(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import buildSearchParams from '../utils/buildSearchParams';
|
|||
import detectActiveThemeMode from '../utils/detectActiveThemeMode';
|
||||
import history from '../utils/history';
|
||||
import lsPreferences from '../utils/localStoragePreferences';
|
||||
import themeBuilder from '../utils/themeBuilder';
|
||||
import AlertController from './common/AlertController';
|
||||
import UserNotificationsController from './common/userNotifications';
|
||||
import ControlPanelView from './controlPanel';
|
||||
|
|
@ -41,6 +42,7 @@ export default function App() {
|
|||
const [scrollPosition, setScrollPosition] = useState<undefined | number>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
themeBuilder.init();
|
||||
const activeProfile = lsPreferences.getActiveProfile();
|
||||
const theme =
|
||||
activeProfile.theme.configured === 'automatic' ? detectActiveThemeMode() : activeProfile.theme.configured;
|
||||
|
|
@ -55,7 +57,7 @@ export default function App() {
|
|||
return (
|
||||
<AppCtxProvider>
|
||||
<Router history={history}>
|
||||
<div className="d-flex flex-column min-vh-100 position-relative">
|
||||
<div className="d-flex flex-column min-vh-100 position-relative whiteBranded">
|
||||
<div className="sr-only sr-only-focusable">
|
||||
<a href="#content">Skip to Main Content</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@
|
|||
}
|
||||
|
||||
.tooltipArrow::before {
|
||||
border-bottom-color: var(--color-1-900) !important;
|
||||
border-bottom-color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
background-color: var(--color-1-900) !important;
|
||||
background-color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
|
|
|||
|
|
@ -23,11 +23,11 @@
|
|||
}
|
||||
|
||||
.tooltipArrow::before {
|
||||
border-bottom-color: var(--color-1-900) !important;
|
||||
border-bottom-color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
background-color: var(--color-1-900) !important;
|
||||
background-color: var(--dark) !important;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ const FilesModal = (props: Props) => {
|
|||
<div className="text-center">
|
||||
<button
|
||||
data-testid="filesModalBtn"
|
||||
className="btn btn-secondary btn-sm text-nowrap btn-block"
|
||||
className="btn btn-outline-secondary btn-sm text-nowrap btn-block"
|
||||
onClick={onOpenModal}
|
||||
aria-label={`Open ${props.title} modal`}
|
||||
disabled={isUndefined(props.files) || props.files.length === 0}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
.hightlighted {
|
||||
background-color: var(--color-3-50);
|
||||
background-color: var(--color-2-500);
|
||||
}
|
||||
|
||||
.checkMark {
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ const InputTypeaheadWithDropdown = (props: Props) => {
|
|||
aria-expanded={!collapsed}
|
||||
>
|
||||
<div className="d-flex flex-row align-items-center justify-content-between">
|
||||
<SmallTitle text={props.label} className="text-secondary font-weight-bold pt-2" />
|
||||
<SmallTitle text={props.label} className="text-dark font-weight-bold pt-2" />
|
||||
|
||||
<MdFilterList className="mt-2 mb-1 text-secondary" />
|
||||
<MdFilterList className="mt-2 mb-1 text-dark" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ const Modal = (props: Props) => {
|
|||
<button
|
||||
data-testid="closeModalFooterBtn"
|
||||
type="button"
|
||||
className="btn btn-sm btn-secondary text-uppercase"
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeModal();
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
@media (hover: hover) {
|
||||
.card:hover {
|
||||
border-color: var(--color-1-700);
|
||||
box-shadow: 0px 0px 5px 0px var(--color-1-900);
|
||||
border-color: var(--color-black-50);
|
||||
box-shadow: 0px 0px 5px 0px var(--color-black-75);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
}
|
||||
|
||||
.activeDropdownItem {
|
||||
background-color: var(--color-black-5);
|
||||
background-color: var(--color-2-500);
|
||||
}
|
||||
|
||||
.truncateWrapper {
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const SearchPackages = (props: Props) => {
|
|||
<button
|
||||
data-testid="searchIconBtn"
|
||||
type="button"
|
||||
className={`btn btn-secondary ml-3 text-center p-0 ${styles.searchBtn}`}
|
||||
className={`btn btn-outline-secondary ml-3 text-center p-0 ${styles.searchBtn}`}
|
||||
disabled={searchQuery === '' || isSearching}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
e.preventDefault();
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ const Sidebar = (props: Props) => {
|
|||
<button
|
||||
data-testid="closeSidebarFooterBtn"
|
||||
type="button"
|
||||
className="ml-auto btn btn-sm btn-secondary"
|
||||
className="ml-auto btn btn-sm btn-outline-secondary"
|
||||
onClick={() => openStatusChange(false)}
|
||||
aria-label="Close"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ exports[`SearchPackages creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Search by "
|
||||
class="btn btn-secondary ml-3 text-center p-0 searchBtn"
|
||||
class="btn btn-outline-secondary ml-3 text-center p-0 searchBtn"
|
||||
data-testid="searchIconBtn"
|
||||
disabled=""
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface HeadingProps {
|
|||
|
||||
const Heading: React.ElementType = (data: HeadingProps) => {
|
||||
const Tag = `h${data.level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
|
||||
return <Tag className={`text-secondary ${styles.header}`}>{data.title || data.children}</Tag>;
|
||||
return <Tag className={`text-dark ${styles.header}`}>{data.title || data.children}</Tag>;
|
||||
};
|
||||
|
||||
const ANIMATION_TIME = 300; //300ms
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
}
|
||||
|
||||
.tooltipArrow::before {
|
||||
border-bottom-color: var(--color-1-900) !important;
|
||||
border-bottom-color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
background-color: var(--color-1-900) !important;
|
||||
background-color: var(--dark) !important;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
min-width: 18px;
|
||||
height: 18px;
|
||||
line-height: 1rem;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.ctxBtn {
|
||||
|
|
@ -34,7 +35,7 @@
|
|||
}
|
||||
|
||||
.caret {
|
||||
bottom: 5px;
|
||||
bottom: 6px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ const UserContext = () => {
|
|||
<div className="d-flex flex-row align-items-center">
|
||||
<button
|
||||
data-testid="ctxBtn"
|
||||
className={`btn btn-primary badge-pill btn-sm pr-3 position-relative ${styles.ctxBtn}`}
|
||||
className={`btn btn-primary badge-pill border-0 btn-sm pr-3 position-relative ${styles.ctxBtn}`}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
fetchOrganizations();
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ exports[`UserContext creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open context"
|
||||
class="btn btn-primary badge-pill btn-sm pr-3 position-relative ctxBtn"
|
||||
class="btn btn-primary badge-pill border-0 btn-sm pr-3 position-relative ctxBtn"
|
||||
data-testid="ctxBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ exports[`ControlPanelView renders correctly 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open context"
|
||||
class="btn btn-primary badge-pill btn-sm pr-3 position-relative ctxBtn"
|
||||
class="btn btn-primary badge-pill border-0 btn-sm pr-3 position-relative ctxBtn"
|
||||
data-testid="ctxBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
border-radius: 50% !important;
|
||||
background-color: var(--color-1-10) !important;
|
||||
line-height: 1rem;
|
||||
background-color: var(--white) !important;
|
||||
border-color: var(--color-1-500);
|
||||
line-height: 0.85rem;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ const MemberCard = (props: Props) => {
|
|||
closeButton={
|
||||
<>
|
||||
<button
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={() => setModalStatus(false)}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
|
|
@ -177,7 +177,7 @@ const MemberCard = (props: Props) => {
|
|||
{isUser ? (
|
||||
<button
|
||||
data-testid="leaveOrRemoveModalBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -193,7 +193,7 @@ const MemberCard = (props: Props) => {
|
|||
) : (
|
||||
<ActionBtn
|
||||
testId="leaveOrRemoveModalBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -210,7 +210,7 @@ const MemberCard = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<button
|
||||
className={`btn btn-light p-0 text-secondary text-center ${styles.btnDropdown}`}
|
||||
className={`btn p-0 text-primary text-center iconSubsWrapper ${styles.btnDropdown}`}
|
||||
onClick={() => setDropdownMenuStatus(true)}
|
||||
aria-label="Open menu"
|
||||
aria-expanded={dropdownMenuStatus}
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ const MemberModal = (props: Props) => {
|
|||
closeButton={
|
||||
<button
|
||||
data-testid="membersFormBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ exports[`Member Card - members section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="leaveOrRemoveModalBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -96,7 +96,7 @@ exports[`Member Card - members section creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open menu"
|
||||
class="btn btn-light p-0 text-secondary text-center btnDropdown"
|
||||
class="btn p-0 text-primary text-center iconSubsWrapper btnDropdown"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ exports[`Members Modal - members section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Invite member"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="membersFormBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ exports[`UserInvitation creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Close modal"
|
||||
class="btn btn-sm btn-secondary text-uppercase"
|
||||
class="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
data-testid="closeModalFooterBtn"
|
||||
disabled=""
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ exports[`Members section index creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="btn btn-secondary btn-sm text-uppercase btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase btnAction"
|
||||
data-testid="addMemberBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ const MembersSection = (props: Props) => {
|
|||
<div>
|
||||
<ActionBtn
|
||||
testId="addMemberBtn"
|
||||
className={`btn btn-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
contentClassName="justify-content-center"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -98,7 +98,7 @@ const MembersSection = (props: Props) => {
|
|||
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setModalMemberOpen(true)}
|
||||
data-testid="addFirstMemberBtn"
|
||||
aria-label="Open modal"
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
border-radius: 50% !important;
|
||||
background-color: var(--color-1-10) !important;
|
||||
line-height: 1rem;
|
||||
border-color: var(--color-1-500);
|
||||
line-height: 0.85rem;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const OrganizationCard = (props: Props) => {
|
|||
{isMember && props.organization.membersCount && props.organization.membersCount > 1 && (
|
||||
<button
|
||||
data-testid="leaveOrgModalBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -157,7 +157,7 @@ const OrganizationCard = (props: Props) => {
|
|||
<div>
|
||||
<button
|
||||
data-testid="acceptInvitationBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
confirmOrganizationMembership();
|
||||
|
|
@ -186,7 +186,7 @@ const OrganizationCard = (props: Props) => {
|
|||
|
||||
{hasDropdownContent && (
|
||||
<button
|
||||
className={`ml-3 btn btn-light p-0 text-secondary text-center ${styles.btnDropdown}`}
|
||||
className={`ml-3 mb-2 btn p-0 text-primary text-center iconSubsWrapper ${styles.btnDropdown}`}
|
||||
onClick={() => setDropdownMenuStatus(true)}
|
||||
aria-label="Open menu"
|
||||
aria-expanded={dropdownMenuStatus}
|
||||
|
|
@ -203,7 +203,7 @@ const OrganizationCard = (props: Props) => {
|
|||
closeButton={
|
||||
<>
|
||||
<button
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={() => setLeaveModalStatus(false)}
|
||||
aria-label="Close modal"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ const OrganizationModal = (props: Props) => {
|
|||
modalClassName={styles.modal}
|
||||
closeButton={
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ exports[`Organization Card - organization section creates snapshot 1`] = `
|
|||
/>
|
||||
<button
|
||||
aria-label="Open modal"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="leaveOrgModalBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -85,7 +85,7 @@ exports[`Organization Card - organization section creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open menu"
|
||||
class="ml-3 btn btn-light p-0 text-secondary text-center btnDropdown"
|
||||
class="ml-3 mb-2 btn p-0 text-primary text-center iconSubsWrapper btnDropdown"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ exports[`OrganizationModal - organizations section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Update organization"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ exports[`Organizations section index creates snapshot 1`] = `
|
|||
<div>
|
||||
<button
|
||||
aria-label="Open modal"
|
||||
class="btn btn-secondary btn-sm text-uppercase btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase btnAction"
|
||||
data-testid="addOrgButton"
|
||||
>
|
||||
<div
|
||||
|
|
@ -132,7 +132,7 @@ exports[`Organizations section index creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Add organization"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ const OrganizationsSection = (props: Props) => {
|
|||
<div>
|
||||
<button
|
||||
data-testid="addOrgButton"
|
||||
className={`btn btn-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
onClick={() => setModalStatus({ open: true })}
|
||||
aria-label="Open modal"
|
||||
>
|
||||
|
|
@ -88,7 +88,7 @@ const OrganizationsSection = (props: Props) => {
|
|||
<button
|
||||
data-testid="addFirstOrgBtn"
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setModalStatus({ open: true })}
|
||||
aria-label="Open modal"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ describe('Badge Modal - repositories section', () => {
|
|||
);
|
||||
expect(
|
||||
getByText(
|
||||
`[](http://localhost/packages/search?repo=${repoMock.name})`
|
||||
`[](http://localhost/packages/search?repo=${repoMock.name})`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -67,7 +67,7 @@ describe('Badge Modal - repositories section', () => {
|
|||
);
|
||||
expect(
|
||||
getByText(
|
||||
`http://localhost/packages/search?repo=${repoMock.name}[image:https://img.shields.io/endpoint?url=http://localhost/badge/repository/${repoMock.name}[Artifact HUB]]`
|
||||
`http://localhost/packages/search?repo=${repoMock.name}[image:https://img.shields.io/endpoint?url=http://localhost/badge/repository/${repoMock.name}[null]]`
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter';
|
|||
import { docco } from 'react-syntax-highlighter/dist/cjs/styles/hljs';
|
||||
|
||||
import { Repository } from '../../../types';
|
||||
import getMetaTag from '../../../utils/getMetaTag';
|
||||
import ButtonCopyToClipboard from '../../common/ButtonCopyToClipboard';
|
||||
import Modal from '../../common/Modal';
|
||||
import Tabs from '../../common/Tabs';
|
||||
|
|
@ -15,10 +16,11 @@ interface Props {
|
|||
}
|
||||
|
||||
const BadgeModal = (props: Props) => {
|
||||
const siteName = getMetaTag('siteName');
|
||||
const origin = window.location.origin;
|
||||
const badgeImage = `https://img.shields.io/endpoint?url=${origin}/badge/repository/${props.repository.name}`;
|
||||
const markdownLink = `[](${origin}/packages/search?repo=${props.repository.name})`;
|
||||
const asciiLink = `${origin}/packages/search?repo=${props.repository.name}[image:${badgeImage}[Artifact HUB]]`;
|
||||
const markdownLink = `[](${origin}/packages/search?repo=${props.repository.name})`;
|
||||
const asciiLink = `${origin}/packages/search?repo=${props.repository.name}[image:${badgeImage}[${siteName}]]`;
|
||||
|
||||
const onCloseModal = () => {
|
||||
props.onClose();
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@
|
|||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
border-radius: 50% !important;
|
||||
background-color: var(--color-1-10) !important;
|
||||
line-height: 1rem;
|
||||
background-color: var(--white) !important;
|
||||
border-color: var(--color-1-500);
|
||||
line-height: 0.85rem;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ const RepositoryCard = (props: Props) => {
|
|||
<Modal
|
||||
modalDialogClassName={styles.modalDialog}
|
||||
className={`d-inline-block ${styles.modal}`}
|
||||
buttonType={`ml-1 btn badge btn-secondary ${styles.btn}`}
|
||||
buttonType={`ml-1 btn badge btn-outline-secondary ${styles.btn}`}
|
||||
buttonContent={
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<HiExclamation className="mr-2" />
|
||||
|
|
@ -207,7 +207,7 @@ const RepositoryCard = (props: Props) => {
|
|||
<Modal
|
||||
modalDialogClassName={styles.modalDialog}
|
||||
className={`d-inline-block ${styles.modal}`}
|
||||
buttonType={`ml-1 btn badge btn-secondary ${styles.btn}`}
|
||||
buttonType={`ml-1 btn badge btn-outline-secondary ${styles.btn}`}
|
||||
buttonContent={
|
||||
<div className="d-flex flex-row align-items-center">
|
||||
<HiExclamation className="mr-2" />
|
||||
|
|
@ -331,7 +331,7 @@ const RepositoryCard = (props: Props) => {
|
|||
|
||||
<button
|
||||
data-testid="getBadgeBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -347,7 +347,7 @@ const RepositoryCard = (props: Props) => {
|
|||
|
||||
<ActionBtn
|
||||
testId="transferRepoBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -363,7 +363,7 @@ const RepositoryCard = (props: Props) => {
|
|||
|
||||
<ActionBtn
|
||||
testId="updateRepoBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -382,7 +382,7 @@ const RepositoryCard = (props: Props) => {
|
|||
|
||||
<ActionBtn
|
||||
testId="deleteRepoDropdownBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -398,7 +398,7 @@ const RepositoryCard = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<button
|
||||
className={`btn btn-light p-0 text-secondary text-center ${styles.btnDropdown}`}
|
||||
className={`btn p-0 text-primary text-center iconSubsWrapper ${styles.btnDropdown}`}
|
||||
onClick={() => setDropdownMenuStatus(true)}
|
||||
aria-label="Open menu"
|
||||
aria-expanded={dropdownMenuStatus}
|
||||
|
|
@ -417,7 +417,7 @@ const RepositoryCard = (props: Props) => {
|
|||
<div className={`position-absolute ${styles.copyBtnWrapper}`}>
|
||||
<ButtonCopyToClipboard
|
||||
text={props.repository.repositoryId}
|
||||
className="btn-link border-0 text-secondary font-weight-bold"
|
||||
className="btn-link border-0 text-dark font-weight-bold"
|
||||
label="Copy repository ID to clipboard"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe('Claim Repository Modal - repositories section', () => {
|
|||
expect(getByTestId('select_claim_orgs')).toBeInTheDocument();
|
||||
expect(getByTestId('claimRepoBtn')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/Please make sure the email used in the metatata file matches with the one you use in Artifact Hub./g)
|
||||
getByText(/Please make sure the email used in the metatata file matches with the one you use in/g)
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('It may take a few minutes for this change to be visible across the Hub.')).toBeInTheDocument();
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ describe('Claim Repository Modal - repositories section', () => {
|
|||
expect(getByTestId('select_claim_orgs')).toBeInTheDocument();
|
||||
expect(getByTestId('claimRepoBtn')).toBeInTheDocument();
|
||||
expect(
|
||||
getByText(/Please make sure the email used in the metatata file matches with the one you use in Artifact Hub./g)
|
||||
getByText(/Please make sure the email used in the metatata file matches with the one you use in/g)
|
||||
).toBeInTheDocument();
|
||||
expect(getByText('It may take a few minutes for this change to be visible across the Hub.')).toBeInTheDocument();
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { AppCtx } from '../../../context/AppCtx';
|
|||
import { ErrorKind, Organization, Repository } from '../../../types';
|
||||
import compoundErrorMessage from '../../../utils/compoundErrorMessage';
|
||||
import { OCI_PREFIX } from '../../../utils/data';
|
||||
import getMetaTag from '../../../utils/getMetaTag';
|
||||
import ExternalLink from '../../common/ExternalLink';
|
||||
import Modal from '../../common/Modal';
|
||||
import RepositoryIcon from '../../common/RepositoryIcon';
|
||||
|
|
@ -26,6 +27,7 @@ interface Props {
|
|||
|
||||
const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
||||
const { ctx } = useContext(AppCtx);
|
||||
const siteName = getMetaTag('siteName');
|
||||
const form = useRef<HTMLFormElement>(null);
|
||||
const [isFetchingOrgs, setIsFetchingOrgs] = useState(false);
|
||||
const [isSending, setIsSending] = useState(false);
|
||||
|
|
@ -181,7 +183,7 @@ const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
|||
closeButton={
|
||||
<button
|
||||
data-testid="claimRepoBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending || isNull(repoItem)}
|
||||
onClick={submitForm}
|
||||
|
|
@ -219,7 +221,7 @@ const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
|||
</ExternalLink>{' '}
|
||||
to your repository and include yourself (or the person who will do the request) as an owner. This will be
|
||||
checked during the ownership claim process. Please make sure the email used in the metatata file matches
|
||||
with the one you use in Artifact Hub.
|
||||
with the one you use in {siteName}.
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
|
|
@ -299,11 +301,11 @@ const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
|||
<label id="claiming" className={`font-weight-bold ${styles.label}`}>
|
||||
Transfer to:
|
||||
</label>
|
||||
<div className="form-check mb-2">
|
||||
<div className="custom-control custom-radio mb-2">
|
||||
<input
|
||||
aria-labelledby="claiming user"
|
||||
data-testid="radio_claim_user"
|
||||
className="form-check-input"
|
||||
className="custom-control-input"
|
||||
type="radio"
|
||||
name="claim"
|
||||
id="user"
|
||||
|
|
@ -312,16 +314,16 @@ const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
|||
onChange={() => handleClaimingFromOpt('user')}
|
||||
required
|
||||
/>
|
||||
<label id="user" className={`form-check-label ${styles.label}`} htmlFor="user">
|
||||
<label id="user" className={`custom-control-label ${styles.label}`} htmlFor="user">
|
||||
My user
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check mb-3">
|
||||
<div className="custom-control custom-radio mb-3">
|
||||
<input
|
||||
aria-labelledby="claiming org"
|
||||
data-testid="radio_claim_org"
|
||||
className="form-check-input"
|
||||
className="custom-control-input"
|
||||
type="radio"
|
||||
name="claim"
|
||||
id="org"
|
||||
|
|
@ -330,7 +332,7 @@ const ClaimRepositoryOwnerShipModal = (props: Props) => {
|
|||
onChange={() => handleClaimingFromOpt('org')}
|
||||
required
|
||||
/>
|
||||
<label id="org" className={`form-check-label ${styles.label}`} htmlFor="org">
|
||||
<label id="org" className={`custom-control-label ${styles.label}`} htmlFor="org">
|
||||
Organization
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const DeletionModal = (props: Props) => {
|
|||
closeButton={
|
||||
<>
|
||||
<button
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={() => props.setDeletionModalStatus(false)}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { AppCtx } from '../../../context/AppCtx';
|
|||
import { ErrorKind, RefInputField, Repository, RepositoryKind, ResourceKind } from '../../../types';
|
||||
import compoundErrorMessage from '../../../utils/compoundErrorMessage';
|
||||
import { OCI_PREFIX, RepoKindDef, REPOSITORY_KINDS } from '../../../utils/data';
|
||||
import getMetaTag from '../../../utils/getMetaTag';
|
||||
import ExternalLink from '../../common/ExternalLink';
|
||||
import InputField from '../../common/InputField';
|
||||
import Modal from '../../common/Modal';
|
||||
|
|
@ -58,9 +59,8 @@ const RepositoryModal = (props: Props) => {
|
|||
setUrlContainsTreeTxt(e.target.value.includes('/tree/'));
|
||||
};
|
||||
|
||||
const allowPrivateRepositories: boolean = document.querySelector(`meta[name='artifacthub:allowPrivateRepositories']`)
|
||||
? document.querySelector(`meta[name='artifacthub:allowPrivateRepositories']`)!.getAttribute('content') === 'true'
|
||||
: false;
|
||||
const allowPrivateRepositories: boolean = getMetaTag('allowPrivateRepositories', true);
|
||||
const siteName = getMetaTag('siteName');
|
||||
|
||||
// Clean API error when form is focused after validation
|
||||
const cleanApiError = () => {
|
||||
|
|
@ -351,7 +351,11 @@ const RepositoryModal = (props: Props) => {
|
|||
<button
|
||||
data-testid="confirmDisabledRepo"
|
||||
type="button"
|
||||
className={classnames('btn btn-sm ml-3', { 'btn-dark': !isValidInput }, { 'btn-danger': isValidInput })}
|
||||
className={classnames(
|
||||
'btn btn-sm ml-3',
|
||||
{ 'btn-outline-secondary': !isValidInput },
|
||||
{ 'btn-danger': isValidInput }
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsDisabled(!isDisabled);
|
||||
|
|
@ -366,7 +370,7 @@ const RepositoryModal = (props: Props) => {
|
|||
) : (
|
||||
<button
|
||||
data-testid="repoBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending || visibleDisabledConfirmation}
|
||||
onClick={submitForm}
|
||||
|
|
@ -417,7 +421,7 @@ const RepositoryModal = (props: Props) => {
|
|||
|
||||
<p>
|
||||
You can enable back your repository at any time and the information available in the source repository
|
||||
will be indexed and made available in Artifact Hub again.
|
||||
will be indexed and made available in {siteName} again.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ const TransferRepositoryModal = (props: Props) => {
|
|||
closeButton={
|
||||
<button
|
||||
data-testid="transferRepoBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
@ -156,10 +156,10 @@ const TransferRepositoryModal = (props: Props) => {
|
|||
>
|
||||
{!isUndefined(organizationName) ? (
|
||||
<>
|
||||
<div className="form-check mb-3">
|
||||
<div className="custom-control custom-radio mb-3">
|
||||
<input
|
||||
data-testid="radio_user"
|
||||
className="form-check-input"
|
||||
className="custom-control-input"
|
||||
type="radio"
|
||||
name="transfer"
|
||||
id="user"
|
||||
|
|
@ -168,15 +168,15 @@ const TransferRepositoryModal = (props: Props) => {
|
|||
onChange={() => setSelectedTransferOption('user')}
|
||||
required
|
||||
/>
|
||||
<label className={`form-check-label font-weight-bold ${styles.label}`} htmlFor="user">
|
||||
<label className={`custom-control-label font-weight-bold ${styles.label}`} htmlFor="user">
|
||||
Transfer to my user
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-check mb-3">
|
||||
<div className="custom-control custom-radio mb-3">
|
||||
<input
|
||||
data-testid="radio_org"
|
||||
className="form-check-input"
|
||||
className="custom-control-input"
|
||||
type="radio"
|
||||
name="transfer"
|
||||
id="org"
|
||||
|
|
@ -185,7 +185,7 @@ const TransferRepositoryModal = (props: Props) => {
|
|||
onChange={() => setSelectedTransferOption('org')}
|
||||
required
|
||||
/>
|
||||
<label className={`form-check-label font-weight-bold ${styles.label}`} htmlFor="org">
|
||||
<label className={`custom-control-label font-weight-bold ${styles.label}`} htmlFor="org">
|
||||
Transfer to organization
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ exports[`Badge Modal - repositories section creates snapshot 1`] = `
|
|||
style="white-space: pre;"
|
||||
>
|
||||
<span>
|
||||
[](http://localhost/packages/search?repo=repoTest)
|
||||
[](http://localhost/packages/search?repo=repoTest)
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
|
|
@ -173,7 +173,7 @@ exports[`Badge Modal - repositories section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Close modal"
|
||||
class="btn btn-sm btn-secondary text-uppercase"
|
||||
class="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
data-testid="closeModalFooterBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ exports[`Repository Card - packages section creates snapshot 1`] = `
|
|||
/>
|
||||
<button
|
||||
aria-label="Open modal"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="getBadgeBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -93,7 +93,7 @@ exports[`Repository Card - packages section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="transferRepoBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -131,7 +131,7 @@ exports[`Repository Card - packages section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="updateRepoBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -163,7 +163,7 @@ exports[`Repository Card - packages section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="deleteRepoDropdownBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
@ -194,7 +194,7 @@ exports[`Repository Card - packages section creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open menu"
|
||||
class="btn btn-light p-0 text-secondary text-center btnDropdown"
|
||||
class="btn p-0 text-primary text-center iconSubsWrapper btnDropdown"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
metadata file
|
||||
</u>
|
||||
</a>
|
||||
to your repository and include yourself (or the person who will do the request) as an owner. This will be checked during the ownership claim process. Please make sure the email used in the metatata file matches with the one you use in Artifact Hub.
|
||||
to your repository and include yourself (or the person who will do the request) as an owner. This will be checked during the ownership claim process. Please make sure the email used in the metatata file matches with the one you use in .
|
||||
</p>
|
||||
</div>
|
||||
<form
|
||||
|
|
@ -132,11 +132,11 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
Transfer to:
|
||||
</label>
|
||||
<div
|
||||
class="form-check mb-2"
|
||||
class="custom-control custom-radio mb-2"
|
||||
>
|
||||
<input
|
||||
aria-labelledby="claiming user"
|
||||
class="form-check-input"
|
||||
class="custom-control-input"
|
||||
data-testid="radio_claim_user"
|
||||
id="user"
|
||||
name="claim"
|
||||
|
|
@ -145,7 +145,7 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
value="user"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label label"
|
||||
class="custom-control-label label"
|
||||
for="user"
|
||||
id="user"
|
||||
>
|
||||
|
|
@ -153,12 +153,12 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="form-check mb-3"
|
||||
class="custom-control custom-radio mb-3"
|
||||
>
|
||||
<input
|
||||
aria-labelledby="claiming org"
|
||||
checked=""
|
||||
class="form-check-input"
|
||||
class="custom-control-input"
|
||||
data-testid="radio_claim_org"
|
||||
id="org"
|
||||
name="claim"
|
||||
|
|
@ -167,7 +167,7 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
value="org"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label label"
|
||||
class="custom-control-label label"
|
||||
for="org"
|
||||
id="org"
|
||||
>
|
||||
|
|
@ -222,7 +222,7 @@ exports[`Claim Repository Modal - repositories section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Claim ownership"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="claimRepoBtn"
|
||||
disabled=""
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ exports[`Deletion modal Modal - packages section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Cancel"
|
||||
class="btn btn-sm btn-light text-uppercase btnLight"
|
||||
class="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-row align-items-center"
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ exports[`Repository Modal - repositories section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Add repository"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="repoBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -52,11 +52,11 @@ exports[`Transfer Repository Modal - packages section creates snapshot 1`] = `
|
|||
novalidate=""
|
||||
>
|
||||
<div
|
||||
class="form-check mb-3"
|
||||
class="custom-control custom-radio mb-3"
|
||||
>
|
||||
<input
|
||||
checked=""
|
||||
class="form-check-input"
|
||||
class="custom-control-input"
|
||||
data-testid="radio_user"
|
||||
id="user"
|
||||
name="transfer"
|
||||
|
|
@ -65,17 +65,17 @@ exports[`Transfer Repository Modal - packages section creates snapshot 1`] = `
|
|||
value="user"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label font-weight-bold label"
|
||||
class="custom-control-label font-weight-bold label"
|
||||
for="user"
|
||||
>
|
||||
Transfer to my user
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="form-check mb-3"
|
||||
class="custom-control custom-radio mb-3"
|
||||
>
|
||||
<input
|
||||
class="form-check-input"
|
||||
class="custom-control-input"
|
||||
data-testid="radio_org"
|
||||
id="org"
|
||||
name="transfer"
|
||||
|
|
@ -84,7 +84,7 @@ exports[`Transfer Repository Modal - packages section creates snapshot 1`] = `
|
|||
value="org"
|
||||
/>
|
||||
<label
|
||||
class="form-check-label font-weight-bold label"
|
||||
class="custom-control-label font-weight-bold label"
|
||||
for="org"
|
||||
>
|
||||
Transfer to organization
|
||||
|
|
@ -137,7 +137,7 @@ exports[`Transfer Repository Modal - packages section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Transfer repository"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="transferRepoBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ exports[`Repository index creates snapshot 1`] = `
|
|||
<div>
|
||||
<button
|
||||
aria-label="Refresh repositories list"
|
||||
class="btn btn-secondary btn-sm text-uppercase mr-0 mr-md-2 btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase mr-0 mr-md-2 btnAction"
|
||||
data-testid="refreshRepoBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -64,7 +64,7 @@ exports[`Repository index creates snapshot 1`] = `
|
|||
</button>
|
||||
<button
|
||||
aria-label="Open claim repository modal"
|
||||
class="btn btn-secondary btn-sm text-uppercase mr-0 mr-md-2 btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase mr-0 mr-md-2 btnAction"
|
||||
data-testid="claimRepoBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -102,7 +102,7 @@ exports[`Repository index creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="btn btn-secondary btn-sm text-uppercase btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase btnAction"
|
||||
data-testid="addRepoBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const RepositoriesSection = (props: Props) => {
|
|||
<div>
|
||||
<button
|
||||
data-testid="refreshRepoBtn"
|
||||
className={`btn btn-secondary btn-sm text-uppercase mr-0 mr-md-2 ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase mr-0 mr-md-2 ${styles.btnAction}`}
|
||||
onClick={fetchRepositories}
|
||||
aria-label="Refresh repositories list"
|
||||
>
|
||||
|
|
@ -104,7 +104,7 @@ const RepositoriesSection = (props: Props) => {
|
|||
|
||||
<button
|
||||
data-testid="claimRepoBtn"
|
||||
className={`btn btn-secondary btn-sm text-uppercase mr-0 mr-md-2 ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase mr-0 mr-md-2 ${styles.btnAction}`}
|
||||
onClick={() => setOpenClaimRepo(true)}
|
||||
aria-label="Open claim repository modal"
|
||||
>
|
||||
|
|
@ -116,7 +116,7 @@ const RepositoriesSection = (props: Props) => {
|
|||
|
||||
<ActionBtn
|
||||
testId="addRepoBtn"
|
||||
className={`btn btn-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
contentClassName="justify-content-center"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
|
|
@ -180,7 +180,7 @@ const RepositoriesSection = (props: Props) => {
|
|||
|
||||
<ActionBtn
|
||||
testId="addFirstRepoBtn"
|
||||
className="btn btn-secondary"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
setModalStatus({ open: true });
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ exports[`Authorization settings index creates snapshot 1`] = `
|
|||
class="mt-4 mt-md-5"
|
||||
>
|
||||
<p>
|
||||
Artifact Hub allows you to setup fine-grained access control based on authorization policies. Authorization polices are written in
|
||||
allows you to setup fine-grained access control based on authorization policies. Authorization polices are written in
|
||||
<a
|
||||
aria-label="Open rego documentation"
|
||||
class="link text-reset link"
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import authorizer from '../../../../../utils/authorizer';
|
|||
import { checkUnsavedPolicyChanges, PolicyChangeAction } from '../../../../../utils/checkUnsavedPolicyChanges';
|
||||
import compoundErrorMessage from '../../../../../utils/compoundErrorMessage';
|
||||
import { PREDEFINED_POLICIES } from '../../../../../utils/data';
|
||||
import getMetaTag from '../../../../../utils/getMetaTag';
|
||||
import isValidJSON from '../../../../../utils/isValidJSON';
|
||||
import prepareRegoPolicyForPlayground from '../../../../../utils/prepareRegoPolicyForPlayground';
|
||||
import stringifyPolicyData from '../../../../../utils/stringifyPolicyData';
|
||||
|
|
@ -62,6 +63,7 @@ const DEFAULT_POLICY_NAME = 'rbac.v1';
|
|||
|
||||
const AuthorizationSection = (props: Props) => {
|
||||
const { ctx, dispatch } = useContext(AppCtx);
|
||||
const siteName = getMetaTag('siteName');
|
||||
const updateActionBtn = useRef<RefActionBtn>(null);
|
||||
const [apiError, setApiError] = useState<string | JSX.Element | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
|
@ -396,7 +398,7 @@ const AuthorizationSection = (props: Props) => {
|
|||
|
||||
<div className="mt-4 mt-md-5" onClick={() => setApiError(null)}>
|
||||
<p>
|
||||
Artifact Hub allows you to setup fine-grained access control based on authorization policies. Authorization
|
||||
{siteName} allows you to setup fine-grained access control based on authorization policies. Authorization
|
||||
polices are written in{' '}
|
||||
<ExternalLink
|
||||
href="https://www.openpolicyagent.org/docs/latest/#rego"
|
||||
|
|
@ -580,7 +582,7 @@ const AuthorizationSection = (props: Props) => {
|
|||
<ActionBtn
|
||||
ref={updateActionBtn}
|
||||
testId="updateAuthorizationPolicyBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
onSaveAuthorizationPolicy();
|
||||
|
|
@ -617,7 +619,7 @@ const AuthorizationSection = (props: Props) => {
|
|||
<>
|
||||
<button
|
||||
data-testid="modalCancelBtn"
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={() => setConfirmationModal({ open: false })}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
|
|
@ -626,7 +628,7 @@ const AuthorizationSection = (props: Props) => {
|
|||
|
||||
<button
|
||||
data-testid="modalOKBtn"
|
||||
className="btn btn-sm btn-primary text-uppercase ml-3"
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase ml-3"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
confirmationModal.onConfirm!();
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ const DeleteOrganization = (props: Props) => {
|
|||
closeButton={
|
||||
<>
|
||||
<button
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={() => setOpenStatus(false)}
|
||||
aria-label="Close"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const UpdateOrganization = (props: Props) => {
|
|||
<div className="mt-4">
|
||||
<ActionBtn
|
||||
testId="updateOrgBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
submitForm();
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ exports[`DeleteOrg creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
class="btn btn-sm btn-light text-uppercase btnLight"
|
||||
class="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-row align-items-center"
|
||||
|
|
|
|||
|
|
@ -173,7 +173,7 @@ exports[`Organization settings index creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Action"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="updateOrgBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
height: 1.75rem;
|
||||
width: 1.75rem;
|
||||
border-radius: 50% !important;
|
||||
background-color: var(--color-1-10) !important;
|
||||
line-height: 1rem;
|
||||
background-color: var(--white) !important;
|
||||
border-color: var(--color-1-500);
|
||||
line-height: 0.85rem;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const APIKeyCard = (props: Props) => {
|
|||
closeButton={
|
||||
<>
|
||||
<button
|
||||
className={`btn btn-sm btn-light text-uppercase ${styles.btnLight}`}
|
||||
className="btn btn-sm btn-outline-secondary text-uppercase"
|
||||
onClick={() => setDeletionModalStatus(false)}
|
||||
aria-label="Cancel"
|
||||
>
|
||||
|
|
@ -126,7 +126,7 @@ const APIKeyCard = (props: Props) => {
|
|||
|
||||
<button
|
||||
data-testid="updateAPIKeyBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -145,7 +145,7 @@ const APIKeyCard = (props: Props) => {
|
|||
|
||||
<button
|
||||
data-testid="deleteAPIKeyModalBtn"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
className="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.preventDefault();
|
||||
closeDropdown();
|
||||
|
|
@ -161,7 +161,7 @@ const APIKeyCard = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<button
|
||||
className={`btn btn-light p-0 text-secondary text-center ${styles.btnDropdown}`}
|
||||
className={`btn p-0 text-primary text-center iconSubsWrapper ${styles.btnDropdown}`}
|
||||
onClick={() => setDropdownMenuStatus(true)}
|
||||
aria-label="Open menu"
|
||||
aria-expanded={dropdownMenuStatus}
|
||||
|
|
@ -180,7 +180,7 @@ const APIKeyCard = (props: Props) => {
|
|||
<div className={`position-absolute ${styles.copyBtnWrapper}`}>
|
||||
<ButtonCopyToClipboard
|
||||
text={props.apiKey.apiKeyId!}
|
||||
className="btn-link border-0 text-secondary font-weight-bold"
|
||||
className="btn-link border-0 text-dark font-weight-bold"
|
||||
label="Copy API key ID to clipboard"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const APIKeyModal = (props: Props) => {
|
|||
const sendBtn = (
|
||||
<button
|
||||
data-testid="apiKeyFormBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ exports[`API key Card - API keys section creates snapshot 1`] = `
|
|||
/>
|
||||
<button
|
||||
aria-label="Open API key modal"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="updateAPIKeyBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -58,7 +58,7 @@ exports[`API key Card - API keys section creates snapshot 1`] = `
|
|||
</button>
|
||||
<button
|
||||
aria-label="Open deletion modal"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-secondary"
|
||||
class="dropdown-item btn btn-sm rounded-0 text-dark"
|
||||
data-testid="deleteAPIKeyModalBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -87,7 +87,7 @@ exports[`API key Card - API keys section creates snapshot 1`] = `
|
|||
<button
|
||||
aria-expanded="false"
|
||||
aria-label="Open menu"
|
||||
class="btn btn-light p-0 text-secondary text-center btnDropdown"
|
||||
class="btn p-0 text-primary text-center iconSubsWrapper btnDropdown"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
|
|
@ -133,7 +133,7 @@ exports[`API key Card - API keys section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Copy API key ID to clipboard"
|
||||
class="btn btn-sm btn-link border-0 text-secondary font-weight-bold"
|
||||
class="btn btn-sm btn-link border-0 text-dark font-weight-bold"
|
||||
data-testid="ctcBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ exports[`APIKeyModal - API keys section creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Add API key"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="apiKeyFormBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ exports[`API keys section index creates snapshot 1`] = `
|
|||
<div>
|
||||
<button
|
||||
aria-label="Open modal to add API key"
|
||||
class="btn btn-secondary btn-sm text-uppercase btnAction"
|
||||
class="btn btn-outline-secondary btn-sm text-uppercase btnAction"
|
||||
data-testid="addAPIKeyBtn"
|
||||
>
|
||||
<div
|
||||
|
|
@ -135,7 +135,7 @@ exports[`API keys section index creates snapshot 1`] = `
|
|||
>
|
||||
<button
|
||||
aria-label="Add API key"
|
||||
class="btn btn-sm btn-secondary"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-testid="apiKeyFormBtn"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ const APIKeysSection = (props: Props) => {
|
|||
<div>
|
||||
<button
|
||||
data-testid="addAPIKeyBtn"
|
||||
className={`btn btn-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
|
||||
onClick={() => setModalStatus({ open: true })}
|
||||
aria-label="Open modal to add API key"
|
||||
>
|
||||
|
|
@ -85,7 +85,7 @@ const APIKeysSection = (props: Props) => {
|
|||
<button
|
||||
data-testid="addFirstAPIKeyBtn"
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
className="btn btn-outline-secondary"
|
||||
onClick={() => setModalStatus({ open: true })}
|
||||
aria-label="Add API key"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ const UpdatePassword = () => {
|
|||
<div className="mt-4 mb-2">
|
||||
<button
|
||||
data-testid="updatePasswordBtn"
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ const UpdateProfile = (props: Props) => {
|
|||
|
||||
<div className="mt-4">
|
||||
<button
|
||||
className="btn btn-sm btn-secondary"
|
||||
className="btn btn-sm btn-outline-secondary"
|
||||
type="button"
|
||||
disabled={isSending}
|
||||
onClick={submitForm}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue