mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-28 19:22:26 +00:00
Compare commits
1 Commits
refactor/c
...
feat/depre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3474ec17a7 |
@@ -170,7 +170,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
|
||||
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
|
||||
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
|
||||
|
||||
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId))
|
||||
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId), types.UserStatusActive)
|
||||
if err != nil {
|
||||
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
Description: "This endpoint creates an invite for a user",
|
||||
Request: new(types.PostableInvite),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.Invite),
|
||||
Response: new(types.User),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
|
||||
@@ -43,73 +43,73 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := router.Handle("/api/v1/invite/{token}", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetInvite), handler.OpenAPIDef{
|
||||
ID: "GetInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Get invite",
|
||||
Description: "This endpoint gets an invite by token",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: new(types.Invite),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
// if err := router.Handle("/api/v1/invite/{token}", handler.New(provider.authZ.OpenAccess(provider.userHandler.GetInvite), handler.OpenAPIDef{
|
||||
// ID: "GetInvite",
|
||||
// Tags: []string{"users"},
|
||||
// Summary: "Get invite",
|
||||
// Description: "This endpoint gets an invite by token",
|
||||
// Request: nil,
|
||||
// RequestContentType: "",
|
||||
// Response: new(types.Invite),
|
||||
// ResponseContentType: "application/json",
|
||||
// SuccessStatusCode: http.StatusOK,
|
||||
// ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
// Deprecated: false,
|
||||
// SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
// })).Methods(http.MethodGet).GetError(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := router.Handle("/api/v1/invite/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteInvite), handler.OpenAPIDef{
|
||||
ID: "DeleteInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Delete invite",
|
||||
Description: "This endpoint deletes an invite by id",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: nil,
|
||||
ResponseContentType: "",
|
||||
SuccessStatusCode: http.StatusNoContent,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
// if err := router.Handle("/api/v1/invite/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.DeleteInvite), handler.OpenAPIDef{
|
||||
// ID: "DeleteInvite",
|
||||
// Tags: []string{"users"},
|
||||
// Summary: "Delete invite",
|
||||
// Description: "This endpoint deletes an invite by id",
|
||||
// Request: nil,
|
||||
// RequestContentType: "",
|
||||
// Response: nil,
|
||||
// ResponseContentType: "",
|
||||
// SuccessStatusCode: http.StatusNoContent,
|
||||
// ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
// Deprecated: false,
|
||||
// SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
// })).Methods(http.MethodDelete).GetError(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListInvite), handler.OpenAPIDef{
|
||||
ID: "ListInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "List invites",
|
||||
Description: "This endpoint lists all invites",
|
||||
Request: nil,
|
||||
RequestContentType: "",
|
||||
Response: make([]*types.Invite, 0),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusOK,
|
||||
ErrorStatusCodes: []int{},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
})).Methods(http.MethodGet).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
// if err := router.Handle("/api/v1/invite", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListInvite), handler.OpenAPIDef{
|
||||
// ID: "ListInvite",
|
||||
// Tags: []string{"users"},
|
||||
// Summary: "List invites",
|
||||
// Description: "This endpoint lists all invites",
|
||||
// Request: nil,
|
||||
// RequestContentType: "",
|
||||
// Response: make([]*types.Invite, 0),
|
||||
// ResponseContentType: "application/json",
|
||||
// SuccessStatusCode: http.StatusOK,
|
||||
// ErrorStatusCodes: []int{},
|
||||
// Deprecated: false,
|
||||
// SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
|
||||
// })).Methods(http.MethodGet).GetError(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := router.Handle("/api/v1/invite/accept", handler.New(provider.authZ.OpenAccess(provider.userHandler.AcceptInvite), handler.OpenAPIDef{
|
||||
ID: "AcceptInvite",
|
||||
Tags: []string{"users"},
|
||||
Summary: "Accept invite",
|
||||
Description: "This endpoint accepts an invite by token",
|
||||
Request: new(types.PostableAcceptInvite),
|
||||
RequestContentType: "application/json",
|
||||
Response: new(types.User),
|
||||
ResponseContentType: "application/json",
|
||||
SuccessStatusCode: http.StatusCreated,
|
||||
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
Deprecated: false,
|
||||
SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
})).Methods(http.MethodPost).GetError(); err != nil {
|
||||
return err
|
||||
}
|
||||
// if err := router.Handle("/api/v1/invite/accept", handler.New(provider.authZ.OpenAccess(provider.userHandler.AcceptInvite), handler.OpenAPIDef{
|
||||
// ID: "AcceptInvite",
|
||||
// Tags: []string{"users"},
|
||||
// Summary: "Accept invite",
|
||||
// Description: "This endpoint accepts an invite by token",
|
||||
// Request: new(types.PostableAcceptInvite),
|
||||
// RequestContentType: "application/json",
|
||||
// Response: new(types.User),
|
||||
// ResponseContentType: "application/json",
|
||||
// SuccessStatusCode: http.StatusCreated,
|
||||
// ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
|
||||
// Deprecated: false,
|
||||
// SecuritySchemes: []handler.OpenAPISecurityScheme{},
|
||||
// })).Methods(http.MethodPost).GetError(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if err := router.Handle("/api/v1/pats", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateAPIKey), handler.OpenAPIDef{
|
||||
ID: "CreateAPIKey",
|
||||
|
||||
@@ -3,9 +3,10 @@ package flagger
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
var (
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
FeatureKafkaSpanEval = featuretypes.MustNewName("kafka_span_eval")
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
FeatureKafkaSpanEval = featuretypes.MustNewName("kafka_span_eval")
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureSoftDeleteUsers = featuretypes.MustNewName("soft_delete_users")
|
||||
)
|
||||
|
||||
func MustNewRegistry() featuretypes.Registry {
|
||||
@@ -34,6 +35,14 @@ func MustNewRegistry() featuretypes.Registry {
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureSoftDeleteUsers,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Stage: featuretypes.StageStable,
|
||||
Description: "Controls whether users are soft deleted or not",
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -141,7 +141,7 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
|
||||
roleMapping := authDomain.AuthDomainConfig().RoleMapping
|
||||
role := roleMapping.NewRoleFromCallbackIdentity(callbackIdentity)
|
||||
|
||||
user, err := types.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID)
|
||||
user, err := types.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, types.UserStatusActive)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -26,24 +26,24 @@ func NewHandler(module root.Module, getter root.Getter) root.Handler {
|
||||
return &handler{module: module, getter: getter}
|
||||
}
|
||||
|
||||
func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
// func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
req := new(types.PostableAcceptInvite)
|
||||
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// req := new(types.PostableAcceptInvite)
|
||||
// if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
user, err := h.module.AcceptInvite(ctx, req.InviteToken, req.Password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// user, err := h.module.AcceptInvite(ctx, req.InviteToken, req.Password)
|
||||
// if err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
render.Success(w, http.StatusCreated, user)
|
||||
}
|
||||
// render.Success(w, http.StatusCreated, user)
|
||||
// }
|
||||
|
||||
func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
@@ -61,7 +61,7 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
|
||||
invitedUsers, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
|
||||
Invites: []types.PostableInvite{req},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -69,7 +69,7 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusCreated, invites[0])
|
||||
render.Success(rw, http.StatusCreated, invitedUsers[0])
|
||||
}
|
||||
|
||||
func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
@@ -103,63 +103,63 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
render.Success(rw, http.StatusCreated, nil)
|
||||
}
|
||||
|
||||
func (h *handler) GetInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
// func (h *handler) GetInvite(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
token := mux.Vars(r)["token"]
|
||||
invite, err := h.module.GetInviteByToken(ctx, token)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// token := mux.Vars(r)["token"]
|
||||
// invite, err := h.module.GetInviteByToken(ctx, token)
|
||||
// if err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
render.Success(w, http.StatusOK, invite)
|
||||
}
|
||||
// render.Success(w, http.StatusOK, invite)
|
||||
// }
|
||||
|
||||
func (h *handler) ListInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
// func (h *handler) ListInvite(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
// if err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
invites, err := h.module.ListInvite(ctx, claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// invites, err := h.module.ListInvite(ctx, claims.OrgID)
|
||||
// if err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
render.Success(w, http.StatusOK, invites)
|
||||
}
|
||||
// render.Success(w, http.StatusOK, invites)
|
||||
// }
|
||||
|
||||
func (h *handler) DeleteInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
// func (h *handler) DeleteInvite(w http.ResponseWriter, r *http.Request) {
|
||||
// ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
// defer cancel()
|
||||
|
||||
id := mux.Vars(r)["id"]
|
||||
// id := mux.Vars(r)["id"]
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
// claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
// if err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
uuid, err := valuer.NewUUID(id)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||
return
|
||||
}
|
||||
// uuid, err := valuer.NewUUID(id)
|
||||
// if err != nil {
|
||||
// render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||
// return
|
||||
// }
|
||||
|
||||
if err := h.module.DeleteInvite(ctx, claims.OrgID, uuid); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
||||
// if err := h.module.DeleteInvite(ctx, claims.OrgID, uuid); err != nil {
|
||||
// render.Error(w, err)
|
||||
// return
|
||||
// }
|
||||
// render.Success(w, http.StatusNoContent, nil)
|
||||
// }
|
||||
|
||||
func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
root "github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -19,6 +20,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/emailtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/dustin/go-humanize"
|
||||
@@ -33,10 +35,11 @@ type Module struct {
|
||||
authz authz.AuthZ
|
||||
analytics analytics.Analytics
|
||||
config user.Config
|
||||
flagger flagger.Flagger
|
||||
}
|
||||
|
||||
// This module is a WIP, don't take inspiration from this.
|
||||
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config) root.Module {
|
||||
func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing emailing.Emailing, providerSettings factory.ProviderSettings, orgSetter organization.Setter, authz authz.AuthZ, analytics analytics.Analytics, config user.Config, flagger flagger.Flagger) root.Module {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
|
||||
return &Module{
|
||||
store: store,
|
||||
@@ -47,57 +50,59 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
|
||||
analytics: analytics,
|
||||
authz: authz,
|
||||
config: config,
|
||||
flagger: flagger,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*types.User, error) {
|
||||
invite, err := m.store.GetInviteByToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*types.User, error) {
|
||||
// invite, err := m.store.GetInviteByToken(ctx, token)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
user, err := types.NewUser(invite.Name, invite.Email, invite.Role, invite.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// user, err := types.NewUser(invite.Name, invite.Email, invite.Role, invite.OrgID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
factorPassword, err := types.NewFactorPassword(password, user.ID.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// factorPassword, err := types.NewFactorPassword(password, user.ID.StringValue())
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
err = m.CreateUser(ctx, user, root.WithFactorPassword(factorPassword))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// err = m.CreateUser(ctx, user, root.WithFactorPassword(factorPassword))
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
if err := m.DeleteInvite(ctx, invite.OrgID.String(), invite.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// if err := m.DeleteInvite(ctx, invite.OrgID.String(), invite.ID); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return user, nil
|
||||
}
|
||||
// return user, nil
|
||||
// }
|
||||
|
||||
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Invite, error) {
|
||||
invite, err := m.store.GetInviteByToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Invite, error) {
|
||||
// invite, err := m.store.GetInviteByToken(ctx, token)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
// return invite, nil
|
||||
// }
|
||||
|
||||
// CreateBulk implements invite.Module.
|
||||
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error) {
|
||||
func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.User, error) {
|
||||
//! TODO this is an incomplete implementation - will fix this
|
||||
creator, err := m.store.GetUser(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invites := make([]*types.Invite, 0, len(bulkInvites.Invites))
|
||||
invitedUsers := make([]*types.User, 0, len(bulkInvites.Invites))
|
||||
|
||||
for _, invite := range bulkInvites.Invites {
|
||||
// check if user exists
|
||||
// check and active user already exists with this email
|
||||
existingUser, err := m.store.GetUserByEmailAndOrgID(ctx, invite.Email, orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
@@ -105,70 +110,91 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
|
||||
|
||||
if existingUser != nil {
|
||||
if err := existingUser.ErrIfRoot(); err != nil {
|
||||
return nil, errors.WithAdditionalf(err, "cannot send invite to root user")
|
||||
return nil, errors.WithAdditionalf(err, "Cannot send invite to root user")
|
||||
}
|
||||
}
|
||||
|
||||
if existingUser != nil {
|
||||
// check if a pending invite already exists
|
||||
if existingUser.Status == types.UserStatusPendingInvite {
|
||||
return nil, errors.New(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email")
|
||||
}
|
||||
|
||||
// if user is in soft deleted state, we reinitiate that
|
||||
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
return nil, errors.New(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "User already exists with the same email")
|
||||
}
|
||||
|
||||
// Check if an invite already exists
|
||||
existingInvite, err := m.store.GetInviteByEmailAndOrgID(ctx, invite.Email, orgID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if existingInvite != nil {
|
||||
return nil, errors.New(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email")
|
||||
}
|
||||
|
||||
role, err := types.NewRole(invite.Role.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newInvite, err := types.NewInvite(invite.Name, role, orgID, invite.Email)
|
||||
// create a new user with pending invite status
|
||||
newUser, err := types.NewUser(invite.Name, invite.Email, role, orgID, types.UserStatusPendingInvite)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newInvite.InviteLink = fmt.Sprintf("%s/signup?token=%s", invite.FrontendBaseUrl, newInvite.Token)
|
||||
invites = append(invites, newInvite)
|
||||
// generate a temp password
|
||||
password, err := types.GenerateFactorPassword(newUser.ID.StringValue())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// store the user and password in db
|
||||
err = m.createUserWithoutGrant(ctx, newUser, root.WithFactorPassword(password))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
invitedUsers = append(invitedUsers, newUser)
|
||||
}
|
||||
|
||||
err = m.store.CreateBulkInvite(ctx, invites)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// send password reset emails to all the invited users
|
||||
for i, invitedUser := range invitedUsers {
|
||||
m.analytics.TrackUser(ctx, orgID.String(), creator.ID.String(), "Invite Sent", map[string]any{
|
||||
"invitee_email": invitedUser.Email,
|
||||
"invitee_role": invitedUser.Role,
|
||||
})
|
||||
|
||||
for i := 0; i < len(invites); i++ {
|
||||
m.analytics.TrackUser(ctx, orgID.String(), creator.ID.String(), "Invite Sent", map[string]any{"invitee_email": invites[i].Email, "invitee_role": invites[i].Role})
|
||||
|
||||
// if the frontend base url is not provided, we don't send the email
|
||||
if bulkInvites.Invites[i].FrontendBaseUrl == "" {
|
||||
m.settings.Logger().InfoContext(ctx, "frontend base url is not provided, skipping email", "invitee_email", invites[i].Email)
|
||||
frontendBaseUrl := bulkInvites.Invites[i].FrontendBaseUrl
|
||||
if frontendBaseUrl == "" {
|
||||
m.settings.Logger().InfoContext(ctx, "frontend base url is not provided, skipping email", "invitee_email", invitedUser.Email)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.emailing.SendHTML(ctx, invites[i].Email.String(), "You're Invited to Join SigNoz", emailtypes.TemplateNameInvitationEmail, map[string]any{
|
||||
"inviter_email": creator.Email,
|
||||
"link": fmt.Sprintf("%s/signup?token=%s", bulkInvites.Invites[i].FrontendBaseUrl, invites[i].Token),
|
||||
}); err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err)
|
||||
// generate reset password token
|
||||
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, invitedUser.ID)
|
||||
if err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to create reset password token for invited user", "error", err)
|
||||
continue
|
||||
}
|
||||
|
||||
resetLink := m.resetLink(frontendBaseUrl, resetPasswordToken.Token)
|
||||
|
||||
tokenLifetime := m.config.Password.Reset.MaxTokenLifetime
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
|
||||
// TODO! improve invitation email text and add expiry details too
|
||||
if err := m.emailing.SendHTML(ctx, invitedUser.Email.String(), "You're Invited to Join SigNoz", emailtypes.TemplateNameInvitationEmail, map[string]any{
|
||||
"inviter_email": creator.Email,
|
||||
"link": resetLink,
|
||||
"Expiry": humanizedTokenLifetime,
|
||||
}); err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to send invite email", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return invites, nil
|
||||
return invitedUsers, nil
|
||||
}
|
||||
|
||||
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
return m.store.ListInvite(ctx, orgID)
|
||||
}
|
||||
// func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.User, error) {
|
||||
// return m.store.ListPendingInviteUsers(ctx, orgID)
|
||||
// }
|
||||
|
||||
func (m *Module) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
return m.store.DeleteInvite(ctx, orgID, id)
|
||||
}
|
||||
// func (m *Module) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
// return m.store.DeleteInvite(ctx, orgID, id)
|
||||
// }
|
||||
|
||||
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
@@ -299,12 +325,23 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
return err
|
||||
}
|
||||
|
||||
if err := module.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil {
|
||||
return err
|
||||
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
|
||||
softDeleteUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureSoftDeleteUsers, evalCtx)
|
||||
|
||||
if softDeleteUsers {
|
||||
user.UpdateStatus(types.UserStatusDeleted)
|
||||
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := module.store.DeleteUser(ctx, orgID.String(), user.ID.StringValue()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Deleted", map[string]any{
|
||||
"deleted_by": deletedBy,
|
||||
"deleted_by": deletedBy,
|
||||
"is_soft_delete": softDeleteUsers,
|
||||
})
|
||||
|
||||
return nil
|
||||
@@ -392,7 +429,7 @@ func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, ema
|
||||
return err
|
||||
}
|
||||
|
||||
resetLink := fmt.Sprintf("%s/password-reset?token=%s", frontendBaseURL, token.Token)
|
||||
resetLink := module.resetLink(frontendBaseURL, token.Token)
|
||||
|
||||
tokenLifetime := module.config.Password.Reset.MaxTokenLifetime
|
||||
humanizedTokenLifetime := strings.TrimSpace(humanize.RelTime(time.Now(), time.Now().Add(tokenLifetime), "", ""))
|
||||
@@ -442,6 +479,25 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
|
||||
return err
|
||||
}
|
||||
|
||||
// update the status of user if this a newly invited user and also grant authz
|
||||
if user.Status == types.UserStatusPendingInvite {
|
||||
err = module.authz.Grant(
|
||||
ctx,
|
||||
user.OrgID,
|
||||
roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role),
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.UpdateStatus(types.UserStatusActive)
|
||||
err = module.store.UpdateUser(ctx, user.OrgID, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return module.store.UpdatePassword(ctx, password)
|
||||
}
|
||||
|
||||
@@ -597,3 +653,7 @@ func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.U
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (module *Module) resetLink(frontendBaseUrl string, token string) string {
|
||||
return fmt.Sprintf("%s/password-reset?token=%s", frontendBaseUrl, token)
|
||||
}
|
||||
|
||||
@@ -26,75 +26,75 @@ func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) typ
|
||||
}
|
||||
|
||||
// CreateBulkInvite implements types.InviteStore.
|
||||
func (store *store) CreateBulkInvite(ctx context.Context, invites []*types.Invite) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(&invites).
|
||||
Exec(ctx)
|
||||
// func (store *store) CreateBulkInvite(ctx context.Context, invites []*types.Invite) error {
|
||||
// _, err := store.sqlstore.BunDB().NewInsert().
|
||||
// Model(&invites).
|
||||
// Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrInviteAlreadyExists, "invite with email: %s already exists in org: %s", invites[0].Email, invites[0].OrgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// if err != nil {
|
||||
// return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrInviteAlreadyExists, "invite with email: %s already exists in org: %s", invites[0].Email, invites[0].OrgID)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Delete implements types.InviteStore.
|
||||
func (store *store) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
_, err := store.sqlstore.BunDB().NewDelete().
|
||||
Model(&types.Invite{}).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with id: %s does not exist in org: %s", id.StringValue(), orgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// func (store *store) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
// _, err := store.sqlstore.BunDB().NewDelete().
|
||||
// Model(&types.Invite{}).
|
||||
// Where("org_id = ?", orgID).
|
||||
// Where("id = ?", id).
|
||||
// Exec(ctx)
|
||||
// if err != nil {
|
||||
// return store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with id: %s does not exist in org: %s", id.StringValue(), orgID)
|
||||
// }
|
||||
// return nil
|
||||
// }
|
||||
|
||||
func (store *store) GetInviteByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.Invite, error) {
|
||||
invite := new(types.Invite)
|
||||
// func (store *store) GetInviteByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.Invite, error) {
|
||||
// invite := new(types.Invite)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).NewSelect().
|
||||
Model(invite).
|
||||
Where("email = ?", email).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with email %s does not exist in org %s", email, orgID)
|
||||
}
|
||||
// err := store.
|
||||
// sqlstore.
|
||||
// BunDBCtx(ctx).NewSelect().
|
||||
// Model(invite).
|
||||
// Where("email = ?", email).
|
||||
// Where("org_id = ?", orgID).
|
||||
// Scan(ctx)
|
||||
// if err != nil {
|
||||
// return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with email %s does not exist in org %s", email, orgID)
|
||||
// }
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
// return invite, nil
|
||||
// }
|
||||
|
||||
func (store *store) GetInviteByToken(ctx context.Context, token string) (*types.GettableInvite, error) {
|
||||
invite := new(types.Invite)
|
||||
// func (store *store) GetInviteByToken(ctx context.Context, token string) (*types.GettableInvite, error) {
|
||||
// invite := new(types.Invite)
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(invite).
|
||||
Where("token = ?", token).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite does not exist", token)
|
||||
}
|
||||
// err := store.
|
||||
// sqlstore.
|
||||
// BunDBCtx(ctx).
|
||||
// NewSelect().
|
||||
// Model(invite).
|
||||
// Where("token = ?", token).
|
||||
// Scan(ctx)
|
||||
// if err != nil {
|
||||
// return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite does not exist", token)
|
||||
// }
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
// return invite, nil
|
||||
// }
|
||||
|
||||
func (store *store) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
invites := new([]*types.Invite)
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(invites).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with org id: %s does not exist", orgID)
|
||||
}
|
||||
return *invites, nil
|
||||
}
|
||||
// func (store *store) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
// invites := new([]*types.Invite)
|
||||
// err := store.sqlstore.BunDB().NewSelect().
|
||||
// Model(invites).
|
||||
// Where("org_id = ?", orgID).
|
||||
// Scan(ctx)
|
||||
// if err != nil {
|
||||
// return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with org id: %s does not exist", orgID)
|
||||
// }
|
||||
// return *invites, nil
|
||||
// }
|
||||
|
||||
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) error {
|
||||
_, err := store.
|
||||
@@ -202,6 +202,7 @@ func (store *store) GetUsersByRoleAndOrgID(ctx context.Context, role types.Role,
|
||||
Model(&users).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("role = ?", role).
|
||||
Where("status = ?", types.UserStatusActive.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -221,6 +222,7 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *typ
|
||||
Column("role").
|
||||
Column("is_root").
|
||||
Column("updated_at").
|
||||
Column("status").
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", user.ID).
|
||||
Exec(ctx)
|
||||
@@ -239,6 +241,7 @@ func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]
|
||||
NewSelect().
|
||||
Model(&users).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("status = ?", types.UserStatusActive.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -574,6 +577,7 @@ func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64,
|
||||
NewSelect().
|
||||
Model(user).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("status = ?", types.UserStatusActive.StringValue()).
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -638,3 +642,21 @@ func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (store *store) ListPendingInviteUsers(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
|
||||
users := []*types.User{}
|
||||
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&users).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("status = ?", types.UserStatusPendingInvite.StringValue()).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
@@ -40,11 +40,11 @@ type Module interface {
|
||||
DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error
|
||||
|
||||
// invite
|
||||
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.Invite, error)
|
||||
ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error)
|
||||
DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error
|
||||
AcceptInvite(ctx context.Context, token string, password string) (*types.User, error)
|
||||
GetInviteByToken(ctx context.Context, token string) (*types.Invite, error)
|
||||
CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, bulkInvites *types.PostableBulkInviteRequest) ([]*types.User, error)
|
||||
// ListInvite(ctx context.Context, orgID string) ([]*types.User, error)
|
||||
// DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error
|
||||
// AcceptInvite(ctx context.Context, token string, password string) (*types.User, error)
|
||||
// GetInviteByToken(ctx context.Context, token string) (*types.Invite, error)
|
||||
|
||||
// API KEY
|
||||
CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error
|
||||
@@ -85,10 +85,10 @@ type Getter interface {
|
||||
type Handler interface {
|
||||
// invite
|
||||
CreateInvite(http.ResponseWriter, *http.Request)
|
||||
AcceptInvite(http.ResponseWriter, *http.Request)
|
||||
GetInvite(http.ResponseWriter, *http.Request) // public function
|
||||
ListInvite(http.ResponseWriter, *http.Request)
|
||||
DeleteInvite(http.ResponseWriter, *http.Request)
|
||||
// AcceptInvite(http.ResponseWriter, *http.Request)
|
||||
// GetInvite(http.ResponseWriter, *http.Request) // public function
|
||||
// ListInvite(http.ResponseWriter, *http.Request)
|
||||
// DeleteInvite(http.ResponseWriter, *http.Request)
|
||||
CreateBulkInvite(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListUsers(http.ResponseWriter, *http.Request)
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestNewHandlers(t *testing.T) {
|
||||
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), flagger)
|
||||
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, flagger)
|
||||
|
||||
querierHandler := querier.NewHandler(providerSettings, nil, nil)
|
||||
handlers := NewHandlers(modules, providerSettings, nil, querierHandler, nil, nil, nil, nil, nil, nil, nil)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/cache"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
@@ -86,10 +87,11 @@ func NewModules(
|
||||
config Config,
|
||||
dashboard dashboard.Module,
|
||||
userGetter user.Getter,
|
||||
flagger flagger.Flagger,
|
||||
) Modules {
|
||||
quickfilter := implquickfilter.NewModule(implquickfilter.NewStore(sqlstore))
|
||||
orgSetter := implorganization.NewSetter(implorganization.NewStore(sqlstore), alertmanager, quickfilter)
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, authz, analytics, config.User)
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, authz, analytics, config.User, flagger)
|
||||
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
|
||||
|
||||
return Modules{
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestNewModules(t *testing.T) {
|
||||
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), flagger)
|
||||
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, nil, nil, nil, nil, nil, nil, nil, queryParser, Config{}, dashboardModule, userGetter, flagger)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
||||
@@ -388,7 +388,7 @@ func New(
|
||||
}
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter)
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard, userGetter, flagger)
|
||||
|
||||
userService := impluser.NewService(providerSettings, impluser.NewStore(sqlstore, providerSettings), modules.User, orgGetter, authz, config.User.Root)
|
||||
|
||||
|
||||
88
pkg/sqlmigration/067_add_status_user.go
Normal file
88
pkg/sqlmigration/067_add_status_user.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type addStatusUser struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func AddStatusUserFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(
|
||||
factory.MustNewName("add_status_user"),
|
||||
func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &addStatusUser{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (migration *addStatusUser) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addStatusUser) Up(ctx context.Context, db *bun.DB) error {
|
||||
if err := migration.sqlschema.ToggleFKEnforcement(ctx, db, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
table, uniqueConstraints, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("users"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
column := &sqlschema.Column{
|
||||
Name: sqlschema.ColumnName("status"),
|
||||
DataType: sqlschema.DataTypeText,
|
||||
Nullable: false,
|
||||
}
|
||||
|
||||
sqls := migration.sqlschema.Operator().AddColumn(table, uniqueConstraints, column, false)
|
||||
|
||||
indexSqls := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "users", ColumnNames: []sqlschema.ColumnName{"email", "org_id"}})
|
||||
|
||||
sqls = append(sqls, indexSqls...)
|
||||
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := migration.sqlschema.ToggleFKEnforcement(ctx, db, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addStatusUser) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
70
pkg/sqlmigration/068_deprecate_user_invite.go
Normal file
70
pkg/sqlmigration/068_deprecate_user_invite.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type deprecateUserInvite struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewDeprecateUserInviteFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(
|
||||
factory.MustNewName("deprecate_user_invite"),
|
||||
func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||
return &deprecateUserInvite{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (migration *deprecateUserInvite) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *deprecateUserInvite) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
table, _, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("user_invite"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dropTableSqls := migration.sqlschema.Operator().DropTable(table)
|
||||
|
||||
for _, sql := range dropTableSqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *deprecateUserInvite) Down(ctx context.Context, db *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
// "encoding/json"
|
||||
// "time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
// "github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -14,37 +14,38 @@ var (
|
||||
ErrInviteNotFound = errors.MustNewCode("invite_not_found")
|
||||
)
|
||||
|
||||
type GettableInvite = Invite
|
||||
// TODO - remove this commented lines
|
||||
// type GettableInvite = Invite
|
||||
|
||||
type Invite struct {
|
||||
bun.BaseModel `bun:"table:user_invite"`
|
||||
// type Invite struct {
|
||||
// bun.BaseModel `bun:"table:user_invite"`
|
||||
|
||||
Identifiable
|
||||
TimeAuditable
|
||||
Name string `bun:"name,type:text" json:"name"`
|
||||
Email valuer.Email `bun:"email,type:text" json:"email"`
|
||||
Token string `bun:"token,type:text" json:"token"`
|
||||
Role Role `bun:"role,type:text" json:"role"`
|
||||
OrgID valuer.UUID `bun:"org_id,type:text" json:"orgId"`
|
||||
// Identifiable
|
||||
// TimeAuditable
|
||||
// Name string `bun:"name,type:text" json:"name"`
|
||||
// Email valuer.Email `bun:"email,type:text" json:"email"`
|
||||
// Token string `bun:"token,type:text" json:"token"`
|
||||
// Role Role `bun:"role,type:text" json:"role"`
|
||||
// OrgID valuer.UUID `bun:"org_id,type:text" json:"orgId"`
|
||||
|
||||
InviteLink string `bun:"-" json:"inviteLink"`
|
||||
}
|
||||
// InviteLink string `bun:"-" json:"inviteLink"`
|
||||
// }
|
||||
|
||||
type InviteEmailData struct {
|
||||
CustomerName string
|
||||
InviterName string
|
||||
InviterEmail string
|
||||
Link string
|
||||
}
|
||||
// type InviteEmailData struct {
|
||||
// CustomerName string
|
||||
// InviterName string
|
||||
// InviterEmail string
|
||||
// Link string
|
||||
// }
|
||||
|
||||
type PostableAcceptInvite struct {
|
||||
DisplayName string `json:"displayName"`
|
||||
InviteToken string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
// type PostableAcceptInvite struct {
|
||||
// DisplayName string `json:"displayName"`
|
||||
// InviteToken string `json:"token"`
|
||||
// Password string `json:"password"`
|
||||
|
||||
// reference URL to track where the register request is coming from
|
||||
SourceURL string `json:"sourceUrl"`
|
||||
}
|
||||
// // reference URL to track where the register request is coming from
|
||||
// SourceURL string `json:"sourceUrl"`
|
||||
// }
|
||||
|
||||
type PostableInvite struct {
|
||||
Name string `json:"name"`
|
||||
@@ -57,45 +58,45 @@ type PostableBulkInviteRequest struct {
|
||||
Invites []PostableInvite `json:"invites"`
|
||||
}
|
||||
|
||||
type GettableCreateInviteResponse struct {
|
||||
InviteToken string `json:"token"`
|
||||
}
|
||||
// type GettableCreateInviteResponse struct {
|
||||
// InviteToken string `json:"token"`
|
||||
// }
|
||||
|
||||
func NewInvite(name string, role Role, orgID valuer.UUID, email valuer.Email) (*Invite, error) {
|
||||
invite := &Invite{
|
||||
Identifiable: Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
},
|
||||
Name: name,
|
||||
Email: email,
|
||||
Token: valuer.GenerateUUID().String(),
|
||||
Role: role,
|
||||
OrgID: orgID,
|
||||
TimeAuditable: TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
// func NewInvite(name string, role Role, orgID valuer.UUID, email valuer.Email) (*Invite, error) {
|
||||
// invite := &Invite{
|
||||
// Identifiable: Identifiable{
|
||||
// ID: valuer.GenerateUUID(),
|
||||
// },
|
||||
// Name: name,
|
||||
// Email: email,
|
||||
// Token: valuer.GenerateUUID().String(),
|
||||
// Role: role,
|
||||
// OrgID: orgID,
|
||||
// TimeAuditable: TimeAuditable{
|
||||
// CreatedAt: time.Now(),
|
||||
// UpdatedAt: time.Now(),
|
||||
// },
|
||||
// }
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
// return invite, nil
|
||||
// }
|
||||
|
||||
func (request *PostableAcceptInvite) UnmarshalJSON(data []byte) error {
|
||||
type Alias PostableAcceptInvite
|
||||
// func (request *PostableAcceptInvite) UnmarshalJSON(data []byte) error {
|
||||
// type Alias PostableAcceptInvite
|
||||
|
||||
var temp Alias
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
// var temp Alias
|
||||
// if err := json.Unmarshal(data, &temp); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
if temp.InviteToken == "" {
|
||||
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invite token is required")
|
||||
}
|
||||
// if temp.InviteToken == "" {
|
||||
// return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invite token is required")
|
||||
// }
|
||||
|
||||
if !IsPasswordValid(temp.Password) {
|
||||
return ErrInvalidPassword
|
||||
}
|
||||
// if !IsPasswordValid(temp.Password) {
|
||||
// return ErrInvalidPassword
|
||||
// }
|
||||
|
||||
*request = PostableAcceptInvite(temp)
|
||||
return nil
|
||||
}
|
||||
// *request = PostableAcceptInvite(temp)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
@@ -23,17 +23,25 @@ var (
|
||||
ErrCodeRootUserOperationUnsupported = errors.MustNewCode("root_user_operation_unsupported")
|
||||
)
|
||||
|
||||
var (
|
||||
UserStatusPendingInvite = valuer.NewString("pending_invite")
|
||||
UserStatusActive = valuer.NewString("active")
|
||||
UserStatusDeleted = valuer.NewString("deleted")
|
||||
ValidUserStatus = []valuer.String{UserStatusPendingInvite, UserStatusActive, UserStatusDeleted}
|
||||
)
|
||||
|
||||
type GettableUser = User
|
||||
|
||||
type User struct {
|
||||
bun.BaseModel `bun:"table:users"`
|
||||
|
||||
Identifiable
|
||||
DisplayName string `bun:"display_name" json:"displayName"`
|
||||
Email valuer.Email `bun:"email" json:"email"`
|
||||
Role Role `bun:"role" json:"role"`
|
||||
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
|
||||
IsRoot bool `bun:"is_root" json:"isRoot"`
|
||||
DisplayName string `bun:"display_name" json:"displayName"`
|
||||
Email valuer.Email `bun:"email" json:"email"`
|
||||
Role Role `bun:"role" json:"role"`
|
||||
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
|
||||
IsRoot bool `bun:"is_root" json:"isRoot"`
|
||||
Status valuer.String `bun:"status" json:"status"`
|
||||
TimeAuditable
|
||||
}
|
||||
|
||||
@@ -45,7 +53,7 @@ type PostableRegisterOrgAndAdmin struct {
|
||||
OrgName string `json:"orgName"`
|
||||
}
|
||||
|
||||
func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUID) (*User, error) {
|
||||
func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUID, status valuer.String) (*User, error) {
|
||||
if email.IsZero() {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
|
||||
}
|
||||
@@ -67,6 +75,7 @@ func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUI
|
||||
Role: role,
|
||||
OrgID: orgID,
|
||||
IsRoot: false,
|
||||
Status: status,
|
||||
TimeAuditable: TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
@@ -92,6 +101,7 @@ func NewRootUser(displayName string, email valuer.Email, orgID valuer.UUID) (*Us
|
||||
Role: RoleAdmin,
|
||||
OrgID: orgID,
|
||||
IsRoot: true,
|
||||
Status: UserStatusActive,
|
||||
TimeAuditable: TimeAuditable{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
@@ -111,6 +121,11 @@ func (u *User) Update(displayName string, role Role) {
|
||||
u.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
func (u *User) UpdateStatus(status valuer.String) {
|
||||
u.Status = status
|
||||
u.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// PromoteToRoot promotes the user to a root user with admin role.
|
||||
func (u *User) PromoteToRoot() {
|
||||
u.IsRoot = true
|
||||
@@ -139,6 +154,7 @@ func NewTraitsFromUser(user *User) map[string]any {
|
||||
"role": user.Role,
|
||||
"email": user.Email.String(),
|
||||
"display_name": user.DisplayName,
|
||||
"status": user.Status,
|
||||
"created_at": user.CreatedAt,
|
||||
}
|
||||
}
|
||||
@@ -161,15 +177,15 @@ func (request *PostableRegisterOrgAndAdmin) UnmarshalJSON(data []byte) error {
|
||||
|
||||
type UserStore interface {
|
||||
// invite
|
||||
CreateBulkInvite(ctx context.Context, invites []*Invite) error
|
||||
ListInvite(ctx context.Context, orgID string) ([]*Invite, error)
|
||||
DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error
|
||||
// CreateBulkInvite(ctx context.Context, invites []*Invite) error
|
||||
// ListInvite(ctx context.Context, orgID string) ([]*Invite, error)
|
||||
// DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error
|
||||
|
||||
// Get invite by token.
|
||||
GetInviteByToken(ctx context.Context, token string) (*Invite, error)
|
||||
// GetInviteByToken(ctx context.Context, token string) (*Invite, error)
|
||||
|
||||
// Get invite by email and org.
|
||||
GetInviteByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*Invite, error)
|
||||
// GetInviteByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*Invite, error)
|
||||
|
||||
// Creates a user.
|
||||
CreateUser(ctx context.Context, user *User) error
|
||||
@@ -195,6 +211,9 @@ type UserStore interface {
|
||||
// List users by email and org ids.
|
||||
ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*User, error)
|
||||
|
||||
// List users in pending invite status
|
||||
ListPendingInviteUsers(ctx context.Context, orgID valuer.UUID) ([]*User, error)
|
||||
|
||||
UpdateUser(ctx context.Context, orgID valuer.UUID, user *User) error
|
||||
DeleteUser(ctx context.Context, orgID string, id string) error
|
||||
|
||||
|
||||
Reference in New Issue
Block a user