mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-18 23:12:36 +00:00
Compare commits
4 Commits
chore/curs
...
platform-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2abd3ddd8b | ||
|
|
9e7f97976b | ||
|
|
c3f35c8ddf | ||
|
|
ba4e93050e |
@@ -4678,6 +4678,8 @@ components:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
identityId:
|
||||
type: string
|
||||
isRoot:
|
||||
type: boolean
|
||||
orgId:
|
||||
|
||||
28
pkg/modules/identity/identity.go
Normal file
28
pkg/modules/identity/identity.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package identity
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
// CreateIdentityWithRoles creates an identity and assigns roles to it
|
||||
CreateIdentityWithRoles(context.Context, valuer.UUID, valuer.UUID, []string) error
|
||||
|
||||
// GrantRole grants a role to an identity
|
||||
GrantRole(context.Context, valuer.UUID, string) error
|
||||
|
||||
// RevokeRole revokes a role from an identity
|
||||
RevokeRole(context.Context, valuer.UUID, string) error
|
||||
|
||||
// GetRoles gets all roles for an identity
|
||||
GetRoles(context.Context, valuer.UUID, valuer.UUID) ([]*roletypes.Role, error)
|
||||
|
||||
// CountByRoleAndOrgID counts identities with a specific role in an org
|
||||
CountByRoleAndOrgID(context.Context, string, valuer.UUID) (int64, error)
|
||||
|
||||
// DeleteIdentity deletes an identity and its associated roles
|
||||
DeleteIdentity(context.Context, valuer.UUID, valuer.UUID) error
|
||||
}
|
||||
81
pkg/modules/identity/implidentity/module.go
Normal file
81
pkg/modules/identity/implidentity/module.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package implidentity
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/identity"
|
||||
"github.com/SigNoz/signoz/pkg/types/identitytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store identitytypes.Store
|
||||
}
|
||||
|
||||
func NewModule(store identitytypes.Store) identity.Module {
|
||||
return &module{
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *module) CreateIdentityWithRoles(ctx context.Context, id valuer.UUID, orgID valuer.UUID, roleNames []string) error {
|
||||
return m.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
// Create identity
|
||||
ident := identitytypes.NewStorableIdentity(id, orgID)
|
||||
if err := m.store.CreateIdentity(ctx, ident); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create identity_role mappings for each role
|
||||
for _, roleName := range roleNames {
|
||||
identityRole := identitytypes.NewStorableIdentityRole(id, roleName)
|
||||
if err := m.store.CreateIdentityRole(ctx, identityRole); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *module) GrantRole(ctx context.Context, identityID valuer.UUID, roleName string) error {
|
||||
storableIdentityRole := identitytypes.NewStorableIdentityRole(identityID, roleName)
|
||||
return m.store.CreateIdentityRole(ctx, storableIdentityRole)
|
||||
}
|
||||
|
||||
func (m *module) RevokeRole(ctx context.Context, identityID valuer.UUID, roleName string) error {
|
||||
return m.store.DeleteIdentityRole(ctx, identityID, roleName)
|
||||
}
|
||||
|
||||
func (m *module) GetRoles(ctx context.Context, id valuer.UUID, orgID valuer.UUID) ([]*roletypes.Role, error) {
|
||||
storableRoles, err := m.store.GetRolesByIdentityID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*roletypes.Role, 0, len(storableRoles))
|
||||
for _, storableRole := range storableRoles {
|
||||
roles = append(roles, roletypes.NewRoleFromStorableRole(storableRole))
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (m *module) DeleteIdentity(ctx context.Context, identityID valuer.UUID, _ valuer.UUID) error {
|
||||
return m.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
if err := m.store.DeleteIdentityRoles(ctx, identityID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.store.DeleteIdentity(ctx, identityID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (m *module) CountByRoleAndOrgID(ctx context.Context, roleName string, orgID valuer.UUID) (int64, error) {
|
||||
return m.store.CountByRoleNameAndOrgID(ctx, roleName, orgID)
|
||||
}
|
||||
126
pkg/modules/identity/implidentity/store.go
Normal file
126
pkg/modules/identity/implidentity/store.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package implidentity
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/identitytypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) identitytypes.Store {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (s *store) CreateIdentity(ctx context.Context, identity *identitytypes.StorableIdentity) error {
|
||||
_, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(identity).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create identity")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) CreateIdentityRole(ctx context.Context, identityRole *identitytypes.StorableIdentityRole) error {
|
||||
_, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewInsert().
|
||||
Model(identityRole).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create identity role")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) DeleteIdentityRole(ctx context.Context, identityID valuer.UUID, roleName string) error {
|
||||
_, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(&identitytypes.StorableIdentityRole{}).
|
||||
Where("identity_id = ?", identityID).
|
||||
Where("role_name = ?", roleName).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete identity role")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) DeleteIdentityRoles(ctx context.Context, identityID valuer.UUID) error {
|
||||
_, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(&identitytypes.StorableIdentityRole{}).
|
||||
Where("identity_id = ?", identityID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete identity roles")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) DeleteIdentity(ctx context.Context, identityID valuer.UUID) error {
|
||||
_, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(&identitytypes.StorableIdentity{}).
|
||||
Where("id = ?", identityID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete identity")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) GetRolesByIdentityID(ctx context.Context, identityID valuer.UUID) ([]*roletypes.StorableRole, error) {
|
||||
var roles []*roletypes.StorableRole
|
||||
err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model(&roles).
|
||||
Join("JOIN identity_role ON identity_role.role_name = role.name").
|
||||
Where("identity_role.identity_id = ?", identityID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to get roles for identity %s", identityID)
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (s *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
|
||||
return s.sqlstore.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
|
||||
return cb(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *store) CountByRoleNameAndOrgID(ctx context.Context, roleName string, orgID valuer.UUID) (int64, error) {
|
||||
count, err := s.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewSelect().
|
||||
Model((*identitytypes.StorableIdentityRole)(nil)).
|
||||
Join("JOIN identity ON identity.id = identity_role.identity_id").
|
||||
Where("identity_role.role_name = ?", roleName).
|
||||
Where("identity.org_id = ?", orgID).
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to count identities by role")
|
||||
}
|
||||
return int64(count), nil
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/identity"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/session"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -28,9 +29,10 @@ type module struct {
|
||||
authDomain authdomain.Module
|
||||
tokenizer tokenizer.Tokenizer
|
||||
orgGetter organization.Getter
|
||||
identity identity.Module
|
||||
}
|
||||
|
||||
func NewModule(providerSettings factory.ProviderSettings, authNs map[authtypes.AuthNProvider]authn.AuthN, user user.Module, userGetter user.Getter, authDomain authdomain.Module, tokenizer tokenizer.Tokenizer, orgGetter organization.Getter) session.Module {
|
||||
func NewModule(providerSettings factory.ProviderSettings, authNs map[authtypes.AuthNProvider]authn.AuthN, user user.Module, userGetter user.Getter, authDomain authdomain.Module, tokenizer tokenizer.Tokenizer, orgGetter organization.Getter, identity identity.Module) session.Module {
|
||||
return &module{
|
||||
settings: factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/session/implsession"),
|
||||
authNs: authNs,
|
||||
@@ -39,6 +41,7 @@ func NewModule(providerSettings factory.ProviderSettings, authNs map[authtypes.A
|
||||
authDomain: authDomain,
|
||||
tokenizer: tokenizer,
|
||||
orgGetter: orgGetter,
|
||||
identity: identity,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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/modules/identity"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
root "github.com/SigNoz/signoz/pkg/modules/user"
|
||||
@@ -33,10 +34,11 @@ type Module struct {
|
||||
authz authz.AuthZ
|
||||
analytics analytics.Analytics
|
||||
config user.Config
|
||||
identity identity.Module
|
||||
}
|
||||
|
||||
// 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, identity identity.Module) root.Module {
|
||||
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/user/impluser")
|
||||
return &Module{
|
||||
store: store,
|
||||
@@ -47,6 +49,7 @@ func NewModule(store types.UserStore, tokenizer tokenizer.Tokenizer, emailing em
|
||||
analytics: analytics,
|
||||
authz: authz,
|
||||
config: config,
|
||||
identity: identity,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +183,12 @@ func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ..
|
||||
}
|
||||
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
// Create identity with roles via identity module
|
||||
if err := module.identity.CreateIdentityWithRoles(ctx, input.ID, input.OrgID, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create user
|
||||
if err := module.store.CreateUser(ctx, input); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -223,21 +232,26 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
|
||||
// Make sure that the request is not demoting the last admin user.
|
||||
if user.Role != "" && user.Role != existingUser.Role && existingUser.Role == types.RoleAdmin {
|
||||
adminUsers, err := m.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
adminCount, err := m.identity.CountByRoleAndOrgID(ctx, roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin), orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(adminUsers) == 1 {
|
||||
if adminCount == 1 {
|
||||
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot demote the last admin")
|
||||
}
|
||||
}
|
||||
|
||||
if user.Role != "" && user.Role != existingUser.Role {
|
||||
oldRole := existingUser.Role
|
||||
newRole := user.Role
|
||||
roleChanged := newRole != "" && newRole != oldRole
|
||||
|
||||
if roleChanged {
|
||||
// Update authz (OpenFGA) first - must be outside transaction, is idempotent
|
||||
err = m.authz.ModifyGrant(ctx,
|
||||
orgID,
|
||||
roletypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role),
|
||||
roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role),
|
||||
roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole),
|
||||
roletypes.MustGetSigNozManagedRoleFromExistingRole(newRole),
|
||||
authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -246,7 +260,38 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
|
||||
}
|
||||
|
||||
existingUser.Update(user.DisplayName, user.Role)
|
||||
if err := m.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
|
||||
|
||||
if roleChanged {
|
||||
// Wrap identity_role sync and user update in a transaction
|
||||
if err := m.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
// Sync identity_role: revoke old role and grant new role
|
||||
if err := m.identity.RevokeRole(ctx, existingUser.IdentityID, roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.identity.GrantRole(ctx, existingUser.IdentityID, roletypes.MustGetSigNozManagedRoleFromExistingRole(newRole)); err != nil {
|
||||
return err
|
||||
}
|
||||
// Update user
|
||||
if err := m.store.UpdateUser(ctx, orgID, existingUser); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// No role change, just update user directly
|
||||
if err := m.store.UpdateUser(ctx, orgID, existingUser); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Analytics and tokenizer cleanup
|
||||
traits := types.NewTraitsFromUser(existingUser)
|
||||
m.analytics.IdentifyUser(ctx, existingUser.OrgID.String(), existingUser.ID.String(), traits)
|
||||
m.analytics.TrackUser(ctx, existingUser.OrgID.String(), existingUser.ID.String(), "User Updated", traits)
|
||||
|
||||
if err := m.tokenizer.DeleteIdentity(ctx, existingUser.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -283,19 +328,40 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
|
||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "integration user cannot be deleted")
|
||||
}
|
||||
|
||||
// don't allow to delete the last admin user
|
||||
adminUsers, err := module.store.GetUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
|
||||
// Get user's roles from identity module
|
||||
userRoles, err := module.identity.GetRoles(ctx, user.IdentityID, user.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(adminUsers) == 1 && user.Role == types.RoleAdmin {
|
||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
||||
// Check if user has admin role
|
||||
adminRoleName := roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin)
|
||||
hasAdminRole := slices.ContainsFunc(userRoles, func(r *roletypes.Role) bool {
|
||||
return r.Name == adminRoleName
|
||||
})
|
||||
|
||||
// don't allow to delete the last admin user
|
||||
if hasAdminRole {
|
||||
adminCount, err := module.identity.CountByRoleAndOrgID(ctx, adminRoleName, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if adminCount == 1 {
|
||||
return errors.New(errors.TypeForbidden, errors.CodeForbidden, "cannot delete the last admin")
|
||||
}
|
||||
}
|
||||
|
||||
// since revoke is idempotant multiple calls to revoke won't cause issues in case of retries
|
||||
err = module.authz.Revoke(ctx, orgID, roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role), authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
||||
if err != nil {
|
||||
// Revoke all roles for the user
|
||||
for _, userRole := range userRoles {
|
||||
err = module.authz.Revoke(ctx, orgID, userRole.Name, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Delete identity and identity_role records
|
||||
if err := module.identity.DeleteIdentity(ctx, user.IdentityID, user.OrgID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -545,7 +611,7 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
|
||||
return err
|
||||
}
|
||||
|
||||
err = module.createUserWithoutGrant(ctx, user, root.WithFactorPassword(password))
|
||||
err = module.createUserWithoutGrant(ctx, user, root.WithFactorPassword(password), root.WithRole(types.RoleAdmin))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -576,6 +642,12 @@ func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
|
||||
createUserOpts := root.NewCreateUserOptions(opts...)
|
||||
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
// Create identity with roles via identity module
|
||||
if err := module.identity.CreateIdentityWithRoles(ctx, input.ID, input.OrgID, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create user
|
||||
if err := module.store.CreateUser(ctx, input); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/authz"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/identity"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
@@ -21,6 +22,7 @@ type service struct {
|
||||
module user.Module
|
||||
orgGetter organization.Getter
|
||||
authz authz.AuthZ
|
||||
identity identity.Module
|
||||
config user.RootConfig
|
||||
stopC chan struct{}
|
||||
}
|
||||
@@ -31,6 +33,7 @@ func NewService(
|
||||
module user.Module,
|
||||
orgGetter organization.Getter,
|
||||
authz authz.AuthZ,
|
||||
identity identity.Module,
|
||||
config user.RootConfig,
|
||||
) user.Service {
|
||||
return &service{
|
||||
@@ -39,6 +42,7 @@ func NewService(
|
||||
module: module,
|
||||
orgGetter: orgGetter,
|
||||
authz: authz,
|
||||
identity: identity,
|
||||
config: config,
|
||||
stopC: make(chan struct{}),
|
||||
}
|
||||
|
||||
@@ -619,6 +619,7 @@ func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
type createUserOptions struct {
|
||||
FactorPassword *types.FactorPassword
|
||||
Role types.Role
|
||||
}
|
||||
|
||||
type CreateUserOption func(*createUserOptions)
|
||||
@@ -17,9 +18,16 @@ func WithFactorPassword(factorPassword *types.FactorPassword) CreateUserOption {
|
||||
}
|
||||
}
|
||||
|
||||
func WithRole(role types.Role) CreateUserOption {
|
||||
return func(o *createUserOptions) {
|
||||
o.Role = role
|
||||
}
|
||||
}
|
||||
|
||||
func NewCreateUserOptions(opts ...CreateUserOption) *createUserOptions {
|
||||
o := &createUserOptions{
|
||||
FactorPassword: nil,
|
||||
Role: types.RoleViewer, // default role
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/authdomain/implauthdomain"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/identity"
|
||||
"github.com/SigNoz/signoz/pkg/modules/identity/implidentity"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/metricsexplorer/implmetricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
@@ -54,6 +56,7 @@ type Modules struct {
|
||||
Preference preference.Module
|
||||
User user.Module
|
||||
UserGetter user.Getter
|
||||
Identity identity.Module
|
||||
SavedView savedview.Module
|
||||
Apdex apdex.Module
|
||||
Dashboard dashboard.Module
|
||||
@@ -88,7 +91,8 @@ func NewModules(
|
||||
) 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)
|
||||
identityModule := implidentity.NewModule(implidentity.NewStore(sqlstore))
|
||||
user := impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), tokenizer, emailing, providerSettings, orgSetter, authz, analytics, config.User, identityModule)
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings))
|
||||
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
|
||||
|
||||
@@ -101,11 +105,12 @@ func NewModules(
|
||||
Dashboard: dashboard,
|
||||
User: user,
|
||||
UserGetter: userGetter,
|
||||
Identity: identityModule,
|
||||
QuickFilter: quickfilter,
|
||||
TraceFunnel: impltracefunnel.NewModule(impltracefunnel.NewStore(sqlstore)),
|
||||
RawDataExport: implrawdataexport.NewModule(querier),
|
||||
AuthDomain: implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs),
|
||||
Session: implsession.NewModule(providerSettings, authNs, user, userGetter, implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs), tokenizer, orgGetter),
|
||||
Session: implsession.NewModule(providerSettings, authNs, user, userGetter, implauthdomain.NewModule(implauthdomain.NewStore(sqlstore), authNs), tokenizer, orgGetter, identityModule),
|
||||
SpanPercentile: implspanpercentile.NewModule(querier, providerSettings),
|
||||
Services: implservices.NewModule(querier, telemetryStore),
|
||||
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
|
||||
|
||||
@@ -169,6 +169,9 @@ func NewSQLMigrationProviderFactories(
|
||||
sqlmigration.NewAddAnonymousPublicDashboardTransactionFactory(sqlstore),
|
||||
sqlmigration.NewAddRootUserFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddUserEmailOrgIDIndexFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewAddIdentityFactory(sqlstore, sqlschema),
|
||||
sqlmigration.NewUpdateUserIdentity(sqlstore, sqlschema),
|
||||
sqlmigration.NewMigrateUserRolesFactory(sqlstore, sqlschema),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -75,8 +75,9 @@ func TestNewProviderFactories(t *testing.T) {
|
||||
})
|
||||
|
||||
assert.NotPanics(t, func() {
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual), instrumentationtest.New().ToProviderSettings()))
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual)), nil)
|
||||
sqlStore := sqlstoretest.New(sqlstore.Config{Provider: "sqlite"}, sqlmock.QueryMatcherEqual)
|
||||
userGetter := impluser.NewGetter(impluser.NewStore(sqlStore, instrumentationtest.New().ToProviderSettings()))
|
||||
orgGetter := implorganization.NewGetter(implorganization.NewStore(sqlStore), nil)
|
||||
telemetryStore := telemetrystoretest.New(telemetrystore.Config{Provider: "clickhouse"}, sqlmock.QueryMatcherEqual)
|
||||
NewStatsReporterProviderFactories(telemetryStore, []statsreporter.StatsCollector{}, orgGetter, userGetter, tokenizertest.NewMockTokenizer(t), version.Build{}, analytics.Config{Enabled: true})
|
||||
})
|
||||
|
||||
@@ -389,7 +389,7 @@ func New(
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, tokenizer, emailing, providerSettings, orgGetter, alertmanager, analytics, querier, telemetrystore, telemetryMetadataStore, authNs, authz, cache, queryParser, config, dashboard)
|
||||
|
||||
userService := impluser.NewService(providerSettings, impluser.NewStore(sqlstore, providerSettings), modules.User, orgGetter, authz, config.User.Root)
|
||||
userService := impluser.NewService(providerSettings, impluser.NewStore(sqlstore, providerSettings), modules.User, orgGetter, authz, modules.Identity, config.User.Root)
|
||||
|
||||
// Initialize all handlers for the modules
|
||||
handlers := NewHandlers(modules, providerSettings, querier, licensing, global, flagger, gateway, telemetryMetadataStore, authz)
|
||||
|
||||
111
pkg/sqlmigration/066_add_identity.go
Normal file
111
pkg/sqlmigration/066_add_identity.go
Normal file
@@ -0,0 +1,111 @@
|
||||
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 addIdentity struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewAddIdentityFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("add_identity"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return &addIdentity{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *addIdentity) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addIdentity) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
sqls := [][]byte{}
|
||||
tableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "identity",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "status", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "org_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"id"},
|
||||
},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("org_id"),
|
||||
ReferencedTableName: sqlschema.TableName("organizations"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
|
||||
Name: "identity_role",
|
||||
Columns: []*sqlschema.Column{
|
||||
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "identity_id", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "role_name", DataType: sqlschema.DataTypeText, Nullable: false},
|
||||
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
|
||||
},
|
||||
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
|
||||
ColumnNames: []sqlschema.ColumnName{"id"},
|
||||
},
|
||||
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
|
||||
{
|
||||
ReferencingColumnName: sqlschema.ColumnName("identity_id"),
|
||||
ReferencedTableName: sqlschema.TableName("identity"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
},
|
||||
},
|
||||
})
|
||||
sqls = append(sqls, tableSQLs...)
|
||||
|
||||
indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{
|
||||
TableName: "identity_role",
|
||||
ColumnNames: []sqlschema.ColumnName{"identity_id", "role_name"},
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *addIdentity) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
138
pkg/sqlmigration/067_update_user_identity.go
Normal file
138
pkg/sqlmigration/067_update_user_identity.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/identitytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
type updateUserIdentity struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewUpdateUserIdentity(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("update_user_identity"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return &updateUserIdentity{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *updateUserIdentity) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *updateUserIdentity) Up(ctx context.Context, db *bun.DB) error {
|
||||
// 1. Disable FK enforcement
|
||||
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()
|
||||
}()
|
||||
|
||||
// 2. Fetch existing users
|
||||
type existingUser struct {
|
||||
bun.BaseModel `bun:"table:users"`
|
||||
ID string `bun:"id"`
|
||||
OrgID string `bun:"org_id"`
|
||||
}
|
||||
var users []*existingUser
|
||||
if err := tx.NewSelect().Model(&users).Scan(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Create identity records for each user
|
||||
identities := make([]*identitytypes.StorableIdentity, 0, len(users))
|
||||
for _, user := range users {
|
||||
identityID, err := valuer.NewUUID(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
orgID, err := valuer.NewUUID(user.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identities = append(identities, identitytypes.NewStorableIdentity(identityID, orgID))
|
||||
}
|
||||
|
||||
if len(identities) > 0 {
|
||||
if _, err := tx.NewInsert().Model(&identities).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Get current table structure
|
||||
table, uniqueConstraints, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("users"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 5. Get existing indices to preserve them after recreation
|
||||
indices, err := migration.sqlschema.GetIndices(ctx, sqlschema.TableName("users"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 6. Build all SQL statements
|
||||
sqls := [][]byte{}
|
||||
|
||||
// Add identity_id column using AddColumn with ColumnName("id") as value
|
||||
identityIdColumn := &sqlschema.Column{
|
||||
Name: "identity_id",
|
||||
DataType: sqlschema.DataTypeText,
|
||||
Nullable: false,
|
||||
}
|
||||
sqls = append(sqls, migration.sqlschema.Operator().AddColumn(table, uniqueConstraints, identityIdColumn, sqlschema.ColumnName("id"))...)
|
||||
|
||||
// Add FK constraint to table definition
|
||||
table.ForeignKeyConstraints = append(table.ForeignKeyConstraints, &sqlschema.ForeignKeyConstraint{
|
||||
ReferencingColumnName: sqlschema.ColumnName("identity_id"),
|
||||
ReferencedTableName: sqlschema.TableName("identity"),
|
||||
ReferencedColumnName: sqlschema.ColumnName("id"),
|
||||
})
|
||||
|
||||
// Recreate table to apply FK constraint
|
||||
sqls = append(sqls, migration.sqlschema.Operator().RecreateTable(table, uniqueConstraints)...)
|
||||
|
||||
// Recreate indices that were lost during table recreation
|
||||
for _, index := range indices {
|
||||
sqls = append(sqls, migration.sqlschema.Operator().CreateIndex(index)...)
|
||||
}
|
||||
|
||||
// 7. Execute all SQL statements
|
||||
for _, sql := range sqls {
|
||||
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 8. Re-enable FK enforcement
|
||||
return migration.sqlschema.ToggleFKEnforcement(ctx, db, true)
|
||||
}
|
||||
|
||||
func (migration *updateUserIdentity) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
92
pkg/sqlmigration/068_migrate_user_roles.go
Normal file
92
pkg/sqlmigration/068_migrate_user_roles.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package sqlmigration
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/sqlschema"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/identitytypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/migrate"
|
||||
)
|
||||
|
||||
// Role name mapping from existing roles to managed role names
|
||||
var existingRoleToManagedRole = map[string]string{
|
||||
"ADMIN": "signoz-admin",
|
||||
"EDITOR": "signoz-editor",
|
||||
"VIEWER": "signoz-viewer",
|
||||
}
|
||||
|
||||
type migrateUserRoles struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
sqlschema sqlschema.SQLSchema
|
||||
}
|
||||
|
||||
func NewMigrateUserRolesFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
|
||||
return factory.NewProviderFactory(factory.MustNewName("migrate_user_roles"), func(ctx context.Context, providerSettings factory.ProviderSettings, config Config) (SQLMigration, error) {
|
||||
return &migrateUserRoles{
|
||||
sqlstore: sqlstore,
|
||||
sqlschema: sqlschema,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (migration *migrateUserRoles) Register(migrations *migrate.Migrations) error {
|
||||
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *migrateUserRoles) Up(ctx context.Context, db *bun.DB) error {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
}()
|
||||
|
||||
type existingUser struct {
|
||||
bun.BaseModel `bun:"table:users"`
|
||||
ID string `bun:"id"`
|
||||
Role string `bun:"role"`
|
||||
}
|
||||
var users []*existingUser
|
||||
if err := tx.NewSelect().Model(&users).Scan(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
identityRoles := make([]*identitytypes.StorableIdentityRole, 0, len(users))
|
||||
for _, user := range users {
|
||||
roleName := existingRoleToManagedRole[user.Role]
|
||||
identityID, err := valuer.NewUUID(user.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identityRoles = append(identityRoles, identitytypes.NewStorableIdentityRole(
|
||||
identityID,
|
||||
roleName,
|
||||
))
|
||||
}
|
||||
|
||||
if len(identityRoles) > 0 {
|
||||
if _, err := tx.NewInsert().Model(&identityRoles).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (migration *migrateUserRoles) Down(context.Context, *bun.DB) error {
|
||||
return nil
|
||||
}
|
||||
55
pkg/types/identitytypes/identity.go
Normal file
55
pkg/types/identitytypes/identity.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package identitytypes
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
var (
|
||||
IdentityStatusActive = valuer.NewString("active")
|
||||
IdentityStatusInactive = valuer.NewString("inactive")
|
||||
)
|
||||
|
||||
type StorableIdentity struct {
|
||||
bun.BaseModel `bun:"table:identity"`
|
||||
|
||||
ID valuer.UUID `bun:"id,pk,type:text" json:"id"`
|
||||
Status string `bun:"status" json:"status"`
|
||||
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
|
||||
CreatedAt time.Time `bun:"created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `bun:"updated_at" json:"updatedAt"`
|
||||
}
|
||||
|
||||
type StorableIdentityRole struct {
|
||||
bun.BaseModel `bun:"table:identity_role"`
|
||||
|
||||
ID valuer.UUID `bun:"id,pk,type:text" json:"id"`
|
||||
IdentityID valuer.UUID `bun:"identity_id" json:"identityId"`
|
||||
RoleName string `bun:"role_name" json:"roleName"`
|
||||
CreatedAt time.Time `bun:"created_at" json:"createdAt"`
|
||||
UpdatedAt time.Time `bun:"updated_at" json:"updatedAt"`
|
||||
}
|
||||
|
||||
func NewStorableIdentity(id valuer.UUID, orgID valuer.UUID) *StorableIdentity {
|
||||
now := time.Now()
|
||||
return &StorableIdentity{
|
||||
ID: id,
|
||||
Status: IdentityStatusActive.StringValue(),
|
||||
OrgID: orgID,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
func NewStorableIdentityRole(identityID valuer.UUID, roleName string) *StorableIdentityRole {
|
||||
now := time.Now()
|
||||
return &StorableIdentityRole{
|
||||
ID: valuer.GenerateUUID(),
|
||||
IdentityID: identityID,
|
||||
RoleName: roleName,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
19
pkg/types/identitytypes/store.go
Normal file
19
pkg/types/identitytypes/store.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package identitytypes
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/roletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
CreateIdentity(context.Context, *StorableIdentity) error
|
||||
CreateIdentityRole(context.Context, *StorableIdentityRole) error
|
||||
DeleteIdentity(context.Context, valuer.UUID) error
|
||||
DeleteIdentityRole(ctx context.Context, identityID valuer.UUID, roleName string) error
|
||||
DeleteIdentityRoles(context.Context, valuer.UUID) error
|
||||
GetRolesByIdentityID(context.Context, valuer.UUID) ([]*roletypes.StorableRole, error)
|
||||
CountByRoleNameAndOrgID(ctx context.Context, roleName string, orgID valuer.UUID) (int64, error)
|
||||
RunInTx(context.Context, func(ctx context.Context) error) error
|
||||
}
|
||||
@@ -32,6 +32,7 @@ type User struct {
|
||||
DisplayName string `bun:"display_name" json:"displayName"`
|
||||
Email valuer.Email `bun:"email" json:"email"`
|
||||
Role Role `bun:"role" json:"role"`
|
||||
IdentityID valuer.UUID `bun:"identity_id" json:"identityId"`
|
||||
OrgID valuer.UUID `bun:"org_id" json:"orgId"`
|
||||
IsRoot bool `bun:"is_root" json:"isRoot"`
|
||||
TimeAuditable
|
||||
@@ -58,13 +59,14 @@ func NewUser(displayName string, email valuer.Email, role Role, orgID valuer.UUI
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgID is required")
|
||||
}
|
||||
|
||||
id := valuer.GenerateUUID()
|
||||
return &User{
|
||||
Identifiable: Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
ID: id,
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Email: email,
|
||||
Role: role,
|
||||
IdentityID: id, // identity_id = user.id (1:1 mapping)
|
||||
OrgID: orgID,
|
||||
IsRoot: false,
|
||||
TimeAuditable: TimeAuditable{
|
||||
@@ -83,13 +85,14 @@ func NewRootUser(displayName string, email valuer.Email, orgID valuer.UUID) (*Us
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgID is required")
|
||||
}
|
||||
|
||||
id := valuer.GenerateUUID()
|
||||
return &User{
|
||||
Identifiable: Identifiable{
|
||||
ID: valuer.GenerateUUID(),
|
||||
ID: id,
|
||||
},
|
||||
DisplayName: displayName,
|
||||
Email: email,
|
||||
Role: RoleAdmin,
|
||||
IdentityID: id, // identity_id = user.id (1:1 mapping)
|
||||
OrgID: orgID,
|
||||
IsRoot: true,
|
||||
TimeAuditable: TimeAuditable{
|
||||
|
||||
Reference in New Issue
Block a user