Compare commits

..

15 Commits

Author SHA1 Message Date
Karan Balani
65327e5143 feat: move user roles to authz and add user_role table 2026-03-16 15:07:44 +05:30
vikrantgupta25
725fb8a3fa feat(authz): fix rebase issues 2026-03-12 01:32:18 +05:30
vikrantgupta25
a893b031bc feat(authz): fix rebase issues 2026-03-12 01:32:18 +05:30
vikrantgupta25
3ae403b23f feat(authz): add support for deleted_at 2026-03-12 01:32:18 +05:30
vikrantgupta25
fd133cb5f8 feat(serviceaccount): add analytics support for service account 2026-03-12 01:32:18 +05:30
vikrantgupta25
7f0dd67805 feat(serviceaccount): fix the old role panic 2026-03-12 01:32:18 +05:30
vikrantgupta25
1c89c65b8f feat(serviceaccount): part changes for user id claims tracking 2026-03-12 01:32:18 +05:30
vikrantgupta25
9035180f46 feat(serviceaccount): add domain checks and rotate sessions fix 2026-03-12 01:32:18 +05:30
vikrantgupta25
17fa2bae92 feat(serviceaccount): migrate existing api keys 2026-03-12 01:32:18 +05:30
vikrantgupta25
1106462d0e feat(serviceaccount): update authz typeables 2026-03-12 01:32:18 +05:30
vikrantgupta25
82c340554b feat(serviceaccount): update openapi spec 2026-03-12 01:32:18 +05:30
vikrantgupta25
e31a77bbdd feat(serviceaccount): deprecate the api keys 2026-03-12 01:32:18 +05:30
vikrantgupta25
33668aa659 feat(serviceaccount): fix openapi spec 2026-03-12 01:32:18 +05:30
vikrantgupta25
11b0739a37 feat(serviceaccount): add support for serviceaccount tokenizer in authn middleware 2026-03-12 01:32:18 +05:30
vikrantgupta25
210563ed98 feat(serviceaccount): add service account tokenizer and clean identity 2026-03-12 01:32:18 +05:30
128 changed files with 3870 additions and 2818 deletions

View File

@@ -18,6 +18,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser"
@@ -25,6 +26,7 @@ import (
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/SigNoz/signoz/pkg/zeus/noopzeus"
@@ -73,14 +75,14 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
signoz.NewSQLStoreProviderFactories(),
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userStore usertypes.UserStore) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing, userStore)
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx))
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser)
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, _ querier.Querier, _ licensing.Licensing, userGetter user.Getter) dashboard.Module {
return impldashboard.NewModule(impldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, userGetter)
},
func(_ licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return noopgateway.NewProviderFactory()

View File

@@ -9,12 +9,12 @@ import (
"github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn"
"github.com/SigNoz/signoz/ee/authn/callbackauthn/samlcallbackauthn"
"github.com/SigNoz/signoz/ee/authz/openfgaauthz"
eequerier "github.com/SigNoz/signoz/ee/querier"
"github.com/SigNoz/signoz/ee/authz/openfgaschema"
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
@@ -29,6 +29,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz"
@@ -36,6 +37,7 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/spf13/cobra"
@@ -95,7 +97,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
sqlstoreFactories,
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userStore usertypes.UserStore) (map[authtypes.AuthNProvider]authn.AuthN, error) {
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing)
if err != nil {
return nil, err
@@ -106,7 +108,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return nil, err
}
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing)
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing, userStore)
if err != nil {
return nil, err
}
@@ -119,8 +121,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), licensing, dashboardModule)
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, userGetter user.Getter) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing, userGetter)
},
func(licensing licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config] {
return httpgateway.NewProviderFactory(licensing)

View File

@@ -1775,7 +1775,7 @@ components:
type: string
key:
type: string
last_used:
last_observed_at:
format: date-time
type: string
name:
@@ -1789,7 +1789,7 @@ components:
- id
- key
- expires_at
- last_used
- last_observed_at
- service_account_id
type: object
ServiceaccounttypesGettableFactorAPIKeyWithKey:
@@ -1833,6 +1833,9 @@ components:
createdAt:
format: date-time
type: string
deletedAt:
format: date-time
type: string
email:
type: string
id:
@@ -1857,6 +1860,7 @@ components:
- roles
- status
- orgID
- deletedAt
type: object
ServiceaccounttypesUpdatableFactorAPIKey:
properties:
@@ -1989,43 +1993,6 @@ components:
userId:
type: string
type: object
TypesGettableAPIKey:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
createdByUser:
$ref: '#/components/schemas/TypesUser'
expiresAt:
format: int64
type: integer
id:
type: string
lastUsed:
format: int64
type: integer
name:
type: string
revoked:
type: boolean
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
updatedByUser:
$ref: '#/components/schemas/TypesUser'
userId:
type: string
required:
- id
type: object
TypesGettableGlobalConfig:
properties:
external_url:
@@ -2087,16 +2054,6 @@ components:
required:
- id
type: object
TypesPostableAPIKey:
properties:
expiresInDays:
format: int64
type: integer
name:
type: string
role:
type: string
type: object
TypesPostableAcceptInvite:
properties:
displayName:
@@ -2161,33 +2118,6 @@ components:
required:
- id
type: object
TypesStorableAPIKey:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
id:
type: string
name:
type: string
revoked:
type: boolean
role:
type: string
token:
type: string
updatedAt:
format: date-time
type: string
updatedBy:
type: string
userId:
type: string
required:
- id
type: object
TypesUser:
properties:
createdAt:
@@ -3861,222 +3791,6 @@ paths:
summary: Update org preference
tags:
- preferences
/api/v1/pats:
get:
deprecated: false
description: This endpoint lists all api keys
operationId: ListAPIKeys
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/TypesGettableAPIKey'
type: array
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List api keys
tags:
- users
post:
deprecated: false
description: This endpoint creates an api key
operationId: CreateAPIKey
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TypesPostableAPIKey'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesGettableAPIKey'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create api key
tags:
- users
/api/v1/pats/{id}:
delete:
deprecated: false
description: This endpoint revokes an api key
operationId: RevokeAPIKey
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Revoke api key
tags:
- users
put:
deprecated: false
description: This endpoint updates an api key
operationId: UpdateAPIKey
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/TypesStorableAPIKey'
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Update api key
tags:
- users
/api/v1/public/dashboards/{id}:
get:
deprecated: false

View File

@@ -13,17 +13,20 @@ import (
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
)
var (
TypeableResourcesRoles = authtypes.MustNewTypeableMetaResources(authtypes.MustNewName("roles"))
)
type provider struct {
pkgAuthzService authz.AuthZ
openfgaServer *openfgaserver.Server
licensing licensing.Licensing
store roletypes.Store
store authtypes.RoleStore
registry []authz.RegisterTypeable
}
@@ -82,23 +85,23 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
return provider.openfgaServer.Write(ctx, additions, deletions)
}
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
return provider.pkgAuthzService.Get(ctx, orgID, id)
}
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
return provider.pkgAuthzService.GetByOrgIDAndName(ctx, orgID, name)
}
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
return provider.pkgAuthzService.List(ctx, orgID)
}
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
return provider.pkgAuthzService.ListByOrgIDAndNames(ctx, orgID, names)
}
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.Role, error) {
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
return provider.pkgAuthzService.ListByOrgIDAndIDs(ctx, orgID, ids)
}
@@ -114,7 +117,7 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
return provider.pkgAuthzService.Revoke(ctx, orgID, names, subject)
}
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*roletypes.Role) error {
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*authtypes.Role) error {
return provider.pkgAuthzService.CreateManagedRoles(ctx, orgID, managedRoles)
}
@@ -136,16 +139,16 @@ func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context,
return provider.Write(ctx, tuples, nil)
}
func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
_, err := provider.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
return provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
}
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) (*roletypes.Role, error) {
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) (*authtypes.Role, error) {
_, err := provider.licensing.GetActive(ctx, orgID)
if err != nil {
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
@@ -159,10 +162,10 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
}
if existingRole != nil {
return roletypes.NewRoleFromStorableRole(existingRole), nil
return authtypes.NewRoleFromStorableRole(existingRole), nil
}
err = provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
err = provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
if err != nil {
return nil, err
}
@@ -217,13 +220,13 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
return objects, nil
}
func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *roletypes.Role) error {
func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
_, err := provider.licensing.GetActive(ctx, orgID)
if err != nil {
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Update(ctx, orgID, roletypes.NewStorableRoleFromRole(role))
return provider.store.Update(ctx, orgID, authtypes.NewStorableRoleFromRole(role))
}
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
@@ -232,12 +235,12 @@ func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, n
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
additionTuples, err := roletypes.GetAdditionTuples(name, orgID, relation, additions)
additionTuples, err := authtypes.GetAdditionTuples(name, orgID, relation, additions)
if err != nil {
return err
}
deletionTuples, err := roletypes.GetDeletionTuples(name, orgID, relation, deletions)
deletionTuples, err := authtypes.GetDeletionTuples(name, orgID, relation, deletions)
if err != nil {
return err
}
@@ -261,7 +264,7 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
return err
}
role := roletypes.NewRoleFromStorableRole(storableRole)
role := authtypes.NewRoleFromStorableRole(storableRole)
err = role.ErrIfManaged()
if err != nil {
return err
@@ -271,7 +274,7 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
}
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
return []authtypes.Typeable{authtypes.TypeableRole, roletypes.TypeableResourcesRoles}
return []authtypes.Typeable{authtypes.TypeableRole, TypeableResourcesRoles}
}
func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID valuer.UUID) ([]*openfgav1.TupleKey, error) {
@@ -283,7 +286,7 @@ func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID va
adminSubject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
},
orgID,
)
@@ -298,7 +301,7 @@ func (provider *provider) getManagedRoleGrantTuples(orgID valuer.UUID, userID va
anonymousSubject,
authtypes.RelationAssignee,
[]authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAnonymousRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAnonymousRoleName),
},
orgID,
)

View File

@@ -2,39 +2,45 @@ module base
type organisation
relations
define read: [user, role#assignee]
define update: [user, role#assignee]
define read: [user, serviceaccount, role#assignee]
define update: [user, serviceaccount, role#assignee]
type user
relations
define read: [user, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
define read: [user, serviceaccount, role#assignee]
define update: [user, serviceaccount, role#assignee]
define delete: [user, serviceaccount, role#assignee]
type serviceaccount
relations
define read: [user, serviceaccount, role#assignee]
define update: [user, serviceaccount, role#assignee]
define delete: [user, serviceaccount, role#assignee]
type anonymous
type role
relations
define assignee: [user, anonymous]
define assignee: [user, serviceaccount, anonymous]
define read: [user, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
define read: [user, serviceaccount, role#assignee]
define update: [user, serviceaccount, role#assignee]
define delete: [user, serviceaccount, role#assignee]
type metaresources
relations
define create: [user, role#assignee]
define list: [user, role#assignee]
define create: [user, serviceaccount, role#assignee]
define list: [user, serviceaccount, role#assignee]
type metaresource
relations
define read: [user, anonymous, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
define read: [user, serviceaccount, anonymous, role#assignee]
define update: [user, serviceaccount, role#assignee]
define delete: [user, serviceaccount, role#assignee]
define block: [user, role#assignee]
define block: [user, serviceaccount, role#assignee]
type telemetryresource
relations
define read: [user, role#assignee]
define read: [user, serviceaccount, role#assignee]

View File

@@ -31,9 +31,22 @@ func (server *Server) Stop(ctx context.Context) error {
}
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, relation authtypes.Relation, typeable authtypes.Typeable, selectors []authtypes.Selector, _ []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil {
return err
subject := ""
switch claims.Principal {
case authtypes.PrincipalUser.StringValue():
user, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil {
return err
}
subject = user
case authtypes.PrincipalServiceAccount.StringValue():
serviceAccount, err := authtypes.NewSubject(authtypes.TypeableServiceAccount, claims.ServiceAccountID, orgID, nil)
if err != nil {
return err
}
subject = serviceAccount
}
tupleSlice, err := typeable.Tuples(subject, relation, selectors, orgID)

View File

@@ -11,6 +11,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/types"
@@ -19,7 +20,6 @@ import (
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
"github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -31,9 +31,9 @@ type module struct {
licensing licensing.Licensing
}
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing, userGetter user.Getter) dashboard.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard")
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser)
pkgDashboardModule := pkgimpldashboard.NewModule(store, settings, analytics, orgGetter, queryParser, userGetter)
return &module{
pkgDashboardModule: pkgDashboardModule,
@@ -214,8 +214,8 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
return module.pkgDashboardModule.Update(ctx, orgID, id, updatedBy, data, diff)
}
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, role, lock)
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, lock bool) error {
return module.pkgDashboardModule.LockUnlock(ctx, orgID, id, updatedBy, lock)
}
func (module *module) MustGetTypeables() []authtypes.Typeable {
@@ -224,7 +224,7 @@ func (module *module) MustGetTypeables() []authtypes.Typeable {
func (module *module) MustGetManagedRoleTransactions() map[string][]*authtypes.Transaction {
return map[string][]*authtypes.Transaction{
roletypes.SigNozAnonymousRoleName: {
authtypes.SigNozAnonymousRoleName: {
{
ID: valuer.GenerateUUID(),
Relation: authtypes.RelationRead,

View File

@@ -63,6 +63,8 @@ func (h *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
h.set.Logger.ErrorContext(ctx, "panic in QueryRange",
"error", r,
"user", claims.UserID,
"principal", claims.Principal,
"service_account", claims.ServiceAccountID,
"payload", string(queryJSON),
"stacktrace", stackTrace,
)

View File

@@ -12,10 +12,9 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/user"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
"go.uber.org/zap"
@@ -49,7 +48,7 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
return
}
apiKey, apiErr := ah.getOrCreateCloudIntegrationPAT(r.Context(), claims.OrgID, cloudProvider)
apiKey, apiErr := ah.getOrCreateCloudIntegrationAPIKey(r.Context(), claims.OrgID, cloudProvider)
if apiErr != nil {
RespondError(w, basemodel.WrapApiError(
apiErr, "couldn't provision PAT for cloud integration:",
@@ -109,32 +108,25 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
ah.Respond(w, result)
}
func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId string, cloudProvider string) (
func (ah *APIHandler) getOrCreateCloudIntegrationAPIKey(ctx context.Context, orgId string, cloudProvider string) (
string, *basemodel.ApiError,
) {
integrationPATName := fmt.Sprintf("%s integration", cloudProvider)
integrationUser, apiErr := ah.getOrCreateCloudIntegrationUser(ctx, orgId, cloudProvider)
integrationServiceAccount, apiErr := ah.getOrCreateCloudIntegrationServiceAccount(ctx, orgId, cloudProvider)
if apiErr != nil {
return "", apiErr
}
orgIdUUID, err := valuer.NewUUID(orgId)
keys, err := ah.Signoz.Modules.ServiceAccount.ListFactorAPIKey(ctx, integrationServiceAccount.ID)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't parse orgId: %w", err,
"couldn't list api keys: %w", err,
))
}
allPats, err := ah.Signoz.Modules.User.ListAPIKeys(ctx, orgIdUUID)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't list PATs: %w", err,
))
}
for _, p := range allPats {
if p.UserID == integrationUser.ID && p.Name == integrationPATName {
return p.Token, nil
for _, key := range keys {
if key.Name == integrationPATName {
return key.Key, nil
}
}
@@ -143,46 +135,35 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
zap.String("cloudProvider", cloudProvider),
)
newPAT, err := types.NewStorableAPIKey(
integrationPATName,
integrationUser.ID,
types.RoleViewer,
0,
)
apiKey, err := integrationServiceAccount.NewFactorAPIKey(integrationPATName, 0)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err,
))
}
err = ah.Signoz.Modules.User.CreateAPIKey(ctx, newPAT)
err = ah.Signoz.Modules.ServiceAccount.CreateFactorAPIKey(ctx, apiKey)
if err != nil {
return "", basemodel.InternalError(fmt.Errorf(
"couldn't create cloud integration PAT: %w", err,
"couldn't create cloud integration api key: %w", err,
))
}
return newPAT.Token, nil
return apiKey.Key, nil
}
func (ah *APIHandler) getOrCreateCloudIntegrationUser(
func (ah *APIHandler) getOrCreateCloudIntegrationServiceAccount(
ctx context.Context, orgId string, cloudProvider string,
) (*types.User, *basemodel.ApiError) {
cloudIntegrationUserName := fmt.Sprintf("%s-integration", cloudProvider)
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", cloudIntegrationUserName))
) (*serviceaccounttypes.ServiceAccount, *basemodel.ApiError) {
serviceAccountName := fmt.Sprintf("%s-integration", cloudProvider)
email := valuer.MustNewEmail(fmt.Sprintf("%s@signoz.io", serviceAccountName))
cloudIntegrationUser, err := types.NewUser(cloudIntegrationUserName, email, types.RoleViewer, valuer.MustNewUUID(orgId), types.UserStatusActive)
serviceAccount := serviceaccounttypes.NewServiceAccount(serviceAccountName, email, []string{authtypes.SigNozViewerRoleName}, serviceaccounttypes.StatusActive, valuer.MustNewUUID(orgId))
serviceAccount, err := ah.Signoz.Modules.ServiceAccount.GetOrCreate(ctx, serviceAccount)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
return nil, basemodel.InternalError(fmt.Errorf("couldn't look for integration service account: %w", err))
}
password := types.MustGenerateFactorPassword(cloudIntegrationUser.ID.StringValue())
cloudIntegrationUser, err = ah.Signoz.Modules.User.GetOrCreateUser(ctx, cloudIntegrationUser, user.WithFactorPassword(password))
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't look for integration user: %w", err))
}
return cloudIntegrationUser, nil
return serviceAccount, nil
}
func (ah *APIHandler) getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (

View File

@@ -216,8 +216,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
}),
otelmux.WithPublicEndpoint(),
))
r.Use(middleware.NewAuthN([]string{"Authorization", "Sec-WebSocket-Protocol"}, s.signoz.Sharder, s.signoz.Tokenizer, s.signoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.signoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.signoz.Instrumentation.Logger(), s.signoz.Sharder).Wrap)
r.Use(middleware.NewAuthN([]string{"Authorization", "Sec-WebSocket-Protocol"}, []string{"SIGNOZ-API-KEY"}, s.signoz.Sharder, s.signoz.Tokenizer, s.signoz.ServiceAccountTokenizer, s.signoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewTimeout(s.signoz.Instrumentation.Logger(),
s.config.APIServer.Timeout.ExcludedRoutes,
s.config.APIServer.Timeout.Default,

View File

@@ -80,21 +80,6 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
alertDataRows := cmock.NewRows(cols, tc.Values)
mock := telemetryStore.Mock()
// Mock metadata queries for FetchTemporalityAndTypeMulti
// First query: fetchMetricsTemporalityAndType (from signoz_metrics time series table)
metadataCols := []cmock.ColumnType{
{Name: "metric_name", Type: "String"},
{Name: "temporality", Type: "String"},
{Name: "type", Type: "String"},
{Name: "is_monotonic", Type: "Bool"},
}
metadataRows := cmock.NewRows(metadataCols, [][]any{
{"probe_success", metrictypes.Unspecified, metrictypes.GaugeType, false},
})
mock.ExpectQuery("*distributed_time_series_v4*").WithArgs(nil, nil, nil).WillReturnRows(metadataRows)
// Second query: fetchMeterSourceMetricsTemporalityAndType (from signoz_meter table)
emptyMetadataRows := cmock.NewRows(metadataCols, [][]any{})
mock.ExpectQuery("*meter*").WithArgs(nil).WillReturnRows(emptyMetadataRows)
// Generate query arguments for the metric query
evalTime := time.Now().UTC()

View File

@@ -2113,7 +2113,7 @@ export interface ServiceaccounttypesFactorAPIKeyDTO {
* @type string
* @format date-time
*/
last_used: Date;
last_observed_at: Date;
/**
* @type string
*/
@@ -2173,6 +2173,11 @@ export interface ServiceaccounttypesServiceAccountDTO {
* @format date-time
*/
createdAt?: Date;
/**
* @type string
* @format date-time
*/
deletedAt: Date;
/**
* @type string
*/
@@ -2340,63 +2345,6 @@ export interface TypesChangePasswordRequestDTO {
userId?: string;
}
export interface TypesGettableAPIKeyDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
createdByUser?: TypesUserDTO;
/**
* @type integer
* @format int64
*/
expiresAt?: number;
/**
* @type string
*/
id: string;
/**
* @type integer
* @format int64
*/
lastUsed?: number;
/**
* @type string
*/
name?: string;
/**
* @type boolean
*/
revoked?: boolean;
/**
* @type string
*/
role?: string;
/**
* @type string
*/
token?: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
updatedByUser?: TypesUserDTO;
/**
* @type string
*/
userId?: string;
}
export interface TypesGettableGlobalConfigDTO {
/**
* @type string
@@ -2490,22 +2438,6 @@ export interface TypesOrganizationDTO {
updatedAt?: Date;
}
export interface TypesPostableAPIKeyDTO {
/**
* @type integer
* @format int64
*/
expiresInDays?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
role?: string;
}
export interface TypesPostableAcceptInviteDTO {
/**
* @type string
@@ -2597,51 +2529,6 @@ export interface TypesResetPasswordTokenDTO {
token?: string;
}
export interface TypesStorableAPIKeyDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name?: string;
/**
* @type boolean
*/
revoked?: boolean;
/**
* @type string
*/
role?: string;
/**
* @type string
*/
token?: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
/**
* @type string
*/
userId?: string;
}
export interface TypesUserDTO {
/**
* @type string
@@ -3106,31 +2993,6 @@ export type GetOrgPreference200 = {
export type UpdateOrgPreferencePathParameters = {
name: string;
};
export type ListAPIKeys200 = {
/**
* @type array
*/
data: TypesGettableAPIKeyDTO[];
/**
* @type string
*/
status: string;
};
export type CreateAPIKey201 = {
data: TypesGettableAPIKeyDTO;
/**
* @type string
*/
status: string;
};
export type RevokeAPIKeyPathParameters = {
id: string;
};
export type UpdateAPIKeyPathParameters = {
id: string;
};
export type GetPublicDashboardDataPathParameters = {
id: string;
};

View File

@@ -22,7 +22,6 @@ import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
AcceptInvite201,
ChangePasswordPathParameters,
CreateAPIKey201,
CreateInvite201,
DeleteInvitePathParameters,
DeleteUserPathParameters,
@@ -33,21 +32,16 @@ import type {
GetResetPasswordTokenPathParameters,
GetUser200,
GetUserPathParameters,
ListAPIKeys200,
ListInvite200,
ListUsers200,
RenderErrorResponseDTO,
RevokeAPIKeyPathParameters,
TypesChangePasswordRequestDTO,
TypesPostableAcceptInviteDTO,
TypesPostableAPIKeyDTO,
TypesPostableBulkInviteRequestDTO,
TypesPostableForgotPasswordDTO,
TypesPostableInviteDTO,
TypesPostableResetPasswordDTO,
TypesStorableAPIKeyDTO,
TypesUserDTO,
UpdateAPIKeyPathParameters,
UpdateUser200,
UpdateUserPathParameters,
} from '../sigNoz.schemas';
@@ -750,349 +744,6 @@ export const useCreateBulkInvite = <
return useMutation(mutationOptions);
};
/**
* This endpoint lists all api keys
* @summary List api keys
*/
export const listAPIKeys = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListAPIKeys200>({
url: `/api/v1/pats`,
method: 'GET',
signal,
});
};
export const getListAPIKeysQueryKey = () => {
return [`/api/v1/pats`] as const;
};
export const getListAPIKeysQueryOptions = <
TData = Awaited<ReturnType<typeof listAPIKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listAPIKeys>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListAPIKeysQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof listAPIKeys>>> = ({
signal,
}) => listAPIKeys(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listAPIKeys>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListAPIKeysQueryResult = NonNullable<
Awaited<ReturnType<typeof listAPIKeys>>
>;
export type ListAPIKeysQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List api keys
*/
export function useListAPIKeys<
TData = Awaited<ReturnType<typeof listAPIKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listAPIKeys>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListAPIKeysQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List api keys
*/
export const invalidateListAPIKeys = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListAPIKeysQueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint creates an api key
* @summary Create api key
*/
export const createAPIKey = (
typesPostableAPIKeyDTO: BodyType<TypesPostableAPIKeyDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateAPIKey201>({
url: `/api/v1/pats`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: typesPostableAPIKeyDTO,
signal,
});
};
export const getCreateAPIKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
> => {
const mutationKey = ['createAPIKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createAPIKey>>,
{ data: BodyType<TypesPostableAPIKeyDTO> }
> = (props) => {
const { data } = props ?? {};
return createAPIKey(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateAPIKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof createAPIKey>>
>;
export type CreateAPIKeyMutationBody = BodyType<TypesPostableAPIKeyDTO>;
export type CreateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create api key
*/
export const useCreateAPIKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createAPIKey>>,
TError,
{ data: BodyType<TypesPostableAPIKeyDTO> },
TContext
> => {
const mutationOptions = getCreateAPIKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint revokes an api key
* @summary Revoke api key
*/
export const revokeAPIKey = ({ id }: RevokeAPIKeyPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/pats/${id}`,
method: 'DELETE',
});
};
export const getRevokeAPIKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof revokeAPIKey>>,
TError,
{ pathParams: RevokeAPIKeyPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof revokeAPIKey>>,
TError,
{ pathParams: RevokeAPIKeyPathParameters },
TContext
> => {
const mutationKey = ['revokeAPIKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof revokeAPIKey>>,
{ pathParams: RevokeAPIKeyPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return revokeAPIKey(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type RevokeAPIKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof revokeAPIKey>>
>;
export type RevokeAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Revoke api key
*/
export const useRevokeAPIKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof revokeAPIKey>>,
TError,
{ pathParams: RevokeAPIKeyPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof revokeAPIKey>>,
TError,
{ pathParams: RevokeAPIKeyPathParameters },
TContext
> => {
const mutationOptions = getRevokeAPIKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint updates an api key
* @summary Update api key
*/
export const updateAPIKey = (
{ id }: UpdateAPIKeyPathParameters,
typesStorableAPIKeyDTO: BodyType<TypesStorableAPIKeyDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/pats/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: typesStorableAPIKeyDTO,
});
};
export const getUpdateAPIKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateAPIKey>>,
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateAPIKey>>,
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
> => {
const mutationKey = ['updateAPIKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateAPIKey>>,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<TypesStorableAPIKeyDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateAPIKey(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateAPIKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof updateAPIKey>>
>;
export type UpdateAPIKeyMutationBody = BodyType<TypesStorableAPIKeyDTO>;
export type UpdateAPIKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update api key
*/
export const useUpdateAPIKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateAPIKey>>,
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateAPIKey>>,
TError,
{
pathParams: UpdateAPIKeyPathParameters;
data: BodyType<TypesStorableAPIKeyDTO>;
},
TContext
> => {
const mutationOptions = getUpdateAPIKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint resets the password by token
* @summary Reset password

View File

@@ -199,6 +199,8 @@ describe('Dashboard landing page actions header tests', () => {
setLayouts: jest.fn(),
setSelectedDashboard: jest.fn(),
updatedTimeRef: { current: null },
toScrollWidgetId: '',
setToScrollWidgetId: jest.fn(),
updateLocalStorageDashboardVariables: jest.fn(),
dashboardQueryRangeCalled: false,
setDashboardQueryRangeCalled: jest.fn(),

View File

@@ -1,9 +1,9 @@
import { renderHook } from '@testing-library/react';
import { useScrollToWidgetIdStore } from 'providers/Dashboard/helpers/scrollToWidgetIdHelper';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useScrollWidgetIntoView } from '../useScrollWidgetIntoView';
jest.mock('providers/Dashboard/helpers/scrollToWidgetIdHelper');
jest.mock('providers/Dashboard/Dashboard');
type MockHTMLElement = {
scrollIntoView: jest.Mock;
@@ -18,35 +18,25 @@ function createMockElement(): MockHTMLElement {
}
describe('useScrollWidgetIntoView', () => {
const mockedUseScrollToWidgetIdStore = useScrollToWidgetIdStore as jest.MockedFunction<
typeof useScrollToWidgetIdStore
const mockedUseDashboard = useDashboard as jest.MockedFunction<
typeof useDashboard
>;
let mockElement: MockHTMLElement;
let ref: React.RefObject<HTMLDivElement>;
let setToScrollWidgetId: jest.Mock;
function mockStore(toScrollWidgetId: string): void {
const storeState = { toScrollWidgetId, setToScrollWidgetId };
mockedUseScrollToWidgetIdStore.mockImplementation(
(selector) =>
selector(
(storeState as unknown) as Parameters<typeof selector>[0],
) as ReturnType<typeof useScrollToWidgetIdStore>,
);
}
beforeEach(() => {
jest.clearAllMocks();
mockElement = createMockElement();
ref = ({
current: mockElement,
} as unknown) as React.RefObject<HTMLDivElement>;
setToScrollWidgetId = jest.fn();
});
it('scrolls into view and focuses when toScrollWidgetId matches widget id', () => {
mockStore('widget-id');
const setToScrollWidgetId = jest.fn();
const mockElement = createMockElement();
const ref = ({
current: mockElement,
} as unknown) as React.RefObject<HTMLDivElement>;
mockedUseDashboard.mockReturnValue(({
toScrollWidgetId: 'widget-id',
setToScrollWidgetId,
} as unknown) as ReturnType<typeof useDashboard>);
renderHook(() => useScrollWidgetIntoView('widget-id', ref));
@@ -59,7 +49,16 @@ describe('useScrollWidgetIntoView', () => {
});
it('does nothing when toScrollWidgetId does not match widget id', () => {
mockStore('other-widget');
const setToScrollWidgetId = jest.fn();
const mockElement = createMockElement();
const ref = ({
current: mockElement,
} as unknown) as React.RefObject<HTMLDivElement>;
mockedUseDashboard.mockReturnValue(({
toScrollWidgetId: 'other-widget',
setToScrollWidgetId,
} as unknown) as ReturnType<typeof useDashboard>);
renderHook(() => useScrollWidgetIntoView('widget-id', ref));

View File

@@ -1,5 +1,5 @@
import { RefObject, useEffect } from 'react';
import { useScrollToWidgetIdStore } from 'providers/Dashboard/helpers/scrollToWidgetIdHelper';
import { useDashboard } from 'providers/Dashboard/Dashboard';
/**
* Scrolls the given widget container into view when the dashboard
@@ -11,10 +11,7 @@ export function useScrollWidgetIntoView<T extends HTMLElement>(
widgetId: string,
widgetContainerRef: RefObject<T>,
): void {
const toScrollWidgetId = useScrollToWidgetIdStore((s) => s.toScrollWidgetId);
const setToScrollWidgetId = useScrollToWidgetIdStore(
(s) => s.setToScrollWidgetId,
);
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
useEffect(() => {
if (toScrollWidgetId === widgetId) {

View File

@@ -1,4 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useScrollWidgetIntoView } from 'container/DashboardContainer/visualization/hooks/useScrollWidgetIntoView';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
@@ -33,6 +34,8 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useScrollWidgetIntoView(widget.id, graphRef);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);

View File

@@ -1,4 +1,5 @@
import { useMemo, useRef } from 'react';
import { useScrollWidgetIntoView } from 'container/DashboardContainer/visualization/hooks/useScrollWidgetIntoView';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
@@ -31,6 +32,8 @@ function HistogramPanel(props: PanelWrapperProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useScrollWidgetIntoView(widget.id, graphRef);
const config = useMemo(() => {
return prepareHistogramPanelConfig({
widget,

View File

@@ -2,6 +2,7 @@ import { useEffect, useMemo, useRef, useState } from 'react';
import TimeSeries from 'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries';
import ChartManager from 'container/DashboardContainer/visualization/components/ChartManager/ChartManager';
import { usePanelContextMenu } from 'container/DashboardContainer/visualization/hooks/usePanelContextMenu';
import { useScrollWidgetIntoView } from 'container/DashboardContainer/visualization/hooks/useScrollWidgetIntoView';
import { PanelWrapperProps } from 'container/PanelWrapper/panelWrapper.types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
@@ -32,6 +33,8 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const { timezone } = useTimezone();
useScrollWidgetIntoView(widget.id, graphRef);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);

View File

@@ -5,7 +5,6 @@ import logEvent from 'api/common/logEvent';
import { DEFAULT_ENTITY_VERSION, ENTITY_VERSION_V5 } from 'constants/app';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useScrollWidgetIntoView } from 'container/DashboardContainer/visualization/hooks/useScrollWidgetIntoView';
import { populateMultipleResults } from 'container/NewWidget/LeftContainer/WidgetGraph/util';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/types';
import { useIsPanelWaitingOnVariable } from 'hooks/dashboard/useVariableFetchState';
@@ -68,7 +67,11 @@ function GridCardGraph({
const [isInternalServerError, setIsInternalServerError] = useState<boolean>(
false,
);
const { setDashboardQueryRangeCalled } = useDashboard();
const {
toScrollWidgetId,
setToScrollWidgetId,
setDashboardQueryRangeCalled,
} = useDashboard();
const {
minTime,
@@ -106,11 +109,20 @@ function GridCardGraph({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const widgetContainerRef = useRef<HTMLDivElement>(null);
const graphRef = useRef<HTMLDivElement>(null);
const isVisible = useIntersectionObserver(widgetContainerRef, undefined, true);
const isVisible = useIntersectionObserver(graphRef, undefined, true);
useScrollWidgetIntoView(widget?.id || '', widgetContainerRef);
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const updatedQuery = widget?.query;
@@ -294,7 +306,7 @@ function GridCardGraph({
: headerMenuList;
return (
<div style={{ height: '100%', width: '100%' }} ref={widgetContainerRef}>
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
{isEmptyLayout ? (
<EmptyWidget />
) : (

View File

@@ -34,7 +34,6 @@ import { cloneDeep, defaultTo, isEmpty, isUndefined } from 'lodash-es';
import { Check, X } from 'lucide-react';
import { DashboardWidgetPageParams } from 'pages/DashboardWidget';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useScrollToWidgetIdStore } from 'providers/Dashboard/helpers/scrollToWidgetIdHelper';
import {
clearSelectedRowWidgetId,
getSelectedRowWidgetId,
@@ -87,12 +86,10 @@ function NewWidget({
enableDrillDown = false,
}: NewWidgetProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
const setToScrollWidgetId = useScrollToWidgetIdStore(
(s) => s.setToScrollWidgetId,
);
const {
selectedDashboard,
setSelectedDashboard,
setToScrollWidgetId,
columnWidths,
} = useDashboard();

View File

@@ -0,0 +1,180 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { ToggleGraphProps } from 'components/Graph/types';
import Uplot from 'components/Uplot';
import GraphManager from 'container/GridCardLayout/GridCard/FullView/GraphManager';
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
import { getUplotClickData } from 'container/QueryTable/Drilldown/drilldownUtils';
import useGraphContextMenu from 'container/QueryTable/Drilldown/useGraphContextMenu';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { getUplotHistogramChartOptions } from 'lib/uPlotLib/getUplotHistogramChartOptions';
import _noop from 'lodash-es/noop';
import { ContextMenu, useCoordinates } from 'periscope/components/ContextMenu';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { buildHistogramData } from './histogram';
import { PanelWrapperProps } from './panelWrapper.types';
function HistogramPanelWrapper({
queryResponse,
widget,
setGraphVisibility,
graphVisibility,
isFullViewMode,
onToggleModelHandler,
onClickHandler,
enableDrillDown = false,
}: PanelWrapperProps): JSX.Element {
const graphRef = useRef<HTMLDivElement>(null);
const legendScrollPositionRef = useRef<number>(0);
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const isDarkMode = useIsDarkMode();
const containerDimensions = useResizeObserver(graphRef);
const {
coordinates,
popoverPosition,
clickedData,
onClose,
onClick,
subMenu,
setSubMenu,
} = useCoordinates();
const { menuItemsConfig } = useGraphContextMenu({
widgetId: widget.id || '',
query: widget.query,
graphData: clickedData,
onClose,
coordinates,
subMenu,
setSubMenu,
contextLinks: widget.contextLinks,
panelType: widget.panelTypes,
queryRange: queryResponse,
});
const clickHandlerWithContextMenu = useCallback(
(...args: any[]) => {
const [
,
,
,
,
metric,
queryData,
absoluteMouseX,
absoluteMouseY,
,
focusedSeries,
] = args;
const data = getUplotClickData({
metric,
queryData,
absoluteMouseX,
absoluteMouseY,
focusedSeries,
});
if (data && data?.record?.queryName) {
onClick(data.coord, { ...data.record, label: data.label });
}
},
[onClick],
);
const histogramData = buildHistogramData(
queryResponse.data?.payload.data.result,
widget?.bucketWidth,
widget?.bucketCount,
widget?.mergeAllActiveQueries,
);
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const lineChartRef = useRef<ToggleGraphProps>();
useEffect(() => {
const {
graphVisibilityStates: localStoredVisibilityState,
} = getLocalStorageGraphVisibilityState({
apiResponse: queryResponse.data?.payload.data.result || [],
name: widget.id,
});
if (setGraphVisibility) {
setGraphVisibility(localStoredVisibilityState);
}
}, [
queryResponse?.data?.payload?.data?.result,
setGraphVisibility,
widget.id,
]);
const histogramOptions = useMemo(
() =>
getUplotHistogramChartOptions({
id: widget.id,
dimensions: containerDimensions,
isDarkMode,
apiResponse: queryResponse.data?.payload,
histogramData,
panelType: widget.panelTypes,
setGraphsVisibilityStates: setGraphVisibility,
graphsVisibilityStates: graphVisibility,
mergeAllQueries: widget.mergeAllActiveQueries,
onClickHandler: enableDrillDown
? clickHandlerWithContextMenu
: onClickHandler ?? _noop,
legendScrollPosition: legendScrollPositionRef.current,
setLegendScrollPosition: (position: number) => {
legendScrollPositionRef.current = position;
},
}),
[
containerDimensions,
graphVisibility,
histogramData,
isDarkMode,
queryResponse.data?.payload,
setGraphVisibility,
widget.id,
widget.mergeAllActiveQueries,
widget.panelTypes,
clickHandlerWithContextMenu,
enableDrillDown,
onClickHandler,
],
);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
<Uplot options={histogramOptions} data={histogramData} ref={lineChartRef} />
<ContextMenu
coordinates={coordinates}
popoverPosition={popoverPosition}
title={menuItemsConfig.header as string}
items={menuItemsConfig.items}
onClose={onClose}
/>
{isFullViewMode && setGraphVisibility && !widget.mergeAllActiveQueries && (
<GraphManager
data={histogramData}
name={widget.id}
options={histogramOptions}
yAxisUnit={widget.yAxisUnit}
onToggleModelHandler={onToggleModelHandler}
setGraphsVisibilityStates={setGraphVisibility}
graphsVisibilityStates={graphVisibility}
lineChartRef={lineChartRef}
/>
)}
</div>
);
}
export default HistogramPanelWrapper;

View File

@@ -0,0 +1,4 @@
.info-text {
margin-top: 8px;
padding: 8px;
}

View File

@@ -0,0 +1,318 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Alert } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import Uplot from 'components/Uplot';
import { PANEL_TYPES } from 'constants/queryBuilder';
import GraphManager from 'container/GridCardLayout/GridCard/FullView/GraphManager';
import { getLocalStorageGraphVisibilityState } from 'container/GridCardLayout/GridCard/utils';
import { getUplotClickData } from 'container/QueryTable/Drilldown/drilldownUtils';
import useGraphContextMenu from 'container/QueryTable/Drilldown/useGraphContextMenu';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { cloneDeep, isEqual, isUndefined } from 'lodash-es';
import _noop from 'lodash-es/noop';
import { ContextMenu, useCoordinates } from 'periscope/components/ContextMenu';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTimezone } from 'providers/Timezone';
import { DataSource } from 'types/common/queryBuilder';
import uPlot from 'uplot';
import { getSortedSeriesData } from 'utils/getSortedSeriesData';
import { getTimeRange } from 'utils/getTimeRange';
import { PanelWrapperProps } from './panelWrapper.types';
import { getTimeRangeFromStepInterval, isApmMetric } from './utils';
import './UplotPanelWrapper.styles.scss';
function UplotPanelWrapper({
queryResponse,
widget,
isFullViewMode,
setGraphVisibility,
graphVisibility,
onToggleModelHandler,
onClickHandler,
onDragSelect,
selectedGraph,
customTooltipElement,
customSeries,
enableDrillDown = false,
}: PanelWrapperProps): JSX.Element {
const { toScrollWidgetId, setToScrollWidgetId } = useDashboard();
const isDarkMode = useIsDarkMode();
const lineChartRef = useRef<ToggleGraphProps>();
const graphRef = useRef<HTMLDivElement>(null);
const legendScrollPositionRef = useRef<{
scrollTop: number;
scrollLeft: number;
}>({
scrollTop: 0,
scrollLeft: 0,
});
const [minTimeScale, setMinTimeScale] = useState<number>();
const [maxTimeScale, setMaxTimeScale] = useState<number>();
const { currentQuery } = useQueryBuilder();
const [hiddenGraph, setHiddenGraph] = useState<{ [key: string]: boolean }>();
useEffect(() => {
if (toScrollWidgetId === widget.id) {
graphRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
graphRef.current?.focus();
setToScrollWidgetId('');
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [queryResponse]);
const containerDimensions = useResizeObserver(graphRef);
const {
coordinates,
popoverPosition,
clickedData,
onClose,
onClick,
subMenu,
setSubMenu,
} = useCoordinates();
const { menuItemsConfig } = useGraphContextMenu({
widgetId: widget.id || '',
query: widget.query,
graphData: clickedData,
onClose,
coordinates,
subMenu,
setSubMenu,
contextLinks: widget.contextLinks,
panelType: widget.panelTypes,
queryRange: queryResponse,
});
useEffect(() => {
const {
graphVisibilityStates: localStoredVisibilityState,
} = getLocalStorageGraphVisibilityState({
apiResponse: queryResponse.data?.payload.data.result || [],
name: widget.id,
});
if (setGraphVisibility) {
setGraphVisibility(localStoredVisibilityState);
}
}, [
queryResponse?.data?.payload?.data?.result,
setGraphVisibility,
widget.id,
]);
if (queryResponse.data && widget.panelTypes === PANEL_TYPES.BAR) {
const sortedSeriesData = getSortedSeriesData(
queryResponse.data?.payload.data.result,
);
queryResponse.data.payload.data.result = sortedSeriesData;
}
const stackedBarChart = useMemo(
() =>
(selectedGraph
? selectedGraph === PANEL_TYPES.BAR
: widget?.panelTypes === PANEL_TYPES.BAR) && widget?.stackedBarChart,
[selectedGraph, widget?.panelTypes, widget?.stackedBarChart],
);
const chartData = useMemo(
() =>
getUPlotChartData(
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
),
[
queryResponse?.data?.payload,
widget.fillSpans,
stackedBarChart,
hiddenGraph,
],
);
useEffect(() => {
if (widget.panelTypes === PANEL_TYPES.BAR && stackedBarChart) {
const graphV = cloneDeep(graphVisibility)?.slice(1);
const isSomeSelectedLegend = graphV?.some((v) => v === false);
if (isSomeSelectedLegend) {
const hiddenIndex = graphV?.findIndex((v) => v === true);
if (!isUndefined(hiddenIndex) && hiddenIndex !== -1) {
const updatedHiddenGraph = { [hiddenIndex]: true };
if (!isEqual(hiddenGraph, updatedHiddenGraph)) {
setHiddenGraph(updatedHiddenGraph);
}
}
}
}
}, [graphVisibility, hiddenGraph, widget.panelTypes, stackedBarChart]);
const { timezone } = useTimezone();
const clickHandlerWithContextMenu = useCallback(
(...args: any[]) => {
const [
xValue,
,
,
,
metric,
queryData,
absoluteMouseX,
absoluteMouseY,
axesData,
focusedSeries,
] = args;
const data = getUplotClickData({
metric,
queryData,
absoluteMouseX,
absoluteMouseY,
focusedSeries,
});
// Compute time range if needed and if axes data is available
let timeRange;
if (axesData && queryData?.queryName) {
// Get the compositeQuery from the response params
const compositeQuery = (queryResponse?.data?.params as any)?.compositeQuery;
if (compositeQuery?.queries) {
// Find the specific query by name from the queries array
const specificQuery = compositeQuery.queries.find(
(query: any) => query.spec?.name === queryData.queryName,
);
// Use the stepInterval from the specific query, fallback to default
const stepInterval = specificQuery?.spec?.stepInterval || 60;
timeRange = getTimeRangeFromStepInterval(
stepInterval,
metric?.clickedTimestamp || xValue, // Use the clicked timestamp if available, otherwise use the click position timestamp
specificQuery?.spec?.signal === DataSource.METRICS &&
isApmMetric(specificQuery?.spec?.aggregations[0]?.metricName),
);
}
}
if (data && data?.record?.queryName) {
onClick(data.coord, { ...data.record, label: data.label, timeRange });
}
},
[onClick, queryResponse],
);
const options = useMemo(
() =>
getUPlotChartOptions({
id: widget?.id,
apiResponse: queryResponse.data?.payload,
dimensions: containerDimensions,
isDarkMode,
onDragSelect,
yAxisUnit: widget?.yAxisUnit,
onClickHandler: enableDrillDown
? clickHandlerWithContextMenu
: onClickHandler ?? _noop,
thresholds: widget.thresholds,
minTimeScale,
maxTimeScale,
softMax: widget.softMax === undefined ? null : widget.softMax,
softMin: widget.softMin === undefined ? null : widget.softMin,
graphsVisibilityStates: graphVisibility,
setGraphsVisibilityStates: setGraphVisibility,
panelType: selectedGraph || widget.panelTypes,
currentQuery,
stackBarChart: stackedBarChart,
hiddenGraph,
setHiddenGraph,
customTooltipElement,
tzDate: (timestamp: number) =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value),
timezone: timezone.value,
customSeries,
isLogScale: widget?.isLogScale,
colorMapping: widget?.customLegendColors,
enhancedLegend: true, // Enable enhanced legend
legendPosition: widget?.legendPosition,
query: widget?.query || currentQuery,
legendScrollPosition: legendScrollPositionRef.current,
setLegendScrollPosition: (position: {
scrollTop: number;
scrollLeft: number;
}) => {
legendScrollPositionRef.current = position;
},
decimalPrecision: widget.decimalPrecision,
}),
[
queryResponse.data?.payload,
containerDimensions,
isDarkMode,
onDragSelect,
clickHandlerWithContextMenu,
minTimeScale,
maxTimeScale,
graphVisibility,
setGraphVisibility,
selectedGraph,
currentQuery,
hiddenGraph,
customTooltipElement,
timezone.value,
customSeries,
enableDrillDown,
onClickHandler,
widget,
stackedBarChart,
],
);
return (
<div style={{ height: '100%', width: '100%' }} ref={graphRef}>
<Uplot options={options} data={chartData} ref={lineChartRef} />
<ContextMenu
coordinates={coordinates}
popoverPosition={popoverPosition}
title={menuItemsConfig.header as string}
items={menuItemsConfig.items}
onClose={onClose}
/>
{stackedBarChart && isFullViewMode && (
<Alert
message="Selecting multiple legends is currently not supported in case of stacked bar charts"
type="info"
className="info-text"
/>
)}
{isFullViewMode && setGraphVisibility && !stackedBarChart && (
<GraphManager
data={chartData}
name={widget.id}
options={options}
yAxisUnit={widget.yAxisUnit}
onToggleModelHandler={onToggleModelHandler}
setGraphsVisibilityStates={setGraphVisibility}
graphsVisibilityStates={graphVisibility}
lineChartRef={lineChartRef}
/>
)}
</div>
);
}
export default UplotPanelWrapper;

View File

@@ -23,10 +23,10 @@ export default {
relations: {
assignee: ['role'],
create: ['metaresources'],
delete: ['user', 'role', 'organization', 'metaresource'],
delete: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
list: ['metaresources'],
read: ['user', 'role', 'organization', 'metaresource'],
update: ['user', 'role', 'organization', 'metaresource'],
read: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
update: ['user', 'serviceaccount', 'role', 'organization', 'metaresource'],
},
},
} as const;

View File

@@ -75,6 +75,8 @@ export const DashboardContext = createContext<IDashboardContext>({
setLayouts: () => {},
setSelectedDashboard: () => {},
updatedTimeRef: {} as React.MutableRefObject<Dayjs | null>,
toScrollWidgetId: '',
setToScrollWidgetId: () => {},
updateLocalStorageDashboardVariables: () => {},
dashboardQueryRangeCalled: false,
setDashboardQueryRangeCalled: () => {},
@@ -93,6 +95,8 @@ export function DashboardProvider({
}: PropsWithChildren): JSX.Element {
const [isDashboardSliderOpen, setIsDashboardSlider] = useState<boolean>(false);
const [toScrollWidgetId, setToScrollWidgetId] = useState<string>('');
const [isDashboardLocked, setIsDashboardLocked] = useState<boolean>(false);
const [
@@ -439,6 +443,7 @@ export function DashboardProvider({
const value: IDashboardContext = useMemo(
() => ({
toScrollWidgetId,
isDashboardSliderOpen,
isDashboardLocked,
handleToggleDashboardSlider,
@@ -452,6 +457,7 @@ export function DashboardProvider({
setPanelMap,
setSelectedDashboard,
updatedTimeRef,
setToScrollWidgetId,
updateLocalStorageDashboardVariables,
dashboardQueryRangeCalled,
setDashboardQueryRangeCalled,
@@ -468,6 +474,7 @@ export function DashboardProvider({
dashboardId,
layouts,
panelMap,
toScrollWidgetId,
updateLocalStorageDashboardVariables,
currentDashboard,
dashboardQueryRangeCalled,

View File

@@ -1,13 +0,0 @@
import { create } from 'zustand';
interface ScrollToWidgetIdState {
toScrollWidgetId: string;
setToScrollWidgetId: (widgetId: string) => void;
}
export const useScrollToWidgetIdStore = create<ScrollToWidgetIdState>(
(set) => ({
toScrollWidgetId: '',
setToScrollWidgetId: (widgetId): void => set({ toScrollWidgetId: widgetId }),
}),
);

View File

@@ -23,6 +23,8 @@ export interface IDashboardContext {
React.SetStateAction<Dashboard | undefined>
>;
updatedTimeRef: React.MutableRefObject<dayjs.Dayjs | null>;
toScrollWidgetId: string;
setToScrollWidgetId: React.Dispatch<React.SetStateAction<string>>;
updateLocalStorageDashboardVariables: (
id: string,
selectedValue:

View File

@@ -4,7 +4,6 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -22,7 +21,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -39,7 +38,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -56,7 +55,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -73,7 +72,7 @@ func (provider *provider) addAuthDomainRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -25,7 +25,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -42,7 +42,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -59,7 +59,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -76,7 +76,7 @@ func (provider *provider) addDashboardRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/gorilla/mux"
)
@@ -23,7 +23,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -41,7 +41,7 @@ func (provider *provider) addFieldsRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addFlaggerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/gatewaytypes"
"github.com/gorilla/mux"
)
@@ -23,7 +23,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -41,7 +41,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -58,7 +58,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -75,7 +75,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -92,7 +92,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -109,7 +109,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -126,7 +126,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -143,7 +143,7 @@ func (provider *provider) addGatewayRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -21,7 +22,7 @@ func (provider *provider) addGlobalRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/metricsexplorertypes"
"github.com/gorilla/mux"
)
@@ -25,7 +25,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -44,7 +44,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -63,7 +63,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -83,7 +83,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -102,7 +102,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -121,7 +121,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -140,7 +140,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -159,7 +159,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -178,7 +178,7 @@ func (provider *provider) addMetricsExplorerRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusInternalServerError},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
@@ -21,7 +22,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -38,7 +39,7 @@ func (provider *provider) addOrgRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusConflict, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -39,7 +39,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +56,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -73,7 +73,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -90,7 +90,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -107,7 +107,7 @@ func (provider *provider) addPreferenceRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/promotetypes"
"github.com/gorilla/mux"
)
@@ -21,7 +21,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
ResponseContentType: "",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleEditor),
SecuritySchemes: newSecuritySchemes(authtypes.RoleEditor),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -37,7 +37,7 @@ func (provider *provider) addPromoteRoutes(router *mux.Router) error {
ResponseContentType: "",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}

View File

@@ -22,7 +22,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/session"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/gorilla/mux"
@@ -236,7 +236,7 @@ func (provider *provider) AddToRouter(router *mux.Router) error {
return nil
}
func newSecuritySchemes(role types.Role) []handler.OpenAPISecurityScheme {
func newSecuritySchemes(role authtypes.LegacyRole) []handler.OpenAPISecurityScheme {
return []handler.OpenAPISecurityScheme{
{Name: ctxtypes.AuthTypeAPIKey.StringValue(), Scopes: []string{role.String()}},
{Name: ctxtypes.AuthTypeTokenizer.StringValue(), Scopes: []string{role.String()}},

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/gorilla/mux"
)
@@ -446,7 +446,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -462,7 +462,7 @@ func (provider *provider) addQuerierRoutes(router *mux.Router) error {
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest},
SecuritySchemes: newSecuritySchemes(types.RoleViewer),
SecuritySchemes: newSecuritySchemes(authtypes.RoleViewer),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/gorilla/mux"
)
@@ -16,14 +15,14 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
Tags: []string{"role"},
Summary: "Create role",
Description: "This endpoint creates a role",
Request: new(roletypes.PostableRole),
Request: new(authtypes.PostableRole),
RequestContentType: "",
Response: new(types.Identifiable),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -35,12 +34,12 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
Description: "This endpoint lists all roles",
Request: nil,
RequestContentType: "",
Response: make([]*roletypes.Role, 0),
Response: make([]*authtypes.Role, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -52,12 +51,12 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
Description: "This endpoint gets a role",
Request: nil,
RequestContentType: "",
Response: new(roletypes.Role),
Response: new(authtypes.Role),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -74,7 +73,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -84,14 +83,14 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
Tags: []string{"role"},
Summary: "Patch role",
Description: "This endpoint patches a role",
Request: new(roletypes.PatchableRole),
Request: new(authtypes.PatchableRole),
RequestContentType: "",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -108,7 +107,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPatch).GetError(); err != nil {
return err
}
@@ -125,7 +124,7 @@ func (provider *provider) addRoleRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusNotImplemented, http.StatusUnavailableForLegalReasons},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -5,6 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/gorilla/mux"
)
@@ -22,7 +23,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -39,7 +40,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +57,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -73,7 +74,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -90,7 +91,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound, http.StatusBadRequest},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -107,7 +108,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -124,7 +125,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -141,7 +142,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -158,7 +159,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -175,7 +176,7 @@ func (provider *provider) addServiceAccountRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}

View File

@@ -4,8 +4,9 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/gorilla/mux"
)
@@ -15,14 +16,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Create invite",
Description: "This endpoint creates an invite for a user",
Request: new(types.PostableInvite),
Request: new(usertypes.PostableInvite),
RequestContentType: "application/json",
Response: new(types.Invite),
Response: new(usertypes.Invite),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -32,13 +33,13 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Create bulk invite",
Description: "This endpoint creates a bulk invite for a user",
Request: new(types.PostableBulkInviteRequest),
Request: new(usertypes.PostableBulkInviteRequest),
RequestContentType: "application/json",
Response: nil,
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -50,7 +51,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint gets an invite by token",
Request: nil,
RequestContentType: "",
Response: new(types.Invite),
Response: new(usertypes.Invite),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
@@ -72,7 +73,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -84,12 +85,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint lists all invites",
Request: nil,
RequestContentType: "",
Response: make([]*types.Invite, 0),
Response: make([]*usertypes.Invite, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -99,9 +100,9 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Accept invite",
Description: "This endpoint accepts an invite by token",
Request: new(types.PostableAcceptInvite),
Request: new(usertypes.PostableAcceptInvite),
RequestContentType: "application/json",
Response: new(types.User),
Response: new(usertypes.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
@@ -111,74 +112,6 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
return err
}
if err := router.Handle("/api/v1/pats", handler.New(provider.authZ.AdminAccess(provider.userHandler.CreateAPIKey), handler.OpenAPIDef{
ID: "CreateAPIKey",
Tags: []string{"users"},
Summary: "Create api key",
Description: "This endpoint creates an api key",
Request: new(types.PostableAPIKey),
RequestContentType: "application/json",
Response: new(types.GettableAPIKey),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusCreated,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/pats", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListAPIKeys), handler.OpenAPIDef{
ID: "ListAPIKeys",
Tags: []string{"users"},
Summary: "List api keys",
Description: "This endpoint lists all api keys",
Request: nil,
RequestContentType: "",
Response: make([]*types.GettableAPIKey, 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/pats/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.UpdateAPIKey), handler.OpenAPIDef{
ID: "UpdateAPIKey",
Tags: []string{"users"},
Summary: "Update api key",
Description: "This endpoint updates an api key",
Request: new(types.StorableAPIKey),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/pats/{id}", handler.New(provider.authZ.AdminAccess(provider.userHandler.RevokeAPIKey), handler.OpenAPIDef{
ID: "RevokeAPIKey",
Tags: []string{"users"},
Summary: "Revoke api key",
Description: "This endpoint revokes an api key",
Request: nil,
RequestContentType: "",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
if err := router.Handle("/api/v1/user", handler.New(provider.authZ.AdminAccess(provider.userHandler.ListUsers), handler.OpenAPIDef{
ID: "ListUsers",
Tags: []string{"users"},
@@ -186,12 +119,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint lists all users",
Request: nil,
RequestContentType: "",
Response: make([]*types.GettableUser, 0),
Response: make([]*usertypes.User, 0),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -203,7 +136,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the user I belong to",
Request: nil,
RequestContentType: "",
Response: new(types.GettableUser),
Response: new(usertypes.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{},
@@ -220,12 +153,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the user by id",
Request: nil,
RequestContentType: "",
Response: new(types.GettableUser),
Response: new(usertypes.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -235,14 +168,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Update user",
Description: "This endpoint updates the user by id",
Request: new(types.User),
Request: new(usertypes.UpdatableUser),
RequestContentType: "application/json",
Response: new(types.GettableUser),
Response: new(usertypes.User),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -259,7 +192,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodDelete).GetError(); err != nil {
return err
}
@@ -271,12 +204,12 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Description: "This endpoint returns the reset password token by id",
Request: nil,
RequestContentType: "",
Response: new(types.ResetPasswordToken),
Response: new(usertypes.ResetPasswordToken),
ResponseContentType: "application/json",
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -286,7 +219,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Reset password",
Description: "This endpoint resets the password by token",
Request: new(types.PostableResetPassword),
Request: new(usertypes.PostableResetPassword),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
@@ -303,14 +236,14 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Change password",
Description: "This endpoint changes the password by id",
Request: new(types.ChangePasswordRequest),
Request: new(usertypes.ChangePasswordRequest),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPost).GetError(); err != nil {
return err
}
@@ -320,7 +253,7 @@ func (provider *provider) addUserRoutes(router *mux.Router) error {
Tags: []string{"users"},
Summary: "Forgot password",
Description: "This endpoint initiates the forgot password flow by sending a reset password email",
Request: new(types.PostableForgotPassword),
Request: new(usertypes.PostableForgotPassword),
RequestContentType: "application/json",
Response: nil,
ResponseContentType: "",

View File

@@ -4,7 +4,7 @@ import (
"net/http"
"github.com/SigNoz/signoz/pkg/http/handler"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/zeustypes"
"github.com/gorilla/mux"
)
@@ -22,7 +22,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}
@@ -39,7 +39,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusOK,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodGet).GetError(); err != nil {
return err
}
@@ -56,7 +56,7 @@ func (provider *provider) addZeusRoutes(router *mux.Router) error {
SuccessStatusCode: http.StatusNoContent,
ErrorStatusCodes: []int{http.StatusBadRequest, http.StatusUnauthorized, http.StatusForbidden, http.StatusNotFound, http.StatusConflict},
Deprecated: false,
SecuritySchemes: newSecuritySchemes(types.RoleAdmin),
SecuritySchemes: newSecuritySchemes(authtypes.RoleAdmin),
})).Methods(http.MethodPut).GetError(); err != nil {
return err
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -17,37 +16,6 @@ func NewStore(sqlstore sqlstore.SQLStore) authtypes.AuthNStore {
return &store{sqlstore: sqlstore}
}
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*types.User, *types.FactorPassword, error) {
user := new(types.User)
factorPassword := new(types.FactorPassword)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Where("email = ?", email).
Where("org_id = ?", orgID).
Where("status = ?", types.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
}
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(factorPassword).
Where("user_id = ?", user.ID).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
}
return user, factorPassword, nil
}
func (store *store) GetAuthDomainFromID(ctx context.Context, domainID valuer.UUID) (*authtypes.AuthDomain, error) {
storableAuthDomain := new(authtypes.StorableAuthDomain)

View File

@@ -5,30 +5,31 @@ import (
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
var _ authn.PasswordAuthN = (*AuthN)(nil)
type AuthN struct {
store authtypes.AuthNStore
store authtypes.AuthNStore
userStore usertypes.UserStore
}
func New(store authtypes.AuthNStore) *AuthN {
return &AuthN{store: store}
func New(store authtypes.AuthNStore, userStore usertypes.UserStore) *AuthN {
return &AuthN{store: store, userStore: userStore}
}
func (a *AuthN) Authenticate(ctx context.Context, email string, password string, orgID valuer.UUID) (*authtypes.Identity, error) {
user, factorPassword, err := a.store.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
user, factorPassword, err := a.userStore.GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx, email, orgID)
if err != nil {
return nil, err
}
if !factorPassword.Equals(password) {
return nil, errors.New(errors.TypeUnauthenticated, types.ErrCodeIncorrectPassword, "invalid email or password")
return nil, errors.New(errors.TypeUnauthenticated, usertypes.ErrCodeIncorrectPassword, "invalid email or password")
}
return authtypes.NewIdentity(user.ID, orgID, user.Email, user.Role), nil
return authtypes.NewIdentity(user.ID, valuer.UUID{}, authtypes.PrincipalUser, orgID, valuer.MustNewEmail(user.Email)), nil
}

View File

@@ -6,7 +6,6 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
)
@@ -30,10 +29,10 @@ type AuthZ interface {
ListObjects(context.Context, string, authtypes.Relation, authtypes.Typeable) ([]*authtypes.Object, error)
// Creates the role.
Create(context.Context, valuer.UUID, *roletypes.Role) error
Create(context.Context, valuer.UUID, *authtypes.Role) error
// Gets the role if it exists or creates one.
GetOrCreate(context.Context, valuer.UUID, *roletypes.Role) (*roletypes.Role, error)
GetOrCreate(context.Context, valuer.UUID, *authtypes.Role) (*authtypes.Role, error)
// Gets the objects associated with the given role and relation.
GetObjects(context.Context, valuer.UUID, valuer.UUID, authtypes.Relation) ([]*authtypes.Object, error)
@@ -42,7 +41,7 @@ type AuthZ interface {
GetResources(context.Context) []*authtypes.Resource
// Patches the role.
Patch(context.Context, valuer.UUID, *roletypes.Role) error
Patch(context.Context, valuer.UUID, *authtypes.Role) error
// Patches the objects in authorization server associated with the given role and relation
PatchObjects(context.Context, valuer.UUID, string, authtypes.Relation, []*authtypes.Object, []*authtypes.Object) error
@@ -51,19 +50,19 @@ type AuthZ interface {
Delete(context.Context, valuer.UUID, valuer.UUID) error
// Gets the role
Get(context.Context, valuer.UUID, valuer.UUID) (*roletypes.Role, error)
Get(context.Context, valuer.UUID, valuer.UUID) (*authtypes.Role, error)
// Gets the role by org_id and name
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*roletypes.Role, error)
GetByOrgIDAndName(context.Context, valuer.UUID, string) (*authtypes.Role, error)
// Lists all the roles for the organization.
List(context.Context, valuer.UUID) ([]*roletypes.Role, error)
List(context.Context, valuer.UUID) ([]*authtypes.Role, error)
// Lists all the roles for the organization filtered by name
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*roletypes.Role, error)
ListByOrgIDAndNames(context.Context, valuer.UUID, []string) ([]*authtypes.Role, error)
// Lists all the roles for the organization filtered by ids
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*roletypes.Role, error)
ListByOrgIDAndIDs(context.Context, valuer.UUID, []valuer.UUID) ([]*authtypes.Role, error)
// Grants a role to the subject based on role name.
Grant(context.Context, valuer.UUID, []string, string) error
@@ -75,7 +74,7 @@ type AuthZ interface {
ModifyGrant(context.Context, valuer.UUID, []string, []string, string) error
// Bootstrap the managed roles.
CreateManagedRoles(context.Context, valuer.UUID, []*roletypes.Role) error
CreateManagedRoles(context.Context, valuer.UUID, []*authtypes.Role) error
// Bootstrap managed roles transactions and user assignments
CreateManagedUserRoleTransactions(context.Context, valuer.UUID, valuer.UUID) error

View File

@@ -5,7 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
@@ -14,11 +14,11 @@ type store struct {
sqlstore sqlstore.SQLStore
}
func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) roletypes.Store {
func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) authtypes.RoleStore {
return &store{sqlstore: sqlstore}
}
func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) error {
func (store *store) Create(ctx context.Context, role *authtypes.StorableRole) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -32,8 +32,8 @@ func (store *store) Create(ctx context.Context, role *roletypes.StorableRole) er
return nil
}
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.StorableRole, error) {
role := new(roletypes.StorableRole)
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.StorableRole, error) {
role := new(authtypes.StorableRole)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -43,14 +43,14 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with id: %s doesn't exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with id: %s doesn't exist", id)
}
return role, nil
}
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.StorableRole, error) {
role := new(roletypes.StorableRole)
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.StorableRole, error) {
role := new(authtypes.StorableRole)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -60,14 +60,14 @@ func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, na
Where("name = ?", name).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with name: %s doesn't exist", name)
return nil, store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with name: %s doesn't exist", name)
}
return role, nil
}
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.StorableRole, error) {
roles := make([]*roletypes.StorableRole, 0)
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.StorableRole, error) {
roles := make([]*authtypes.StorableRole, 0)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -82,8 +82,8 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.S
return roles, nil
}
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.StorableRole, error) {
roles := make([]*roletypes.StorableRole, 0)
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.StorableRole, error) {
roles := make([]*authtypes.StorableRole, 0)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -97,18 +97,14 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
}
if len(roles) != len(names) {
return nil, store.sqlstore.WrapNotFoundErrf(
nil,
roletypes.ErrCodeRoleNotFound,
"not all roles found for the provided names: %v", names,
)
return nil, errors.Newf(errors.TypeInvalidInput, authtypes.ErrCodeRoleNotFound, "not all roles found for the provided names: %v", names)
}
return roles, nil
}
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.StorableRole, error) {
roles := make([]*roletypes.StorableRole, 0)
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.StorableRole, error) {
roles := make([]*authtypes.StorableRole, 0)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -122,17 +118,13 @@ func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, id
}
if len(roles) != len(ids) {
return nil, store.sqlstore.WrapNotFoundErrf(
nil,
roletypes.ErrCodeRoleNotFound,
"not all roles found for the provided ids: %v", ids,
)
return nil, errors.Newf(errors.TypeInvalidInput, authtypes.ErrCodeRoleNotFound, "not all roles found for the provided ids: %v", ids)
}
return roles, nil
}
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *roletypes.StorableRole) error {
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *authtypes.StorableRole) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -153,12 +145,12 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
sqlstore.
BunDBCtx(ctx).
NewDelete().
Model(new(roletypes.StorableRole)).
Model(new(authtypes.StorableRole)).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, roletypes.ErrCodeRoleNotFound, "role with id %s doesn't exist", id)
return store.sqlstore.WrapNotFoundErrf(err, authtypes.ErrCodeRoleNotFound, "role with id %s doesn't exist", id)
}
return nil

View File

@@ -8,7 +8,6 @@ import (
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/factory"
@@ -19,7 +18,7 @@ import (
type provider struct {
server *openfgaserver.Server
store roletypes.Store
store authtypes.RoleStore
}
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile) factory.ProviderFactory[authz.AuthZ, authz.Config] {
@@ -68,61 +67,61 @@ func (provider *provider) ListObjects(ctx context.Context, subject string, relat
return provider.server.ListObjects(ctx, subject, relation, typeable)
}
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*roletypes.Role, error) {
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
storableRole, err := provider.store.Get(ctx, orgID, id)
if err != nil {
return nil, err
}
return roletypes.NewRoleFromStorableRole(storableRole), nil
return authtypes.NewRoleFromStorableRole(storableRole), nil
}
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*roletypes.Role, error) {
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
storableRole, err := provider.store.GetByOrgIDAndName(ctx, orgID, name)
if err != nil {
return nil, err
}
return roletypes.NewRoleFromStorableRole(storableRole), nil
return authtypes.NewRoleFromStorableRole(storableRole), nil
}
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*roletypes.Role, error) {
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
storableRoles, err := provider.store.List(ctx, orgID)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
roles := make([]*authtypes.Role, len(storableRoles))
for idx, storableRole := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storableRole)
roles[idx] = authtypes.NewRoleFromStorableRole(storableRole)
}
return roles, nil
}
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*roletypes.Role, error) {
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
storableRoles, err := provider.store.ListByOrgIDAndNames(ctx, orgID, names)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
roles := make([]*authtypes.Role, len(storableRoles))
for idx, storable := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
}
return roles, nil
}
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.Role, error) {
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
storableRoles, err := provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
if err != nil {
return nil, err
}
roles := make([]*roletypes.Role, len(storableRoles))
roles := make([]*authtypes.Role, len(storableRoles))
for idx, storable := range storableRoles {
roles[idx] = roletypes.NewRoleFromStorableRole(storable)
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
}
return roles, nil
@@ -179,10 +178,10 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
return provider.Write(ctx, nil, tuples)
}
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*roletypes.Role) error {
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*authtypes.Role) error {
err := provider.store.RunInTx(ctx, func(ctx context.Context) error {
for _, role := range managedRoles {
err := provider.store.Create(ctx, roletypes.NewStorableRoleFromRole(role))
err := provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
if err != nil {
return err
}
@@ -199,15 +198,15 @@ func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID,
}
func (provider *provider) CreateManagedUserRoleTransactions(ctx context.Context, orgID valuer.UUID, userID valuer.UUID) error {
return provider.Grant(ctx, orgID, []string{roletypes.SigNozAdminRoleName}, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
return provider.Grant(ctx, orgID, []string{authtypes.SigNozAdminRoleName}, authtypes.MustNewSubject(authtypes.TypeableUser, userID.String(), orgID, nil))
}
func (setter *provider) Create(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
func (setter *provider) Create(_ context.Context, _ valuer.UUID, _ *authtypes.Role) error {
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) GetOrCreate(_ context.Context, _ valuer.UUID, _ *roletypes.Role) (*roletypes.Role, error) {
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
func (provider *provider) GetOrCreate(_ context.Context, _ valuer.UUID, _ *authtypes.Role) (*authtypes.Role, error) {
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
@@ -215,19 +214,19 @@ func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource
}
func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id valuer.UUID, relation authtypes.Relation) ([]*authtypes.Object, error) {
return nil, errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
return nil, errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) Patch(_ context.Context, _ valuer.UUID, _ *roletypes.Role) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
func (provider *provider) Patch(_ context.Context, _ valuer.UUID, _ *authtypes.Role) error {
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) PatchObjects(_ context.Context, _ valuer.UUID, _ string, _ authtypes.Relation, _, _ []*authtypes.Object) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID) error {
return errors.Newf(errors.TypeUnsupported, roletypes.ErrCodeRoleUnsupported, "not implemented")
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
}
func (provider *provider) MustGetTypeables() []authtypes.Typeable {

View File

@@ -2,9 +2,11 @@ module base
type user
type serviceaccount
type role
relations
define assignee: [user]
define assignee: [user, serviceaccount]
type organisation
relations

View File

@@ -128,9 +128,22 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openf
}
func (server *Server) CheckWithTupleCreation(ctx context.Context, claims authtypes.Claims, orgID valuer.UUID, _ authtypes.Relation, _ authtypes.Typeable, _ []authtypes.Selector, roleSelectors []authtypes.Selector) error {
subject, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil {
return err
subject := ""
switch claims.Principal {
case authtypes.PrincipalUser.StringValue():
user, err := authtypes.NewSubject(authtypes.TypeableUser, claims.UserID, orgID, nil)
if err != nil {
return err
}
subject = user
case authtypes.PrincipalServiceAccount.StringValue():
serviceAccount, err := authtypes.NewSubject(authtypes.TypeableServiceAccount, claims.ServiceAccountID, orgID, nil)
if err != nil {
return err
}
subject = serviceAccount
}
tupleSlice, err := authtypes.TypeableRole.Tuples(subject, authtypes.RelationAssignee, roleSelectors, orgID)

View File

@@ -9,7 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/render"
"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"
"github.com/gorilla/mux"
)
@@ -30,13 +29,13 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
return
}
req := new(roletypes.PostableRole)
req := new(authtypes.PostableRole)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return
}
role := roletypes.NewRole(req.Name, req.Description, roletypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID))
role := authtypes.NewRole(req.Name, req.Description, authtypes.RoleTypeCustom, valuer.MustNewUUID(claims.OrgID))
err = handler.authz.Create(ctx, valuer.MustNewUUID(claims.OrgID), role)
if err != nil {
render.Error(rw, err)
@@ -56,7 +55,7 @@ func (handler *handler) Get(rw http.ResponseWriter, r *http.Request) {
id, ok := mux.Vars(r)["id"]
if !ok {
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
return
}
roleID, err := valuer.NewUUID(id)
@@ -84,7 +83,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
id, ok := mux.Vars(r)["id"]
if !ok {
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "id is missing from the request"))
return
}
roleID, err := valuer.NewUUID(id)
@@ -95,7 +94,7 @@ func (handler *handler) GetObjects(rw http.ResponseWriter, r *http.Request) {
relationStr, ok := mux.Vars(r)["relation"]
if !ok {
render.Error(rw, errors.New(errors.TypeInvalidInput, roletypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
render.Error(rw, errors.New(errors.TypeInvalidInput, authtypes.ErrCodeRoleInvalidInput, "relation is missing from the request"))
return
}
relation, err := authtypes.NewRelation(relationStr)
@@ -150,7 +149,7 @@ func (handler *handler) Patch(rw http.ResponseWriter, r *http.Request) {
return
}
req := new(roletypes.PatchableRole)
req := new(authtypes.PatchableRole)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(rw, err)
return

View File

@@ -1,143 +0,0 @@
package middleware
import (
"context"
"log/slog"
"net/http"
"time"
"github.com/SigNoz/signoz/pkg/sharder"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"golang.org/x/sync/singleflight"
)
const (
apiKeyCrossOrgMessage string = "::API-KEY-CROSS-ORG::"
)
type APIKey struct {
store sqlstore.SQLStore
uuid *authtypes.UUID
headers []string
logger *slog.Logger
sharder sharder.Sharder
sfGroup *singleflight.Group
}
func NewAPIKey(store sqlstore.SQLStore, headers []string, logger *slog.Logger, sharder sharder.Sharder) *APIKey {
return &APIKey{
store: store,
uuid: authtypes.NewUUID(),
headers: headers,
logger: logger,
sharder: sharder,
sfGroup: &singleflight.Group{},
}
}
func (a *APIKey) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var values []string
var apiKeyToken string
var apiKey types.StorableAPIKey
for _, header := range a.headers {
values = append(values, r.Header.Get(header))
}
ctx, err := a.uuid.ContextFromRequest(r.Context(), values...)
if err != nil {
next.ServeHTTP(w, r)
return
}
apiKeyToken, ok := authtypes.UUIDFromContext(ctx)
if !ok {
next.ServeHTTP(w, r)
return
}
err = a.
store.
BunDB().
NewSelect().
Model(&apiKey).
Where("token = ?", apiKeyToken).
Scan(r.Context())
if err != nil {
next.ServeHTTP(w, r)
return
}
// allow the APIKey if expires_at is not set
if apiKey.ExpiresAt.Before(time.Now()) && !apiKey.ExpiresAt.Equal(types.NEVER_EXPIRES) {
next.ServeHTTP(w, r)
return
}
// get user from db
user := types.User{}
err = a.store.BunDB().NewSelect().Model(&user).Where("id = ?", apiKey.UserID).Scan(r.Context())
if err != nil {
next.ServeHTTP(w, r)
return
}
jwt := authtypes.Claims{
UserID: user.ID.String(),
Role: apiKey.Role,
Email: user.Email.String(),
OrgID: user.OrgID.String(),
}
ctx = authtypes.NewContextWithClaims(ctx, jwt)
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
next.ServeHTTP(w, r)
return
}
if err := a.sharder.IsMyOwnedKey(r.Context(), types.NewOrganizationKey(valuer.MustNewUUID(claims.OrgID))); err != nil {
a.logger.ErrorContext(r.Context(), apiKeyCrossOrgMessage, "claims", claims, "error", err)
next.ServeHTTP(w, r)
return
}
ctx = ctxtypes.SetAuthType(ctx, ctxtypes.AuthTypeAPIKey)
comment := ctxtypes.CommentFromContext(ctx)
comment.Set("auth_type", ctxtypes.AuthTypeAPIKey.StringValue())
comment.Set("user_id", claims.UserID)
comment.Set("org_id", claims.OrgID)
r = r.WithContext(ctxtypes.NewContextWithComment(ctx, comment))
next.ServeHTTP(w, r)
lastUsedCtx := context.WithoutCancel(r.Context())
_, _, _ = a.sfGroup.Do(apiKey.ID.StringValue(), func() (any, error) {
apiKey.LastUsed = time.Now()
_, err = a.
store.
BunDB().
NewUpdate().
Model(&apiKey).
Column("last_used").
Where("token = ?", apiKeyToken).
Where("revoked = false").
Exec(lastUsedCtx)
if err != nil {
a.logger.ErrorContext(lastUsedCtx, "failed to update last used of api key", "error", err)
}
return true, nil
})
})
}

View File

@@ -22,31 +22,50 @@ const (
)
type AuthN struct {
tokenizer tokenizer.Tokenizer
headers []string
sharder sharder.Sharder
logger *slog.Logger
sfGroup *singleflight.Group
tokenizer tokenizer.Tokenizer
serviceAccountTokenizer tokenizer.Tokenizer
headers []string
serviceAccountHeaders []string
sharder sharder.Sharder
logger *slog.Logger
sfGroup *singleflight.Group
}
func NewAuthN(headers []string, sharder sharder.Sharder, tokenizer tokenizer.Tokenizer, logger *slog.Logger) *AuthN {
func NewAuthN(
headers []string,
serviceAccountHeaders []string,
sharder sharder.Sharder,
tokenizer tokenizer.Tokenizer,
serviceAccountTokenizer tokenizer.Tokenizer,
logger *slog.Logger,
) *AuthN {
return &AuthN{
headers: headers,
sharder: sharder,
tokenizer: tokenizer,
logger: logger,
sfGroup: &singleflight.Group{},
headers: headers,
serviceAccountHeaders: serviceAccountHeaders,
sharder: sharder,
tokenizer: tokenizer,
serviceAccountTokenizer: serviceAccountTokenizer,
logger: logger,
sfGroup: &singleflight.Group{},
}
}
func (a *AuthN) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var values []string
var userHeaderValues []string
for _, header := range a.headers {
values = append(values, r.Header.Get(header))
userHeaderValues = append(userHeaderValues, r.Header.Get(header))
}
ctx, authType, activeTokenizer, err := a.authenticateUser(r.Context(), userHeaderValues...)
if err != nil {
var saHeaderValues []string
for _, header := range a.serviceAccountHeaders {
saHeaderValues = append(saHeaderValues, r.Header.Get(header))
}
ctx, authType, activeTokenizer, err = a.authenticateServiceAccount(ctx, saHeaderValues...)
}
ctx, err := a.contextFromRequest(r.Context(), values...)
if err != nil {
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
@@ -67,27 +86,34 @@ func (a *AuthN) Wrap(next http.Handler) http.Handler {
return
}
ctx = ctxtypes.SetAuthType(ctx, ctxtypes.AuthTypeTokenizer)
ctx = ctxtypes.SetAuthType(ctx, authType)
comment := ctxtypes.CommentFromContext(ctx)
comment.Set("auth_type", ctxtypes.AuthTypeTokenizer.StringValue())
comment.Set("tokenizer_provider", a.tokenizer.Config().Provider)
comment.Set("auth_type", authType.StringValue())
comment.Set("tokenizer_provider", activeTokenizer.Config().Provider)
comment.Set("user_id", claims.UserID)
comment.Set("service_account_id", claims.ServiceAccountID)
comment.Set("principal", claims.Principal)
comment.Set("org_id", claims.OrgID)
r = r.WithContext(ctxtypes.NewContextWithComment(ctx, comment))
next.ServeHTTP(w, r)
accessToken, err := authtypes.AccessTokenFromContext(r.Context())
// Track last observed at for the active tokenizer.
var token string
if authType == ctxtypes.AuthTypeAPIKey {
token, err = authtypes.ServiceAccountAPIKeyFromContext(r.Context())
} else {
token, err = authtypes.AccessTokenFromContext(r.Context())
}
if err != nil {
next.ServeHTTP(w, r)
return
}
lastObservedAtCtx := context.WithoutCancel(r.Context())
_, _, _ = a.sfGroup.Do(accessToken, func() (any, error) {
if err := a.tokenizer.SetLastObservedAt(lastObservedAtCtx, accessToken, time.Now()); err != nil {
_, _, _ = a.sfGroup.Do(token, func() (any, error) {
if err := activeTokenizer.SetLastObservedAt(lastObservedAtCtx, token, time.Now()); err != nil {
a.logger.ErrorContext(lastObservedAtCtx, "failed to set last observed at", "error", err)
return false, err
}
@@ -97,23 +123,60 @@ func (a *AuthN) Wrap(next http.Handler) http.Handler {
})
}
func (a *AuthN) contextFromRequest(ctx context.Context, values ...string) (context.Context, error) {
func (a *AuthN) authenticateUser(ctx context.Context, values ...string) (context.Context, ctxtypes.AuthType, tokenizer.Tokenizer, error) {
ctx, err := a.contextFromAccessToken(ctx, values...)
if err != nil {
return ctx, err
return ctx, ctxtypes.AuthTypeTokenizer, a.tokenizer, err
}
accessToken, err := authtypes.AccessTokenFromContext(ctx)
if err != nil {
return ctx, err
return ctx, ctxtypes.AuthTypeTokenizer, a.tokenizer, err
}
authenticatedUser, err := a.tokenizer.GetIdentity(ctx, accessToken)
identity, err := a.tokenizer.GetIdentity(ctx, accessToken)
if err != nil {
return ctx, err
return ctx, ctxtypes.AuthTypeTokenizer, a.tokenizer, err
}
return authtypes.NewContextWithClaims(ctx, authenticatedUser.ToClaims()), nil
ctx = authtypes.NewContextWithClaims(ctx, identity.ToClaims())
return ctx, ctxtypes.AuthTypeTokenizer, a.tokenizer, nil
}
func (a *AuthN) authenticateServiceAccount(ctx context.Context, values ...string) (context.Context, ctxtypes.AuthType, tokenizer.Tokenizer, error) {
ctx, err := a.contextFromServiceAccountAPIKey(ctx, values...)
if err != nil {
return ctx, ctxtypes.AuthTypeAPIKey, a.serviceAccountTokenizer, err
}
apiKey, err := authtypes.ServiceAccountAPIKeyFromContext(ctx)
if err != nil {
return ctx, ctxtypes.AuthTypeAPIKey, a.serviceAccountTokenizer, err
}
identity, err := a.serviceAccountTokenizer.GetIdentity(ctx, apiKey)
if err != nil {
return ctx, ctxtypes.AuthTypeAPIKey, a.serviceAccountTokenizer, err
}
ctx = authtypes.NewContextWithClaims(ctx, identity.ToClaims())
return ctx, ctxtypes.AuthTypeAPIKey, a.serviceAccountTokenizer, nil
}
func (a *AuthN) contextFromServiceAccountAPIKey(ctx context.Context, values ...string) (context.Context, error) {
var value string
for _, v := range values {
if v != "" {
value = v
break
}
}
if value == "" {
return ctx, errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "missing api key header")
}
return authtypes.NewContextWithServiceAccountAPIKey(ctx, value), nil
}
func (a *AuthN) contextFromAccessToken(ctx context.Context, values ...string) (context.Context, error) {

View File

@@ -9,8 +9,6 @@ import (
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -42,23 +40,10 @@ func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && authtype == ctxtypes.AuthTypeAPIKey.StringValue() {
if err := claims.IsViewer(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
return
}
selectors := []authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozEditorRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozViewerRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozViewerRoleName),
}
err = middleware.authzService.CheckWithTupleCreation(
@@ -94,22 +79,9 @@ func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && authtype == ctxtypes.AuthTypeAPIKey.StringValue() {
if err := claims.IsEditor(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
return
}
selectors := []authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozEditorRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozEditorRoleName),
}
err = middleware.authzService.CheckWithTupleCreation(
@@ -145,21 +117,8 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
return
}
commentCtx := ctxtypes.CommentFromContext(ctx)
authtype, ok := commentCtx.Map()["auth_type"]
if ok && authtype == ctxtypes.AuthTypeAPIKey.StringValue() {
if err := claims.IsAdmin(); err != nil {
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
return
}
selectors := []authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, roletypes.SigNozAdminRoleName),
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
}
err = middleware.authzService.CheckWithTupleCreation(
@@ -188,17 +147,33 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
func (middleware *AuthZ) SelfAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
ctx := req.Context()
claims, err := authtypes.ClaimsFromContext(ctx)
if err != nil {
render.Error(rw, err)
return
}
id := mux.Vars(req)["id"]
if err := claims.IsSelfAccess(id); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
selectors := []authtypes.Selector{
authtypes.MustNewSelector(authtypes.TypeRole, authtypes.SigNozAdminRoleName),
}
err = middleware.authzService.CheckWithTupleCreation(
ctx,
claims,
valuer.MustNewUUID(claims.OrgID),
authtypes.RelationAssignee,
authtypes.TypeableRole,
selectors,
selectors,
)
if err != nil {
id := mux.Vars(req)["id"]
if err := claims.IsSelfAccess(id); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
}
next(rw, req)

View File

@@ -43,7 +43,7 @@ type Module interface {
Update(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, data dashboardtypes.UpdatableDashboard, diff int) (*dashboardtypes.Dashboard, error)
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error
LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, lock bool) error
Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error

View File

@@ -58,7 +58,7 @@ func (handler *handler) Create(rw http.ResponseWriter, r *http.Request) {
dashboardMigrator.Migrate(ctx, req)
}
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.UserID), req)
dashboard, err := handler.module.Create(ctx, orgID, claims.Email, valuer.MustNewUUID(claims.GetIdentityID()), req)
if err != nil {
render.Error(rw, err)
return
@@ -156,7 +156,7 @@ func (handler *handler) LockUnlock(rw http.ResponseWriter, r *http.Request) {
return
}
err = handler.module.LockUnlock(ctx, orgID, dashboardID, claims.Email, claims.Role, *req.Locked)
err = handler.module.LockUnlock(ctx, orgID, dashboardID, valuer.MustNewEmail(claims.Email).String(), *req.Locked)
if err != nil {
render.Error(rw, err)
return

View File

@@ -9,6 +9,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -24,9 +25,10 @@ type module struct {
analytics analytics.Analytics
orgGetter organization.Getter
queryParser queryparser.QueryParser
userGetter user.Getter
}
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser) dashboard.Module {
func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, userGetter user.Getter) dashboard.Module {
scopedProviderSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard")
return &module{
store: store,
@@ -34,6 +36,7 @@ func NewModule(store dashboardtypes.Store, settings factory.ProviderSettings, an
analytics: analytics,
orgGetter: orgGetter,
queryParser: queryParser,
userGetter: userGetter,
}
}
@@ -99,13 +102,13 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, id valuer.U
return dashboard, nil
}
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, role types.Role, lock bool) error {
func (module *module) LockUnlock(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, lock bool) error {
dashboard, err := module.Get(ctx, orgID, id)
if err != nil {
return err
}
err = dashboard.LockUnlock(lock, role, updatedBy)
err = dashboard.LockUnlock(lock, updatedBy)
if err != nil {
return err
}

View File

@@ -111,7 +111,12 @@ func (handler *handler) Update(rw http.ResponseWriter, r *http.Request) {
return
}
serviceAccount.Update(req.Name, req.Email, req.Roles)
err = serviceAccount.Update(req.Name, req.Email, req.Roles)
if err != nil {
render.Error(rw, err)
return
}
err = handler.module.Update(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount)
if err != nil {
render.Error(rw, err)
@@ -147,7 +152,12 @@ func (handler *handler) UpdateStatus(rw http.ResponseWriter, r *http.Request) {
return
}
serviceAccount.UpdateStatus(req.Status)
err = serviceAccount.UpdateStatus(req.Status)
if err != nil {
render.Error(rw, err)
return
}
err = handler.module.UpdateStatus(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount)
if err != nil {
render.Error(rw, err)
@@ -290,7 +300,7 @@ func (handler *handler) UpdateFactorAPIKey(rw http.ResponseWriter, r *http.Reque
}
factorAPIKey.Update(req.Name, req.ExpiresAt)
err = handler.module.UpdateFactorAPIKey(ctx, serviceAccount.ID, factorAPIKey)
err = handler.module.UpdateFactorAPIKey(ctx, valuer.MustNewUUID(claims.OrgID), serviceAccount.ID, factorAPIKey)
if err != nil {
render.Error(rw, err)
return

View File

@@ -3,8 +3,10 @@ package implserviceaccount
import (
"context"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/emailing"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -14,15 +16,16 @@ import (
)
type module struct {
store serviceaccounttypes.Store
authz authz.AuthZ
emailing emailing.Emailing
settings factory.ScopedProviderSettings
store serviceaccounttypes.Store
authz authz.AuthZ
emailing emailing.Emailing
analytics analytics.Analytics
settings factory.ScopedProviderSettings
}
func NewModule(store serviceaccounttypes.Store, authz authz.AuthZ, emailing emailing.Emailing, providerSettings factory.ProviderSettings) serviceaccount.Module {
func NewModule(store serviceaccounttypes.Store, authz authz.AuthZ, emailing emailing.Emailing, analytics analytics.Analytics, providerSettings factory.ProviderSettings) serviceaccount.Module {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount")
return &module{store: store, authz: authz, emailing: emailing, settings: settings}
return &module{store: store, authz: authz, emailing: emailing, analytics: analytics, settings: settings}
}
func (module *module) Create(ctx context.Context, orgID valuer.UUID, serviceAccount *serviceaccounttypes.ServiceAccount) error {
@@ -33,7 +36,7 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, serviceAcco
}
// authz actions cannot run in sql transactions
err = module.authz.Grant(ctx, orgID, serviceAccount.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, serviceAccount.ID.String(), orgID, nil))
err = module.authz.Grant(ctx, orgID, serviceAccount.Roles, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, serviceAccount.ID.String(), orgID, nil))
if err != nil {
return err
}
@@ -57,9 +60,31 @@ func (module *module) Create(ctx context.Context, orgID valuer.UUID, serviceAcco
return err
}
module.analytics.IdentifyUser(ctx, orgID.String(), serviceAccount.ID.String(), serviceAccount.Traits())
module.analytics.TrackUser(ctx, orgID.String(), serviceAccount.ID.String(), "Service Account Created", serviceAccount.Traits())
return nil
}
func (module *module) GetOrCreate(ctx context.Context, serviceAccount *serviceaccounttypes.ServiceAccount) (*serviceaccounttypes.ServiceAccount, error) {
existingServiceAccount, err := module.store.GetByOrgIDAndName(ctx, serviceAccount.OrgID, serviceAccount.Name)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return nil, err
}
if existingServiceAccount != nil {
return serviceAccount, nil
}
err = module.Create(ctx, serviceAccount.OrgID, serviceAccount)
if err != nil {
return nil, err
}
module.analytics.IdentifyUser(ctx, serviceAccount.OrgID.String(), serviceAccount.ID.String(), serviceAccount.Traits())
module.analytics.TrackUser(ctx, serviceAccount.OrgID.String(), serviceAccount.ID.String(), "Service Account Created", serviceAccount.Traits())
return serviceAccount, nil
}
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*serviceaccounttypes.ServiceAccount, error) {
storableServiceAccount, err := module.store.Get(ctx, orgID, id)
if err != nil {
@@ -138,7 +163,7 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, input *serv
// gets the role diff if any to modify grants.
grants, revokes := serviceAccount.PatchRoles(input)
err = module.authz.ModifyGrant(ctx, orgID, revokes, grants, authtypes.MustNewSubject(authtypes.TypeableUser, serviceAccount.ID.String(), orgID, nil))
err = module.authz.ModifyGrant(ctx, orgID, revokes, grants, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, serviceAccount.ID.String(), orgID, nil))
if err != nil {
return err
}
@@ -167,32 +192,37 @@ func (module *module) Update(ctx context.Context, orgID valuer.UUID, input *serv
return err
}
module.analytics.IdentifyUser(ctx, orgID.String(), input.ID.String(), input.Traits())
module.analytics.TrackUser(ctx, orgID.String(), input.ID.String(), "Service Account Updated", input.Traits())
return nil
}
func (module *module) UpdateStatus(ctx context.Context, orgID valuer.UUID, input *serviceaccounttypes.ServiceAccount) error {
serviceAccount, err := module.Get(ctx, orgID, input.ID)
err := module.authz.Revoke(ctx, orgID, input.Roles, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, input.ID.String(), orgID, nil))
if err != nil {
return err
}
if input.Status == serviceAccount.Status {
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
// revoke all the API keys on disable
err := module.store.RevokeAllFactorAPIKeys(ctx, input.ID)
if err != nil {
return err
}
// update the status but do not delete the role mappings as we will use them for audits
err = module.store.Update(ctx, orgID, serviceaccounttypes.NewStorableServiceAccount(input))
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
switch input.Status {
case serviceaccounttypes.StatusActive:
err := module.activateServiceAccount(ctx, orgID, input)
if err != nil {
return err
}
case serviceaccounttypes.StatusDisabled:
err := module.disableServiceAccount(ctx, orgID, input)
if err != nil {
return err
}
}
module.analytics.TrackUser(ctx, orgID.String(), input.ID.String(), "Service Account Deleted", map[string]any{})
return nil
}
@@ -203,7 +233,7 @@ func (module *module) Delete(ctx context.Context, orgID valuer.UUID, id valuer.U
}
// revoke from authz first as this cannot run in sql transaction
err = module.authz.Revoke(ctx, orgID, serviceAccount.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, serviceAccount.ID.String(), orgID, nil))
err = module.authz.Revoke(ctx, orgID, serviceAccount.Roles, authtypes.MustNewSubject(authtypes.TypeableServiceAccount, serviceAccount.ID.String(), orgID, nil))
if err != nil {
return err
}
@@ -255,6 +285,7 @@ func (module *module) CreateFactorAPIKey(ctx context.Context, factorAPIKey *serv
module.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err)
}
module.analytics.TrackUser(ctx, serviceAccount.OrgID, serviceAccount.ID.String(), "API Key created", factorAPIKey.Traits())
return nil
}
@@ -276,8 +307,14 @@ func (module *module) ListFactorAPIKey(ctx context.Context, serviceAccountID val
return serviceaccounttypes.NewFactorAPIKeyFromStorables(storables), nil
}
func (module *module) UpdateFactorAPIKey(ctx context.Context, serviceAccountID valuer.UUID, factorAPIKey *serviceaccounttypes.FactorAPIKey) error {
return module.store.UpdateFactorAPIKey(ctx, serviceAccountID, serviceaccounttypes.NewStorableFactorAPIKey(factorAPIKey))
func (module *module) UpdateFactorAPIKey(ctx context.Context, orgID valuer.UUID, serviceAccountID valuer.UUID, factorAPIKey *serviceaccounttypes.FactorAPIKey) error {
err := module.store.UpdateFactorAPIKey(ctx, serviceAccountID, serviceaccounttypes.NewStorableFactorAPIKey(factorAPIKey))
if err != nil {
return err
}
module.analytics.TrackUser(ctx, orgID.String(), serviceAccountID.String(), "API Key updated", factorAPIKey.Traits())
return nil
}
func (module *module) RevokeFactorAPIKey(ctx context.Context, serviceAccountID valuer.UUID, id valuer.UUID) error {
@@ -305,47 +342,22 @@ func (module *module) RevokeFactorAPIKey(ctx context.Context, serviceAccountID v
module.settings.Logger().ErrorContext(ctx, "failed to send email", "error", err)
}
module.analytics.TrackUser(ctx, serviceAccount.OrgID, serviceAccountID.String(), "API Key revoked", factorAPIKey.Traits())
return nil
}
func (module *module) disableServiceAccount(ctx context.Context, orgID valuer.UUID, input *serviceaccounttypes.ServiceAccount) error {
err := module.authz.Revoke(ctx, orgID, input.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.String(), orgID, nil))
if err != nil {
return err
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
count, err := module.store.CountByOrgID(ctx, orgID)
if err == nil {
stats["serviceaccount.count"] = count
}
err = module.store.RunInTx(ctx, func(ctx context.Context) error {
// revoke all the API keys on disable
err := module.store.RevokeAllFactorAPIKeys(ctx, input.ID)
if err != nil {
return err
}
// update the status but do not delete the role mappings as we will reuse them on activation.
err = module.Update(ctx, orgID, input)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
count, err = module.store.CountFactorAPIKeysByOrgID(ctx, orgID)
if err == nil {
stats["serviceaccount.keys.count"] = count
}
return nil
}
func (module *module) activateServiceAccount(ctx context.Context, orgID valuer.UUID, input *serviceaccounttypes.ServiceAccount) error {
err := module.authz.Grant(ctx, orgID, input.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.String(), orgID, nil))
if err != nil {
return err
}
err = module.Update(ctx, orgID, input)
if err != nil {
return err
}
return nil
return stats, nil
}

View File

@@ -65,6 +65,25 @@ func (store *store) GetByID(ctx context.Context, id valuer.UUID) (*serviceaccoun
return storable, nil
}
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*serviceaccounttypes.StorableServiceAccount, error) {
storable := new(serviceaccounttypes.StorableServiceAccount)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(storable).
Where("org_id = ?", orgID).
Where("name = ?", name).
Where("status = ?", serviceaccounttypes.StatusActive).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, serviceaccounttypes.ErrCodeServiceAccountNotFound, "service account with name: %s doesn't exist in org: %s", name, orgID.String())
}
return storable, nil
}
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*serviceaccounttypes.StorableServiceAccount, error) {
storables := make([]*serviceaccounttypes.StorableServiceAccount, 0)
@@ -146,6 +165,23 @@ func (store *store) GetServiceAccountRoles(ctx context.Context, id valuer.UUID)
return storables, nil
}
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
storable := new(serviceaccounttypes.StorableServiceAccount)
count, err := store.
sqlstore.
BunDB().
NewSelect().
Model(storable).
Where("org_id = ?", orgID).
Count(ctx)
if err != nil {
return 0, err
}
return int64(count), nil
}
func (store *store) ListServiceAccountRolesByOrgID(ctx context.Context, orgID valuer.UUID) ([]*serviceaccounttypes.StorableServiceAccountRole, error) {
storables := make([]*serviceaccounttypes.StorableServiceAccountRole, 0)
@@ -188,7 +224,7 @@ func (store *store) CreateFactorAPIKey(ctx context.Context, storable *serviceacc
Model(storable).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, serviceaccounttypes.ErrCodeServiceAccountFactorAPIKeyAlreadyExists, "api key with name: %s already exists for service account: %s", storable.Name, storable.ServiceAccountID)
return store.sqlstore.WrapAlreadyExistsErrf(err, serviceaccounttypes.ErrCodeAPIKeyAlreadyExists, "api key with name: %s already exists for service account: %s", storable.Name, storable.ServiceAccountID)
}
return nil
@@ -206,7 +242,24 @@ func (store *store) GetFactorAPIKey(ctx context.Context, serviceAccountID valuer
Where("service_account_id = ?", serviceAccountID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, serviceaccounttypes.ErrCodeServiceAccounFactorAPIKeytNotFound, "api key with id: %s doesn't exist for service account: %s", id, serviceAccountID)
return nil, store.sqlstore.WrapNotFoundErrf(err, serviceaccounttypes.ErrCodeAPIKeytNotFound, "api key with id: %s doesn't exist for service account: %s", id, serviceAccountID)
}
return storable, nil
}
func (store *store) GetFactorAPIKeyByKey(ctx context.Context, key string) (*serviceaccounttypes.StorableFactorAPIKey, error) {
storable := new(serviceaccounttypes.StorableFactorAPIKey)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(storable).
Where("key = ?", key).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, serviceaccounttypes.ErrCodeAPIKeytNotFound, "api key with key: %s doesn't exist", key)
}
return storable, nil
@@ -229,6 +282,44 @@ func (store *store) ListFactorAPIKey(ctx context.Context, serviceAccountID value
return storables, nil
}
func (store *store) ListFactorAPIKeyByOrgID(ctx context.Context, orgID valuer.UUID) ([]*serviceaccounttypes.StorableFactorAPIKey, error) {
storables := make([]*serviceaccounttypes.StorableFactorAPIKey, 0)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(&storables).
Join("JOIN service_account").
JoinOn("service_account.id = factor_api_key.service_account_id").
Where("service_account.org_id = ?", orgID).
Scan(ctx)
if err != nil {
return nil, err
}
return storables, nil
}
func (store *store) CountFactorAPIKeysByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
storable := new(serviceaccounttypes.StorableFactorAPIKey)
count, err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(storable).
Join("JOIN service_account").
JoinOn("service_account.id = factor_api_key.service_account_id").
Where("service_account.org_id = ?", orgID).
Count(ctx)
if err != nil {
return 0, err
}
return int64(count), nil
}
func (store *store) UpdateFactorAPIKey(ctx context.Context, serviceAccountID valuer.UUID, storable *serviceaccounttypes.StorableFactorAPIKey) error {
_, err := store.
sqlstore.
@@ -244,6 +335,30 @@ func (store *store) UpdateFactorAPIKey(ctx context.Context, serviceAccountID val
return nil
}
func (store *store) UpdateLastObservedAtByKey(ctx context.Context, apiKeyToLastObservedAt []map[string]any) error {
values := store.
sqlstore.
BunDBCtx(ctx).
NewValues(&apiKeyToLastObservedAt)
_, err := store.
sqlstore.
BunDBCtx(ctx).
NewUpdate().
With("update_cte", values).
Model((*serviceaccounttypes.StorableFactorAPIKey)(nil)).
TableExpr("update_cte").
Set("last_observed_at = update_cte.last_observed_at").
Where("factor_api_key.key = update_cte.key").
Where("factor_api_key.service_account_id = update_cte.service_account_id").
Exec(ctx)
if err != nil {
return err
}
return nil
}
func (store *store) RevokeFactorAPIKey(ctx context.Context, serviceAccountID valuer.UUID, id valuer.UUID) error {
_, err := store.
sqlstore.

View File

@@ -4,6 +4,7 @@ import (
"context"
"net/http"
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -12,6 +13,9 @@ type Module interface {
// Creates a new service account for an organization.
Create(context.Context, valuer.UUID, *serviceaccounttypes.ServiceAccount) error
// Gets or creates a service account by name
GetOrCreate(context.Context, *serviceaccounttypes.ServiceAccount) (*serviceaccounttypes.ServiceAccount, error)
// Gets a service account by id.
Get(context.Context, valuer.UUID, valuer.UUID) (*serviceaccounttypes.ServiceAccount, error)
@@ -40,10 +44,12 @@ type Module interface {
ListFactorAPIKey(context.Context, valuer.UUID) ([]*serviceaccounttypes.FactorAPIKey, error)
// Updates an existing API key for a service account
UpdateFactorAPIKey(context.Context, valuer.UUID, *serviceaccounttypes.FactorAPIKey) error
UpdateFactorAPIKey(context.Context, valuer.UUID, valuer.UUID, *serviceaccounttypes.FactorAPIKey) error
// Revokes an existing API key for a service account
RevokeFactorAPIKey(context.Context, valuer.UUID, valuer.UUID) error
statsreporter.StatsCollector
}
type Handler interface {

View File

@@ -17,6 +17,7 @@ import (
"github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -28,6 +29,7 @@ type module struct {
authDomain authdomain.Module
tokenizer tokenizer.Tokenizer
orgGetter organization.Getter
roleStore authtypes.RoleStore
}
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 {
@@ -66,7 +68,7 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
}
// filter out deleted users
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
users = slices.DeleteFunc(users, func(user *usertypes.StorableUser) bool { return user.ErrIfDeleted() != nil })
// Since email is a valuer, we can be sure that it is a valid email and we can split it to get the domain name.
name := strings.Split(email.String(), "@")[1]
@@ -90,7 +92,7 @@ func (module *module) GetSessionContext(ctx context.Context, email valuer.Email,
context.Exists = true
for _, user := range users {
idx := slices.IndexFunc(orgs, func(org *types.Organization) bool {
return org.ID == user.OrgID
return org.ID == valuer.MustNewUUID(user.OrgID)
})
if idx == -1 {
@@ -142,9 +144,12 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
}
roleMapping := authDomain.AuthDomainConfig().RoleMapping
role := roleMapping.NewRoleFromCallbackIdentity(callbackIdentity)
managedRoles := roleMapping.ManagedRolesFromCallbackIdentity(callbackIdentity)
user, err := types.NewUser(callbackIdentity.Name, callbackIdentity.Email, role, callbackIdentity.OrgID, types.UserStatusActive)
// pass only valid or fallback to viewer
validRoles, err := module.resolveValidRoles(ctx, callbackIdentity.OrgID, managedRoles, callbackIdentity.Email)
user, err := usertypes.NewUser(callbackIdentity.Name, callbackIdentity.Email, validRoles, callbackIdentity.OrgID, usertypes.UserStatusActive)
if err != nil {
return "", err
}
@@ -158,7 +163,7 @@ func (module *module) CreateCallbackAuthNSession(ctx context.Context, authNProvi
return "", errors.WithAdditionalf(err, "root user can only authenticate via password")
}
token, err := module.tokenizer.CreateToken(ctx, authtypes.NewIdentity(user.ID, user.OrgID, user.Email, user.Role), map[string]string{})
token, err := module.tokenizer.CreateToken(ctx, authtypes.NewIdentity(user.ID, valuer.UUID{}, authtypes.PrincipalUser, user.OrgID, user.Email), map[string]string{})
if err != nil {
return "", err
}
@@ -222,3 +227,39 @@ func getProvider[T authn.AuthN](authNProvider authtypes.AuthNProvider, authNs ma
return provider, nil
}
// resolveValidRoles validates role names against the database
// returns only roles that exist. If none are valid, falls back to signoz-viewer role
func (module *module) resolveValidRoles(ctx context.Context, orgID valuer.UUID, roles []string, email valuer.Email) ([]string, error) {
storableRoles, err := module.roleStore.ListByOrgIDAndNames(ctx, orgID, roles)
if err != nil {
return nil, err
}
validRoles := make([]string, 0, len(storableRoles))
validSet := make(map[string]struct{})
for _, sr := range storableRoles {
validRoles = append(validRoles, sr.Name)
validSet[sr.Name] = struct{}{}
}
// log ignored roles
if len(validRoles) < len(roles) {
var ignored []string
for _, r := range roles {
if _, ok := validSet[r]; !ok {
ignored = append(ignored, r)
}
}
module.settings.Logger().WarnContext(ctx, "ignoring non-existent roles from SSO mapping", "ignored_roles", ignored, "email", email)
}
// fallback to viewer if no valid roles
if len(validRoles) == 0 {
module.settings.Logger().WarnContext(ctx, "no valid roles from SSO mapping, falling back to viewer",
"email", email)
validRoles = []string{authtypes.SigNozViewerRoleName}
}
return validRoles, nil
}

View File

@@ -35,7 +35,7 @@ func (h *handler) GetSpanPercentileDetails(w http.ResponseWriter, r *http.Reques
return
}
result, err := h.module.GetSpanPercentile(r.Context(), valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), spanPercentileRequest)
result, err := h.module.GetSpanPercentile(r.Context(), valuer.MustNewUUID(claims.OrgID), spanPercentileRequest)
if err != nil {
render.Error(w, err)
return

View File

@@ -28,7 +28,7 @@ func NewModule(
}
}
func (m *module) GetSpanPercentile(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, req *spanpercentiletypes.SpanPercentileRequest) (*spanpercentiletypes.SpanPercentileResponse, error) {
func (m *module) GetSpanPercentile(ctx context.Context, orgID valuer.UUID, req *spanpercentiletypes.SpanPercentileRequest) (*spanpercentiletypes.SpanPercentileResponse, error) {
ctx = ctxtypes.NewContextWithCommentVals(ctx, map[string]string{
instrumentationtypes.CodeNamespace: "spanpercentile",
instrumentationtypes.CodeFunctionName: "GetSpanPercentile",

View File

@@ -9,7 +9,7 @@ import (
)
type Module interface {
GetSpanPercentile(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, req *spanpercentiletypes.SpanPercentileRequest) (*spanpercentiletypes.SpanPercentileResponse, error)
GetSpanPercentile(ctx context.Context, orgID valuer.UUID, req *spanpercentiletypes.SpanPercentileRequest) (*spanpercentiletypes.SpanPercentileResponse, error)
}
type Handler interface {

View File

@@ -8,6 +8,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/tracefunnel"
"github.com/SigNoz/signoz/pkg/types"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -30,7 +31,7 @@ func (module *module) Create(ctx context.Context, timestamp int64, name string,
funnel.CreatedBy = userID.String()
// Set up the user relationship
funnel.CreatedByUser = &types.User{
funnel.CreatedByUser = &usertypes.User{
Identifiable: types.Identifiable{
ID: userID,
},

View File

@@ -5,7 +5,7 @@ import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
@@ -68,7 +68,7 @@ func (c Config) Validate() error {
if c.Root.Password == "" {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password is required when root user is enabled")
}
if !types.IsPasswordValid(c.Root.Password) {
if !usertypes.IsPasswordValid(c.Root.Password) {
return errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "user::root::password does not meet password requirements")
}
}

View File

@@ -6,25 +6,21 @@ import (
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type getter struct {
store types.UserStore
store usertypes.UserStore
flagger flagger.Flagger
}
func NewGetter(store types.UserStore, flagger flagger.Flagger) user.Getter {
func NewGetter(store usertypes.UserStore, flagger flagger.Flagger) user.Getter {
return &getter{store: store, flagger: flagger}
}
func (module *getter) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
return module.store.GetRootUserByOrgID(ctx, orgID)
}
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.User, error) {
func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableUser, error) {
users, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {
return nil, err
@@ -35,40 +31,13 @@ func (module *getter) ListByOrgID(ctx context.Context, orgID valuer.UUID) ([]*ty
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
if hideRootUsers {
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.IsRoot })
users = slices.DeleteFunc(users, func(user *usertypes.StorableUser) bool { return user.IsRoot })
}
return users, nil
}
func (module *getter) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
users, err := module.store.GetUsersByEmail(ctx, email)
if err != nil {
return nil, err
}
return users, nil
}
func (module *getter) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
user, err := module.store.GetByOrgIDAndID(ctx, orgID, id)
if err != nil {
return nil, err
}
return user, nil
}
func (module *getter) Get(ctx context.Context, id valuer.UUID) (*types.User, error) {
user, err := module.store.GetUser(ctx, id)
if err != nil {
return nil, err
}
return user, nil
}
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
func (module *getter) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.StorableUser, error) {
users, err := module.store.ListUsersByEmailAndOrgIDs(ctx, email, orgIDs)
if err != nil {
return nil, err
@@ -95,7 +64,7 @@ func (module *getter) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.
return counts, nil
}
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
func (module *getter) GetFactorPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
factorPassword, err := module.store.GetPasswordByUserID(ctx, userID)
if err != nil {
return nil, err

View File

@@ -11,9 +11,8 @@ import (
"github.com/SigNoz/signoz/pkg/http/binding"
"github.com/SigNoz/signoz/pkg/http/render"
root "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/integrationtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux"
)
@@ -31,7 +30,7 @@ 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)
req := new(usertypes.PostableAcceptInvite)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(w, err)
return
@@ -56,14 +55,14 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
return
}
var req types.PostableInvite
var req usertypes.PostableInvite
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err)
return
}
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &types.PostableBulkInviteRequest{
Invites: []types.PostableInvite{req},
invites, err := h.module.CreateBulkInvite(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID), &usertypes.PostableBulkInviteRequest{
Invites: []usertypes.PostableInvite{req},
})
if err != nil {
render.Error(rw, err)
@@ -83,7 +82,7 @@ func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
return
}
var req types.PostableBulkInviteRequest
var req usertypes.PostableBulkInviteRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(rw, err)
return
@@ -169,7 +168,7 @@ func (h *handler) GetUser(w http.ResponseWriter, r *http.Request) {
return
}
user, err := h.getter.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
user, err := h.module.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
if err != nil {
render.Error(w, err)
return
@@ -188,7 +187,7 @@ func (h *handler) GetMyUser(w http.ResponseWriter, r *http.Request) {
return
}
user, err := h.getter.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
user, err := h.module.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
@@ -207,14 +206,14 @@ func (h *handler) ListUsers(w http.ResponseWriter, r *http.Request) {
return
}
users, err := h.getter.ListByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
users, err := h.module.GetUsersByOrgID(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil {
render.Error(w, err)
return
}
// temp code - show only active users
users = slices.DeleteFunc(users, func(user *types.User) bool { return user.Status != types.UserStatusActive })
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusActive })
render.Success(w, http.StatusOK, users)
}
@@ -231,7 +230,7 @@ func (h *handler) UpdateUser(w http.ResponseWriter, r *http.Request) {
return
}
var user types.User
var user usertypes.UpdatableUser
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
render.Error(w, err)
return
@@ -278,13 +277,13 @@ func (handler *handler) GetResetPasswordToken(w http.ResponseWriter, r *http.Req
return
}
user, err := handler.getter.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
user, err := handler.module.GetByOrgIDAndID(ctx, valuer.MustNewUUID(claims.OrgID), valuer.MustNewUUID(id))
if err != nil {
render.Error(w, err)
return
}
token, err := handler.module.GetOrCreateResetPasswordToken(ctx, user.ID)
token, err := handler.module.GetOrCreateResetPasswordToken(ctx, valuer.MustNewUUID(claims.OrgID), user.ID)
if err != nil {
render.Error(w, err)
return
@@ -297,7 +296,7 @@ func (handler *handler) ResetPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(types.PostableResetPassword)
req := new(usertypes.PostableResetPassword)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
render.Error(w, err)
return
@@ -316,7 +315,7 @@ func (handler *handler) ChangePassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
var req types.ChangePasswordRequest
var req usertypes.ChangePasswordRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, err)
return
@@ -335,7 +334,7 @@ func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
req := new(types.PostableForgotPassword)
req := new(usertypes.PostableForgotPassword)
if err := binding.JSON.BindBody(r.Body, req); err != nil {
render.Error(w, err)
return
@@ -349,172 +348,3 @@ func (h *handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) CreateAPIKey(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
}
req := new(types.PostableAPIKey)
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
return
}
apiKey, err := types.NewStorableAPIKey(
req.Name,
valuer.MustNewUUID(claims.UserID),
req.Role,
req.ExpiresInDays,
)
if err != nil {
render.Error(w, err)
return
}
err = h.module.CreateAPIKey(ctx, apiKey)
if err != nil {
render.Error(w, err)
return
}
createdApiKey, err := h.module.GetAPIKey(ctx, valuer.MustNewUUID(claims.OrgID), apiKey.ID)
if err != nil {
render.Error(w, err)
return
}
// just corrected the status code, response is same,
render.Success(w, http.StatusCreated, createdApiKey)
}
func (h *handler) ListAPIKeys(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
}
apiKeys, err := h.module.ListAPIKeys(ctx, valuer.MustNewUUID(claims.OrgID))
if err != nil {
render.Error(w, err)
return
}
// for backward compatibility
if len(apiKeys) == 0 {
render.Success(w, http.StatusOK, []types.GettableAPIKey{})
return
}
result := make([]*types.GettableAPIKey, len(apiKeys))
for i, apiKey := range apiKeys {
result[i] = types.NewGettableAPIKeyFromStorableAPIKey(apiKey)
}
render.Success(w, http.StatusOK, result)
}
func (h *handler) UpdateAPIKey(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
}
req := types.StorableAPIKey{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode api key"))
return
}
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
//get the API Key
existingAPIKey, err := h.module.GetAPIKey(ctx, valuer.MustNewUUID(claims.OrgID), id)
if err != nil {
render.Error(w, err)
return
}
// get the user
createdByUser, err := h.getter.Get(ctx, existingAPIKey.UserID)
if err != nil {
render.Error(w, err)
return
}
if slices.Contains(integrationtypes.AllIntegrationUserEmails, integrationtypes.IntegrationUserEmail(createdByUser.Email.String())) {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
return
}
err = h.module.UpdateAPIKey(ctx, id, &req, valuer.MustNewUUID(claims.UserID))
if err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}
func (h *handler) RevokeAPIKey(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
}
idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
//get the API Key
existingAPIKey, err := h.module.GetAPIKey(ctx, valuer.MustNewUUID(claims.OrgID), id)
if err != nil {
render.Error(w, err)
return
}
// get the user
createdByUser, err := h.getter.Get(ctx, existingAPIKey.UserID)
if err != nil {
render.Error(w, err)
return
}
if slices.Contains(integrationtypes.AllIntegrationUserEmails, integrationtypes.IntegrationUserEmail(createdByUser.Email.String())) {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "API Keys for integration users cannot be revoked"))
return
}
if err := h.module.RevokeAPIKey(ctx, id, valuer.MustNewUUID(claims.UserID)); err != nil {
render.Error(w, err)
return
}
render.Success(w, http.StatusNoContent, nil)
}

View File

@@ -11,6 +11,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"
@@ -18,14 +19,15 @@ 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/integrationtypes"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/dustin/go-humanize"
)
type Module struct {
store types.UserStore
store usertypes.UserStore
tokenizer tokenizer.Tokenizer
emailing emailing.Emailing
settings factory.ScopedProviderSettings
@@ -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 usertypes.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,12 +50,13 @@ 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) {
func (m *Module) AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error) {
// get the user by reset password token
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
storableUser, err := m.store.GetUserByResetPasswordToken(ctx, token)
if err != nil {
return nil, err
}
@@ -64,7 +68,7 @@ func (m *Module) AcceptInvite(ctx context.Context, token string, password string
}
// query the user again
user, err = m.store.GetByOrgIDAndID(ctx, user.OrgID, user.ID)
user, err := m.GetByOrgIDAndID(ctx, valuer.MustNewUUID(storableUser.OrgID), storableUser.ID)
if err != nil {
return nil, err
}
@@ -72,22 +76,27 @@ func (m *Module) AcceptInvite(ctx context.Context, token string, password string
return user, nil
}
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Invite, error) {
func (m *Module) GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error) {
// get the user
user, err := m.store.GetUserByResetPasswordToken(ctx, token)
storableUser, err := m.store.GetUserByResetPasswordToken(ctx, token)
if err != nil {
return nil, err
}
user, err := m.GetByOrgIDAndID(ctx, valuer.MustNewUUID(storableUser.OrgID), storableUser.ID)
if err != nil {
return nil, err
}
// create a dummy invite obj for backward compatibility
invite := &types.Invite{
invite := &usertypes.Invite{
Identifiable: types.Identifiable{
ID: user.ID,
},
Name: user.DisplayName,
Email: user.Email,
Token: token,
Role: user.Role,
Roles: user.Roles,
OrgID: user.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: user.CreatedAt,
@@ -99,18 +108,27 @@ func (m *Module) GetInviteByToken(ctx context.Context, token string) (*types.Inv
}
// 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 *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error) {
creator, err := m.store.GetUser(ctx, userID)
if err != nil {
return nil, err
}
// validate all emails to be invited
// validate all emails & roles to be invited
emails := make([]string, len(bulkInvites.Invites))
var allRolesFromRequest []string
seenRolesFromRequest := make(map[string]struct{})
for idx, invite := range bulkInvites.Invites {
emails[idx] = invite.Email.StringValue()
// for role name validation
for _, role := range invite.Roles {
if _, ok := seenRolesFromRequest[role]; !ok {
seenRolesFromRequest[role] = struct{}{}
allRolesFromRequest = append(allRolesFromRequest, role)
}
}
}
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{types.UserStatusActive.StringValue(), types.UserStatusPendingInvite.StringValue()})
users, err := m.store.GetUsersByEmailsOrgIDAndStatuses(ctx, orgID, emails, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
if err != nil {
return nil, err
}
@@ -120,41 +138,47 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return nil, errors.WithAdditionalf(err, "Cannot send invite to root user")
}
if users[0].Status == types.UserStatusPendingInvite {
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email: %s", users[0].Email.StringValue())
if users[0].Status == usertypes.UserStatusPendingInvite {
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "An invite already exists for this email: %s", users[0].Email)
}
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "User already exists with this email: %s", users[0].Email.StringValue())
return nil, errors.Newf(errors.TypeAlreadyExists, errors.CodeAlreadyExists, "User already exists with this email: %s", users[0].Email)
}
roles, err := m.authz.ListByOrgIDAndNames(ctx, orgID, allRolesFromRequest)
if err != nil {
return nil, err
}
// create a map of (role name -> role)
roleNameToRoleMap := make(map[string]*authtypes.Role)
for _, role := range roles {
roleNameToRoleMap[role.Name] = role
}
type userWithResetToken struct {
User *types.User
ResetPasswordToken *types.ResetPasswordToken
User *usertypes.User
ResetPasswordToken *usertypes.ResetPasswordToken
}
newUsersWithResetToken := make([]*userWithResetToken, len(bulkInvites.Invites))
if err := m.store.RunInTx(ctx, func(ctx context.Context) error {
for idx, invite := range bulkInvites.Invites {
role, err := types.NewRole(invite.Role.String())
if err != nil {
return err
}
// create a new user with pending invite status
newUser, err := types.NewUser(invite.Name, invite.Email, role, orgID, types.UserStatusPendingInvite)
newUser, err := usertypes.NewUser(invite.Name, invite.Email, invite.Roles, orgID, usertypes.UserStatusPendingInvite)
if err != nil {
return err
}
// store the user and password in db
// store the user and user_role entries in db
err = m.createUserWithoutGrant(ctx, newUser)
if err != nil {
return err
}
// generate reset password token
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, newUser.ID)
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, orgID, newUser.ID)
if err != nil {
m.settings.Logger().ErrorContext(ctx, "failed to create reset password token for invited user", "error", err)
return err
@@ -170,23 +194,23 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return nil, err
}
invites := make([]*types.Invite, len(bulkInvites.Invites))
invites := make([]*usertypes.Invite, len(bulkInvites.Invites))
// send password reset emails to all the invited users
for idx, userWithToken := range newUsersWithResetToken {
m.analytics.TrackUser(ctx, orgID.String(), creator.ID.String(), "Invite Sent", map[string]any{
"invitee_email": userWithToken.User.Email,
"invitee_role": userWithToken.User.Role,
"invitee_roles": userWithToken.User.Roles,
})
invite := &types.Invite{
invite := &usertypes.Invite{
Identifiable: types.Identifiable{
ID: userWithToken.User.ID,
},
Name: userWithToken.User.DisplayName,
Email: userWithToken.User.Email,
Token: userWithToken.ResetPasswordToken.Token,
Role: userWithToken.User.Role,
Roles: userWithToken.User.Roles,
OrgID: userWithToken.User.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: userWithToken.User.CreatedAt,
@@ -219,37 +243,36 @@ func (m *Module) CreateBulkInvite(ctx context.Context, orgID valuer.UUID, userID
return invites, nil
}
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
// find all the users with pending_invite status
users, err := m.store.ListUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error) {
users, err := m.GetUsersByOrgID(ctx, valuer.MustNewUUID(orgID))
if err != nil {
return nil, err
}
pendingUsers := slices.DeleteFunc(users, func(user *types.User) bool { return user.Status != types.UserStatusPendingInvite })
pendingUsers := slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.Status != usertypes.UserStatusPendingInvite })
var invites []*types.Invite
var invites []*usertypes.Invite
for _, pUser := range pendingUsers {
// get the reset password token
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, pUser.ID)
resetPasswordToken, err := m.GetOrCreateResetPasswordToken(ctx, pUser.OrgID, pUser.ID)
if err != nil {
return nil, err
}
// create a dummy invite obj for backward compatibility
invite := &types.Invite{
invite := &usertypes.Invite{
Identifiable: types.Identifiable{
ID: pUser.ID,
},
Name: pUser.DisplayName,
Email: pUser.Email,
Token: resetPasswordToken.Token,
Role: pUser.Role,
Roles: pUser.Roles,
OrgID: pUser.OrgID,
TimeAuditable: types.TimeAuditable{
CreatedAt: pUser.CreatedAt,
UpdatedAt: pUser.UpdatedAt, // dummy
UpdatedAt: pUser.UpdatedAt,
},
}
@@ -259,17 +282,28 @@ func (m *Module) ListInvite(ctx context.Context, orgID string) ([]*types.Invite,
return invites, nil
}
func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
createUserOpts := root.NewCreateUserOptions(opts...)
// since assign is idempotant multiple calls to assign won't cause issues in case of retries.
err := module.authz.Grant(ctx, input.OrgID, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(input.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
func (module *Module) CreateUser(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
// validate the roles are valid
_, err := module.authz.ListByOrgIDAndNames(ctx, input.OrgID, input.Roles)
if err != nil {
return err
}
// since assign is idempotant multiple calls to assign won't cause issues in case of retries, also we cannot run this in a transaction for now
err = module.authz.Grant(ctx, input.OrgID, input.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, input.ID.StringValue(), input.OrgID, nil))
if err != nil {
return err
}
createUserOpts := root.NewCreateUserOptions(opts...)
storableUser := usertypes.NewStorableUser(input)
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
if err := module.store.CreateUser(ctx, input); err != nil {
if err := module.store.CreateUser(ctx, storableUser); err != nil {
return err
}
// create user_role junction entries
if err := module.createUserRoleEntries(ctx, input); err != nil {
return err
}
@@ -284,15 +318,46 @@ func (module *Module) CreateUser(ctx context.Context, input *types.User, opts ..
return err
}
traitsOrProperties := types.NewTraitsFromUser(input)
traitsOrProperties := usertypes.NewTraitsFromUser(input)
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
return nil
}
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error) {
existingUser, err := m.store.GetUser(ctx, valuer.MustNewUUID(id))
func (m *Module) GetByOrgIDAndID(ctx context.Context, orgID, id valuer.UUID) (*usertypes.User, error) {
storableUser, err := m.store.GetUser(ctx, id)
if err != nil {
return nil, err
}
storableUserRoles, err := m.store.GetUserRoles(ctx, id)
if err != nil {
return nil, err
}
roleIDs := make([]valuer.UUID, len(storableUserRoles))
for idx, storableUserRole := range storableUserRoles {
roleIDs[idx] = valuer.MustNewUUID(storableUserRole.RoleID)
}
roles, err := m.authz.ListByOrgIDAndIDs(ctx, orgID, roleIDs)
if err != nil {
return nil, err
}
roleNames, err := usertypes.NewRolesFromStorableUserRoles(storableUserRoles, roles)
if err != nil {
return nil, err
}
user := usertypes.NewUserFromStorables(storableUser, roleNames)
return user, nil
}
func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.UpdatableUser, updatedBy string) (*usertypes.User, error) {
existingUser, err := m.GetByOrgIDAndID(ctx, orgID, valuer.MustNewUUID(id))
if err != nil {
return nil, err
}
@@ -309,18 +374,21 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
return nil, errors.WithAdditionalf(err, "cannot update pending user")
}
requestor, err := m.store.GetUser(ctx, valuer.MustNewUUID(updatedBy))
requestor, err := m.GetByOrgIDAndID(ctx, orgID, valuer.MustNewUUID(updatedBy))
if err != nil {
return nil, err
}
if user.Role != "" && user.Role != existingUser.Role && requestor.Role != types.RoleAdmin {
grants, revokes := existingUser.PatchRoles(user.Roles)
rolesChanged := (len(grants) > 0) || (len(revokes) > 0)
if rolesChanged && !slices.Contains(requestor.Roles, authtypes.SigNozAdminRoleName) {
return nil, errors.New(errors.TypeForbidden, errors.CodeForbidden, "only admins can change roles")
}
// 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.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
if rolesChanged && slices.Contains(existingUser.Roles, authtypes.SigNozAdminRoleName) && !slices.Contains(user.Roles, authtypes.SigNozAdminRoleName) {
adminUsers, err := m.store.GetActiveUsersByRoleNameAndOrgID(ctx, authtypes.SigNozAdminRoleName, orgID)
if err != nil {
return nil, err
}
@@ -330,32 +398,48 @@ func (m *Module) UpdateUser(ctx context.Context, orgID valuer.UUID, id string, u
}
}
if user.Role != "" && user.Role != existingUser.Role {
err = m.authz.ModifyGrant(ctx,
orgID,
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(existingUser.Role)},
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil),
)
if rolesChanged {
// can't run in txn
err = m.authz.ModifyGrant(ctx, orgID, revokes, grants, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
if err != nil {
return nil, err
}
}
existingUser.Update(user.DisplayName, user.Role)
if err := m.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return nil, err
existingUser.Update(user.DisplayName, user.Roles)
if rolesChanged {
err = m.store.RunInTx(ctx, func(ctx context.Context) error {
// update the user
if err := m.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return err
}
// delete old role entries and create new ones
if err := m.store.DeleteUserRoles(ctx, existingUser.ID); err != nil {
return err
}
// create new ones
if err := m.createUserRoleEntries(ctx, existingUser); err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
}
return existingUser, nil
}
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
if err := module.store.UpdateUser(ctx, orgID, user); err != nil {
func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error {
if err := module.store.UpdateUser(ctx, orgID, usertypes.NewStorableUser(user)); err != nil {
return err
}
traits := types.NewTraitsFromUser(user)
traits := usertypes.NewTraitsFromUser(user)
module.analytics.IdentifyUser(ctx, user.OrgID.String(), user.ID.String(), traits)
module.analytics.TrackUser(ctx, user.OrgID.String(), user.ID.String(), "User Updated", traits)
@@ -367,7 +451,7 @@ func (module *Module) UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user
}
func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id string, deletedBy string) error {
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(id))
user, err := module.GetByOrgIDAndID(ctx, orgID, valuer.MustNewUUID(id))
if err != nil {
return err
}
@@ -385,17 +469,17 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
}
// don't allow to delete the last admin user
adminUsers, err := module.store.GetActiveUsersByRoleAndOrgID(ctx, types.RoleAdmin, orgID)
adminUsers, err := module.store.GetActiveUsersByRoleNameAndOrgID(ctx, authtypes.SigNozAdminRoleName, orgID)
if err != nil {
return err
}
if len(adminUsers) == 1 && user.Role == types.RoleAdmin {
if len(adminUsers) == 1 && slices.Contains(user.Roles, authtypes.SigNozAdminRoleName) {
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, []string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)}, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
err = module.authz.Revoke(ctx, orgID, user.Roles, authtypes.MustNewSubject(authtypes.TypeableUser, id, orgID, nil))
if err != nil {
return err
}
@@ -412,8 +496,8 @@ func (module *Module) DeleteUser(ctx context.Context, orgID valuer.UUID, id stri
return nil
}
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error) {
user, err := module.store.GetUser(ctx, userID)
func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, orgID, userID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
user, err := module.GetByOrgIDAndID(ctx, orgID, userID)
if err != nil {
return nil, err
}
@@ -435,7 +519,7 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
if password == nil {
// if the user does not have a password, we need to create a new one (common for SSO/SAML users)
password = types.MustGenerateFactorPassword(userID.String())
password = usertypes.MustGenerateFactorPassword(userID.String())
if err := module.store.CreatePassword(ctx, password); err != nil {
return nil, err
@@ -461,7 +545,7 @@ func (module *Module) GetOrCreateResetPasswordToken(ctx context.Context, userID
}
// create a new token
resetPasswordToken, err := types.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
resetPasswordToken, err := usertypes.NewResetPasswordToken(password.ID, time.Now().Add(module.config.Password.Reset.MaxTokenLifetime))
if err != nil {
return nil, err
}
@@ -492,7 +576,7 @@ func (module *Module) ForgotPassword(ctx context.Context, orgID valuer.UUID, ema
return errors.WithAdditionalf(err, "cannot reset password for root user")
}
token, err := module.GetOrCreateResetPasswordToken(ctx, user.ID)
token, err := module.GetOrCreateResetPasswordToken(ctx, orgID, user.ID)
if err != nil {
module.settings.Logger().ErrorContext(ctx, "failed to create reset password token", "error", err)
return err
@@ -535,17 +619,17 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
return err
}
user, err := module.store.GetUser(ctx, valuer.MustNewUUID(password.UserID))
storableUser, err := module.store.GetUser(ctx, valuer.MustNewUUID(password.UserID))
if err != nil {
return err
}
// handle deleted user
if err := user.ErrIfDeleted(); err != nil {
if err := storableUser.ErrIfDeleted(); err != nil {
return errors.WithAdditionalf(err, "deleted users cannot reset their password")
}
if err := user.ErrIfRoot(); err != nil {
if err := storableUser.ErrIfRoot(); err != nil {
return errors.WithAdditionalf(err, "cannot reset password for root user")
}
@@ -553,12 +637,34 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
return err
}
storableUserRoles, err := module.store.GetUserRoles(ctx, storableUser.ID)
if err != nil {
return err
}
roleIDs := make([]valuer.UUID, len(storableUserRoles))
for idx, storableUserRole := range storableUserRoles {
roleIDs[idx] = valuer.MustNewUUID(storableUserRole.RoleID)
}
roles, err := module.authz.ListByOrgIDAndIDs(ctx, valuer.MustNewUUID(storableUser.OrgID), roleIDs)
if err != nil {
return err
}
roleNames, err := usertypes.NewRolesFromStorableUserRoles(storableUserRoles, roles)
if err != nil {
return err
}
user := usertypes.NewUserFromStorables(storableUser, roleNames)
// since grant is idempotent, multiple calls won't cause issues in case of retries
if user.Status == types.UserStatusPendingInvite {
if user.Status == usertypes.UserStatusPendingInvite {
if err = module.authz.Grant(
ctx,
user.OrgID,
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
user.Roles,
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
); err != nil {
return err
@@ -566,11 +672,11 @@ func (module *Module) UpdatePasswordByResetPasswordToken(ctx context.Context, to
}
return module.store.RunInTx(ctx, func(ctx context.Context) error {
if user.Status == types.UserStatusPendingInvite {
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
if user.Status == usertypes.UserStatusPendingInvite {
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
return err
}
if err := module.store.UpdateUser(ctx, user.OrgID, user); err != nil {
if err := module.UpdateAnyUser(ctx, user.OrgID, user); err != nil {
return err
}
}
@@ -607,7 +713,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
}
if !password.Equals(oldpasswd) {
return errors.New(errors.TypeInvalidInput, types.ErrCodeIncorrectPassword, "old password is incorrect")
return errors.New(errors.TypeInvalidInput, usertypes.ErrCodeIncorrectPassword, "old password is incorrect")
}
if err := password.Update(passwd); err != nil {
@@ -631,7 +737,7 @@ func (module *Module) UpdatePassword(ctx context.Context, userID valuer.UUID, ol
return module.tokenizer.DeleteTokensByUserID(ctx, userID)
}
func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opts ...root.CreateUserOption) (*types.User, error) {
func (module *Module) GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...root.CreateUserOption) (*usertypes.User, error) {
existingUser, err := module.GetNonDeletedUserByEmailAndOrgID(ctx, user.Email, user.OrgID)
if err != nil {
if !errors.Ast(err, errors.TypeNotFound) {
@@ -641,9 +747,9 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opt
if existingUser != nil {
// for users logging through SSO flow but are having status as pending_invite
if existingUser.Status == types.UserStatusPendingInvite {
if existingUser.Status == usertypes.UserStatusPendingInvite {
// respect the role coming from the SSO
existingUser.Update("", user.Role)
existingUser.Update("", user.Roles)
// activate the user
if err = module.activatePendingUser(ctx, existingUser); err != nil {
return nil, err
@@ -661,38 +767,18 @@ func (module *Module) GetOrCreateUser(ctx context.Context, user *types.User, opt
return user, nil
}
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
return m.store.CreateAPIKey(ctx, apiKey)
}
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
return m.store.UpdateAPIKey(ctx, id, apiKey, updaterID)
}
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
return m.store.ListAPIKeys(ctx, orgID)
}
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
return m.store.GetAPIKey(ctx, orgID, id)
}
func (m *Module) RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error {
return m.store.RevokeAPIKey(ctx, id, removedByUserID)
}
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*types.User, error) {
user, err := types.NewRootUser(name, email, organization.ID)
func (module *Module) CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, passwd string) (*usertypes.User, error) {
user, err := usertypes.NewRootUser(name, email, organization.ID)
if err != nil {
return nil, err
}
password, err := types.NewFactorPassword(passwd, user.ID.StringValue())
password, err := usertypes.NewFactorPassword(passwd, user.ID.StringValue())
if err != nil {
return nil, err
}
managedRoles := roletypes.NewManagedRoles(organization.ID)
managedRoles := authtypes.NewManagedRoles(organization.ID)
err = module.authz.CreateManagedUserRoleTransactions(ctx, organization.ID, user.ID)
if err != nil {
return nil, err
@@ -724,50 +810,91 @@ func (module *Module) CreateFirstUser(ctx context.Context, organization *types.O
return user, nil
}
func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{types.UserStatusActive.StringValue(), types.UserStatusDeleted.StringValue(), types.UserStatusPendingInvite.StringValue()})
if err == nil {
stats["user.count"] = counts[types.UserStatusActive] + counts[types.UserStatusDeleted] + counts[types.UserStatusPendingInvite]
stats["user.count.active"] = counts[types.UserStatusActive]
stats["user.count.deleted"] = counts[types.UserStatusDeleted]
stats["user.count.pending_invite"] = counts[types.UserStatusPendingInvite]
func (module *Module) GetUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.User, error) {
storableUsers, err := module.store.ListUsersByOrgID(ctx, orgID)
if err != nil {
return nil, err
}
count, err := module.store.CountAPIKeyByOrgID(ctx, orgID)
userIDs := make([]valuer.UUID, len(storableUsers))
for idx, storableUser := range storableUsers {
userIDs[idx] = storableUser.ID
}
storableUserRoles, err := module.store.ListUserRolesByOrgIDAndUserIDs(ctx, orgID, userIDs)
if err != nil {
return nil, err
}
userIDToRoleIDs, roleIDs := usertypes.GetUniqueRolesAndUserMapping(storableUserRoles)
roles, err := module.authz.ListByOrgIDAndIDs(ctx, orgID, roleIDs)
if err != nil {
return nil, err
}
// fill in the role fetched data back to service account
users := usertypes.NewUsersFromRolesMaps(storableUsers, roles, userIDToRoleIDs)
// filter root users if feature flag `hide_root_users` is true
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
hideRootUsers := module.flagger.BooleanOrEmpty(ctx, flagger.FeatureHideRootUser, evalCtx)
if hideRootUsers {
users = slices.DeleteFunc(users, func(user *usertypes.User) bool { return user.IsRoot })
}
return users, nil
}
func (module *Module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
stats := make(map[string]any)
counts, err := module.store.CountByOrgIDAndStatuses(ctx, orgID, []string{usertypes.UserStatusActive.StringValue(), usertypes.UserStatusDeleted.StringValue(), usertypes.UserStatusPendingInvite.StringValue()})
if err == nil {
stats["factor.api_key.count"] = count
stats["user.count"] = counts[usertypes.UserStatusActive] + counts[usertypes.UserStatusDeleted] + counts[usertypes.UserStatusPendingInvite]
stats["user.count.active"] = counts[usertypes.UserStatusActive]
stats["user.count.deleted"] = counts[usertypes.UserStatusDeleted]
stats["user.count.pending_invite"] = counts[usertypes.UserStatusPendingInvite]
}
return stats, nil
}
// this function restricts that only one non-deleted user email can exist for an org ID, if found more, it throws an error
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error) {
func (module *Module) GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error) {
existingUsers, err := module.store.GetUsersByEmailAndOrgID(ctx, email, orgID)
if err != nil {
return nil, err
}
// filter out the deleted users
existingUsers = slices.DeleteFunc(existingUsers, func(user *types.User) bool { return user.ErrIfDeleted() != nil })
existingUsers = slices.DeleteFunc(existingUsers, func(user *usertypes.StorableUser) bool { return user.ErrIfDeleted() != nil })
if len(existingUsers) > 1 {
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "Multiple non-deleted users found for email %s in org_id: %s", email.StringValue(), orgID.StringValue())
}
if len(existingUsers) == 1 {
return existingUsers[0], nil
existingUser, err := module.GetByOrgIDAndID(ctx, orgID, existingUsers[0].ID)
if err != nil {
return nil, err
}
return existingUser, nil
}
return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "No non-deleted user found with email %s in org_id: %s", email.StringValue(), orgID.StringValue())
}
func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.User, opts ...root.CreateUserOption) error {
func (module *Module) createUserWithoutGrant(ctx context.Context, input *usertypes.User, opts ...root.CreateUserOption) error {
createUserOpts := root.NewCreateUserOptions(opts...)
storableUser := usertypes.NewStorableUser(input)
if err := module.store.RunInTx(ctx, func(ctx context.Context) error {
if err := module.store.CreateUser(ctx, input); err != nil {
if err := module.store.CreateUser(ctx, storableUser); err != nil {
return err
}
// create user_role junction entries
if err := module.createUserRoleEntries(ctx, input); err != nil {
return err
}
@@ -782,31 +909,46 @@ func (module *Module) createUserWithoutGrant(ctx context.Context, input *types.U
return err
}
traitsOrProperties := types.NewTraitsFromUser(input)
traitsOrProperties := usertypes.NewTraitsFromUser(input)
module.analytics.IdentifyUser(ctx, input.OrgID.String(), input.ID.String(), traitsOrProperties)
module.analytics.TrackUser(ctx, input.OrgID.String(), input.ID.String(), "User Created", traitsOrProperties)
return nil
}
func (module *Module) activatePendingUser(ctx context.Context, user *types.User) error {
func (module *Module) activatePendingUser(ctx context.Context, user *usertypes.User) error {
err := module.authz.Grant(
ctx,
user.OrgID,
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(user.Role)},
user.Roles,
authtypes.MustNewSubject(authtypes.TypeableUser, user.ID.StringValue(), user.OrgID, nil),
)
if err != nil {
return err
}
if err := user.UpdateStatus(types.UserStatusActive); err != nil {
if err := user.UpdateStatus(usertypes.UserStatusActive); err != nil {
return err
}
err = module.store.UpdateUser(ctx, user.OrgID, user)
err = module.UpdateAnyUser(ctx, user.OrgID, user)
if err != nil {
return err
}
return nil
}
func (module *Module) createUserRoleEntries(ctx context.Context, user *usertypes.User) error {
if len(user.Roles) == 0 {
return nil
}
storableRoles, err := module.authz.ListByOrgIDAndNames(ctx, user.OrgID, user.Roles)
if err != nil {
return err
}
userRoles := usertypes.NewStorableUserRoles(user.ID, storableRoles)
return module.store.CreateUserRoles(ctx, userRoles)
}

View File

@@ -2,6 +2,7 @@ package impluser
import (
"context"
"slices"
"time"
"github.com/SigNoz/signoz/pkg/authz"
@@ -11,13 +12,13 @@ import (
"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/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type service struct {
settings factory.ScopedProviderSettings
store types.UserStore
store usertypes.UserStore
module user.Module
orgGetter organization.Getter
authz authz.AuthZ
@@ -27,7 +28,7 @@ type service struct {
func NewService(
providerSettings factory.ProviderSettings,
store types.UserStore,
store usertypes.UserStore,
module user.Module,
orgGetter organization.Getter,
authz authz.AuthZ,
@@ -130,7 +131,7 @@ func (s *service) reconcileByName(ctx context.Context) error {
}
func (s *service) reconcileRootUser(ctx context.Context, orgID valuer.UUID) error {
existingRoot, err := s.store.GetRootUserByOrgID(ctx, orgID)
existingRoot, err := s.getRootUserByOrgID(ctx, orgID)
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
@@ -149,18 +150,18 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
}
if existingUser != nil {
oldRole := existingUser.Role
oldRoles := existingUser.Roles
existingUser.PromoteToRoot()
if err := s.module.UpdateAnyUser(ctx, orgID, existingUser); err != nil {
return err
}
if oldRole != types.RoleAdmin {
if !slices.Contains(oldRoles, authtypes.SigNozAdminRoleName) {
if err := s.authz.ModifyGrant(ctx,
orgID,
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(oldRole)},
[]string{roletypes.MustGetSigNozManagedRoleFromExistingRole(types.RoleAdmin)},
oldRoles,
existingUser.Roles,
authtypes.MustNewSubject(authtypes.TypeableUser, existingUser.ID.StringValue(), orgID, nil),
); err != nil {
return err
@@ -171,12 +172,12 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
}
// Create new root user
newUser, err := types.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
newUser, err := usertypes.NewRootUser(s.config.Email.String(), s.config.Email, orgID)
if err != nil {
return err
}
factorPassword, err := types.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, newUser.ID.StringValue())
if err != nil {
return err
}
@@ -184,7 +185,7 @@ func (s *service) createOrPromoteRootUser(ctx context.Context, orgID valuer.UUID
return s.module.CreateUser(ctx, newUser, user.WithFactorPassword(factorPassword))
}
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *types.User) error {
func (s *service) updateExistingRootUser(ctx context.Context, orgID valuer.UUID, existingRoot *usertypes.User) error {
existingRoot.PromoteToRoot()
if existingRoot.Email != s.config.Email {
@@ -204,7 +205,7 @@ func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
return err
}
factorPassword, err := types.NewFactorPassword(s.config.Password, userID.StringValue())
factorPassword, err := usertypes.NewFactorPassword(s.config.Password, userID.StringValue())
if err != nil {
return err
}
@@ -222,3 +223,34 @@ func (s *service) setPassword(ctx context.Context, userID valuer.UUID) error {
return nil
}
func (s *service) getRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.User, error) {
existingStorableRootUser, err := s.store.GetRootUserByOrgID(ctx, orgID)
if err != nil {
return nil, err // caller must handle the type not found error
}
storableRootUserRoles, err := s.store.GetUserRoles(ctx, existingStorableRootUser.ID)
if err != nil {
return nil, err
}
roleIDs := make([]valuer.UUID, len(storableRootUserRoles))
for idx, storableUserRole := range storableRootUserRoles {
roleIDs[idx] = valuer.MustNewUUID(storableUserRole.RoleID)
}
roles, err := s.authz.ListByOrgIDAndIDs(ctx, orgID, roleIDs)
if err != nil {
return nil, err
}
roleNames, err := usertypes.NewRolesFromStorableUserRoles(storableRootUserRoles, roles)
if err != nil {
return nil, err
}
rootUser := usertypes.NewUserFromStorables(existingStorableRootUser, roleNames)
return rootUser, nil
}

View File

@@ -3,15 +3,14 @@ package impluser
import (
"context"
"database/sql"
"sort"
"time"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
@@ -21,11 +20,11 @@ type store struct {
settings factory.ProviderSettings
}
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.UserStore {
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) usertypes.UserStore {
return &store{sqlstore: sqlstore, settings: settings}
}
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) error {
func (store *store) CreatePassword(ctx context.Context, password *usertypes.FactorPassword) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -33,13 +32,13 @@ func (store *store) CreatePassword(ctx context.Context, password *types.FactorPa
Model(password).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrPasswordAlreadyExists, "password for user %s already exists", password.UserID)
}
return nil
}
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
func (store *store) CreateUser(ctx context.Context, user *usertypes.StorableUser) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -47,13 +46,13 @@ func (store *store) CreateUser(ctx context.Context, user *types.User) error {
Model(user).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrUserAlreadyExists, "user with email %s already exists in org %s", user.Email, user.OrgID)
}
return nil
}
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*types.User, error) {
var users []*types.User
func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]*usertypes.StorableUser, error) {
var users []*usertypes.StorableUser
err := store.
sqlstore.
@@ -69,8 +68,8 @@ func (store *store) GetUsersByEmail(ctx context.Context, email valuer.Email) ([]
return users, nil
}
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, error) {
user := new(types.User)
func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*usertypes.StorableUser, error) {
user := new(usertypes.StorableUser)
err := store.
sqlstore.
@@ -80,14 +79,14 @@ func (store *store) GetUser(ctx context.Context, id valuer.UUID) (*types.User, e
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
}
return user, nil
}
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.User, error) {
user := new(types.User)
func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*usertypes.StorableUser, error) {
user := new(usertypes.StorableUser)
err := store.
sqlstore.
@@ -98,14 +97,14 @@ func (store *store) GetByOrgIDAndID(ctx context.Context, orgID valuer.UUID, id v
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user with id %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with id %s does not exist", id)
}
return user, nil
}
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*types.User, error) {
var users []*types.User
func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) ([]*usertypes.StorableUser, error) {
var users []*usertypes.StorableUser
err := store.
sqlstore.
@@ -122,26 +121,24 @@ func (store *store) GetUsersByEmailAndOrgID(ctx context.Context, email valuer.Em
return users, nil
}
func (store *store) GetActiveUsersByRoleAndOrgID(ctx context.Context, role types.Role, orgID valuer.UUID) ([]*types.User, error) {
var users []*types.User
func (store *store) GetActiveUsersByRoleNameAndOrgID(ctx context.Context, roleName string, orgID valuer.UUID) ([]*usertypes.StorableUser, error) {
var users []*usertypes.StorableUser
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
err := store.sqlstore.BunDBCtx(ctx).NewSelect().
Model(&users).
Where("org_id = ?", orgID).
Where("role = ?", role).
Where("status = ?", types.UserStatusActive.StringValue()).
Join("JOIN user_role ON user_role.user_id = users.id").
Join("JOIN role ON role.id = user_role.role_id").
Where("users.org_id = ?", orgID).
Where("role.name = ?", roleName).
Where("users.status = ?", usertypes.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, err
}
return users, nil
}
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *types.User) error {
func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *usertypes.StorableUser) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -149,7 +146,6 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *typ
Model(user).
Column("display_name").
Column("email").
Column("role").
Column("is_root").
Column("updated_at").
Column("status").
@@ -157,13 +153,13 @@ func (store *store) UpdateUser(ctx context.Context, orgID valuer.UUID, user *typ
Where("id = ?", user.ID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user does not exist in org: %s", orgID)
}
return nil
}
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*types.GettableUser, error) {
users := []*types.User{}
func (store *store) ListUsersByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableUser, error) {
users := []*usertypes.StorableUser{}
err := store.
sqlstore.
@@ -191,7 +187,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// get the password id
var password types.FactorPassword
var password usertypes.FactorPassword
err = tx.NewSelect().
Model(&password).
Where("user_id = ?", id).
@@ -202,7 +198,7 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete reset password request
_, err = tx.NewDelete().
Model(new(types.ResetPasswordToken)).
Model(new(usertypes.ResetPasswordToken)).
Where("password_id = ?", password.ID.String()).
Exec(ctx)
if err != nil {
@@ -211,22 +207,13 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
// delete factor password
_, err = tx.NewDelete().
Model(new(types.FactorPassword)).
Model(new(usertypes.FactorPassword)).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete factor password")
}
// delete api keys
_, err = tx.NewDelete().
Model(&types.StorableAPIKey{}).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete API keys")
}
// delete user_preference
_, err = tx.NewDelete().
Model(new(preferencetypes.StorableUserPreference)).
@@ -245,9 +232,18 @@ func (store *store) DeleteUser(ctx context.Context, orgID string, id string) err
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete tokens")
}
// delete user_role entries
_, err = tx.NewDelete().
Model(new(usertypes.StorableUserRole)).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete user roles")
}
// delete user
_, err = tx.NewDelete().
Model(new(types.User)).
Model(new(usertypes.StorableUser)).
Where("org_id = ?", orgID).
Where("id = ?", id).
Exec(ctx)
@@ -275,7 +271,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// get the password id
var password types.FactorPassword
var password usertypes.FactorPassword
err = tx.NewSelect().
Model(&password).
Where("user_id = ?", id).
@@ -286,7 +282,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// delete reset password request
_, err = tx.NewDelete().
Model(new(types.ResetPasswordToken)).
Model(new(usertypes.ResetPasswordToken)).
Where("password_id = ?", password.ID.String()).
Exec(ctx)
if err != nil {
@@ -295,22 +291,13 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// delete factor password
_, err = tx.NewDelete().
Model(new(types.FactorPassword)).
Model(new(usertypes.FactorPassword)).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete factor password")
}
// delete api keys
_, err = tx.NewDelete().
Model(&types.StorableAPIKey{}).
Where("user_id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete API keys")
}
// delete user_preference
_, err = tx.NewDelete().
Model(new(preferencetypes.StorableUserPreference)).
@@ -332,8 +319,8 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
// soft delete user
now := time.Now()
_, err = tx.NewUpdate().
Model(new(types.User)).
Set("status = ?", types.UserStatusDeleted).
Model(new(usertypes.StorableUser)).
Set("status = ?", usertypes.UserStatusDeleted).
Set("deleted_at = ?", now).
Set("updated_at = ?", now).
Where("org_id = ?", orgID).
@@ -351,7 +338,7 @@ func (store *store) SoftDeleteUser(ctx context.Context, orgID string, id string)
return nil
}
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *types.ResetPasswordToken) error {
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordToken *usertypes.ResetPasswordToken) error {
_, err := store.
sqlstore.
BunDBCtx(ctx).
@@ -359,14 +346,14 @@ func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordT
Model(resetPasswordToken).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrResetPasswordTokenAlreadyExists, "reset password token for password %s already exists", resetPasswordToken.PasswordID)
}
return nil
}
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*usertypes.FactorPassword, error) {
password := new(usertypes.FactorPassword)
err := store.
sqlstore.
@@ -376,14 +363,14 @@ func (store *store) GetPassword(ctx context.Context, id valuer.UUID) (*types.Fac
Where("id = ?", id).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrPasswordNotFound, "password with id: %s does not exist", id)
}
return password, nil
}
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*types.FactorPassword, error) {
password := new(types.FactorPassword)
func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID) (*usertypes.FactorPassword, error) {
password := new(usertypes.FactorPassword)
err := store.
sqlstore.
@@ -393,13 +380,13 @@ func (store *store) GetPasswordByUserID(ctx context.Context, userID valuer.UUID)
Where("user_id = ?", userID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", userID)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrPasswordNotFound, "password for user %s does not exist", userID)
}
return password, nil
}
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*types.ResetPasswordToken, error) {
resetPasswordToken := new(types.ResetPasswordToken)
func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) (*usertypes.ResetPasswordToken, error) {
resetPasswordToken := new(usertypes.ResetPasswordToken)
err := store.
sqlstore.
@@ -409,7 +396,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
Where("password_id = ?", passwordID).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token for password %s does not exist", passwordID)
}
return resetPasswordToken, nil
@@ -417,7 +404,7 @@ func (store *store) GetResetPasswordTokenByPasswordID(ctx context.Context, passw
func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, passwordID valuer.UUID) error {
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().
Model(&types.ResetPasswordToken{}).
Model(&usertypes.ResetPasswordToken{}).
Where("password_id = ?", passwordID).
Exec(ctx)
if err != nil {
@@ -427,8 +414,8 @@ func (store *store) DeleteResetPasswordTokenByPasswordID(ctx context.Context, pa
return nil
}
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*types.ResetPasswordToken, error) {
resetPasswordRequest := new(types.ResetPasswordToken)
func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*usertypes.ResetPasswordToken, error) {
resetPasswordRequest := new(usertypes.ResetPasswordToken)
err := store.
sqlstore.
@@ -438,132 +425,27 @@ func (store *store) GetResetPasswordToken(ctx context.Context, token string) (*t
Where("token = ?", token).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token does not exist")
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrResetPasswordTokenNotFound, "reset password token does not exist")
}
return resetPasswordRequest, nil
}
func (store *store) UpdatePassword(ctx context.Context, factorPassword *types.FactorPassword) error {
func (store *store) UpdatePassword(ctx context.Context, factorPassword *usertypes.FactorPassword) error {
_, err := store.sqlstore.BunDBCtx(ctx).
NewUpdate().
Model(factorPassword).
Where("user_id = ?", factorPassword.UserID).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
return store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrPasswordNotFound, "password for user %s does not exist", factorPassword.UserID)
}
return nil
}
// --- API KEY ---
func (store *store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
_, err := store.sqlstore.BunDB().NewInsert().
Model(apiKey).
Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
}
return nil
}
func (store *store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
apiKey.UpdatedBy = updaterID.String()
apiKey.UpdatedAt = time.Now()
_, err := store.sqlstore.BunDB().NewUpdate().
Model(apiKey).
Column("role", "name", "updated_at", "updated_by").
Where("id = ?", id).
Where("revoked = false").
Exec(ctx)
if err != nil {
return store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
return nil
}
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
orgUserAPIKeys := new(types.OrgUserAPIKey)
if err := store.sqlstore.BunDB().NewSelect().
Model(orgUserAPIKeys).
Relation("Users").
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("revoked = false")
},
).
Relation("Users.APIKeys.CreatedByUser").
Relation("Users.APIKeys.UpdatedByUser").
Where("id = ?", orgID).
Scan(ctx); err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to fetch API keys")
}
// Flatten the API keys from all users
var allAPIKeys []*types.StorableAPIKeyUser
for _, user := range orgUserAPIKeys.Users {
if user.APIKeys != nil {
allAPIKeys = append(allAPIKeys, user.APIKeys...)
}
}
// sort the API keys by updated_at
sort.Slice(allAPIKeys, func(i, j int) bool {
return allAPIKeys[i].UpdatedAt.After(allAPIKeys[j].UpdatedAt)
})
return allAPIKeys, nil
}
func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
updatedAt := time.Now().Unix()
_, err := store.sqlstore.BunDB().NewUpdate().
Model(&types.StorableAPIKey{}).
Set("revoked = ?", true).
Set("updated_by = ?", revokedByUserID).
Set("updated_at = ?", updatedAt).
Where("id = ?", id).
Exec(ctx)
if err != nil {
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to revoke API key")
}
return nil
}
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
apiKey := new(types.OrgUserAPIKey)
if err := store.sqlstore.BunDB().NewSelect().
Model(apiKey).
Relation("Users").
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
return q.Where("revoked = false").Where("storable_api_key.id = ?", id).
OrderExpr("storable_api_key.updated_at DESC").Limit(1)
},
).
Relation("Users.APIKeys.CreatedByUser").
Relation("Users.APIKeys.UpdatedByUser").
Scan(ctx); err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
// flatten the API keys
flattenedAPIKeys := []*types.StorableAPIKeyUser{}
for _, user := range apiKey.Users {
if user.APIKeys != nil {
flattenedAPIKeys = append(flattenedAPIKeys, user.APIKeys...)
}
}
if len(flattenedAPIKeys) == 0 {
return nil, store.sqlstore.WrapNotFoundErrf(errors.New(errors.TypeNotFound, errors.CodeNotFound, "API key with id: %s does not exist"), types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
}
return flattenedAPIKeys[0], nil
}
func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
user := new(types.User)
user := new(usertypes.StorableUser)
count, err := store.
sqlstore.
@@ -580,7 +462,7 @@ func (store *store) CountByOrgID(ctx context.Context, orgID valuer.UUID) (int64,
}
func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, statuses []string) (map[valuer.String]int64, error) {
user := new(types.User)
user := new(usertypes.StorableUser)
var results []struct {
Status valuer.String `bun:"status"`
Count int64 `bun:"count"`
@@ -609,32 +491,14 @@ func (store *store) CountByOrgIDAndStatuses(ctx context.Context, orgID valuer.UU
return counts, nil
}
func (store *store) CountAPIKeyByOrgID(ctx context.Context, orgID valuer.UUID) (int64, error) {
apiKey := new(types.StorableAPIKey)
count, err := store.
sqlstore.
BunDB().
NewSelect().
Model(apiKey).
Join("JOIN users ON users.id = storable_api_key.user_id").
Where("org_id = ?", orgID).
Count(ctx)
if err != nil {
return 0, err
}
return int64(count), nil
}
func (store *store) RunInTx(ctx context.Context, cb func(ctx context.Context) error) error {
return store.sqlstore.RunInTxCtx(ctx, nil, func(ctx context.Context) error {
return cb(ctx)
})
}
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*types.User, error) {
user := new(types.User)
func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (*usertypes.StorableUser, error) {
user := new(usertypes.StorableUser)
err := store.
sqlstore.
BunDBCtx(ctx).
@@ -644,13 +508,13 @@ func (store *store) GetRootUserByOrgID(ctx context.Context, orgID valuer.UUID) (
Where("is_root = ?", true).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "root user for org %s not found", orgID)
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "root user for org %s not found", orgID)
}
return user, nil
}
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*types.User, error) {
users := []*types.User{}
func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.Email, orgIDs []valuer.UUID) ([]*usertypes.StorableUser, error) {
users := []*usertypes.StorableUser{}
err := store.
sqlstore.
BunDB().
@@ -666,27 +530,27 @@ func (store *store) ListUsersByEmailAndOrgIDs(ctx context.Context, email valuer.
return users, nil
}
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*types.User, error) {
user := new(types.User)
func (store *store) GetUserByResetPasswordToken(ctx context.Context, token string) (*usertypes.StorableUser, error) {
user := new(usertypes.StorableUser)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Join(`JOIN factor_password ON factor_password.user_id = "user".id`).
Join("JOIN factor_password ON factor_password.user_id = users.id").
Join("JOIN reset_password_token ON reset_password_token.password_id = factor_password.id").
Where("reset_password_token.token = ?", token).
Scan(ctx)
if err != nil {
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrCodeUserNotFound, "user not found for reset password token")
return nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user not found for reset password token")
}
return user, nil
}
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*types.User, error) {
users := []*types.User{}
func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID valuer.UUID, emails []string, statuses []string) ([]*usertypes.StorableUser, error) {
users := []*usertypes.StorableUser{}
err := store.
sqlstore.
@@ -703,3 +567,90 @@ func (store *store) GetUsersByEmailsOrgIDAndStatuses(ctx context.Context, orgID
return users, nil
}
func (store *store) GetActiveUserAndFactorPasswordByEmailAndOrgID(ctx context.Context, email string, orgID valuer.UUID) (*usertypes.StorableUser, *usertypes.FactorPassword, error) {
user := new(usertypes.StorableUser)
factorPassword := new(usertypes.FactorPassword)
err := store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(user).
Where("email = ?", email).
Where("org_id = ?", orgID).
Where("status = ?", usertypes.UserStatusActive.StringValue()).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodeUserNotFound, "user with email %s in org %s not found", email, orgID)
}
err = store.
sqlstore.
BunDBCtx(ctx).
NewSelect().
Model(factorPassword).
Where("user_id = ?", user.ID).
Scan(ctx)
if err != nil {
return nil, nil, store.sqlstore.WrapNotFoundErrf(err, usertypes.ErrCodePasswordNotFound, "user with email %s in org %s does not have password", email, orgID)
}
return user, factorPassword, nil
}
func (store *store) CreateUserRoles(ctx context.Context, userRoles []*usertypes.StorableUserRole) error {
_, err := store.sqlstore.BunDBCtx(ctx).NewInsert().Model(&userRoles).Exec(ctx)
if err != nil {
return store.sqlstore.WrapAlreadyExistsErrf(err, usertypes.ErrCodeUserRoleAlreadyExists, "duplicate role assignments for service account")
}
return nil
}
func (store *store) GetUserRoles(ctx context.Context, userID valuer.UUID) ([]*usertypes.StorableUserRole, error) {
userRoles := make([]*usertypes.StorableUserRole, 0)
err := store.sqlstore.BunDBCtx(ctx).NewSelect().Model(&userRoles).Where("user_id = ?", userID).Scan(ctx)
if err != nil {
return nil, err
}
return userRoles, nil
}
func (store *store) DeleteUserRoles(ctx context.Context, userID valuer.UUID) error {
_, err := store.sqlstore.BunDBCtx(ctx).NewDelete().Model(new(usertypes.StorableUserRole)).Where("user_id = ?", userID).Exec(ctx)
if err != nil {
return err
}
return nil
}
func (store *store) ListUserRolesByOrgID(ctx context.Context, orgID valuer.UUID) ([]*usertypes.StorableUserRole, error) {
storableUserRoles := make([]*usertypes.StorableUserRole, 0)
err := store.sqlstore.BunDBCtx(ctx).NewSelect().Model(&storableUserRoles).
Join("JOIN users").
JoinOn("users.id = user_role.user_id").
Where("users.org_id = ?", orgID).Scan(ctx)
if err != nil {
return nil, err
}
return storableUserRoles, nil
}
func (store *store) ListUserRolesByOrgIDAndUserIDs(ctx context.Context, orgID valuer.UUID, userIDs []valuer.UUID) ([]*usertypes.StorableUserRole, error) {
storableUserRoles := make([]*usertypes.StorableUserRole, 0)
err := store.sqlstore.BunDBCtx(ctx).NewSelect().Model(&storableUserRoles).
Join("JOIN users").
JoinOn("users.id = user_role.user_id").
Where("users.org_id = ?", orgID).Where("users.id IN (?)", bun.In(userIDs)).Scan(ctx)
if err != nil {
return nil, err
}
return storableUserRoles, nil
}

View File

@@ -1,17 +1,17 @@
package user
import (
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type createUserOptions struct {
FactorPassword *types.FactorPassword
FactorPassword *usertypes.FactorPassword
}
type CreateUserOption func(*createUserOptions)
func WithFactorPassword(factorPassword *types.FactorPassword) CreateUserOption {
func WithFactorPassword(factorPassword *usertypes.FactorPassword) CreateUserOption {
return func(o *createUserOptions) {
o.FactorPassword = factorPassword
}

View File

@@ -6,22 +6,26 @@ import (
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
)
type Module interface {
// Creates the organization and the first user of that organization.
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*types.User, error)
CreateFirstUser(ctx context.Context, organization *types.Organization, name string, email valuer.Email, password string) (*usertypes.User, error)
// Creates a user and sends an analytics event.
CreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) error
CreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) error
// Get user by Org ID and ID
GetByOrgIDAndID(ctx context.Context, orgID, id valuer.UUID) (*usertypes.User, error)
// Get or create a user. If a user with the same email and orgID already exists, it returns the existing user.
GetOrCreateUser(ctx context.Context, user *types.User, opts ...CreateUserOption) (*types.User, error)
GetOrCreateUser(ctx context.Context, user *usertypes.User, opts ...CreateUserOption) (*usertypes.User, error)
// Get or Create a reset password token for a user. If the password does not exist, a new one is randomly generated and inserted. The function
// is idempotent and can be called multiple times.
GetOrCreateResetPasswordToken(ctx context.Context, userID valuer.UUID) (*types.ResetPasswordToken, error)
GetOrCreateResetPasswordToken(ctx context.Context, orgID, userID valuer.UUID) (*usertypes.ResetPasswordToken, error)
// Updates password of a user using a reset password token. It also deletes all reset password tokens for the user.
// This is used to reset the password of a user when they forget their password.
@@ -33,48 +37,32 @@ type Module interface {
// Initiate forgot password flow for a user
ForgotPassword(ctx context.Context, orgID valuer.UUID, email valuer.Email, frontendBaseURL string) error
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *types.User, updatedBy string) (*types.User, error)
UpdateUser(ctx context.Context, orgID valuer.UUID, id string, user *usertypes.UpdatableUser, updatedBy string) (*usertypes.User, error)
// UpdateAnyUser updates a user and persists the changes to the database along with the analytics and identity deletion.
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *types.User) error
UpdateAnyUser(ctx context.Context, orgID valuer.UUID, user *usertypes.User) error
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)
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 *usertypes.PostableBulkInviteRequest) ([]*usertypes.Invite, error)
ListInvite(ctx context.Context, orgID string) ([]*usertypes.Invite, error)
AcceptInvite(ctx context.Context, token string, password string) (*usertypes.User, error)
GetInviteByToken(ctx context.Context, token string) (*usertypes.Invite, error)
// API KEY
CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error
UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error
ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error)
RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error
GetAPIKey(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*types.StorableAPIKeyUser, error)
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*usertypes.User, error)
GetNonDeletedUserByEmailAndOrgID(ctx context.Context, email valuer.Email, orgID valuer.UUID) (*types.User, error)
// Get all the users for an org id
GetUsersByOrgID(context.Context, valuer.UUID) ([]*usertypes.User, error)
statsreporter.StatsCollector
}
type Getter interface {
// Get root user by org id.
GetRootUserByOrgID(context.Context, valuer.UUID) (*types.User, error)
// Get gets the users based on the given id
ListByOrgID(context.Context, valuer.UUID) ([]*types.User, error)
// Get users by email.
GetUsersByEmail(context.Context, valuer.Email) ([]*types.User, error)
// Get user by orgID and id.
GetByOrgIDAndID(context.Context, valuer.UUID, valuer.UUID) (*types.User, error)
// Get user by id.
Get(context.Context, valuer.UUID) (*types.User, error)
ListByOrgID(context.Context, valuer.UUID) ([]*usertypes.StorableUser, error)
// List users by email and org ids.
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*types.User, error)
ListUsersByEmailAndOrgIDs(context.Context, valuer.Email, []valuer.UUID) ([]*usertypes.StorableUser, error)
// Count users by org id.
CountByOrgID(context.Context, valuer.UUID) (int64, error)
@@ -83,7 +71,7 @@ type Getter interface {
CountByOrgIDAndStatuses(context.Context, valuer.UUID, []string) (map[valuer.String]int64, error)
// Get factor password by user id.
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*types.FactorPassword, error)
GetFactorPasswordByUserID(context.Context, valuer.UUID) (*usertypes.FactorPassword, error)
}
type Handler interface {
@@ -106,10 +94,4 @@ type Handler interface {
ResetPassword(http.ResponseWriter, *http.Request)
ChangePassword(http.ResponseWriter, *http.Request)
ForgotPassword(http.ResponseWriter, *http.Request)
// API KEY
CreateAPIKey(http.ResponseWriter, *http.Request)
ListAPIKeys(http.ResponseWriter, *http.Request)
UpdateAPIKey(http.ResponseWriter, *http.Request)
RevokeAPIKey(http.ResponseWriter, *http.Request)
}

View File

@@ -60,6 +60,8 @@ func (handler *handler) QueryRange(rw http.ResponseWriter, req *http.Request) {
handler.set.Logger.ErrorContext(ctx, "panic in QueryRange",
"error", r,
"user", claims.UserID,
"service_account", claims.ServiceAccountID,
"principal", claims.Principal,
"payload", string(queryJSON),
"stacktrace", stackTrace,
)
@@ -160,6 +162,8 @@ func (handler *handler) QueryRawStream(rw http.ResponseWriter, req *http.Request
handler.set.Logger.ErrorContext(ctx, "panic in QueryRawStream",
"error", r,
"user", claims.UserID,
"service_account", claims.ServiceAccountID,
"principal", claims.Principal,
"payload", string(queryJSON),
"stacktrace", stackTrace,
)
@@ -309,9 +313,9 @@ func (handler *handler) logEvent(ctx context.Context, referrer string, event *qb
}
if !event.HasData {
handler.analytics.TrackUser(ctx, claims.OrgID, claims.UserID, "Telemetry Query Returned Empty", properties)
handler.analytics.TrackUser(ctx, claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Empty", properties)
return
}
handler.analytics.TrackUser(ctx, claims.OrgID, claims.UserID, "Telemetry Query Returned Results", properties)
handler.analytics.TrackUser(ctx, claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Results", properties)
}

View File

@@ -20,7 +20,6 @@ import (
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
"github.com/SigNoz/signoz/pkg/types/metrictypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/dustin/go-humanize"
"golang.org/x/exp/maps"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
@@ -159,8 +158,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
metricNames := make([]string, 0)
for idx, query := range req.CompositeQuery.Queries {
event.QueryType = query.Type.StringValue()
switch query.Type {
case qbtypes.QueryTypeBuilder:
if query.Type == qbtypes.QueryTypeBuilder {
if spec, ok := query.Spec.(qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]); ok {
for _, agg := range spec.Aggregations {
if agg.MetricName != "" {
@@ -238,7 +236,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
req.CompositeQuery.Queries[idx].Spec = spec
}
case qbtypes.QueryTypePromQL:
} else if query.Type == qbtypes.QueryTypePromQL {
event.MetricsUsed = true
switch spec := query.Spec.(type) {
case qbtypes.PromQuery:
@@ -249,7 +247,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
req.CompositeQuery.Queries[idx].Spec = spec
}
case qbtypes.QueryTypeClickHouseSQL:
} else if query.Type == qbtypes.QueryTypeClickHouseSQL {
switch spec := query.Spec.(type) {
case qbtypes.ClickHouseQuery:
if strings.TrimSpace(spec.Query) != "" {
@@ -258,7 +256,7 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
event.TracesUsed = strings.Contains(spec.Query, "signoz_traces")
}
}
case qbtypes.QueryTypeTraceOperator:
} else if query.Type == qbtypes.QueryTypeTraceOperator {
if spec, ok := query.Spec.(qbtypes.QueryBuilderTraceOperator); ok {
if spec.StepInterval.Seconds() == 0 {
spec.StepInterval = qbtypes.Step{
@@ -278,9 +276,23 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
}
// Fetch temporality for all metrics at once
var metricTemporality map[string]metrictypes.Temporality
var metricTypes map[string]metrictypes.Type
if len(metricNames) > 0 {
var err error
metricTemporality, metricTypes, err = q.metadataStore.FetchTemporalityAndTypeMulti(ctx, req.Start, req.End, metricNames...)
if err != nil {
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
// Continue without temporality - statement builder will handle unspecified
metricTemporality = make(map[string]metrictypes.Temporality)
metricTypes = make(map[string]metrictypes.Type)
}
q.logger.DebugContext(ctx, "fetched metric temporalities and types", "metric_temporality", metricTemporality, "metric_types", metricTypes)
}
queries := make(map[string]qbtypes.Query)
steps := make(map[string]qbtypes.Step)
missingMetrics := []string{}
for _, query := range req.CompositeQuery.Queries {
var queryName string
@@ -362,26 +374,15 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
queries[spec.Name] = bq
steps[spec.Name] = spec.StepInterval
case qbtypes.QueryBuilderQuery[qbtypes.MetricAggregation]:
var metricTemporality map[string]metrictypes.Temporality
var metricTypes map[string]metrictypes.Type
if len(metricNames) > 0 {
var err error
metricTemporality, metricTypes, err = q.metadataStore.FetchTemporalityAndTypeMulti(ctx, req.Start, req.End, metricNames...)
if err != nil {
q.logger.WarnContext(ctx, "failed to fetch metric temporality", "error", err, "metrics", metricNames)
return nil, errors.NewInternalf(errors.CodeInternal, "failed to fetch metrics temporality")
}
q.logger.DebugContext(ctx, "fetched metric temporalities and types", "metric_temporality", metricTemporality, "metric_types", metricTypes)
}
for i := range spec.Aggregations {
if spec.Aggregations[i].MetricName != "" && spec.Aggregations[i].Temporality == metrictypes.Unknown {
if temp, ok := metricTemporality[spec.Aggregations[i].MetricName]; ok && temp != metrictypes.Unknown {
spec.Aggregations[i].Temporality = temp
}
}
// TODO(srikanthccv): warn when the metric is missing
if spec.Aggregations[i].Temporality == metrictypes.Unknown {
missingMetrics = append(missingMetrics, spec.Aggregations[i].MetricName)
continue
spec.Aggregations[i].Temporality = metrictypes.Unspecified
}
if spec.Aggregations[i].MetricName != "" && spec.Aggregations[i].Type == metrictypes.UnspecifiedType {
@@ -408,24 +409,6 @@ func (q *querier) QueryRange(ctx context.Context, orgID valuer.UUID, req *qbtype
}
}
}
if len(missingMetrics) > 0 {
lastSeenInfo, _ := q.metadataStore.FetchLastSeenInfoMulti(ctx, missingMetrics...)
lastSeenStr := func(name string) string {
if ts, ok := lastSeenInfo[name]; ok && ts > 0 {
ago := humanize.RelTime(time.UnixMilli(ts), time.Now(), "ago", "from now")
return fmt.Sprintf("%s (last seen %s)", name, ago)
}
return name
}
if len(missingMetrics) == 1 {
return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the metric %s in the query time range", lastSeenStr(missingMetrics[0]))
}
parts := make([]string, len(missingMetrics))
for i, m := range missingMetrics {
parts[i] = lastSeenStr(m)
}
return nil, errors.NewNotFoundf(errors.CodeNotFound, "no data found for the following metrics in the query time range: %s", strings.Join(parts, ", "))
}
qbResp, qbErr := q.run(ctx, orgID, queries, req, steps, event)
if qbResp != nil {
qbResp.QBEvent = event
@@ -680,7 +663,7 @@ func (q *querier) run(
}
// executeWithCache executes a query using the bucket cache
func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query qbtypes.Query, step qbtypes.Step, _ bool) (*qbtypes.Result, error) {
func (q *querier) executeWithCache(ctx context.Context, orgID valuer.UUID, query qbtypes.Query, step qbtypes.Step, noCache bool) (*qbtypes.Result, error) {
// Get cached data and missing ranges
cachedResult, missingRanges := q.bucketCache.GetMissRanges(ctx, orgID, query, step)

View File

@@ -72,6 +72,7 @@ import (
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
"github.com/SigNoz/signoz/pkg/types/ruletypes"
traceFunnels "github.com/SigNoz/signoz/pkg/types/tracefunneltypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"go.uber.org/zap"
@@ -512,7 +513,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
router.HandleFunc("/api/v1/dashboards/{id}", am.ViewAccess(aH.Get)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Update)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{id}", am.EditAccess(aH.Signoz.Handlers.Dashboard.Delete)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/dashboards/{id}/lock", am.EditAccess(aH.Signoz.Handlers.Dashboard.LockUnlock)).Methods(http.MethodPut)
router.HandleFunc("/api/v1/dashboards/{id}/lock", am.AdminAccess(aH.Signoz.Handlers.Dashboard.LockUnlock)).Methods(http.MethodPut)
router.HandleFunc("/api/v2/variables/query", am.ViewAccess(aH.queryDashboardVarsV2)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/explorer/views", am.ViewAccess(aH.Signoz.Handlers.SavedView.List)).Methods(http.MethodGet)
@@ -1565,7 +1566,7 @@ func (aH *APIHandler) registerEvent(w http.ResponseWriter, r *http.Request) {
if errv2 == nil {
switch request.EventType {
case model.TrackEvent:
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.UserID, request.EventName, request.Attributes)
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.GetIdentityID(), request.EventName, request.Attributes)
}
aH.WriteJSON(w, r, map[string]string{"data": "Event Processed Successfully"})
} else {
@@ -2033,7 +2034,7 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
var req types.PostableRegisterOrgAndAdmin
var req usertypes.PostableRegisterOrgAndAdmin
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
render.Error(w, err)
return
@@ -4669,7 +4670,7 @@ func (aH *APIHandler) sendQueryResultEvents(r *http.Request, result []*v3.Result
// Check if result is empty or has no data
if len(result) == 0 {
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.UserID, "Telemetry Query Returned Empty", properties)
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Empty", properties)
return
}
@@ -4679,18 +4680,18 @@ func (aH *APIHandler) sendQueryResultEvents(r *http.Request, result []*v3.Result
if len(result[0].List) == 0 {
// Check if first result has no table data
if result[0].Table == nil {
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.UserID, "Telemetry Query Returned Empty", properties)
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Empty", properties)
return
}
if len(result[0].Table.Rows) == 0 {
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.UserID, "Telemetry Query Returned Empty", properties)
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Empty", properties)
return
}
}
}
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.UserID, "Telemetry Query Returned Results", properties)
aH.Signoz.Analytics.TrackUser(r.Context(), claims.OrgID, claims.GetIdentityID(), "Telemetry Query Returned Results", properties)
}

View File

@@ -195,13 +195,12 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
}),
otelmux.WithPublicEndpoint(),
))
r.Use(middleware.NewAuthN([]string{"Authorization", "Sec-WebSocket-Protocol"}, s.signoz.Sharder, s.signoz.Tokenizer, s.signoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAuthN([]string{"Authorization", "Sec-WebSocket-Protocol"}, []string{"SIGNOZ-API-KEY"}, s.signoz.Sharder, s.signoz.Tokenizer, s.signoz.ServiceAccountTokenizer, s.signoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewTimeout(s.signoz.Instrumentation.Logger(),
s.config.APIServer.Timeout.ExcludedRoutes,
s.config.APIServer.Timeout.Default,
s.config.APIServer.Timeout.Max,
).Wrap)
r.Use(middleware.NewAPIKey(s.signoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.signoz.Instrumentation.Logger(), s.signoz.Sharder).Wrap)
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
r.Use(middleware.NewComment().Wrap)

View File

@@ -76,21 +76,6 @@ func TestManager_TestNotification_SendUnmatched_ThresholdRule(t *testing.T) {
alertDataRows := cmock.NewRows(cols, tc.Values)
mock := mockStore.Mock()
// Mock metadata queries for FetchTemporalityAndTypeMulti
// First query: fetchMetricsTemporalityAndType (from signoz_metrics time series table)
metadataCols := []cmock.ColumnType{
{Name: "metric_name", Type: "String"},
{Name: "temporality", Type: "String"},
{Name: "type", Type: "String"},
{Name: "is_monotonic", Type: "Bool"},
}
metadataRows := cmock.NewRows(metadataCols, [][]any{
{"probe_success", metrictypes.Unspecified, metrictypes.GaugeType, false},
})
mock.ExpectQuery("*distributed_time_series_v4*").WithArgs(nil, nil, nil).WillReturnRows(metadataRows)
// Second query: fetchMeterSourceMetricsTemporalityAndType (from signoz_meter table)
emptyMetadataRows := cmock.NewRows(metadataCols, [][]any{})
mock.ExpectQuery("*meter*").WithArgs(nil).WillReturnRows(emptyMetadataRows)
// Generate query arguments for the metric query
evalTime := time.Now().UTC()

View File

@@ -9,10 +9,11 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
)
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
emailPasswordAuthN := emailpasswordauthn.New(store)
func NewAuthNs(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userStore usertypes.UserStore) (map[authtypes.AuthNProvider]authn.AuthN, error) {
emailPasswordAuthN := emailpasswordauthn.New(store, userStore)
googleCallbackAuthN, err := googlecallbackauthn.New(ctx, store, providerSettings)
if err != nil {

View File

@@ -43,14 +43,13 @@ func TestNewHandlers(t *testing.T) {
emailing := emailingtest.New()
queryParser := queryparser.New(providerSettings)
require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
require.NoError(t, err)
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), flagger)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser, userGetter)
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)

View File

@@ -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"
@@ -89,10 +90,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{
@@ -113,6 +115,6 @@ func NewModules(
Services: implservices.NewModule(querier, telemetryStore),
MetricsExplorer: implmetricsexplorer.NewModule(telemetryStore, telemetryMetadataStore, cache, ruleStore, dashboard, providerSettings, config.MetricsExplorer),
Promote: implpromote.NewModule(telemetryMetadataStore, telemetryStore),
ServiceAccount: implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, emailing, providerSettings),
ServiceAccount: implserviceaccount.NewModule(implserviceaccount.NewStore(sqlstore), authz, emailing, analytics, providerSettings),
}
}

View File

@@ -42,14 +42,14 @@ func TestNewModules(t *testing.T) {
emailing := emailingtest.New()
queryParser := queryparser.New(providerSettings)
require.NoError(t, err)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser)
flagger, err := flagger.New(context.Background(), instrumentationtest.New().ToProviderSettings(), flagger.Config{}, flagger.MustNewRegistry())
require.NoError(t, err)
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), flagger)
dashboardModule := impldashboard.NewModule(impldashboard.NewStore(sqlstore), providerSettings, nil, orgGetter, queryParser, userGetter)
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++ {

View File

@@ -27,6 +27,7 @@ import (
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/preference/implpreference"
"github.com/SigNoz/signoz/pkg/modules/promote/implpromote"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/session/implsession"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
@@ -55,6 +56,7 @@ import (
"github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/jwttokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/opaquetokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/serviceaccounttokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/tokenizerstore/sqltokenizerstore"
"github.com/SigNoz/signoz/pkg/types/alertmanagertypes"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
@@ -172,6 +174,11 @@ func NewSQLMigrationProviderFactories(
sqlmigration.NewMigrateRulesV4ToV5Factory(sqlstore, telemetryStore),
sqlmigration.NewAddStatusUserFactory(sqlstore, sqlschema),
sqlmigration.NewDeprecateUserInviteFactory(sqlstore, sqlschema),
sqlmigration.NewAddServiceAccountFactory(sqlstore, sqlschema),
sqlmigration.NewDeprecateAPIKeyFactory(sqlstore, sqlschema),
sqlmigration.NewServiceAccountAuthzactory(sqlstore),
sqlmigration.NewAddUserRoleFactory(sqlstore, sqlschema),
sqlmigration.NewAddUserRoleAuthzFactory(sqlstore),
)
}
@@ -265,9 +272,11 @@ func NewAPIServerProviderFactories(orgGetter organization.Getter, authz authz.Au
func NewTokenizerProviderFactories(cache cache.Cache, sqlstore sqlstore.SQLStore, orgGetter organization.Getter) factory.NamedMap[factory.ProviderFactory[tokenizer.Tokenizer, tokenizer.Config]] {
tokenStore := sqltokenizerstore.NewStore(sqlstore)
apiKeyStore := implserviceaccount.NewStore(sqlstore)
return factory.MustNewNamedMap(
opaquetokenizer.NewFactory(cache, tokenStore, orgGetter),
jwttokenizer.NewFactory(cache, tokenStore),
serviceaccounttokenizer.NewFactory(cache, apiKeyStore, orgGetter),
)
}

View File

@@ -21,6 +21,8 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount/implserviceaccount"
"github.com/SigNoz/signoz/pkg/modules/user"
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
@@ -38,8 +40,10 @@ import (
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/telemetrytraces"
pkgtokenizer "github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/serviceaccounttokenizer"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
@@ -48,29 +52,30 @@ import (
type SigNoz struct {
*factory.Registry
Instrumentation instrumentation.Instrumentation
Analytics analytics.Analytics
Cache cache.Cache
Web web.Web
SQLStore sqlstore.SQLStore
TelemetryStore telemetrystore.TelemetryStore
TelemetryMetadataStore telemetrytypes.MetadataStore
Prometheus prometheus.Prometheus
Alertmanager alertmanager.Alertmanager
Querier querier.Querier
APIServer apiserver.APIServer
Zeus zeus.Zeus
Licensing licensing.Licensing
Emailing emailing.Emailing
Sharder sharder.Sharder
StatsReporter statsreporter.StatsReporter
Tokenizer pkgtokenizer.Tokenizer
Authz authz.AuthZ
Modules Modules
Handlers Handlers
QueryParser queryparser.QueryParser
Flagger flagger.Flagger
Gateway gateway.Gateway
Instrumentation instrumentation.Instrumentation
Analytics analytics.Analytics
Cache cache.Cache
Web web.Web
SQLStore sqlstore.SQLStore
TelemetryStore telemetrystore.TelemetryStore
TelemetryMetadataStore telemetrytypes.MetadataStore
Prometheus prometheus.Prometheus
Alertmanager alertmanager.Alertmanager
Querier querier.Querier
APIServer apiserver.APIServer
Zeus zeus.Zeus
Licensing licensing.Licensing
Emailing emailing.Emailing
Sharder sharder.Sharder
StatsReporter statsreporter.StatsReporter
Tokenizer pkgtokenizer.Tokenizer
ServiceAccountTokenizer pkgtokenizer.Tokenizer
Authz authz.AuthZ
Modules Modules
Handlers Handlers
QueryParser queryparser.QueryParser
Flagger flagger.Flagger
Gateway gateway.Gateway
}
func New(
@@ -86,9 +91,9 @@ func New(
sqlSchemaProviderFactories func(sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]],
sqlstoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]],
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error),
authNsCallback func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing, userStore usertypes.UserStore) (map[authtypes.AuthNProvider]authn.AuthN, error),
authzCallback func(context.Context, sqlstore.SQLStore, licensing.Licensing, dashboard.Module) factory.ProviderFactory[authz.AuthZ, authz.Config],
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing) dashboard.Module,
dashboardModuleCallback func(sqlstore.SQLStore, factory.ProviderSettings, analytics.Analytics, organization.Getter, queryparser.QueryParser, querier.Querier, licensing.Licensing, user.Getter) dashboard.Module,
gatewayProviderFactory func(licensing.Licensing) factory.ProviderFactory[gateway.Gateway, gateway.Config],
querierHandlerCallback func(factory.ProviderSettings, querier.Querier, analytics.Analytics) querier.Handler,
) (*SigNoz, error) {
@@ -279,8 +284,15 @@ func New(
return nil, err
}
apiKeyStore := implserviceaccount.NewStore(sqlstore)
serviceAccountTokenizer, err := serviceaccounttokenizer.New(ctx, providerSettings, config.Tokenizer, cache, apiKeyStore, orgGetter)
if err != nil {
return nil, err
}
// Initialize user getter
userGetter := impluser.NewGetter(impluser.NewStore(sqlstore, providerSettings), flagger)
userStore := impluser.NewStore(sqlstore, providerSettings)
userGetter := impluser.NewGetter(userStore, flagger)
licensingProviderFactory := licenseProviderFactory(sqlstore, zeus, orgGetter, analytics)
licensing, err := licensingProviderFactory.New(
@@ -296,7 +308,7 @@ func New(
queryParser := queryparser.New(providerSettings)
// Initialize dashboard module (needed for authz registry)
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, queryParser, querier, licensing)
dashboard := dashboardModuleCallback(sqlstore, providerSettings, analytics, orgGetter, queryParser, querier, licensing, userGetter)
// Initialize authz
authzProviderFactory := authzCallback(ctx, sqlstore, licensing, dashboard)
@@ -349,7 +361,7 @@ func New(
// Initialize authns
store := sqlauthnstore.NewStore(sqlstore)
authNs, err := authNsCallback(ctx, providerSettings, store, licensing)
authNs, err := authNsCallback(ctx, providerSettings, store, licensing, userStore)
if err != nil {
return nil, err
}
@@ -388,7 +400,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)
@@ -421,6 +433,7 @@ func New(
tokenizer,
config,
modules.AuthDomain,
modules.ServiceAccount,
}
// Initialize stats reporter from the available stats reporter provider factories
@@ -443,6 +456,7 @@ func New(
factory.NewNamedService(factory.MustNewName("licensing"), licensing),
factory.NewNamedService(factory.MustNewName("statsreporter"), statsReporter),
factory.NewNamedService(factory.MustNewName("tokenizer"), tokenizer),
factory.NewNamedService(factory.MustNewName("serviceaccounttokenizer"), serviceAccountTokenizer),
factory.NewNamedService(factory.MustNewName("authz"), authz),
factory.NewNamedService(factory.MustNewName("user"), userService),
)
@@ -451,28 +465,29 @@ func New(
}
return &SigNoz{
Registry: registry,
Analytics: analytics,
Instrumentation: instrumentation,
Cache: cache,
Web: web,
SQLStore: sqlstore,
TelemetryStore: telemetrystore,
TelemetryMetadataStore: telemetryMetadataStore,
Prometheus: prometheus,
Alertmanager: alertmanager,
Querier: querier,
APIServer: apiserver,
Zeus: zeus,
Licensing: licensing,
Emailing: emailing,
Sharder: sharder,
Tokenizer: tokenizer,
Authz: authz,
Modules: modules,
Handlers: handlers,
QueryParser: queryParser,
Flagger: flagger,
Gateway: gateway,
Registry: registry,
Analytics: analytics,
Instrumentation: instrumentation,
Cache: cache,
Web: web,
SQLStore: sqlstore,
TelemetryStore: telemetrystore,
TelemetryMetadataStore: telemetryMetadataStore,
Prometheus: prometheus,
Alertmanager: alertmanager,
Querier: querier,
APIServer: apiserver,
Zeus: zeus,
Licensing: licensing,
Emailing: emailing,
Sharder: sharder,
Tokenizer: tokenizer,
ServiceAccountTokenizer: serviceAccountTokenizer,
Authz: authz,
Modules: modules,
Handlers: handlers,
QueryParser: queryParser,
Flagger: flagger,
Gateway: gateway,
}, nil
}

View File

@@ -2,9 +2,11 @@ package sqlmigration
import (
"context"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
@@ -16,12 +18,12 @@ type funnel struct {
types.Identifiable // funnel id
types.TimeAuditable
types.UserAuditable
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
Description string `json:"description" bun:"description,type:text"` // funnel description
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
Tags string `json:"tags" bun:"tags,type:text"`
CreatedByUser *types.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
Name string `json:"funnel_name" bun:"name,type:text,notnull"` // funnel name
Description string `json:"description" bun:"description,type:text"` // funnel description
OrgID valuer.UUID `json:"org_id" bun:"org_id,type:varchar,notnull"`
Steps []funnelStep `json:"steps" bun:"steps,type:text,notnull"`
Tags string `json:"tags" bun:"tags,type:text"`
CreatedByUser *usertypes.User `json:"user" bun:"rel:belongs-to,join:created_by=id"`
}
type funnelStep struct {

View File

@@ -7,7 +7,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
@@ -54,7 +54,7 @@ func (migration *addManagedRoles) Up(ctx context.Context, db *bun.DB) error {
return err
}
managedRoles := []*roletypes.StorableRole{}
managedRoles := []*authtypes.StorableRole{}
for _, orgIDStr := range orgIDs {
orgID, err := valuer.NewUUID(orgIDStr)
if err != nil {
@@ -62,20 +62,20 @@ func (migration *addManagedRoles) Up(ctx context.Context, db *bun.DB) error {
}
// signoz admin
signozAdminRole := roletypes.NewRole(roletypes.SigNozAdminRoleName, roletypes.SigNozAdminRoleDescription, roletypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozAdminRole))
signozAdminRole := authtypes.NewRole(authtypes.SigNozAdminRoleName, authtypes.SigNozAdminRoleDescription, authtypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozAdminRole))
// signoz editor
signozEditorRole := roletypes.NewRole(roletypes.SigNozEditorRoleName, roletypes.SigNozEditorRoleDescription, roletypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozEditorRole))
signozEditorRole := authtypes.NewRole(authtypes.SigNozEditorRoleName, authtypes.SigNozEditorRoleDescription, authtypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozEditorRole))
// signoz viewer
signozViewerRole := roletypes.NewRole(roletypes.SigNozViewerRoleName, roletypes.SigNozViewerRoleDescription, roletypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozViewerRole))
signozViewerRole := authtypes.NewRole(authtypes.SigNozViewerRoleName, authtypes.SigNozViewerRoleDescription, authtypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozViewerRole))
// signoz anonymous
signozAnonymousRole := roletypes.NewRole(roletypes.SigNozAnonymousRoleName, roletypes.SigNozAnonymousRoleDescription, roletypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, roletypes.NewStorableRoleFromRole(signozAnonymousRole))
signozAnonymousRole := authtypes.NewRole(authtypes.SigNozAnonymousRoleName, authtypes.SigNozAnonymousRoleDescription, authtypes.RoleTypeManaged, orgID)
managedRoles = append(managedRoles, authtypes.NewStorableRoleFromRole(signozAnonymousRole))
}
if len(managedRoles) > 0 {

View File

@@ -6,7 +6,7 @@ import (
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/roletypes"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/oklog/ulid/v2"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
@@ -83,7 +83,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName+"#assignee", "userset", tupleID, now,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName+"#assignee", "userset", tupleID, now,
)
if err != nil {
return err
@@ -102,7 +102,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName+"#assignee", "TUPLE_OPERATION_WRITE", tupleID, now,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role:organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName+"#assignee", "TUPLE_OPERATION_WRITE", tupleID, now,
)
if err != nil {
return err
@@ -113,7 +113,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName, "assignee", "userset", tupleID, now,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName, "assignee", "userset", tupleID, now,
)
if err != nil {
return err
@@ -132,7 +132,7 @@ func (migration *addAnonymousPublicDashboardTransaction) Up(ctx context.Context,
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+roletypes.SigNozAnonymousRoleName, "assignee", 0, tupleID, now,
storeID, "metaresource", "organization/"+orgID+"/public-dashboard/*", "read", "role", "organization/"+orgID+"/role/"+authtypes.SigNozAnonymousRoleName, "assignee", 0, tupleID, now,
)
if err != nil {
return err

View File

@@ -0,0 +1,121 @@
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 addServiceAccount struct {
sqlschema sqlschema.SQLSchema
sqlstore sqlstore.SQLStore
}
func NewAddServiceAccountFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_service_account"), func(_ context.Context, _ factory.ProviderSettings, _ Config) (SQLMigration, error) {
return &addServiceAccount{
sqlschema: sqlschema,
sqlstore: sqlstore,
}, nil
})
}
func (migration *addServiceAccount) Register(migrations *migrate.Migrations) error {
err := migrations.Register(migration.Up, migration.Down)
if err != nil {
return err
}
return nil
}
func (migration *addServiceAccount) 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: "service_account",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "deleted_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "email", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "status", DataType: sqlschema.DataTypeText, 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...)
indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "service_account", ColumnNames: []sqlschema.ColumnName{"name", "org_id", "deleted_at"}})
sqls = append(sqls, indexSQLs...)
tableSQLs = migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "service_account_role",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "service_account_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "role_id", DataType: sqlschema.DataTypeText, Nullable: false},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("service_account_id"),
ReferencedTableName: sqlschema.TableName("service_account"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
{
ReferencingColumnName: sqlschema.ColumnName("role_id"),
ReferencedTableName: sqlschema.TableName("role"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, tableSQLs...)
indexSQLs = migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "service_account_role", ColumnNames: []sqlschema.ColumnName{"service_account_id", "role_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
}
return nil
}
func (a *addServiceAccount) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,309 @@
package sqlmigration
import (
"context"
"database/sql"
"time"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
type oldFactorAPIKey68 struct {
bun.BaseModel `bun:"table:factor_api_key"`
types.Identifiable
CreatedAt time.Time `bun:"created_at"`
UpdatedAt time.Time `bun:"updated_at"`
Token string `bun:"token"`
Role string `bun:"role"`
Name string `bun:"name"`
ExpiresAt time.Time `bun:"expires_at"`
LastUsed time.Time `bun:"last_used"`
Revoked bool `bun:"revoked"`
UserID string `bun:"user_id"`
}
type oldUser68 struct {
bun.BaseModel `bun:"table:users"`
types.Identifiable
DisplayName string `bun:"display_name"`
Email string `bun:"email"`
OrgID string `bun:"org_id"`
}
type oldRole68 struct {
bun.BaseModel `bun:"table:role"`
types.Identifiable
Name string `bun:"name"`
OrgID string `bun:"org_id"`
}
type newServiceAccount68 struct {
bun.BaseModel `bun:"table:service_account"`
types.Identifiable
CreatedAt time.Time `bun:"created_at"`
UpdatedAt time.Time `bun:"updated_at"`
Name string `bun:"name"`
Email string `bun:"email"`
Status string `bun:"status"`
OrgID string `bun:"org_id"`
}
type newServiceAccountRole68 struct {
bun.BaseModel `bun:"table:service_account_role"`
types.Identifiable
CreatedAt time.Time `bun:"created_at"`
UpdatedAt time.Time `bun:"updated_at"`
ServiceAccountID string `bun:"service_account_id"`
RoleID string `bun:"role_id"`
}
type newFactorAPIKey68 struct {
bun.BaseModel `bun:"table:factor_api_key"`
types.Identifiable
CreatedAt time.Time `bun:"created_at"`
UpdatedAt time.Time `bun:"updated_at"`
Name string `bun:"name"`
Key string `bun:"key"`
ExpiresAt uint64 `bun:"expires_at"`
LastObservedAt time.Time `bun:"last_observed_at"`
ServiceAccountID string `bun:"service_account_id"`
}
type deprecateAPIKey struct {
sqlstore sqlstore.SQLStore
sqlschema sqlschema.SQLSchema
}
func NewDeprecateAPIKeyFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("deprecate_api_key"), func(_ context.Context, _ factory.ProviderSettings, c Config) (SQLMigration, error) {
return &deprecateAPIKey{
sqlstore: sqlstore,
sqlschema: sqlschema,
}, nil
})
}
func (migration *deprecateAPIKey) Register(migrations *migrate.Migrations) error {
err := migrations.Register(migration.Up, migration.Down)
if err != nil {
return err
}
return nil
}
func (migration *deprecateAPIKey) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
// get all the api keys
oldKeys := make([]*oldFactorAPIKey68, 0)
err = tx.NewSelect().Model(&oldKeys).Where("revoked = ?", false).Scan(ctx)
if err != nil && err != sql.ErrNoRows {
return err
}
// get all the unique users
userIDs := make(map[string]struct{})
for _, key := range oldKeys {
userIDs[key.UserID] = struct{}{}
}
userIDList := make([]string, 0, len(userIDs))
for uid := range userIDs {
userIDList = append(userIDList, uid)
}
userMap := make(map[string]*oldUser68)
if len(userIDList) > 0 {
users := make([]*oldUser68, 0)
err = tx.NewSelect().Model(&users).Where("id IN (?)", bun.In(userIDList)).Scan(ctx)
if err != nil && err != sql.ErrNoRows {
return err
}
for _, u := range users {
userMap[u.ID.String()] = u
}
}
// get the role ids
type orgRoleKey struct {
OrgID string
RoleName string
}
roleMap := make(map[orgRoleKey]string)
if len(userMap) > 0 {
orgIDs := make(map[string]struct{})
for _, u := range userMap {
orgIDs[u.OrgID] = struct{}{}
}
orgIDList := make([]string, 0, len(orgIDs))
for oid := range orgIDs {
orgIDList = append(orgIDList, oid)
}
roles := make([]*oldRole68, 0)
err = tx.NewSelect().Model(&roles).Where("org_id IN (?)", bun.In(orgIDList)).Scan(ctx)
if err != nil && err != sql.ErrNoRows {
return err
}
for _, r := range roles {
roleMap[orgRoleKey{OrgID: r.OrgID, RoleName: r.Name}] = r.ID.String()
}
}
serviceAccounts := make([]*newServiceAccount68, 0)
serviceAccountRoles := make([]*newServiceAccountRole68, 0)
newKeys := make([]*newFactorAPIKey68, 0)
now := time.Now()
for _, oldKey := range oldKeys {
user, ok := userMap[oldKey.UserID]
if !ok {
// this should never happen as a key cannot exist without a user
continue
}
saID := valuer.GenerateUUID()
serviceAccounts = append(serviceAccounts, &newServiceAccount68{
Identifiable: types.Identifiable{ID: saID},
CreatedAt: now,
UpdatedAt: now,
Name: oldKey.Name,
Email: user.Email,
Status: "active",
OrgID: user.OrgID,
})
managedRoleName, ok := authtypes.ExistingRoleToSigNozManagedRoleMap[authtypes.LegacyRole(oldKey.Role)]
if !ok {
managedRoleName = authtypes.SigNozViewerRoleName
}
roleID, ok := roleMap[orgRoleKey{OrgID: user.OrgID, RoleName: managedRoleName}]
if ok {
serviceAccountRoles = append(serviceAccountRoles, &newServiceAccountRole68{
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
CreatedAt: now,
UpdatedAt: now,
ServiceAccountID: saID.String(),
RoleID: roleID,
})
}
var expiresAtUnix uint64
if !oldKey.ExpiresAt.IsZero() && oldKey.ExpiresAt.Unix() > 0 {
expiresAtUnix = uint64(oldKey.ExpiresAt.Unix())
}
// Convert last_used to last_observed_at.
lastObservedAt := oldKey.LastUsed
if lastObservedAt.IsZero() {
lastObservedAt = oldKey.CreatedAt
}
newKeys = append(newKeys, &newFactorAPIKey68{
Identifiable: oldKey.Identifiable,
CreatedAt: oldKey.CreatedAt,
UpdatedAt: oldKey.UpdatedAt,
Name: oldKey.Name,
Key: oldKey.Token,
ExpiresAt: expiresAtUnix,
LastObservedAt: lastObservedAt,
ServiceAccountID: saID.String(),
})
}
if len(serviceAccounts) > 0 {
if _, err := tx.NewInsert().Model(&serviceAccounts).Exec(ctx); err != nil {
return err
}
}
if len(serviceAccountRoles) > 0 {
if _, err := tx.NewInsert().Model(&serviceAccountRoles).Exec(ctx); err != nil {
return err
}
}
sqls := [][]byte{}
deprecatedFactorAPIKey, _, err := migration.sqlschema.GetTable(ctx, sqlschema.TableName("factor_api_key"))
if err != nil {
return err
}
dropTableSQLS := migration.sqlschema.Operator().DropTable(deprecatedFactorAPIKey)
sqls = append(sqls, dropTableSQLS...)
tableSQLs := migration.sqlschema.Operator().CreateTable(&sqlschema.Table{
Name: "factor_api_key",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "name", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "key", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "created_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "updated_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "expires_at", DataType: sqlschema.DataTypeInteger, Nullable: false},
{Name: "last_observed_at", DataType: sqlschema.DataTypeTimestamp, Nullable: false},
{Name: "service_account_id", DataType: sqlschema.DataTypeText, Nullable: false},
},
PrimaryKeyConstraint: &sqlschema.PrimaryKeyConstraint{
ColumnNames: []sqlschema.ColumnName{"id"},
},
ForeignKeyConstraints: []*sqlschema.ForeignKeyConstraint{
{
ReferencingColumnName: sqlschema.ColumnName("service_account_id"),
ReferencedTableName: sqlschema.TableName("service_account"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, tableSQLs...)
indexSQLs := migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "factor_api_key", ColumnNames: []sqlschema.ColumnName{"key"}})
sqls = append(sqls, indexSQLs...)
indexSQLs = migration.sqlschema.Operator().CreateIndex(&sqlschema.UniqueIndex{TableName: "factor_api_key", ColumnNames: []sqlschema.ColumnName{"name", "service_account_id"}})
sqls = append(sqls, indexSQLs...)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
if len(newKeys) > 0 {
if _, err := tx.NewInsert().Model(&newKeys).Exec(ctx); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *deprecateAPIKey) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,148 @@
package sqlmigration
import (
"context"
"database/sql"
"time"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/oklog/ulid/v2"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate"
)
type addServiceAccountAuthz struct {
sqlstore sqlstore.SQLStore
}
func NewServiceAccountAuthzactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_service_account_authz"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
return &addServiceAccountAuthz{sqlstore: sqlstore}, nil
})
}
func (migration *addServiceAccountAuthz) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addServiceAccountAuthz) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
var storeID string
err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID)
if err != nil {
return err
}
type saRoleTuple struct {
ServiceAccountID string
OrgID string
RoleName string
}
rows, err := tx.QueryContext(ctx, `
SELECT sa.id, sa.org_id, r.name
FROM service_account sa
JOIN service_account_role sar ON sar.service_account_id = sa.id
JOIN role r ON r.id = sar.role_id
`)
if err != nil && err != sql.ErrNoRows {
return err
}
defer rows.Close()
tuples := make([]saRoleTuple, 0)
for rows.Next() {
var t saRoleTuple
if err := rows.Scan(&t.ServiceAccountID, &t.OrgID, &t.RoleName); err != nil {
return err
}
tuples = append(tuples, t)
}
for _, t := range tuples {
entropy := ulid.DefaultEntropy()
now := time.Now().UTC()
tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String()
objectID := "organization/" + t.OrgID + "/role/" + t.RoleName
saUserID := "organization/" + t.OrgID + "/serviceaccount/" + t.ServiceAccountID
if migration.sqlstore.BunDB().Dialect().Name() == dialect.PG {
result, err := tx.ExecContext(ctx, `
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
storeID, "role", objectID, "assignee", "serviceaccount:"+saUserID, "user", tupleID, now,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
continue
}
_, err = tx.ExecContext(ctx, `
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "role", objectID, "assignee", "serviceaccount:"+saUserID, "TUPLE_OPERATION_WRITE", tupleID, now,
)
if err != nil {
return err
}
} else {
result, err := tx.ExecContext(ctx, `
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
storeID, "role", objectID, "assignee", "serviceaccount", saUserID, "", "user", tupleID, now,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
continue
}
_, err = tx.ExecContext(ctx, `
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "role", objectID, "assignee", "serviceaccount", saUserID, "", 0, tupleID, now,
)
if err != nil {
return err
}
}
}
return tx.Commit()
}
func (migration *addServiceAccountAuthz) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,195 @@
package sqlmigration
import (
"context"
"time"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
"github.com/uptrace/bun/migrate"
)
var (
userRoleToSigNozManagedRoleMap = map[string]string{
"ADMIN": "signoz-admin",
"EDITOR": "signoz-editor",
"VIEWER": "signoz-viewer",
}
)
type userRow struct {
ID string `bun:"id"`
Role string `bun:"role"`
OrgID string `bun:"org_id"`
}
type roleRow struct {
ID string `bun:"id"`
Name string `bun:"name"`
OrgID string `bun:"org_id"`
}
type orgRoleKey struct {
OrgID string
RoleName string
}
type addUserRole struct {
sqlstore sqlstore.SQLStore
sqlschema sqlschema.SQLSchema
}
type userRoleRow struct {
bun.BaseModel `bun:"table:user_role"`
types.Identifiable
CreatedAt time.Time `bun:"created_at"`
UpdatedAt time.Time `bun:"updated_at"`
UserID string `bun:"user_id"`
RoleID string `bun:"role_id"`
}
func NewAddUserRoleFactory(sqlstore sqlstore.SQLStore, sqlschema sqlschema.SQLSchema) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_user_role"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
return &addUserRole{
sqlstore: sqlstore,
sqlschema: sqlschema,
}, nil
})
}
func (migration *addUserRole) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addUserRole) 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: "user_role",
Columns: []*sqlschema.Column{
{Name: "id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "user_id", DataType: sqlschema.DataTypeText, Nullable: false},
{Name: "role_id", 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("user_id"),
ReferencedTableName: sqlschema.TableName("users"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
{
ReferencingColumnName: sqlschema.ColumnName("role_id"),
ReferencedTableName: sqlschema.TableName("role"),
ReferencedColumnName: sqlschema.ColumnName("id"),
},
},
})
sqls = append(sqls, tableSQLs...)
indexSQLs := migration.sqlschema.Operator().CreateIndex(
&sqlschema.UniqueIndex{
TableName: "user_role",
ColumnNames: []sqlschema.ColumnName{"user_id", "role_id"},
},
)
sqls = append(sqls, indexSQLs...)
for _, sql := range sqls {
if _, err := tx.ExecContext(ctx, string(sql)); err != nil {
return err
}
}
// fill the new user_role table for existing users
var users []userRow
err = tx.NewSelect().TableExpr("users").ColumnExpr("id, role, org_id").Where("status != ?", "deleted").Scan(ctx, &users)
if err != nil {
return err
}
if len(users) == 0 {
return tx.Commit()
}
orgIDs := make(map[string]struct{})
for _, u := range users {
orgIDs[u.OrgID] = struct{}{}
}
orgIDList := make([]string, 0, len(orgIDs))
for oid := range orgIDs {
orgIDList = append(orgIDList, oid)
}
var roles []roleRow
err = tx.NewSelect().TableExpr("role").ColumnExpr("id, name, org_id").Where("org_id IN (?)", bun.In(orgIDList)).Scan(ctx, &roles)
if err != nil {
return err
}
roleMap := make(map[orgRoleKey]string)
for _, r := range roles {
roleMap[orgRoleKey{OrgID: r.OrgID, RoleName: r.Name}] = r.ID
}
now := time.Now()
userRoles := make([]*userRoleRow, 0, len(users))
for _, u := range users {
managedRoleName, ok := userRoleToSigNozManagedRoleMap[u.Role]
if !ok {
managedRoleName = "signoz-viewer" // fallback
}
roleID, ok := roleMap[orgRoleKey{OrgID: u.OrgID, RoleName: managedRoleName}]
if !ok {
continue // user needs to get access again
}
userRoles = append(userRoles, &userRoleRow{
Identifiable: types.Identifiable{ID: valuer.GenerateUUID()},
UserID: u.ID,
RoleID: roleID,
CreatedAt: now,
UpdatedAt: now,
})
}
if len(userRoles) > 0 {
if _, err := tx.NewInsert().Model(&userRoles).Exec(ctx); err != nil {
return err
}
}
if err := tx.Commit(); err != nil {
return err
}
return nil
}
func (migration *addUserRole) Down(ctx context.Context, db *bun.DB) error {
return nil
}

View File

@@ -0,0 +1,149 @@
package sqlmigration
import (
"context"
"database/sql"
"time"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/oklog/ulid/v2"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
"github.com/uptrace/bun/migrate"
)
type addUserRoleAuthz struct {
sqlstore sqlstore.SQLStore
}
func NewAddUserRoleAuthzFactory(sqlstore sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
return factory.NewProviderFactory(factory.MustNewName("add_user_role_authz"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
return &addUserRoleAuthz{sqlstore: sqlstore}, nil
})
}
func (migration *addUserRoleAuthz) Register(migrations *migrate.Migrations) error {
if err := migrations.Register(migration.Up, migration.Down); err != nil {
return err
}
return nil
}
func (migration *addUserRoleAuthz) Up(ctx context.Context, db *bun.DB) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback()
}()
var storeID string
err = tx.QueryRowContext(ctx, `SELECT id FROM store WHERE name = ? LIMIT 1`, "signoz").Scan(&storeID)
if err != nil {
return err
}
type userRoleTuple struct {
UserID string
OrgID string
RoleName string
}
rows, err := tx.QueryContext(ctx, `
SELECT u.id, u.org_id, r.name
FROM users u
JOIN user_role ur ON ur.user_id = u.id
JOIN role r ON r.id = ur.role_id
WHERE u.status != 'deleted'
`)
if err != nil && err != sql.ErrNoRows {
return err
}
defer rows.Close()
tuples := make([]userRoleTuple, 0)
for rows.Next() {
var t userRoleTuple
if err := rows.Scan(&t.UserID, &t.OrgID, &t.RoleName); err != nil {
return err
}
tuples = append(tuples, t)
}
for _, t := range tuples {
entropy := ulid.DefaultEntropy()
now := time.Now().UTC()
tupleID := ulid.MustNew(ulid.Timestamp(now), entropy).String()
objectID := "organization/" + t.OrgID + "/role/" + t.RoleName
userID := "organization/" + t.OrgID + "/user/" + t.UserID
if migration.sqlstore.BunDB().Dialect().Name() == dialect.PG {
result, err := tx.ExecContext(ctx, `
INSERT INTO tuple (store, object_type, object_id, relation, _user, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, _user) DO NOTHING`,
storeID, "role", objectID, "assignee", "user:"+userID, "user", tupleID, now,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
continue
}
_, err = tx.ExecContext(ctx, `
INSERT INTO changelog (store, object_type, object_id, relation, _user, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "role", objectID, "assignee", "user:"+userID, "TUPLE_OPERATION_WRITE", tupleID, now,
)
if err != nil {
return err
}
} else {
result, err := tx.ExecContext(ctx, `
INSERT INTO tuple (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, user_type, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation) DO NOTHING`,
storeID, "role", objectID, "assignee", "user", userID, "", "user", tupleID, now,
)
if err != nil {
return err
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
continue
}
_, err = tx.ExecContext(ctx, `
INSERT INTO changelog (store, object_type, object_id, relation, user_object_type, user_object_id, user_relation, operation, ulid, inserted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT (store, ulid, object_type) DO NOTHING`,
storeID, "role", objectID, "assignee", "user", userID, "", 0, tupleID, now,
)
if err != nil {
return err
}
}
}
return tx.Commit()
}
func (migration *addUserRoleAuthz) Down(context.Context, *bun.DB) error {
return nil
}

View File

@@ -13,9 +13,9 @@ import (
"github.com/SigNoz/signoz/pkg/statsreporter"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
"github.com/SigNoz/signoz/pkg/types/usertypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/version"
"go.opentelemetry.io/otel/attribute"
@@ -175,7 +175,7 @@ func (provider *provider) Report(ctx context.Context) error {
}
for _, user := range users {
traits := types.NewTraitsFromUser(user)
traits := usertypes.NewTraitsFromStorableUser(user)
if maxLastObservedAt, ok := maxLastObservedAtPerUserID[user.ID]; ok {
traits["auth_token.last_observed_at.max.time"] = maxLastObservedAt.UTC()
traits["auth_token.last_observed_at.max.time_unix"] = maxLastObservedAt.Unix()

View File

@@ -1928,37 +1928,3 @@ func (t *telemetryMetaStore) GetFirstSeenFromMetricMetadata(ctx context.Context,
return result, nil
}
func (t *telemetryMetaStore) FetchLastSeenInfoMulti(ctx context.Context, metricNames ...string) (map[string]int64, error) {
sb := sqlbuilder.Select(
"metric_name",
"max(unix_milli)",
).
From(t.metricsDBName + "." + telemetrymetrics.TimeseriesV4TableName)
sb.Where(sb.In("metric_name", metricNames))
sb.GroupBy("metric_name")
query, args := sb.BuildWithFlavor(sqlbuilder.ClickHouse)
t.logger.DebugContext(ctx, "fetching metric last seen timestamp", "query", query, "args", args)
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, args...)
if err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to fetch metric last seen info")
}
defer rows.Close()
lastSeenInfo := make(map[string]int64)
for rows.Next() {
var metricName string
var unix_milli int64
if err := rows.Scan(&metricName, &unix_milli); err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to scan last seen info result")
}
lastSeenInfo[metricName] = unix_milli
}
if err := rows.Err(); err != nil {
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "error iterating over metrics temporality rows")
}
return lastSeenInfo, nil
}

View File

@@ -17,6 +17,9 @@ type Config struct {
// Config for the JWT tokenizer.
JWT JWTConfig `mapstructure:"jwt"`
// Config for the serviceAccount tokenizer.
ServiceAccount ServiceAccountConfig `mapstructure:"service_account"`
// Rotation config
Rotation RotationConfig `mapstructure:"rotation"`
@@ -37,6 +40,11 @@ type JWTConfig struct {
Secret string `mapstructure:"secret"`
}
type ServiceAccountConfig struct {
// GC config
GC GCConfig `mapstructure:"gc"`
}
type GCConfig struct {
// The interval to perform garbage collection.
Interval time.Duration `mapstructure:"interval"`
@@ -81,6 +89,11 @@ func newConfig() factory.Config {
JWT: JWTConfig{
Secret: "",
},
ServiceAccount: ServiceAccountConfig{
GC: GCConfig{
Interval: 1 * time.Hour, // 1 hour
},
},
Rotation: RotationConfig{
Interval: 30 * time.Minute, // 30 minutes
Duration: 60 * time.Second, // 60 seconds

View File

@@ -2,7 +2,6 @@ package jwttokenizer
import (
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
"github.com/golang-jwt/jwt/v5"
)
@@ -10,21 +9,15 @@ var _ jwt.ClaimsValidator = (*Claims)(nil)
type Claims struct {
jwt.RegisteredClaims
UserID string `json:"id"`
Email string `json:"email"`
Role types.Role `json:"role"`
OrgID string `json:"orgId"`
UserID string `json:"id"`
Email string `json:"email"`
OrgID string `json:"orgId"`
}
func (c *Claims) Validate() error {
if c.UserID == "" {
return errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "id is required")
}
// The problem is that when the "role" field is missing entirely from the JSON (as opposed to being present but empty), the UnmarshalJSON method for Role isn't called at all.
// The JSON decoder just sets the Role field to its zero value ("").
if c.Role == "" {
return errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "role is required")
}
if c.OrgID == "" {
return errors.New(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "orgId is required")

View File

@@ -76,7 +76,6 @@ func (provider *provider) Start(ctx context.Context) error {
func (provider *provider) CreateToken(ctx context.Context, identity *authtypes.Identity, meta map[string]string) (*authtypes.Token, error) {
accessTokenClaims := Claims{
UserID: identity.UserID.String(),
Role: identity.Role,
Email: identity.Email.String(),
OrgID: identity.OrgID.String(),
RegisteredClaims: jwt.RegisteredClaims{
@@ -92,7 +91,6 @@ func (provider *provider) CreateToken(ctx context.Context, identity *authtypes.I
refreshTokenClaims := Claims{
UserID: identity.UserID.String(),
Role: identity.Role,
Email: identity.Email.String(),
OrgID: identity.OrgID.String(),
RegisteredClaims: jwt.RegisteredClaims{
@@ -115,17 +113,7 @@ func (provider *provider) GetIdentity(ctx context.Context, accessToken string) (
return nil, err
}
// check claimed role
identity, err := provider.getOrSetIdentity(ctx, emptyOrgID, valuer.MustNewUUID(claims.UserID))
if err != nil {
return nil, err
}
if identity.Role != claims.Role {
return nil, errors.Newf(errors.TypeUnauthenticated, errors.CodeUnauthenticated, "claim role mismatch")
}
return authtypes.NewIdentity(valuer.MustNewUUID(claims.UserID), valuer.MustNewUUID(claims.OrgID), valuer.MustNewEmail(claims.Email), claims.Role), nil
return authtypes.NewIdentity(valuer.MustNewUUID(claims.UserID), valuer.UUID{}, authtypes.PrincipalUser, valuer.MustNewUUID(claims.OrgID), valuer.MustNewEmail(claims.Email)), nil
}
func (provider *provider) DeleteToken(ctx context.Context, accessToken string) error {

View File

@@ -14,7 +14,6 @@ import (
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
"github.com/SigNoz/signoz/pkg/tokenizer"
"github.com/SigNoz/signoz/pkg/tokenizer/tokenizerstore/sqltokenizerstore"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/stretchr/testify/assert"
@@ -62,7 +61,6 @@ func TestLastObservedAt_Concurrent(t *testing.T) {
&authtypes.Identity{
UserID: valuer.GenerateUUID(),
OrgID: orgID,
Role: types.RoleAdmin,
Email: valuer.MustNewEmail("test@test.com"),
},
map[string]string{},
@@ -74,7 +72,6 @@ func TestLastObservedAt_Concurrent(t *testing.T) {
&authtypes.Identity{
UserID: valuer.GenerateUUID(),
OrgID: orgID,
Role: types.RoleAdmin,
Email: valuer.MustNewEmail("test@test.com"),
},
map[string]string{},

Some files were not shown because too many files have changed in this diff Show More