mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-05 13:22:00 +00:00
225 lines
6.0 KiB
Go
225 lines
6.0 KiB
Go
package impluser
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/authz"
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/factory"
|
|
"github.com/SigNoz/signoz/pkg/modules/organization"
|
|
"github.com/SigNoz/signoz/pkg/modules/user"
|
|
"github.com/SigNoz/signoz/pkg/types"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
)
|
|
|
|
type service struct {
|
|
settings factory.ScopedProviderSettings
|
|
store types.UserStore
|
|
module user.Module
|
|
orgGetter organization.Getter
|
|
authz authz.AuthZ
|
|
config user.RootConfig
|
|
stopC chan struct{}
|
|
}
|
|
|
|
func NewService(
|
|
providerSettings factory.ProviderSettings,
|
|
store types.UserStore,
|
|
module user.Module,
|
|
orgGetter organization.Getter,
|
|
authz authz.AuthZ,
|
|
config user.RootConfig,
|
|
) user.Service {
|
|
return &service{
|
|
settings: factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/pkg/modules/user"),
|
|
store: store,
|
|
module: module,
|
|
orgGetter: orgGetter,
|
|
authz: authz,
|
|
config: config,
|
|
stopC: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (s *service) Start(ctx context.Context) error {
|
|
if !s.config.Enabled {
|
|
<-s.stopC
|
|
return nil
|
|
}
|
|
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
err := s.reconcile(ctx)
|
|
if err == nil {
|
|
s.settings.Logger().InfoContext(ctx, "root user reconciliation completed successfully")
|
|
<-s.stopC
|
|
return nil
|
|
}
|
|
|
|
s.settings.Logger().WarnContext(ctx, "root user reconciliation failed, retrying", "error", err)
|
|
|
|
select {
|
|
case <-s.stopC:
|
|
return nil
|
|
case <-ticker.C:
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *service) Stop(ctx context.Context) error {
|
|
close(s.stopC)
|
|
return nil
|
|
}
|
|
|
|
func (s *service) reconcile(ctx context.Context) error {
|
|
if !s.config.Org.ID.IsZero() {
|
|
return s.reconcileWithOrgID(ctx)
|
|
}
|
|
|
|
return s.reconcileByName(ctx)
|
|
}
|
|
|
|
func (s *service) reconcileWithOrgID(ctx context.Context) error {
|
|
org, err := s.orgGetter.Get(ctx, s.config.Org.ID)
|
|
if err != nil {
|
|
if !errors.Ast(err, errors.TypeNotFound) {
|
|
return err // something really went wrong
|
|
}
|
|
|
|
// org was not found using id check if we can find an org using name
|
|
|
|
existingOrgByName, nameErr := s.orgGetter.GetByName(ctx, s.config.Org.Name)
|
|
if nameErr != nil && !errors.Ast(nameErr, errors.TypeNotFound) {
|
|
return nameErr // something really went wrong
|
|
}
|
|
|
|
// we found an org using name
|
|
if existingOrgByName != nil {
|
|
// the existing org has the same name as config but org id is different inform user with actionable message
|
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "organization with name %q already exists with a different ID %s (expected %s)", s.config.Org.Name, existingOrgByName.ID.StringValue(), s.config.Org.ID.StringValue())
|
|
}
|
|
|
|
// default - we did not found any org using id and name both - create a new org
|
|
newOrg := types.NewOrganizationWithID(s.config.Org.ID, s.config.Org.Name, s.config.Org.Name)
|
|
_, err = s.module.CreateFirstUser(ctx, newOrg, s.config.Email.String(), s.config.Email, s.config.Password)
|
|
return err
|
|
}
|
|
|
|
return s.reconcileRootUser(ctx, org.ID)
|
|
}
|
|
|
|
func (s *service) reconcileByName(ctx context.Context) error {
|
|
org, err := s.orgGetter.GetByName(ctx, s.config.Org.Name)
|
|
if err != nil {
|
|
if errors.Ast(err, errors.TypeNotFound) {
|
|
newOrg := types.NewOrganization(s.config.Org.Name, s.config.Org.Name)
|
|
_, err := s.module.CreateFirstUser(ctx, newOrg, s.config.Email.String(), s.config.Email, s.config.Password)
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
return s.reconcileRootUser(ctx, org.ID)
|
|
}
|
|
|
|
func (s *service) reconcileRootUser(ctx context.Context, orgID valuer.UUID) error {
|
|
existingRoot, err := s.store.GetRootUserByOrgID(ctx, orgID)
|
|
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
|
return err
|
|
}
|
|
|
|
if existingRoot == nil {
|
|
return s.createOrPromoteRootUser(ctx, orgID)
|
|
}
|
|
|
|
return s.updateExistingRootUser(ctx, orgID, existingRoot)
|
|
}
|
|
|
|
func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID) error {
|
|
existingUser, err := s.module.GetNonDeletedUserByEmailAndOrgID(ctx, s.config.Email, orgID)
|
|
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
|
return err
|
|
}
|
|
|
|
if existingUser != nil {
|
|
oldRole := existingUser.Role
|
|
|
|
existingUser.PromoteToRoot()
|
|
if err := s.module.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
|
|
return err
|
|
}
|
|
|
|
if oldRole != types.RoleAdmin {
|
|
if err := s.authz.ModifyGrant(ctx,
|
|
orgID,
|
|
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)},
|
|
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin)},
|
|
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return s.setPassword(ctx, existingUser.ID)
|
|
}
|
|
|
|
// Create new root user
|
|
newUser, err := types.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
factorPassword, err := types.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.module.CreateUser(ctx, newUser, user.WithFactorPassword(factorPassword))
|
|
}
|
|
|
|
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *types.User) error {
|
|
existingRoot.PromoteToRoot()
|
|
|
|
if existingRoot.Email != s.config.Email {
|
|
existingRoot.UpdateEmail(s.config.Email)
|
|
if err := s.module.UpdateAnyUser(ctx, orgID, existingRoot); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return s.setPassword(ctx, existingRoot.ID)
|
|
}
|
|
|
|
func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
|
|
password, err := s.store.GetPasswordByUserID(ctx, userID)
|
|
if err != nil {
|
|
if !errors.Ast(err, errors.TypeNotFound) {
|
|
return err
|
|
}
|
|
|
|
factorPassword, err := types.NewFactorPassword(s.config.Password, userID.StringValue())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.store.CreatePassword(ctx, factorPassword)
|
|
}
|
|
|
|
if !password.Equals(s.config.Password) {
|
|
if err := password.Update(s.config.Password); err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.store.UpdatePassword(ctx, password)
|
|
}
|
|
|
|
return nil
|
|
}
|