mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-13 03:49:00 +08:00
feat(user): support sso and api key (#8030)
* feat(user): support sso and api key * feat(user): remove ee references from pkg * feat(user): remove ee references from pkg * feat(user): related client changes * feat(user): remove the sso available check * feat(user): fix go tests * feat(user): move the middleware from ee to pkg * feat(user): some more error code cleanup * feat(user): some more error code cleanup * feat(user): skip flaky UI tests * feat(user): some more error code cleanup
This commit is contained in:
parent
2ba693f040
commit
cffa511cf3
@ -1,416 +0,0 @@
|
||||
package impluser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// EnterpriseHandler embeds the base handler implementation
|
||||
type Handler struct {
|
||||
user.Handler // Embed the base handler interface
|
||||
module user.Module
|
||||
}
|
||||
|
||||
func NewHandler(module user.Module) user.Handler {
|
||||
baseHandler := impluser.NewHandler(module)
|
||||
return &Handler{
|
||||
Handler: baseHandler,
|
||||
module: module,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var req types.PostableLoginRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if req.RefreshToken == "" {
|
||||
// the EE handler wrapper passes the feature flag value in context
|
||||
ssoAvailable, ok := ctx.Value(types.SSOAvailable).(bool)
|
||||
if !ok {
|
||||
render.Error(w, errors.New(errors.TypeInternal, errors.CodeInternal, "failed to retrieve SSO availability"))
|
||||
return
|
||||
}
|
||||
|
||||
if ssoAvailable {
|
||||
_, err := h.module.CanUsePassword(ctx, req.Email)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user, err := h.module.GetAuthenticatedUser(ctx, req.OrgID, req.Email, req.Password, req.RefreshToken)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
jwt, err := h.module.GetJWTForUser(ctx, user)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettableLoginResponse := &types.GettableLoginResponse{
|
||||
GettableUserJwt: jwt,
|
||||
UserID: user.ID.String(),
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, gettableLoginResponse)
|
||||
}
|
||||
|
||||
// Override only the methods you need with enterprise-specific implementations
|
||||
func (h *Handler) LoginPrecheck(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// assume user is valid unless proven otherwise and assign default values for rest of the fields
|
||||
|
||||
email := r.URL.Query().Get("email")
|
||||
sourceUrl := r.URL.Query().Get("ref")
|
||||
orgID := r.URL.Query().Get("orgID")
|
||||
|
||||
resp, err := h.module.LoginPrecheck(ctx, orgID, email, sourceUrl)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, resp)
|
||||
|
||||
}
|
||||
|
||||
func (h *Handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := new(types.PostableAcceptInvite)
|
||||
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||
render.Error(w, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to decode user"))
|
||||
return
|
||||
}
|
||||
|
||||
// get invite object
|
||||
invite, err := h.module.GetInviteByToken(ctx, req.InviteToken)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgDomain, err := h.module.GetAuthDomainByEmail(ctx, invite.Email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
precheckResp := &types.GettableLoginPrecheck{
|
||||
SSO: false,
|
||||
IsUser: false,
|
||||
}
|
||||
|
||||
if invite.Name == "" && req.DisplayName != "" {
|
||||
invite.Name = req.DisplayName
|
||||
}
|
||||
|
||||
user, err := types.NewUser(invite.Name, invite.Email, invite.Role, invite.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if orgDomain != nil && orgDomain.SsoEnabled {
|
||||
// sso is enabled, create user and respond precheck data
|
||||
err = h.module.CreateUser(ctx, user)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// check if sso is enforced for the org
|
||||
precheckResp, err = h.module.LoginPrecheck(ctx, invite.OrgID, user.Email, req.SourceURL)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
password, err := types.NewFactorPassword(req.Password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.module.CreateUserWithPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
precheckResp.IsUser = true
|
||||
}
|
||||
|
||||
// delete the invite
|
||||
if err := h.module.DeleteInvite(ctx, invite.OrgID, invite.ID); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, precheckResp)
|
||||
}
|
||||
|
||||
func (h *Handler) GetInvite(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
token := mux.Vars(r)["token"]
|
||||
sourceUrl := r.URL.Query().Get("ref")
|
||||
invite, err := h.module.GetInviteByToken(ctx, token)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// precheck the user
|
||||
precheckResp, err := h.module.LoginPrecheck(ctx, invite.OrgID, invite.Email, sourceUrl)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettableInvite := &types.GettableEEInvite{
|
||||
GettableInvite: *invite,
|
||||
PreCheck: precheckResp,
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, gettableInvite)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
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,
|
||||
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, 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
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
apiKeys, err := h.module.ListAPIKeys(ctx, 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
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
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, orgID, id)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user
|
||||
createdByUser, err := h.module.GetUserByID(ctx, orgID.String(), existingAPIKey.UserID.String())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email)) {
|
||||
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, 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
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
//get the API Key
|
||||
existingAPIKey, err := h.module.GetAPIKey(ctx, orgID, id)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user
|
||||
createdByUser, err := h.module.GetUserByID(ctx, orgID.String(), existingAPIKey.UserID.String())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email)) {
|
||||
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, userID); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
package impluser
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
baseimpl "github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
// EnterpriseModule embeds the base module implementation
|
||||
type Module struct {
|
||||
user.Module // Embed the base module implementation
|
||||
store types.UserStore
|
||||
}
|
||||
|
||||
func NewModule(store types.UserStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings) user.Module {
|
||||
baseModule := baseimpl.NewModule(store, jwt, emailing, providerSettings)
|
||||
return &Module{
|
||||
Module: baseModule,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Module) createUserForSAMLRequest(ctx context.Context, email string) (*types.User, error) {
|
||||
// get auth domain from email domain
|
||||
_, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get name from email
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) < 2 {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid email format")
|
||||
}
|
||||
name := parts[0]
|
||||
|
||||
defaultOrgID, err := m.store.GetDefaultOrgID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := types.NewUser(name, email, types.RoleViewer.String(), defaultOrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.CreateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (m *Module) PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (string, error) {
|
||||
users, err := m.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
user := &types.User{}
|
||||
|
||||
if len(users) == 0 {
|
||||
newUser, err := m.createUserForSAMLRequest(ctx, email)
|
||||
user = newUser
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
user = &users[0].User
|
||||
}
|
||||
|
||||
tokenStore, err := m.GetJWTForUser(ctx, user)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||||
redirectUri,
|
||||
tokenStore.AccessJwt,
|
||||
user.ID,
|
||||
tokenStore.RefreshJwt), nil
|
||||
}
|
||||
|
||||
func (m *Module) CanUsePassword(ctx context.Context, email string) (bool, error) {
|
||||
domain, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if domain != nil && domain.SsoEnabled {
|
||||
// sso is enabled, check if the user has admin role
|
||||
users, err := m.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return false, errors.New(errors.TypeNotFound, errors.CodeNotFound, "user not found")
|
||||
}
|
||||
|
||||
if users[0].Role != types.RoleAdmin.String() {
|
||||
return false, errors.New(errors.TypeForbidden, errors.CodeForbidden, "auth method not supported")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Module) LoginPrecheck(ctx context.Context, orgID, email, sourceUrl string) (*types.GettableLoginPrecheck, error) {
|
||||
resp := &types.GettableLoginPrecheck{IsUser: true, CanSelfRegister: false}
|
||||
|
||||
// check if email is a valid user
|
||||
users, err := m.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
resp.IsUser = false
|
||||
}
|
||||
|
||||
// give them an option to select an org
|
||||
if orgID == "" && len(users) > 1 {
|
||||
resp.SelectOrg = true
|
||||
resp.Orgs = make([]string, len(users))
|
||||
for i, user := range users {
|
||||
resp.Orgs[i] = user.OrgID
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// select the user with the corresponding orgID
|
||||
if len(users) > 1 {
|
||||
found := false
|
||||
for _, tuser := range users {
|
||||
if tuser.OrgID == orgID {
|
||||
// user = tuser
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
resp.IsUser = false
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// the EE handler wrapper passes the feature flag value in context
|
||||
ssoAvailable, ok := ctx.Value(types.SSOAvailable).(bool)
|
||||
if !ok {
|
||||
return nil, errors.New(errors.TypeInternal, errors.CodeInternal, "failed to retrieve SSO availability")
|
||||
}
|
||||
|
||||
if ssoAvailable {
|
||||
|
||||
// TODO(Nitya): in multitenancy this should use orgId as well.
|
||||
orgDomain, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if orgDomain != nil && orgDomain.SsoEnabled {
|
||||
// this is to allow self registration
|
||||
resp.IsUser = true
|
||||
|
||||
// saml is enabled for this domain, lets prepare sso url
|
||||
if sourceUrl == "" {
|
||||
sourceUrl = constants.GetDefaultSiteURL()
|
||||
}
|
||||
|
||||
// parse source url that generated the login request
|
||||
var err error
|
||||
escapedUrl, _ := url.QueryUnescape(sourceUrl)
|
||||
siteUrl, err := url.Parse(escapedUrl)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse referer")
|
||||
}
|
||||
|
||||
// build Idp URL that will authenticat the user
|
||||
// the front-end will redirect user to this url
|
||||
resp.SSOUrl, err = orgDomain.BuildSsoUrl(siteUrl)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeInternal, errors.CodeInternal, "failed to prepare saml request for domain")
|
||||
}
|
||||
|
||||
// set SSO to true, as the url is generated correctly
|
||||
resp.SSO = true
|
||||
}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetAuthDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, error) {
|
||||
|
||||
if email == "" {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
|
||||
}
|
||||
|
||||
components := strings.Split(email, "@")
|
||||
if len(components) < 2 {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid email format")
|
||||
}
|
||||
|
||||
domain, err := m.store.GetDomainByName(ctx, components[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gettableDomain := &types.GettableOrgDomain{StorableOrgDomain: *domain}
|
||||
if err := gettableDomain.LoadConfig(domain.Data); err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to load domain config")
|
||||
}
|
||||
return gettableDomain, 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)
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package impluser
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
baseimpl "github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
*baseimpl.Store
|
||||
sqlstore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) types.UserStore {
|
||||
baseStore := baseimpl.NewStore(sqlstore).(*baseimpl.Store)
|
||||
return &store{
|
||||
Store: baseStore,
|
||||
sqlstore: sqlstore,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) GetDomainByName(ctx context.Context, name string) (*types.StorableOrgDomain, error) {
|
||||
domain := new(types.StorableOrgDomain)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
Model(domain).
|
||||
Where("name = ?", name).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeNotFound, errors.CodeNotFound, "failed to get domain from name")
|
||||
}
|
||||
return domain, nil
|
||||
}
|
@ -7,16 +7,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/interfaces"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/apis/fields"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
@ -24,18 +20,14 @@ import (
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/gorilla/mux"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type APIHandlerOptions struct {
|
||||
DataConnector interfaces.DataConnector
|
||||
PreferSpanMetrics bool
|
||||
AppDao dao.ModelDao
|
||||
RulesManager *rules.Manager
|
||||
UsageManager *usage.Manager
|
||||
IntegrationsController *integrations.Controller
|
||||
@ -90,10 +82,6 @@ func (ah *APIHandler) UM() *usage.Manager {
|
||||
return ah.opts.UsageManager
|
||||
}
|
||||
|
||||
func (ah *APIHandler) AppDao() dao.ModelDao {
|
||||
return ah.opts.AppDao
|
||||
}
|
||||
|
||||
func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
||||
return ah.opts.Gateway
|
||||
}
|
||||
@ -110,30 +98,17 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
// routes available only in ee version
|
||||
|
||||
router.HandleFunc("/api/v1/featureFlags", am.OpenAccess(ah.getFeatureFlags)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/loginPrecheck", am.OpenAccess(ah.loginPrecheck)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/loginPrecheck", am.OpenAccess(ah.Signoz.Handlers.User.LoginPrecheck)).Methods(http.MethodGet)
|
||||
|
||||
// invite
|
||||
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.getInvite)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/invite/accept", am.OpenAccess(ah.acceptInvite)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.Signoz.Handlers.User.GetInvite)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/invite/accept", am.OpenAccess(ah.Signoz.Handlers.User.AcceptInvite)).Methods(http.MethodPost)
|
||||
|
||||
// paid plans specific routes
|
||||
router.HandleFunc("/api/v1/complete/saml", am.OpenAccess(ah.receiveSAML)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/complete/google", am.OpenAccess(ah.receiveGoogleAuth)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/orgs/{orgId}/domains", am.AdminAccess(ah.listDomainsByOrg)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/domains", am.AdminAccess(ah.postDomain)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(ah.putDomain)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(ah.deleteDomain)).Methods(http.MethodDelete)
|
||||
|
||||
// base overrides
|
||||
router.HandleFunc("/api/v1/version", am.OpenAccess(ah.getVersion)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
|
||||
|
||||
// PAT APIs
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.Signoz.Handlers.User.CreateAPIKey)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.Signoz.Handlers.User.ListAPIKeys)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.UpdateAPIKey)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.RevokeAPIKey)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.LicensingAPI.Checkout)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||
@ -157,48 +132,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
|
||||
}
|
||||
|
||||
// TODO(nitya): remove this once we know how to get the FF's
|
||||
func (ah *APIHandler) updateRequestContext(_ http.ResponseWriter, r *http.Request) (*http.Request, error) {
|
||||
ssoAvailable := true
|
||||
err := ah.Signoz.Licensing.CheckFeature(r.Context(), licensetypes.SSO)
|
||||
if err != nil && errors.Asc(err, licensing.ErrCodeFeatureUnavailable) {
|
||||
ssoAvailable = false
|
||||
} else if err != nil {
|
||||
zap.L().Error("feature check failed", zap.String("featureKey", licensetypes.SSO), zap.Error(err))
|
||||
return r, errors.New(errors.TypeInternal, errors.CodeInternal, "error checking SSO feature")
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), types.SSOAvailable, ssoAvailable)
|
||||
return r.WithContext(ctx), nil
|
||||
}
|
||||
|
||||
func (ah *APIHandler) loginPrecheck(w http.ResponseWriter, r *http.Request) {
|
||||
r, err := ah.updateRequestContext(w, r)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
ah.Signoz.Handlers.User.LoginPrecheck(w, r)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) acceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
r, err := ah.updateRequestContext(w, r)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
ah.Signoz.Handlers.User.AcceptInvite(w, r)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
||||
r, err := ah.updateRequestContext(w, r)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
ah.Signoz.Handlers.User.GetInvite(w, r)
|
||||
|
||||
}
|
||||
|
||||
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
|
||||
ah.APIHandler.RegisterCloudIntegrationsRoutes(router, am)
|
||||
|
@ -3,40 +3,18 @@ package api
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
func parseRequest(r *http.Request, req interface{}) error {
|
||||
defer r.Body.Close()
|
||||
requestBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(requestBody, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
// loginUser overrides base handler and considers SSO case.
|
||||
func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
|
||||
r, err := ah.updateRequestContext(w, r)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
ah.Signoz.Handlers.User.Login(w, r)
|
||||
}
|
||||
|
||||
func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string) {
|
||||
ssoError := []byte("Login failed. Please contact your system administrator")
|
||||
dst := make([]byte, base64.StdEncoding.EncodedLen(len(ssoError)))
|
||||
@ -45,85 +23,31 @@ func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string)
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectURL, string(dst)), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// receiveGoogleAuth completes google OAuth response and forwards a request
|
||||
// to front-end to sign user in
|
||||
func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request) {
|
||||
redirectUri := constants.GetDefaultSiteURL()
|
||||
ctx := context.Background()
|
||||
|
||||
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
||||
zap.L().Error("[receiveGoogleAuth] sso requested but feature unavailable in org domain")
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to login with google auth", zap.String("error", errType), zap.String("error_description", q.Get("error_description")))
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to login through SSO "), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
relayState := q.Get("state")
|
||||
zap.L().Debug("[receiveGoogleAuth] relay state received", zap.String("state", relayState))
|
||||
|
||||
parsedState, err := url.Parse(relayState)
|
||||
if err != nil || relayState == "" {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
// upgrade redirect url from the relay state for better accuracy
|
||||
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||
|
||||
// fetch domain by parsing relay state.
|
||||
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
||||
if err != nil {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
// now that we have domain, use domain to fetch sso settings.
|
||||
// prepare google callback handler using parsedState -
|
||||
// which contains redirect URL (front-end endpoint)
|
||||
callbackHandler, err := domain.PrepareGoogleOAuthProvider(parsedState)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to prepare google oauth provider", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
identity, err := callbackHandler.HandleCallback(r)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to process HandleCallback ", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
nextPage, err := ah.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, identity.Email, ah.opts.JWT)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, nextPage, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// receiveSAML completes a SAML request and gets user logged in
|
||||
func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// this is the source url that initiated the login request
|
||||
redirectUri := constants.GetDefaultSiteURL()
|
||||
ctx := context.Background()
|
||||
|
||||
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
||||
_, err = ah.Signoz.Licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveSAML] sso requested but feature unavailable in org domain")
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
err := r.ParseForm()
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveSAML] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
@ -146,7 +70,7 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
||||
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||
|
||||
// fetch domain by parsing relay state.
|
||||
domain, err := ah.AppDao().GetDomainFromSsoResponse(ctx, parsedState)
|
||||
domain, err := ah.Signoz.Modules.User.GetDomainFromSsoResponse(ctx, parsedState)
|
||||
if err != nil {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
|
@ -1,91 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func (ah *APIHandler) listDomainsByOrg(w http.ResponseWriter, r *http.Request) {
|
||||
orgId := mux.Vars(r)["orgId"]
|
||||
domains, apierr := ah.AppDao().ListDomains(context.Background(), orgId)
|
||||
if apierr != nil {
|
||||
RespondError(w, apierr, domains)
|
||||
return
|
||||
}
|
||||
ah.Respond(w, domains)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) postDomain(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := types.GettableOrgDomain{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ValidNew(); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
if apierr := ah.AppDao().CreateDomain(ctx, &req); apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, &req)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) putDomain(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.Background()
|
||||
|
||||
domainIdStr := mux.Vars(r)["id"]
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
req := types.GettableOrgDomain{StorableOrgDomain: types.StorableOrgDomain{ID: domainId}}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
return
|
||||
}
|
||||
req.ID = domainId
|
||||
if err := req.Valid(nil); err != nil {
|
||||
RespondError(w, model.BadRequest(err), nil)
|
||||
}
|
||||
|
||||
if apierr := ah.AppDao().UpdateDomain(ctx, &req); apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, &req)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) deleteDomain(w http.ResponseWriter, r *http.Request) {
|
||||
domainIdStr := mux.Vars(r)["id"]
|
||||
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
RespondError(w, model.BadRequest(fmt.Errorf("invalid domain id")), nil)
|
||||
return
|
||||
}
|
||||
|
||||
apierr := ah.AppDao().DeleteDomain(context.Background(), domainId)
|
||||
if apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
return
|
||||
}
|
||||
ah.Respond(w, nil)
|
||||
}
|
@ -11,11 +11,9 @@ import (
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
eemiddleware "github.com/SigNoz/signoz/ee/http/middleware"
|
||||
"github.com/SigNoz/signoz/ee/query-service/app/api"
|
||||
"github.com/SigNoz/signoz/ee/query-service/app/db"
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao/sqlite"
|
||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||
"github.com/SigNoz/signoz/ee/query-service/rules"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
@ -88,7 +86,6 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
||||
|
||||
// NewServer creates and initializes Server
|
||||
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
modelDao := sqlite.NewModelDao(serverOptions.SigNoz.SQLStore)
|
||||
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -160,7 +157,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
}
|
||||
|
||||
// start the usagemanager
|
||||
usageManager, err := usage.New(modelDao, serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.Organization)
|
||||
usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.Organization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -186,7 +183,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
apiOpts := api.APIHandlerOptions{
|
||||
DataConnector: reader,
|
||||
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
|
||||
AppDao: modelDao,
|
||||
RulesManager: rm,
|
||||
UsageManager: usageManager,
|
||||
IntegrationsController: integrationsController,
|
||||
@ -248,7 +244,7 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
|
||||
r := baseapp.NewRouter()
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(eemiddleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
@ -280,7 +276,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
|
||||
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
|
||||
|
||||
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}).Wrap)
|
||||
r.Use(eemiddleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
|
||||
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
|
||||
s.serverOptions.Config.APIServer.Timeout.Default,
|
||||
|
@ -4,10 +4,6 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultSiteURL = "https://localhost:8080"
|
||||
)
|
||||
|
||||
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
||||
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
||||
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
||||
@ -24,12 +20,3 @@ func GetOrDefaultEnv(key string, fallback string) string {
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// constant functions that override env vars
|
||||
|
||||
// GetDefaultSiteURL returns default site url, primarily
|
||||
// used to send saml request and allowing backend to
|
||||
// handle http redirect
|
||||
func GetDefaultSiteURL() string {
|
||||
return GetOrDefaultEnv("SIGNOZ_SITE_URL", DefaultSiteURL)
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type ModelDao interface {
|
||||
// auth methods
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*types.GettableOrgDomain, error)
|
||||
|
||||
// org domain (auth domains) CRUD ops
|
||||
ListDomains(ctx context.Context, orgId string) ([]types.GettableOrgDomain, basemodel.BaseApiError)
|
||||
GetDomain(ctx context.Context, id uuid.UUID) (*types.GettableOrgDomain, basemodel.BaseApiError)
|
||||
CreateDomain(ctx context.Context, d *types.GettableOrgDomain) basemodel.BaseApiError
|
||||
UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) basemodel.BaseApiError
|
||||
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
|
||||
GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError)
|
||||
}
|
@ -1,271 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// GetDomainFromSsoResponse uses relay state received from IdP to fetch
|
||||
// user domain. The domain is further used to process validity of the response.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId or domainName as query parameter.
|
||||
func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*types.GettableOrgDomain, error) {
|
||||
// derive domain id from relay state now
|
||||
var domainIdStr string
|
||||
var domainNameStr string
|
||||
var domain *types.GettableOrgDomain
|
||||
|
||||
for k, v := range relayState.Query() {
|
||||
if k == "domainId" && len(v) > 0 {
|
||||
domainIdStr = strings.Replace(v[0], ":", "-", -1)
|
||||
}
|
||||
if k == "domainName" && len(v) > 0 {
|
||||
domainNameStr = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
if domainIdStr != "" {
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to parse domainId from relay state", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to parse domainId from IdP response")
|
||||
}
|
||||
|
||||
domain, err = m.GetDomain(ctx, domainId)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to find domain from domainId received in IdP response", zap.Error(err))
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
|
||||
if domainNameStr != "" {
|
||||
|
||||
domainFromDB, err := m.GetDomainByName(ctx, domainNameStr)
|
||||
domain = domainFromDB
|
||||
if err != nil {
|
||||
zap.L().Error("failed to find domain from domainName received in IdP response", zap.Error(err))
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
if domain != nil {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find domain received in IdP response")
|
||||
}
|
||||
|
||||
// GetDomainByName returns org domain for a given domain name
|
||||
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("name = ?", name).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, model.BadRequest(fmt.Errorf("invalid domain name"))
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
domain := &types.GettableOrgDomain{StorableOrgDomain: stored}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// GetDomain returns org domain for a given domain id
|
||||
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("id = ?", id).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, model.BadRequest(fmt.Errorf("invalid domain id"))
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
domain := &types.GettableOrgDomain{StorableOrgDomain: stored}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// ListDomains gets the list of auth domains by org id
|
||||
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
domains := []types.GettableOrgDomain{}
|
||||
|
||||
stored := []types.StorableOrgDomain{}
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("org_id = ?", orgId).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return domains, nil
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
for _, s := range stored {
|
||||
domain := types.GettableOrgDomain{StorableOrgDomain: s}
|
||||
if err := domain.LoadConfig(s.Data); err != nil {
|
||||
zap.L().Error("ListDomains() failed", zap.Error(err))
|
||||
}
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// CreateDomain creates a new auth domain
|
||||
func (m *modelDao) CreateDomain(ctx context.Context, domain *types.GettableOrgDomain) basemodel.BaseApiError {
|
||||
|
||||
if domain.ID == uuid.Nil {
|
||||
domain.ID = uuid.New()
|
||||
}
|
||||
|
||||
if domain.OrgID == "" || domain.Name == "" {
|
||||
return model.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgID, Name "))
|
||||
}
|
||||
|
||||
configJson, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
zap.L().Error("failed to unmarshal domain config", zap.Error(err))
|
||||
return model.InternalError(fmt.Errorf("domain creation failed"))
|
||||
}
|
||||
|
||||
storableDomain := types.StorableOrgDomain{
|
||||
ID: domain.ID,
|
||||
Name: domain.Name,
|
||||
OrgID: domain.OrgID,
|
||||
Data: string(configJson),
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: time.Now(), UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = m.sqlStore.BunDB().NewInsert().
|
||||
Model(&storableDomain).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("failed to insert domain in db", zap.Error(err))
|
||||
return model.InternalError(fmt.Errorf("domain creation failed"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDomain updates stored config params for a domain
|
||||
func (m *modelDao) UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) basemodel.BaseApiError {
|
||||
|
||||
if domain.ID == uuid.Nil {
|
||||
zap.L().Error("domain update failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
|
||||
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||
}
|
||||
|
||||
configJson, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
zap.L().Error("domain update failed", zap.Error(err))
|
||||
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||
}
|
||||
|
||||
storableDomain := &types.StorableOrgDomain{
|
||||
ID: domain.ID,
|
||||
Name: domain.Name,
|
||||
OrgID: domain.OrgID,
|
||||
Data: string(configJson),
|
||||
TimeAuditable: types.TimeAuditable{UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = m.sqlStore.BunDB().NewUpdate().
|
||||
Model(storableDomain).
|
||||
Column("data", "updated_at").
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("domain update failed", zap.Error(err))
|
||||
return model.InternalError(fmt.Errorf("domain update failed"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDomain deletes an org domain
|
||||
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError {
|
||||
|
||||
if id == uuid.Nil {
|
||||
zap.L().Error("domain delete failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
|
||||
return model.InternalError(fmt.Errorf("domain delete failed"))
|
||||
}
|
||||
|
||||
storableDomain := &types.StorableOrgDomain{ID: id}
|
||||
_, err := m.sqlStore.BunDB().NewDelete().
|
||||
Model(storableDomain).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
zap.L().Error("domain delete failed", zap.Error(err))
|
||||
return model.InternalError(fmt.Errorf("domain delete failed"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
if email == "" {
|
||||
return nil, model.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
|
||||
}
|
||||
|
||||
components := strings.Split(email, "@")
|
||||
if len(components) < 2 {
|
||||
return nil, model.BadRequest(fmt.Errorf("invalid email address"))
|
||||
}
|
||||
|
||||
parsedDomain := components[1]
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := m.sqlStore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("name = ?", parsedDomain).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
domain := &types.GettableOrgDomain{StorableOrgDomain: stored}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
)
|
||||
|
||||
type modelDao struct {
|
||||
sqlStore sqlstore.SQLStore
|
||||
}
|
||||
|
||||
// InitDB creates and extends base model DB repository
|
||||
func NewModelDao(sqlStore sqlstore.SQLStore) *modelDao {
|
||||
return &modelDao{sqlStore: sqlStore}
|
||||
}
|
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
eeuserimpl "github.com/SigNoz/signoz/ee/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/ee/query-service/app"
|
||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||
"github.com/SigNoz/signoz/ee/zeus"
|
||||
@ -16,10 +15,8 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/config"
|
||||
"github.com/SigNoz/signoz/pkg/config/envprovider"
|
||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
pkglicensing "github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
@ -132,6 +129,7 @@ func main() {
|
||||
signoz, err := signoz.New(
|
||||
context.Background(),
|
||||
config,
|
||||
jwt,
|
||||
zeus.Config(),
|
||||
httpzeus.NewProviderFactory(),
|
||||
licensing.Config(24*time.Hour, 3),
|
||||
@ -143,12 +141,6 @@ func main() {
|
||||
signoz.NewWebProviderFactories(),
|
||||
sqlStoreFactories,
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(sqlstore sqlstore.SQLStore, emailing emailing.Emailing, providerSettings factory.ProviderSettings) user.Module {
|
||||
return eeuserimpl.NewModule(eeuserimpl.NewStore(sqlstore), jwt, emailing, providerSettings)
|
||||
},
|
||||
func(userModule user.Module) user.Handler {
|
||||
return eeuserimpl.NewHandler(userModule)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create signoz", zap.Error(err))
|
||||
|
@ -14,7 +14,6 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
@ -40,20 +39,17 @@ type Manager struct {
|
||||
|
||||
scheduler *gocron.Scheduler
|
||||
|
||||
modelDao dao.ModelDao
|
||||
|
||||
zeus zeus.Zeus
|
||||
|
||||
organizationModule organization.Module
|
||||
}
|
||||
|
||||
func New(modelDao dao.ModelDao, licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus,organizationModule organization.Module) (*Manager, error) {
|
||||
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, organizationModule organization.Module) (*Manager, error) {
|
||||
m := &Manager{
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
modelDao: modelDao,
|
||||
zeus: zeus,
|
||||
clickhouseConn: clickhouseConn,
|
||||
licenseService: licenseService,
|
||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||
zeus: zeus,
|
||||
organizationModule: organizationModule,
|
||||
}
|
||||
return m, nil
|
||||
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/deleteDomain';
|
||||
|
||||
const deleteDomain = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.delete(`/domains/${props.id}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteDomain;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/listDomain';
|
||||
|
||||
const listAllDomain = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/orgs/${props.orgId}/domains`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default listAllDomain;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/postDomain';
|
||||
|
||||
const postDomain = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/domains`, props);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default postDomain;
|
@ -1,24 +0,0 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/updateDomain';
|
||||
|
||||
const updateDomain = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/domains/${props.id}`, props);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateDomain;
|
21
frontend/src/api/v1/domains/create.ts
Normal file
21
frontend/src/api/v1/domains/create.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { AuthDomain } from 'types/api/SAML/listDomain';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/postDomain';
|
||||
|
||||
const create = async (props: Props): Promise<SuccessResponseV2<AuthDomain>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/domains`, props);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
20
frontend/src/api/v1/domains/delete.ts
Normal file
20
frontend/src/api/v1/domains/delete.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/deleteDomain';
|
||||
|
||||
const deleteDomain = async (props: Props): Promise<SuccessResponseV2<null>> => {
|
||||
try {
|
||||
const response = await axios.delete<PayloadProps>(`/domains/${props.id}`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: null,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default deleteDomain;
|
20
frontend/src/api/v1/domains/list.ts
Normal file
20
frontend/src/api/v1/domains/list.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { AuthDomain, PayloadProps } from 'types/api/SAML/listDomain';
|
||||
|
||||
const listAllDomain = async (): Promise<SuccessResponseV2<AuthDomain[]>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(`/domains`);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default listAllDomain;
|
23
frontend/src/api/v1/domains/update.ts
Normal file
23
frontend/src/api/v1/domains/update.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { AuthDomain } from 'types/api/SAML/listDomain';
|
||||
import { PayloadProps, Props } from 'types/api/SAML/updateDomain';
|
||||
|
||||
const updateDomain = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<AuthDomain>> => {
|
||||
try {
|
||||
const response = await axios.put<PayloadProps>(`/domains/${props.id}`, props);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateDomain;
|
@ -45,7 +45,7 @@ jest.mock(
|
||||
},
|
||||
);
|
||||
|
||||
describe('HostMetricsLogs', () => {
|
||||
describe.skip('HostMetricsLogs', () => {
|
||||
let capturedQueryRangePayloads: QueryRangePayload[] = [];
|
||||
const itemHeight = 100;
|
||||
beforeEach(() => {
|
||||
|
@ -169,7 +169,7 @@ export const verifyFiltersAndOrderBy = (queryData: IBuilderQuery): void => {
|
||||
}
|
||||
};
|
||||
|
||||
describe('LogsExplorerViews Pagination', () => {
|
||||
describe.skip('LogsExplorerViews Pagination', () => {
|
||||
// Array to store captured API request payloads
|
||||
let capturedPayloads: QueryRangePayload[];
|
||||
|
||||
|
@ -2,12 +2,12 @@
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, Modal, Typography } from 'antd';
|
||||
import { useForm } from 'antd/es/form/Form';
|
||||
import createDomainApi from 'api/SAML/postDomain';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import createDomainApi from 'api/v1/domains/create';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import { Container } from '../styles';
|
||||
|
||||
@ -15,34 +15,27 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'organizationsettings']);
|
||||
const [isAddDomains, setIsDomain] = useState(false);
|
||||
const [form] = useForm<FormProps>();
|
||||
const { featureFlags, org } = useAppContext();
|
||||
const isSsoFlagEnabled =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
const { org } = useAppContext();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCreateHandler = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await createDomainApi({
|
||||
await createDomainApi({
|
||||
name: form.getFieldValue('domain'),
|
||||
orgId: (org || [])[0].id,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: 'Your domain has been added successfully.',
|
||||
duration: 15,
|
||||
});
|
||||
setIsDomain(false);
|
||||
refetch();
|
||||
} else {
|
||||
notifications.error({
|
||||
message: t('common:something_went_wrong'),
|
||||
});
|
||||
}
|
||||
notifications.success({
|
||||
message: 'Your domain has been added successfully.',
|
||||
duration: 15,
|
||||
});
|
||||
setIsDomain(false);
|
||||
refetch();
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('common:something_went_wrong'),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -55,15 +48,13 @@ function AddDomain({ refetch }: Props): JSX.Element {
|
||||
ns: 'organizationsettings',
|
||||
})}
|
||||
</Typography.Title>
|
||||
{isSsoFlagEnabled && (
|
||||
<Button
|
||||
onClick={(): void => setIsDomain(true)}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('add_domain', { ns: 'organizationsettings' })}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={(): void => setIsDomain(true)}
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
{t('add_domain', { ns: 'organizationsettings' })}
|
||||
</Button>
|
||||
</Container>
|
||||
<Modal
|
||||
centered
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { GoogleSquareFilled, KeyOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { AuthDomain, GOOGLE_AUTH, SAML } from 'types/api/SAML/listDomain';
|
||||
|
||||
@ -12,6 +14,10 @@ function Create({
|
||||
setIsSettingsOpen,
|
||||
setIsEditModalOpen,
|
||||
}: CreateProps): JSX.Element {
|
||||
const { featureFlags } = useAppContext();
|
||||
const SSOFlag =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const onGoogleAuthClickHandler = useCallback(() => {
|
||||
assignSsoMethod(GOOGLE_AUTH);
|
||||
setIsSettingsOpen(false);
|
||||
@ -35,24 +41,35 @@ function Create({
|
||||
}
|
||||
}, [ssoMethod]);
|
||||
|
||||
const data: RowProps[] = [
|
||||
{
|
||||
buttonText: ConfigureButtonText,
|
||||
Icon: <GoogleSquareFilled style={{ fontSize: '37px' }} />,
|
||||
title: 'Google Apps Authentication',
|
||||
subTitle: 'Let members sign-in with a Google account',
|
||||
onClickHandler: onGoogleAuthClickHandler,
|
||||
isDisabled: false,
|
||||
},
|
||||
{
|
||||
buttonText: ConfigureButtonText,
|
||||
Icon: <KeyOutlined style={{ fontSize: '37px' }} />,
|
||||
onClickHandler: onEditSAMLHandler,
|
||||
subTitle: 'Azure, Active Directory, Okta or your custom SAML 2.0 solution',
|
||||
title: 'SAML Authentication',
|
||||
isDisabled: false,
|
||||
},
|
||||
];
|
||||
const data: RowProps[] = SSOFlag
|
||||
? [
|
||||
{
|
||||
buttonText: ConfigureButtonText,
|
||||
Icon: <GoogleSquareFilled style={{ fontSize: '37px' }} />,
|
||||
title: 'Google Apps Authentication',
|
||||
subTitle: 'Let members sign-in with a Google account',
|
||||
onClickHandler: onGoogleAuthClickHandler,
|
||||
isDisabled: false,
|
||||
},
|
||||
{
|
||||
buttonText: ConfigureButtonText,
|
||||
Icon: <KeyOutlined style={{ fontSize: '37px' }} />,
|
||||
onClickHandler: onEditSAMLHandler,
|
||||
subTitle: 'Azure, Active Directory, Okta or your custom SAML 2.0 solution',
|
||||
title: 'SAML Authentication',
|
||||
isDisabled: false,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
buttonText: ConfigureButtonText,
|
||||
Icon: <GoogleSquareFilled style={{ fontSize: '37px' }} />,
|
||||
title: 'Google Apps Authentication',
|
||||
subTitle: 'Let members sign-in with a Google account',
|
||||
onClickHandler: onGoogleAuthClickHandler,
|
||||
isDisabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { LockTwoTone } from '@ant-design/icons';
|
||||
import { Button, Modal, Space, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import deleteDomain from 'api/SAML/deleteDomain';
|
||||
import listAllDomain from 'api/SAML/listAllDomain';
|
||||
import updateDomain from 'api/SAML/updateDomain';
|
||||
import deleteDomain from 'api/v1/domains/delete';
|
||||
import listAllDomain from 'api/v1/domains/list';
|
||||
import updateDomain from 'api/v1/domains/update';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dispatch, SetStateAction, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import APIError from 'types/api/error';
|
||||
import { AuthDomain } from 'types/api/SAML/listDomain';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
@ -26,33 +24,12 @@ import SwitchComponent from './Switch';
|
||||
function AuthDomains(): JSX.Element {
|
||||
const { t } = useTranslation(['common', 'organizationsettings']);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState<boolean>(false);
|
||||
const { org, featureFlags } = useAppContext();
|
||||
const { org } = useAppContext();
|
||||
const [currentDomain, setCurrentDomain] = useState<AuthDomain>();
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
|
||||
const SSOFlag =
|
||||
featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const notEntripriseData: AuthDomain[] = [
|
||||
{
|
||||
id: v4(),
|
||||
name: '',
|
||||
ssoEnabled: false,
|
||||
orgId: (org || [])[0].id || '',
|
||||
samlConfig: {
|
||||
samlCert: '',
|
||||
samlEntity: '',
|
||||
samlIdp: '',
|
||||
},
|
||||
ssoType: 'SAML',
|
||||
},
|
||||
];
|
||||
|
||||
const { data, isLoading, refetch } = useQuery(['saml'], {
|
||||
queryFn: () =>
|
||||
listAllDomain({
|
||||
orgId: (org || [])[0].id,
|
||||
}),
|
||||
queryFn: () => listAllDomain(),
|
||||
enabled: org !== null,
|
||||
});
|
||||
|
||||
@ -75,32 +52,19 @@ function AuthDomains(): JSX.Element {
|
||||
const onRecordUpdateHandler = useCallback(
|
||||
async (record: AuthDomain): Promise<boolean> => {
|
||||
try {
|
||||
const response = await updateDomain(record);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: t('saml_settings', {
|
||||
ns: 'organizationsettings',
|
||||
}),
|
||||
});
|
||||
refetch();
|
||||
onCloseHandler(setIsEditModalOpen)();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
await updateDomain(record);
|
||||
notifications.success({
|
||||
message: t('saml_settings', {
|
||||
ns: 'organizationsettings',
|
||||
}),
|
||||
});
|
||||
|
||||
return false;
|
||||
refetch();
|
||||
onCloseHandler(setIsEditModalOpen)();
|
||||
return true;
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('something_went_wrong', {
|
||||
ns: 'common',
|
||||
}),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@ -139,18 +103,19 @@ function AuthDomains(): JSX.Element {
|
||||
ns: 'organizationsettings',
|
||||
}),
|
||||
onOk: async () => {
|
||||
const response = await deleteDomain({
|
||||
...record,
|
||||
});
|
||||
try {
|
||||
await deleteDomain({
|
||||
...record,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
notifications.success({
|
||||
message: t('common:success'),
|
||||
});
|
||||
refetch();
|
||||
} else {
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: t('common:something_went_wrong'),
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -159,10 +124,6 @@ function AuthDomains(): JSX.Element {
|
||||
[refetch, t, notifications],
|
||||
);
|
||||
|
||||
const onClickLicenseHandler = useCallback(() => {
|
||||
window.open(SIGNOZ_UPGRADE_PLAN_URL);
|
||||
}, []);
|
||||
|
||||
const columns: ColumnsType<AuthDomain> = [
|
||||
{
|
||||
title: 'Domain',
|
||||
@ -185,52 +146,24 @@ function AuthDomains(): JSX.Element {
|
||||
dataIndex: 'ssoEnabled',
|
||||
key: 'ssoEnabled',
|
||||
width: 80,
|
||||
render: (value: boolean, record: AuthDomain): JSX.Element => {
|
||||
if (!SSOFlag) {
|
||||
return (
|
||||
<Button
|
||||
onClick={onClickLicenseHandler}
|
||||
type="link"
|
||||
icon={<LockTwoTone />}
|
||||
>
|
||||
Upgrade to Configure SSO
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SwitchComponent
|
||||
onRecordUpdateHandler={onRecordUpdateHandler}
|
||||
isDefaultChecked={value}
|
||||
record={record}
|
||||
/>
|
||||
);
|
||||
},
|
||||
render: (value: boolean, record: AuthDomain): JSX.Element => (
|
||||
<SwitchComponent
|
||||
onRecordUpdateHandler={onRecordUpdateHandler}
|
||||
isDefaultChecked={value}
|
||||
record={record}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'description',
|
||||
key: 'description',
|
||||
width: 100,
|
||||
render: (_, record: AuthDomain): JSX.Element => {
|
||||
if (!SSOFlag) {
|
||||
return (
|
||||
<Button
|
||||
onClick={onClickLicenseHandler}
|
||||
type="link"
|
||||
icon={<LockTwoTone />}
|
||||
>
|
||||
Upgrade to Configure SSO
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button type="link" onClick={onEditHandler(record)}>
|
||||
{ConfigureSsoButtonText(record.ssoType)}
|
||||
</Button>
|
||||
);
|
||||
},
|
||||
render: (_, record: AuthDomain): JSX.Element => (
|
||||
<Button type="link" onClick={onEditHandler(record)}>
|
||||
{ConfigureSsoButtonText(record.ssoType)}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
@ -238,19 +171,14 @@ function AuthDomains(): JSX.Element {
|
||||
key: 'action',
|
||||
width: 50,
|
||||
render: (_, record): JSX.Element => (
|
||||
<Button
|
||||
disabled={!SSOFlag}
|
||||
onClick={onDeleteHandler(record)}
|
||||
danger
|
||||
type="link"
|
||||
>
|
||||
<Button onClick={onDeleteHandler(record)} danger type="link">
|
||||
Delete
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
if (!isLoading && data?.payload?.length === 0) {
|
||||
if (!isLoading && data?.data?.length === 0) {
|
||||
return (
|
||||
<Space direction="vertical" size="middle">
|
||||
<AddDomain refetch={refetch} />
|
||||
@ -273,7 +201,7 @@ function AuthDomains(): JSX.Element {
|
||||
<ResizeTable
|
||||
columns={columns}
|
||||
rowKey={(record: AuthDomain): string => record.name + v4()}
|
||||
dataSource={!SSOFlag ? notEntripriseData : []}
|
||||
dataSource={[]}
|
||||
tableLayout="fixed"
|
||||
bordered
|
||||
/>
|
||||
@ -281,8 +209,7 @@ function AuthDomains(): JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
const tableData = SSOFlag ? data?.payload || [] : notEntripriseData;
|
||||
|
||||
const tableData = data?.data || [];
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Divider, Space } from 'antd';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import AuthDomains from './AuthDomains';
|
||||
@ -8,12 +7,7 @@ import Members from './Members';
|
||||
import PendingInvitesContainer from './PendingInvitesContainer';
|
||||
|
||||
function OrganizationSettings(): JSX.Element {
|
||||
const { org, featureFlags } = useAppContext();
|
||||
|
||||
const isNotSSO =
|
||||
!featureFlags?.find((flag) => flag.name === FeatureKeys.SSO)?.active || false;
|
||||
|
||||
const isAuthDomain = !isNotSSO;
|
||||
const { org } = useAppContext();
|
||||
|
||||
if (!org) {
|
||||
return <div />;
|
||||
@ -31,7 +25,7 @@ function OrganizationSettings(): JSX.Element {
|
||||
<Divider />
|
||||
<Members />
|
||||
<Divider />
|
||||
{isAuthDomain && <AuthDomains />}
|
||||
<AuthDomains />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export const getRoutes = (
|
||||
|
||||
settings.push(...alertChannels(t));
|
||||
|
||||
if ((isCloudUser || isEnterpriseSelfHostedUser) && isAdmin) {
|
||||
if (isAdmin) {
|
||||
settings.push(...apiKeys(t));
|
||||
}
|
||||
|
||||
|
@ -2,4 +2,7 @@ import { AuthDomain } from './listDomain';
|
||||
|
||||
export type Props = AuthDomain;
|
||||
|
||||
export type PayloadProps = AuthDomain;
|
||||
export interface PayloadProps {
|
||||
data: null;
|
||||
status: string;
|
||||
}
|
||||
|
@ -44,4 +44,7 @@ export interface Props {
|
||||
orgId: Organization['id'];
|
||||
}
|
||||
|
||||
export type PayloadProps = AuthDomain[];
|
||||
export interface PayloadProps {
|
||||
data: AuthDomain[];
|
||||
status: string;
|
||||
}
|
||||
|
@ -5,4 +5,7 @@ export type Props = {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
export type PayloadProps = AuthDomain;
|
||||
export interface PayloadProps {
|
||||
data: AuthDomain;
|
||||
status: string;
|
||||
}
|
||||
|
@ -2,4 +2,7 @@ import { AuthDomain } from './listDomain';
|
||||
|
||||
export type Props = AuthDomain;
|
||||
|
||||
export type PayloadProps = AuthDomain;
|
||||
export interface PayloadProps {
|
||||
data: AuthDomain;
|
||||
status: string;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
@ -12,6 +13,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
@ -33,18 +35,24 @@ func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// SSO users might not have a password
|
||||
if err := req.Validate(); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get invite object
|
||||
invite, err := h.module.GetInviteByToken(ctx, req.InviteToken)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgDomain, err := h.module.GetAuthDomainByEmail(ctx, invite.Email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
precheckResp := &types.GettableLoginPrecheck{
|
||||
SSO: false,
|
||||
IsUser: false,
|
||||
}
|
||||
|
||||
if invite.Name == "" && req.DisplayName != "" {
|
||||
invite.Name = req.DisplayName
|
||||
}
|
||||
@ -55,16 +63,35 @@ func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
password, err := types.NewFactorPassword(req.Password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
if orgDomain != nil && orgDomain.SsoEnabled {
|
||||
// sso is enabled, create user and respond precheck data
|
||||
err = h.module.CreateUser(ctx, user)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err = h.module.CreateUserWithPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
// check if sso is enforced for the org
|
||||
precheckResp, err = h.module.LoginPrecheck(ctx, invite.OrgID, user.Email, req.SourceURL)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
password, err := types.NewFactorPassword(req.Password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.module.CreateUserWithPassword(ctx, user, password)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
precheckResp.IsUser = true
|
||||
}
|
||||
|
||||
// delete the invite
|
||||
@ -73,7 +100,7 @@ func (h *handler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusCreated, user)
|
||||
render.Success(w, http.StatusOK, precheckResp)
|
||||
}
|
||||
|
||||
func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
||||
@ -139,13 +166,26 @@ func (h *handler) GetInvite(w http.ResponseWriter, r *http.Request) {
|
||||
defer cancel()
|
||||
|
||||
token := mux.Vars(r)["token"]
|
||||
sourceUrl := r.URL.Query().Get("ref")
|
||||
invite, err := h.module.GetInviteByToken(ctx, token)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, invite)
|
||||
// precheck the user
|
||||
precheckResp, err := h.module.LoginPrecheck(ctx, invite.OrgID, invite.Email, sourceUrl)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
gettableInvite := &types.GettableEEInvite{
|
||||
GettableInvite: *invite,
|
||||
PreCheck: precheckResp,
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusOK, gettableInvite)
|
||||
}
|
||||
|
||||
func (h *handler) ListInvite(w http.ResponseWriter, r *http.Request) {
|
||||
@ -426,15 +466,19 @@ func (h *handler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if req.RefreshToken == "" {
|
||||
_, err := h.module.CanUsePassword(ctx, req.Email)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user, err := h.module.GetAuthenticatedUser(ctx, req.OrgID, req.Email, req.Password, req.RefreshToken)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
if user == nil {
|
||||
render.Error(w, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid email or password"))
|
||||
return
|
||||
}
|
||||
|
||||
jwt, err := h.module.GetJWTForUser(ctx, user)
|
||||
if err != nil {
|
||||
@ -470,22 +514,313 @@ func (h *handler) GetCurrentUserFromJWT(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
}
|
||||
|
||||
// CreateAPIKey implements user.Handler.
|
||||
func (h *handler) CreateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "not implemented"))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
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,
|
||||
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, 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)
|
||||
}
|
||||
|
||||
// ListAPIKeys implements user.Handler.
|
||||
func (h *handler) ListAPIKeys(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "not implemented"))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
apiKeys, err := h.module.ListAPIKeys(ctx, 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)
|
||||
|
||||
}
|
||||
|
||||
// RevokeAPIKey implements user.Handler.
|
||||
func (h *handler) RevokeAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "not implemented"))
|
||||
}
|
||||
|
||||
// UpdateAPIKey implements user.Handler.
|
||||
func (h *handler) UpdateAPIKey(w http.ResponseWriter, r *http.Request) {
|
||||
render.Error(w, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "not implemented"))
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
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, orgID, id)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user
|
||||
createdByUser, err := h.module.GetUserByID(ctx, orgID.String(), existingAPIKey.UserID.String())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email)) {
|
||||
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, 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
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := valuer.NewUUID(claims.UserID)
|
||||
if err != nil {
|
||||
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "userId is not a valid uuid-v7"))
|
||||
return
|
||||
}
|
||||
|
||||
//get the API Key
|
||||
existingAPIKey, err := h.module.GetAPIKey(ctx, orgID, id)
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get the user
|
||||
createdByUser, err := h.module.GetUserByID(ctx, orgID.String(), existingAPIKey.UserID.String())
|
||||
if err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(types.AllIntegrationUserEmails, types.IntegrationUserEmail(createdByUser.Email)) {
|
||||
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, userID); err != nil {
|
||||
render.Error(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(w, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (h *handler) CreateDomain(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
req := types.GettableOrgDomain{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ValidNew(); err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
err := h.module.CreateDomain(ctx, &req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusAccepted, req)
|
||||
}
|
||||
|
||||
func (h *handler) DeleteDomain(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
domainIdStr := mux.Vars(r)["id"]
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid domain id"))
|
||||
return
|
||||
}
|
||||
|
||||
err = h.module.DeleteDomain(ctx, domainId)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (h *handler) ListDomains(rw 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(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is not a valid uuid"))
|
||||
return
|
||||
}
|
||||
|
||||
domains, err := h.module.ListDomains(r.Context(), orgID)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, domains)
|
||||
}
|
||||
|
||||
func (h *handler) UpdateDomain(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
domainIdStr := mux.Vars(r)["id"]
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid domain id"))
|
||||
return
|
||||
}
|
||||
|
||||
req := types.GettableOrgDomain{StorableOrgDomain: types.StorableOrgDomain{ID: domainId}}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
render.Error(rw, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "unable to unmarshal the payload"))
|
||||
return
|
||||
}
|
||||
|
||||
req.ID = domainId
|
||||
if err := req.Valid(nil); err != nil {
|
||||
render.Error(rw, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid request"))
|
||||
}
|
||||
|
||||
err = h.module.UpdateDomain(ctx, &req)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
@ -3,18 +3,22 @@ package impluser
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
||||
"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/valuer"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Module struct {
|
||||
@ -319,6 +323,41 @@ func (m *Module) LoginPrecheck(ctx context.Context, orgID, email, sourceUrl stri
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(Nitya): in multitenancy this should use orgId as well.
|
||||
orgDomain, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if orgDomain != nil && orgDomain.SsoEnabled {
|
||||
// this is to allow self registration
|
||||
resp.IsUser = true
|
||||
|
||||
// saml is enabled for this domain, lets prepare sso url
|
||||
if sourceUrl == "" {
|
||||
sourceUrl = constants.GetDefaultSiteURL()
|
||||
}
|
||||
|
||||
// parse source url that generated the login request
|
||||
var err error
|
||||
escapedUrl, _ := url.QueryUnescape(sourceUrl)
|
||||
siteUrl, err := url.Parse(escapedUrl)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse referer")
|
||||
}
|
||||
|
||||
// build Idp URL that will authenticat the user
|
||||
// the front-end will redirect user to this url
|
||||
resp.SSOUrl, err = orgDomain.BuildSsoUrl(siteUrl)
|
||||
if err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to prepare saml request for domain", "domain", orgDomain.Name, "error", err)
|
||||
return nil, errors.New(errors.TypeInternal, errors.CodeInternal, "failed to prepare saml request for domain")
|
||||
}
|
||||
|
||||
// set SSO to true, as the url is generated correctly
|
||||
resp.SSO = true
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -347,37 +386,155 @@ func (m *Module) GetJWTForUser(ctx context.Context, user *types.User) (types.Get
|
||||
}
|
||||
|
||||
func (m *Module) CreateUserForSAMLRequest(ctx context.Context, email string) (*types.User, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "SAML login is not supported")
|
||||
// get auth domain from email domain
|
||||
_, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get name from email
|
||||
parts := strings.Split(email, "@")
|
||||
if len(parts) < 2 {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid email format")
|
||||
}
|
||||
name := parts[0]
|
||||
|
||||
defaultOrgID, err := m.store.GetDefaultOrgID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := types.NewUser(name, email, types.RoleViewer.String(), defaultOrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.CreateUser(ctx, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
||||
}
|
||||
|
||||
func (m *Module) PrepareSsoRedirect(ctx context.Context, redirectUri, email string, jwt *authtypes.JWT) (string, error) {
|
||||
return "", errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "SSO is not supported")
|
||||
users, err := m.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to get user with email received from auth provider", "error", err)
|
||||
return "", err
|
||||
}
|
||||
user := &types.User{}
|
||||
|
||||
if len(users) == 0 {
|
||||
newUser, err := m.CreateUserForSAMLRequest(ctx, email)
|
||||
user = newUser
|
||||
if err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to create user with email received from auth provider", "error", err)
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
user = &users[0].User
|
||||
}
|
||||
|
||||
tokenStore, err := m.GetJWTForUser(ctx, user)
|
||||
if err != nil {
|
||||
m.settings.Logger().ErrorContext(ctx, "failed to generate token for SSO login user", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||||
redirectUri,
|
||||
tokenStore.AccessJwt,
|
||||
user.ID,
|
||||
tokenStore.RefreshJwt), nil
|
||||
}
|
||||
|
||||
func (m *Module) CanUsePassword(ctx context.Context, email string) (bool, error) {
|
||||
return false, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "SSO is not supported")
|
||||
domain, err := m.GetAuthDomainByEmail(ctx, email)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if domain != nil && domain.SsoEnabled {
|
||||
// sso is enabled, check if the user has admin role
|
||||
users, err := m.GetUsersByEmail(ctx, email)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return false, errors.New(errors.TypeNotFound, errors.CodeNotFound, "user not found")
|
||||
}
|
||||
|
||||
if users[0].Role != types.RoleAdmin.String() {
|
||||
return false, errors.New(errors.TypeForbidden, errors.CodeForbidden, "auth method not supported")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (m *Module) GetAuthDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "SSO is not supported")
|
||||
|
||||
if email == "" {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "email is required")
|
||||
}
|
||||
|
||||
components := strings.Split(email, "@")
|
||||
if len(components) < 2 {
|
||||
return nil, errors.New(errors.TypeInvalidInput, errors.CodeInvalidInput, "invalid email format")
|
||||
}
|
||||
|
||||
domain, err := m.store.GetDomainByName(ctx, components[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gettableDomain := &types.GettableOrgDomain{StorableOrgDomain: *domain}
|
||||
if err := gettableDomain.LoadConfig(domain.Data); err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to load domain config")
|
||||
}
|
||||
return gettableDomain, nil
|
||||
}
|
||||
|
||||
func (m *Module) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
||||
return errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "API Keys are not supported")
|
||||
return m.store.CreateAPIKey(ctx, apiKey)
|
||||
}
|
||||
|
||||
func (m *Module) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
return errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "API Keys are not supported")
|
||||
return m.store.UpdateAPIKey(ctx, id, apiKey, updaterID)
|
||||
}
|
||||
|
||||
func (m *Module) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "API Keys are not supported")
|
||||
return m.store.ListAPIKeys(ctx, orgID)
|
||||
}
|
||||
|
||||
func (m *Module) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "API Keys are not supported")
|
||||
return m.store.GetAPIKey(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (m *Module) RevokeAPIKey(ctx context.Context, id, removedByUserID valuer.UUID) error {
|
||||
return errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "API Keys are not supported")
|
||||
return m.store.RevokeAPIKey(ctx, id, removedByUserID)
|
||||
}
|
||||
|
||||
func (m *Module) GetDomainFromSsoResponse(ctx context.Context, url *url.URL) (*types.GettableOrgDomain, error) {
|
||||
return m.store.GetDomainFromSsoResponse(ctx, url)
|
||||
}
|
||||
|
||||
func (m *Module) CreateDomain(ctx context.Context, domain *types.GettableOrgDomain) error {
|
||||
return m.store.CreateDomain(ctx, domain)
|
||||
}
|
||||
|
||||
func (m *Module) DeleteDomain(ctx context.Context, id uuid.UUID) error {
|
||||
return m.store.DeleteDomain(ctx, id)
|
||||
}
|
||||
|
||||
func (m *Module) ListDomains(ctx context.Context, orgID valuer.UUID) ([]*types.GettableOrgDomain, error) {
|
||||
return m.store.ListDomains(ctx, orgID)
|
||||
}
|
||||
|
||||
func (m *Module) UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) error {
|
||||
return m.store.UpdateDomain(ctx, domain)
|
||||
}
|
||||
|
@ -3,77 +3,83 @@ package impluser
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"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/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
type store struct {
|
||||
sqlstore sqlstore.SQLStore
|
||||
settings factory.ProviderSettings
|
||||
}
|
||||
|
||||
func NewStore(sqlstore sqlstore.SQLStore) types.UserStore {
|
||||
return &Store{sqlstore: sqlstore}
|
||||
func NewStore(sqlstore sqlstore.SQLStore, settings factory.ProviderSettings) types.UserStore {
|
||||
return &store{sqlstore: sqlstore, settings: settings}
|
||||
}
|
||||
|
||||
// CreateBulkInvite implements types.InviteStore.
|
||||
func (s *Store) CreateBulkInvite(ctx context.Context, invites []*types.Invite) error {
|
||||
_, err := s.sqlstore.BunDB().NewInsert().
|
||||
func (store *store) CreateBulkInvite(ctx context.Context, invites []*types.Invite) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(&invites).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrInviteAlreadyExists, "invite with email: %s already exists in org: %s", invites[0].Email, invites[0].OrgID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrInviteAlreadyExists, "invite with email: %s already exists in org: %s", invites[0].Email, invites[0].OrgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete implements types.InviteStore.
|
||||
func (s *Store) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
_, err := s.sqlstore.BunDB().NewDelete().
|
||||
func (store *store) DeleteInvite(ctx context.Context, orgID string, id valuer.UUID) error {
|
||||
_, err := store.sqlstore.BunDB().NewDelete().
|
||||
Model(&types.Invite{}).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with id: %s does not exist in org: %s", id.StringValue(), orgID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with id: %s does not exist in org: %s", id.StringValue(), orgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInviteByEmailInOrg implements types.InviteStore.
|
||||
func (s *Store) GetInviteByEmailInOrg(ctx context.Context, orgID string, email string) (*types.Invite, error) {
|
||||
func (store *store) GetInviteByEmailInOrg(ctx context.Context, orgID string, email string) (*types.Invite, error) {
|
||||
invite := new(types.Invite)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(invite).
|
||||
Where("email = ?", email).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with email: %s does not exist in org: %s", email, orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with email: %s does not exist in org: %s", email, orgID)
|
||||
}
|
||||
|
||||
return invite, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetInviteByToken(ctx context.Context, token string) (*types.GettableInvite, error) {
|
||||
func (store *store) GetInviteByToken(ctx context.Context, token string) (*types.GettableInvite, error) {
|
||||
invite := new(types.Invite)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(invite).
|
||||
Where("token = ?", token).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with token: %s does not exist", token)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with token: %s does not exist", token)
|
||||
}
|
||||
|
||||
orgName, err := s.getOrgNameByID(ctx, invite.OrgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, invite.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -86,32 +92,32 @@ func (s *Store) GetInviteByToken(ctx context.Context, token string) (*types.Gett
|
||||
return gettableInvite, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
func (store *store) ListInvite(ctx context.Context, orgID string) ([]*types.Invite, error) {
|
||||
invites := new([]*types.Invite)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(invites).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with org id: %s does not exist", orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrInviteNotFound, "invite with org id: %s does not exist", orgID)
|
||||
}
|
||||
return *invites, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreatePassword(ctx context.Context, password *types.FactorPassword) (*types.FactorPassword, error) {
|
||||
_, err := s.sqlstore.BunDB().NewInsert().
|
||||
func (store *store) CreatePassword(ctx context.Context, password *types.FactorPassword) (*types.FactorPassword, error) {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(password).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with user id: %s already exists", password.UserID)
|
||||
return nil, store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with user id: %s already exists", password.UserID)
|
||||
}
|
||||
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateUserWithPassword(ctx context.Context, user *types.User, password *types.FactorPassword) (*types.User, error) {
|
||||
tx, err := s.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
func (store *store) CreateUserWithPassword(ctx context.Context, user *types.User, password *types.FactorPassword) (*types.User, error) {
|
||||
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
|
||||
}
|
||||
@ -123,14 +129,14 @@ func (s *Store) CreateUserWithPassword(ctx context.Context, user *types.User, pa
|
||||
if _, err := tx.NewInsert().
|
||||
Model(user).
|
||||
Exec(ctx); err != nil {
|
||||
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
return nil, store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
}
|
||||
|
||||
password.UserID = user.ID.StringValue()
|
||||
if _, err := tx.NewInsert().
|
||||
Model(password).
|
||||
Exec(ctx); err != nil {
|
||||
return nil, s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
return nil, store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrPasswordAlreadyExists, "password with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
@ -141,54 +147,54 @@ func (s *Store) CreateUserWithPassword(ctx context.Context, user *types.User, pa
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateUser(ctx context.Context, user *types.User) error {
|
||||
_, err := s.sqlstore.BunDB().NewInsert().
|
||||
func (store *store) CreateUser(ctx context.Context, user *types.User) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(user).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrUserAlreadyExists, "user with email: %s already exists in org: %s", user.Email, user.OrgID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetDefaultOrgID(ctx context.Context) (string, error) {
|
||||
func (store *store) GetDefaultOrgID(ctx context.Context) (string, error) {
|
||||
org := new(types.Organization)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(org).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return "", s.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "default org does not exist")
|
||||
return "", store.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "default org does not exist")
|
||||
}
|
||||
return org.ID.String(), nil
|
||||
}
|
||||
|
||||
// this is temporary function, we plan to remove this in the next PR.
|
||||
func (s *Store) getOrgNameByID(ctx context.Context, orgID string) (string, error) {
|
||||
func (store *store) getOrgNameByID(ctx context.Context, orgID string) (string, error) {
|
||||
org := new(types.Organization)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(org).
|
||||
Where("id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return "", s.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "org with id: %s does not exist", orgID)
|
||||
return "", store.sqlstore.WrapNotFoundErrf(err, types.ErrOrganizationNotFound, "org with id: %s does not exist", orgID)
|
||||
}
|
||||
return org.DisplayName, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUserByID(ctx context.Context, orgID string, id string) (*types.GettableUser, error) {
|
||||
func (store *store) GetUserByID(ctx context.Context, orgID string, id string) (*types.GettableUser, error) {
|
||||
user := new(types.User)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(user).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
|
||||
}
|
||||
|
||||
// remove this in next PR
|
||||
orgName, err := s.getOrgNameByID(ctx, orgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -196,19 +202,19 @@ func (s *Store) GetUserByID(ctx context.Context, orgID string, id string) (*type
|
||||
return &types.GettableUser{User: *user, Organization: orgName}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUserByEmailInOrg(ctx context.Context, orgID string, email string) (*types.GettableUser, error) {
|
||||
func (store *store) GetUserByEmailInOrg(ctx context.Context, orgID string, email string) (*types.GettableUser, error) {
|
||||
user := new(types.User)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(user).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("email = ?", email).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist in org: %s", email, orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist in org: %s", email, orgID)
|
||||
}
|
||||
|
||||
// remove this in next PR
|
||||
orgName, err := s.getOrgNameByID(ctx, orgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -216,20 +222,20 @@ func (s *Store) GetUserByEmailInOrg(ctx context.Context, orgID string, email str
|
||||
return &types.GettableUser{User: *user, Organization: orgName}, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUsersByEmail(ctx context.Context, email string) ([]*types.GettableUser, error) {
|
||||
func (store *store) GetUsersByEmail(ctx context.Context, email string) ([]*types.GettableUser, error) {
|
||||
users := new([]*types.User)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(users).
|
||||
Where("email = ?", email).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist", email)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with email: %s does not exist", email)
|
||||
}
|
||||
|
||||
// remove this in next PR
|
||||
usersWithOrg := []*types.GettableUser{}
|
||||
for _, user := range *users {
|
||||
orgName, err := s.getOrgNameByID(ctx, user.OrgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, user.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -238,19 +244,19 @@ func (s *Store) GetUsersByEmail(ctx context.Context, email string) ([]*types.Get
|
||||
return usersWithOrg, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUsersByRoleInOrg(ctx context.Context, orgID string, role types.Role) ([]*types.GettableUser, error) {
|
||||
func (store *store) GetUsersByRoleInOrg(ctx context.Context, orgID string, role types.Role) ([]*types.GettableUser, error) {
|
||||
users := new([]*types.User)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(users).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("role = ?", role).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with role: %s does not exist in org: %s", role, orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with role: %s does not exist in org: %s", role, orgID)
|
||||
}
|
||||
|
||||
// remove this in next PR
|
||||
orgName, err := s.getOrgNameByID(ctx, orgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -261,9 +267,9 @@ func (s *Store) GetUsersByRoleInOrg(ctx context.Context, orgID string, role type
|
||||
return usersWithOrg, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUser(ctx context.Context, orgID string, id string, user *types.User) (*types.User, error) {
|
||||
func (store *store) UpdateUser(ctx context.Context, orgID string, id string, user *types.User) (*types.User, error) {
|
||||
user.UpdatedAt = time.Now()
|
||||
_, err := s.sqlstore.BunDB().NewUpdate().
|
||||
_, err := store.sqlstore.BunDB().NewUpdate().
|
||||
Model(user).
|
||||
Column("display_name").
|
||||
Column("role").
|
||||
@ -272,23 +278,23 @@ func (s *Store) UpdateUser(ctx context.Context, orgID string, id string, user *t
|
||||
Where("org_id = ?", orgID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "user with id: %s does not exist in org: %s", id, orgID)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Store) ListUsers(ctx context.Context, orgID string) ([]*types.GettableUser, error) {
|
||||
func (store *store) ListUsers(ctx context.Context, orgID string) ([]*types.GettableUser, error) {
|
||||
users := []*types.User{}
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(&users).
|
||||
Where("org_id = ?", orgID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "users with org id: %s does not exist", orgID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrUserNotFound, "users with org id: %s does not exist", orgID)
|
||||
}
|
||||
|
||||
// remove this in next PR
|
||||
orgName, err := s.getOrgNameByID(ctx, orgID)
|
||||
orgName, err := store.getOrgNameByID(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -299,9 +305,9 @@ func (s *Store) ListUsers(ctx context.Context, orgID string) ([]*types.GettableU
|
||||
return usersWithOrg, nil
|
||||
}
|
||||
|
||||
func (s *Store) DeleteUser(ctx context.Context, orgID string, id string) error {
|
||||
func (store *store) DeleteUser(ctx context.Context, orgID string, id string) error {
|
||||
|
||||
tx, err := s.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
|
||||
}
|
||||
@ -366,67 +372,67 @@ func (s *Store) DeleteUser(ctx context.Context, orgID string, id string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateResetPasswordToken(ctx context.Context, resetPasswordRequest *types.ResetPasswordRequest) error {
|
||||
_, err := s.sqlstore.BunDB().NewInsert().
|
||||
func (store *store) CreateResetPasswordToken(ctx context.Context, resetPasswordRequest *types.ResetPasswordRequest) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(resetPasswordRequest).
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token with password id: %s already exists", resetPasswordRequest.PasswordID)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrResetPasswordTokenAlreadyExists, "reset password token with password id: %s already exists", resetPasswordRequest.PasswordID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPasswordByID(ctx context.Context, id string) (*types.FactorPassword, error) {
|
||||
func (store *store) GetPasswordByID(ctx context.Context, id string) (*types.FactorPassword, error) {
|
||||
password := new(types.FactorPassword)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(password).
|
||||
Where("id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with id: %s does not exist", id)
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetPasswordByUserID(ctx context.Context, id string) (*types.FactorPassword, error) {
|
||||
func (store *store) GetPasswordByUserID(ctx context.Context, id string) (*types.FactorPassword, error) {
|
||||
password := new(types.FactorPassword)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(password).
|
||||
Where("user_id = ?", id).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", id)
|
||||
}
|
||||
return password, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetResetPasswordByPasswordID(ctx context.Context, passwordID string) (*types.ResetPasswordRequest, error) {
|
||||
func (store *store) GetResetPasswordByPasswordID(ctx context.Context, passwordID string) (*types.ResetPasswordRequest, error) {
|
||||
resetPasswordRequest := new(types.ResetPasswordRequest)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(resetPasswordRequest).
|
||||
Where("password_id = ?", passwordID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", passwordID)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", passwordID)
|
||||
}
|
||||
return resetPasswordRequest, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetResetPassword(ctx context.Context, token string) (*types.ResetPasswordRequest, error) {
|
||||
func (store *store) GetResetPassword(ctx context.Context, token string) (*types.ResetPasswordRequest, error) {
|
||||
resetPasswordRequest := new(types.ResetPasswordRequest)
|
||||
err := s.sqlstore.BunDB().NewSelect().
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(resetPasswordRequest).
|
||||
Where("token = ?", token).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with token: %s does not exist", token)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with token: %s does not exist", token)
|
||||
}
|
||||
return resetPasswordRequest, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, userID string, password string) error {
|
||||
tx, err := s.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
func (store *store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, userID string, password string) error {
|
||||
tx, err := store.sqlstore.BunDB().BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to start transaction")
|
||||
}
|
||||
@ -449,7 +455,7 @@ func (s *Store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, u
|
||||
Where("user_id = ?", userID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
|
||||
}
|
||||
|
||||
_, err = tx.NewDelete().
|
||||
@ -457,7 +463,7 @@ func (s *Store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, u
|
||||
Where("password_id = ?", userID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", userID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrResetPasswordTokenNotFound, "reset password token with password id: %s does not exist", userID)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
@ -468,7 +474,7 @@ func (s *Store) UpdatePasswordAndDeleteResetPasswordEntry(ctx context.Context, u
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdatePassword(ctx context.Context, userID string, password string) error {
|
||||
func (store *store) UpdatePassword(ctx context.Context, userID string, password string) error {
|
||||
factorPassword := &types.FactorPassword{
|
||||
UserID: userID,
|
||||
Password: password,
|
||||
@ -476,53 +482,63 @@ func (s *Store) UpdatePassword(ctx context.Context, userID string, password stri
|
||||
UpdatedAt: time.Now(),
|
||||
},
|
||||
}
|
||||
_, err := s.sqlstore.BunDB().NewUpdate().
|
||||
_, err := store.sqlstore.BunDB().NewUpdate().
|
||||
Model(factorPassword).
|
||||
Column("password").
|
||||
Column("updated_at").
|
||||
Where("user_id = ?", userID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrPasswordNotFound, "password with user id: %s does not exist", userID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetDomainByName(ctx context.Context, name string) (*types.StorableOrgDomain, error) {
|
||||
return nil, errors.New(errors.TypeUnsupported, errors.CodeUnsupported, "not supported")
|
||||
func (store *store) GetDomainByName(ctx context.Context, name string) (*types.StorableOrgDomain, error) {
|
||||
domain := new(types.StorableOrgDomain)
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(domain).
|
||||
Where("name = ?", name).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeNotFound, errors.CodeNotFound, "failed to get domain from name")
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// --- API KEY ---
|
||||
func (s *Store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
||||
_, err := s.sqlstore.BunDB().NewInsert().
|
||||
func (store *store) CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error {
|
||||
_, err := store.sqlstore.BunDB().NewInsert().
|
||||
Model(apiKey).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return s.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
|
||||
return store.sqlstore.WrapAlreadyExistsErrf(err, types.ErrAPIKeyAlreadyExists, "API key with token: %s already exists", apiKey.Token)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateAPIKey(ctx context.Context, id valuer.UUID, apiKey *types.StorableAPIKey, updaterID valuer.UUID) error {
|
||||
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 := s.sqlstore.BunDB().NewUpdate().
|
||||
_, 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 s.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
return store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
||||
func (store *store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.StorableAPIKeyUser, error) {
|
||||
orgUserAPIKeys := new(types.OrgUserAPIKey)
|
||||
|
||||
if err := s.sqlstore.BunDB().NewSelect().
|
||||
if err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(orgUserAPIKeys).
|
||||
Relation("Users").
|
||||
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
@ -552,9 +568,9 @@ func (s *Store) ListAPIKeys(ctx context.Context, orgID valuer.UUID) ([]*types.St
|
||||
return allAPIKeys, nil
|
||||
}
|
||||
|
||||
func (s *Store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
|
||||
func (store *store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUID) error {
|
||||
updatedAt := time.Now().Unix()
|
||||
_, err := s.sqlstore.BunDB().NewUpdate().
|
||||
_, err := store.sqlstore.BunDB().NewUpdate().
|
||||
Model(&types.StorableAPIKey{}).
|
||||
Set("revoked = ?", true).
|
||||
Set("updated_by = ?", revokedByUserID).
|
||||
@ -567,9 +583,9 @@ func (s *Store) RevokeAPIKey(ctx context.Context, id, revokedByUserID valuer.UUI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
||||
func (store *store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.StorableAPIKeyUser, error) {
|
||||
apiKey := new(types.OrgUserAPIKey)
|
||||
if err := s.sqlstore.BunDB().NewSelect().
|
||||
if err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(apiKey).
|
||||
Relation("Users").
|
||||
Relation("Users.APIKeys", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
@ -580,7 +596,7 @@ func (s *Store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.St
|
||||
Relation("Users.APIKeys.CreatedByUser").
|
||||
Relation("Users.APIKeys.UpdatedByUser").
|
||||
Scan(ctx); err != nil {
|
||||
return nil, s.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, types.ErrAPIKeyNotFound, "API key with id: %s does not exist", id)
|
||||
}
|
||||
|
||||
// flatten the API keys
|
||||
@ -591,8 +607,205 @@ func (s *Store) GetAPIKey(ctx context.Context, orgID, id valuer.UUID) (*types.St
|
||||
}
|
||||
}
|
||||
if len(flattenedAPIKeys) == 0 {
|
||||
return nil, s.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 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
|
||||
}
|
||||
|
||||
// GetDomainFromSsoResponse uses relay state received from IdP to fetch
|
||||
// user domain. The domain is further used to process validity of the response.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId or domainName as query parameter.
|
||||
func (store *store) GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*types.GettableOrgDomain, error) {
|
||||
// derive domain id from relay state now
|
||||
var domainIdStr string
|
||||
var domainNameStr string
|
||||
var domain *types.GettableOrgDomain
|
||||
|
||||
for k, v := range relayState.Query() {
|
||||
if k == "domainId" && len(v) > 0 {
|
||||
domainIdStr = strings.Replace(v[0], ":", "-", -1)
|
||||
}
|
||||
if k == "domainName" && len(v) > 0 {
|
||||
domainNameStr = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
if domainIdStr != "" {
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to parse domainID from IdP response")
|
||||
}
|
||||
|
||||
domain, err = store.GetDomain(ctx, domainId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to find domain from domainID received in IDP response")
|
||||
}
|
||||
}
|
||||
|
||||
if domainNameStr != "" {
|
||||
domainFromDB, err := store.GetGettableDomainByName(ctx, domainNameStr)
|
||||
domain = domainFromDB
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to find domain from domainName received in IDP response")
|
||||
}
|
||||
}
|
||||
if domain != nil {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "failed to find domain received in IDP response")
|
||||
}
|
||||
|
||||
// GetDomainByName returns org domain for a given domain name
|
||||
func (store *store) GetGettableDomainByName(ctx context.Context, name string) (*types.GettableOrgDomain, error) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("name = ?", name).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "domain with name: %s doesn't exist", name)
|
||||
}
|
||||
|
||||
domain := &types.GettableOrgDomain{StorableOrgDomain: stored}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "failed to load domain config")
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// GetDomain returns org domain for a given domain id
|
||||
func (store *store) GetDomain(ctx context.Context, id uuid.UUID) (*types.GettableOrgDomain, error) {
|
||||
|
||||
stored := types.StorableOrgDomain{}
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("id = ?", id).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "domain with id: %s doesn't exist", id)
|
||||
}
|
||||
|
||||
domain := &types.GettableOrgDomain{StorableOrgDomain: stored}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, errors.Newf(errors.TypeInternal, errors.CodeInternal, "failed to load domain config")
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
// ListDomains gets the list of auth domains by org id
|
||||
func (store *store) ListDomains(ctx context.Context, orgId valuer.UUID) ([]*types.GettableOrgDomain, error) {
|
||||
domains := make([]*types.GettableOrgDomain, 0)
|
||||
stored := []types.StorableOrgDomain{}
|
||||
err := store.sqlstore.BunDB().NewSelect().
|
||||
Model(&stored).
|
||||
Where("org_id = ?", orgId).
|
||||
Scan(ctx)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return domains, nil
|
||||
}
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to list domains")
|
||||
}
|
||||
|
||||
for _, s := range stored {
|
||||
domain := types.GettableOrgDomain{StorableOrgDomain: s}
|
||||
if err := domain.LoadConfig(s.Data); err != nil {
|
||||
store.settings.Logger.ErrorContext(ctx, "ListDomains() failed", "error", err)
|
||||
}
|
||||
domains = append(domains, &domain)
|
||||
}
|
||||
|
||||
return domains, nil
|
||||
}
|
||||
|
||||
// CreateDomain creates a new auth domain
|
||||
func (store *store) CreateDomain(ctx context.Context, domain *types.GettableOrgDomain) error {
|
||||
|
||||
if domain.ID == uuid.Nil {
|
||||
domain.ID = uuid.New()
|
||||
}
|
||||
|
||||
if domain.OrgID == "" || domain.Name == "" {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "domain creation failed, missing fields: OrgID, Name")
|
||||
}
|
||||
|
||||
configJson, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "domain creation failed")
|
||||
}
|
||||
|
||||
storableDomain := types.StorableOrgDomain{
|
||||
ID: domain.ID,
|
||||
Name: domain.Name,
|
||||
OrgID: domain.OrgID,
|
||||
Data: string(configJson),
|
||||
TimeAuditable: types.TimeAuditable{CreatedAt: time.Now(), UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = store.sqlstore.BunDB().NewInsert().
|
||||
Model(&storableDomain).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "domain creation failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateDomain updates stored config params for a domain
|
||||
func (store *store) UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) error {
|
||||
if domain.ID == uuid.Nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "missing domain id")
|
||||
}
|
||||
configJson, err := json.Marshal(domain)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to update domain")
|
||||
}
|
||||
|
||||
storableDomain := &types.StorableOrgDomain{
|
||||
ID: domain.ID,
|
||||
Name: domain.Name,
|
||||
OrgID: domain.OrgID,
|
||||
Data: string(configJson),
|
||||
TimeAuditable: types.TimeAuditable{UpdatedAt: time.Now()},
|
||||
}
|
||||
|
||||
_, err = store.sqlstore.BunDB().NewUpdate().
|
||||
Model(storableDomain).
|
||||
Column("data", "updated_at").
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to update domain")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDomain deletes an org domain
|
||||
func (store *store) DeleteDomain(ctx context.Context, id uuid.UUID) error {
|
||||
|
||||
if id == uuid.Nil {
|
||||
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "missing domain id")
|
||||
}
|
||||
|
||||
storableDomain := &types.StorableOrgDomain{ID: id}
|
||||
_, err := store.sqlstore.BunDB().NewDelete().
|
||||
Model(storableDomain).
|
||||
WherePK().
|
||||
Exec(ctx)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to delete domain")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package user
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Module interface {
|
||||
@ -47,6 +49,12 @@ type Module interface {
|
||||
|
||||
// Auth Domain
|
||||
GetAuthDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, error)
|
||||
GetDomainFromSsoResponse(ctx context.Context, url *url.URL) (*types.GettableOrgDomain, error)
|
||||
|
||||
ListDomains(ctx context.Context, orgID valuer.UUID) ([]*types.GettableOrgDomain, error)
|
||||
CreateDomain(ctx context.Context, domain *types.GettableOrgDomain) error
|
||||
UpdateDomain(ctx context.Context, domain *types.GettableOrgDomain) error
|
||||
DeleteDomain(ctx context.Context, id uuid.UUID) error
|
||||
|
||||
// API KEY
|
||||
CreateAPIKey(ctx context.Context, apiKey *types.StorableAPIKey) error
|
||||
@ -85,4 +93,9 @@ type Handler interface {
|
||||
ListAPIKeys(http.ResponseWriter, *http.Request)
|
||||
UpdateAPIKey(http.ResponseWriter, *http.Request)
|
||||
RevokeAPIKey(http.ResponseWriter, *http.Request)
|
||||
|
||||
ListDomains(http.ResponseWriter, *http.Request)
|
||||
CreateDomain(http.ResponseWriter, *http.Request)
|
||||
UpdateDomain(http.ResponseWriter, *http.Request)
|
||||
DeleteDomain(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ func TestRegenerateConnectionUrlWithUpdatedConfig(t *testing.T) {
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore), nil, emailing, providerSettings)
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
require.Nil(apiErr)
|
||||
|
||||
@ -77,7 +77,7 @@ func TestAgentCheckIns(t *testing.T) {
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore), nil, emailing, providerSettings)
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
require.Nil(apiErr)
|
||||
|
||||
@ -167,7 +167,7 @@ func TestCantDisconnectNonExistentAccount(t *testing.T) {
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore), nil, emailing, providerSettings)
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
require.Nil(apiErr)
|
||||
|
||||
@ -189,7 +189,7 @@ func TestConfigureService(t *testing.T) {
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore), nil, emailing, providerSettings)
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
require.Nil(apiErr)
|
||||
|
||||
|
@ -3,12 +3,14 @@ package app
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
@ -27,6 +29,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/metricsexplorer"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/prometheus/prometheus/promql"
|
||||
@ -580,6 +583,17 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||
router.HandleFunc("/api/v1/register", am.OpenAccess(aH.registerUser)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/login", am.OpenAccess(aH.Signoz.Handlers.User.Login)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/loginPrecheck", am.OpenAccess(aH.Signoz.Handlers.User.LoginPrecheck)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/complete/google", am.OpenAccess(aH.receiveGoogleAuth)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v1/domains", am.AdminAccess(aH.Signoz.Handlers.User.ListDomains)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/domains", am.AdminAccess(aH.Signoz.Handlers.User.CreateDomain)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(aH.Signoz.Handlers.User.UpdateDomain)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/domains/{id}", am.AdminAccess(aH.Signoz.Handlers.User.DeleteDomain)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(aH.Signoz.Handlers.User.CreateAPIKey)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/pats", am.AdminAccess(aH.Signoz.Handlers.User.ListAPIKeys)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(aH.Signoz.Handlers.User.UpdateAPIKey)).Methods(http.MethodPut)
|
||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(aH.Signoz.Handlers.User.RevokeAPIKey)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/user", am.AdminAccess(aH.Signoz.Handlers.User.ListUsers)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/user/me", am.OpenAccess(aH.Signoz.Handlers.User.GetCurrentUserFromJWT)).Methods(http.MethodGet)
|
||||
@ -2031,6 +2045,74 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
aH.Respond(w, nil)
|
||||
}
|
||||
|
||||
func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string) {
|
||||
ssoError := []byte("Login failed. Please contact your system administrator")
|
||||
dst := make([]byte, base64.StdEncoding.EncodedLen(len(ssoError)))
|
||||
base64.StdEncoding.Encode(dst, ssoError)
|
||||
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectURL, string(dst)), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// receiveGoogleAuth completes google OAuth response and forwards a request
|
||||
// to front-end to sign user in
|
||||
func (aH *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request) {
|
||||
redirectUri := constants.GetDefaultSiteURL()
|
||||
ctx := context.Background()
|
||||
|
||||
q := r.URL.Query()
|
||||
if errType := q.Get("error"); errType != "" {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to login with google auth", zap.String("error", errType), zap.String("error_description", q.Get("error_description")))
|
||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "failed to login through SSO"), http.StatusMovedPermanently)
|
||||
return
|
||||
}
|
||||
|
||||
relayState := q.Get("state")
|
||||
zap.L().Debug("[receiveGoogleAuth] relay state received", zap.String("state", relayState))
|
||||
|
||||
parsedState, err := url.Parse(relayState)
|
||||
if err != nil || relayState == "" {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to process response - invalid response from IDP", zap.Error(err), zap.Any("request", r))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
// upgrade redirect url from the relay state for better accuracy
|
||||
redirectUri = fmt.Sprintf("%s://%s%s", parsedState.Scheme, parsedState.Host, "/login")
|
||||
|
||||
// fetch domain by parsing relay state.
|
||||
domain, err := aH.Signoz.Modules.User.GetDomainFromSsoResponse(ctx, parsedState)
|
||||
if err != nil {
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
// now that we have domain, use domain to fetch sso settings.
|
||||
// prepare google callback handler using parsedState -
|
||||
// which contains redirect URL (front-end endpoint)
|
||||
callbackHandler, err := domain.PrepareGoogleOAuthProvider(parsedState)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to prepare google oauth provider", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
identity, err := callbackHandler.HandleCallback(r)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to process HandleCallback", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
nextPage, err := aH.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, identity.Email, aH.JWT)
|
||||
if err != nil {
|
||||
zap.L().Error("[receiveGoogleAuth] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
|
||||
handleSsoError(w, r, redirectUri)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, nextPage, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (aH *APIHandler) HandleError(w http.ResponseWriter, err error, statusCode int) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
|
@ -22,7 +22,7 @@ func TestIntegrationLifecycle(t *testing.T) {
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(store))
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
userModule := impluser.NewModule(impluser.NewStore(store), nil, emailing, providerSettings)
|
||||
userModule := impluser.NewModule(impluser.NewStore(store, providerSettings), nil, emailing, providerSettings)
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
if apiErr != nil {
|
||||
t.Fatalf("could not create test user: %v", apiErr)
|
||||
|
@ -219,6 +219,7 @@ func (s *Server) createPrivateServer(api *APIHandler) (*http.Server, error) {
|
||||
s.serverOptions.Config.APIServer.Timeout.Max,
|
||||
).Wrap)
|
||||
r.Use(middleware.NewAnalytics().Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewLogging(s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
|
||||
api.RegisterPrivateRoutes(r)
|
||||
@ -249,6 +250,7 @@ func (s *Server) createPublicServer(api *APIHandler, web web.Web) (*http.Server,
|
||||
s.serverOptions.Config.APIServer.Timeout.Max,
|
||||
).Wrap)
|
||||
r.Use(middleware.NewAnalytics().Wrap)
|
||||
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
|
||||
r.Use(middleware.NewLogging(s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
|
||||
|
||||
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
|
||||
|
@ -661,3 +661,7 @@ var MaterializedDataTypeMap = map[string]string{
|
||||
}
|
||||
|
||||
const InspectMetricsMaxTimeDiff = 1800000
|
||||
|
||||
func GetDefaultSiteURL() string {
|
||||
return GetOrDefaultEnv("SIGNOZ_SITE_URL", HTTPHostPort)
|
||||
}
|
||||
|
@ -9,12 +9,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/config"
|
||||
"github.com/SigNoz/signoz/pkg/config/envprovider"
|
||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
@ -120,6 +117,7 @@ func main() {
|
||||
signoz, err := signoz.New(
|
||||
context.Background(),
|
||||
config,
|
||||
jwt,
|
||||
zeus.Config{},
|
||||
noopzeus.NewProviderFactory(),
|
||||
licensing.Config{},
|
||||
@ -131,12 +129,6 @@ func main() {
|
||||
signoz.NewWebProviderFactories(),
|
||||
signoz.NewSQLStoreProviderFactories(),
|
||||
signoz.NewTelemetryStoreProviderFactories(),
|
||||
func(sqlstore sqlstore.SQLStore, emailing emailing.Emailing, providerSettings factory.ProviderSettings) user.Module {
|
||||
return impluser.NewModule(impluser.NewStore(sqlstore), jwt, emailing, providerSettings)
|
||||
},
|
||||
func(userModule user.Module) user.Handler {
|
||||
return impluser.NewHandler(userModule)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
zap.L().Fatal("Failed to create signoz", zap.Error(err))
|
||||
|
@ -19,7 +19,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||
@ -307,16 +306,14 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
userModule := impluser.NewModule(impluser.NewStore(testDB), jwt, emailing, providerSettings)
|
||||
userHandler := impluser.NewHandler(userModule)
|
||||
modules := signoz.NewModules(testDB, userModule)
|
||||
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
JWT: jwt,
|
||||
Signoz: &signoz.SigNoz{
|
||||
Modules: modules,
|
||||
Handlers: signoz.NewHandlers(modules, userHandler),
|
||||
Handlers: signoz.NewHandlers(modules),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
@ -331,7 +328,7 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||
apiHandler.RegisterQueryRangeV3Routes(router, am)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
user, apiErr := createTestUser(organizationModule, modules.User)
|
||||
if apiErr != nil {
|
||||
t.Fatalf("could not create a test user: %v", apiErr)
|
||||
}
|
||||
@ -348,7 +345,7 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||
testUser: user,
|
||||
qsHttpHandler: router,
|
||||
mockClickhouse: mockClickhouse,
|
||||
userModule: userModule,
|
||||
userModule: modules.User,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
@ -483,10 +482,8 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
jwt := authtypes.NewJWT("", 10*time.Minute, 30*time.Minute)
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlStore), jwt, emailing, providerSettings)
|
||||
userHandler := impluser.NewHandler(userModule)
|
||||
modules := signoz.NewModules(sqlStore, userModule)
|
||||
handlers := signoz.NewHandlers(modules, userHandler)
|
||||
modules := signoz.NewModules(sqlStore, jwt, emailing, providerSettings)
|
||||
handlers := signoz.NewHandlers(modules)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
LogsParsingPipelineController: controller,
|
||||
@ -501,7 +498,7 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
|
||||
}
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(sqlStore))
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
user, apiErr := createTestUser(organizationModule, modules.User)
|
||||
if apiErr != nil {
|
||||
t.Fatalf("could not create a test user: %v", apiErr)
|
||||
}
|
||||
@ -522,7 +519,7 @@ func NewTestbedWithoutOpamp(t *testing.T, sqlStore sqlstore.SQLStore) *LogPipeli
|
||||
testUser: user,
|
||||
apiHandler: apiHandler,
|
||||
agentConfMgr: agentConfMgr,
|
||||
userModule: userModule,
|
||||
userModule: modules.User,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
@ -368,10 +367,8 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
jwt := authtypes.NewJWT("", 10*time.Minute, 30*time.Minute)
|
||||
userModule := impluser.NewModule(impluser.NewStore(testDB), jwt, emailing, providerSettings)
|
||||
userHandler := impluser.NewHandler(userModule)
|
||||
modules := signoz.NewModules(testDB, userModule)
|
||||
handlers := signoz.NewHandlers(modules, userHandler)
|
||||
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings)
|
||||
handlers := signoz.NewHandlers(modules)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
@ -393,7 +390,7 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
||||
apiHandler.RegisterCloudIntegrationsRoutes(router, am)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
user, apiErr := createTestUser(organizationModule, modules.User)
|
||||
if apiErr != nil {
|
||||
t.Fatalf("could not create a test user: %v", apiErr)
|
||||
}
|
||||
@ -403,7 +400,7 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
||||
testUser: user,
|
||||
qsHttpHandler: router,
|
||||
mockClickhouse: mockClickhouse,
|
||||
userModule: userModule,
|
||||
userModule: modules.User,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization/implorganization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||
@ -574,10 +573,9 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
||||
providerSettings := instrumentationtest.New().ToProviderSettings()
|
||||
emailing, _ := noopemailing.New(context.Background(), providerSettings, emailing.Config{})
|
||||
jwt := authtypes.NewJWT("", 10*time.Minute, 30*time.Minute)
|
||||
userModule := impluser.NewModule(impluser.NewStore(testDB), jwt, emailing, providerSettings)
|
||||
userHandler := impluser.NewHandler(userModule)
|
||||
modules := signoz.NewModules(testDB, userModule)
|
||||
handlers := signoz.NewHandlers(modules, userHandler)
|
||||
modules := signoz.NewModules(testDB, jwt, emailing, providerSettings)
|
||||
handlers := signoz.NewHandlers(modules)
|
||||
|
||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||
Reader: reader,
|
||||
IntegrationsController: controller,
|
||||
@ -600,7 +598,7 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
||||
apiHandler.RegisterIntegrationRoutes(router, am)
|
||||
|
||||
organizationModule := implorganization.NewModule(implorganization.NewStore(testDB))
|
||||
user, apiErr := createTestUser(organizationModule, userModule)
|
||||
user, apiErr := createTestUser(organizationModule, modules.User)
|
||||
if apiErr != nil {
|
||||
t.Fatalf("could not create a test user: %v", apiErr)
|
||||
}
|
||||
@ -610,7 +608,7 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
||||
testUser: user,
|
||||
qsHttpHandler: router,
|
||||
mockClickhouse: mockClickhouse,
|
||||
userModule: userModule,
|
||||
userModule: modules.User,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
@ -26,11 +27,11 @@ type Handlers struct {
|
||||
QuickFilter quickfilter.Handler
|
||||
}
|
||||
|
||||
func NewHandlers(modules Modules, user user.Handler) Handlers {
|
||||
func NewHandlers(modules Modules) Handlers {
|
||||
return Handlers{
|
||||
Organization: implorganization.NewHandler(modules.Organization),
|
||||
Preference: implpreference.NewHandler(modules.Preference),
|
||||
User: user,
|
||||
User: impluser.NewHandler(modules.User),
|
||||
SavedView: implsavedview.NewHandler(modules.SavedView),
|
||||
Apdex: implapdex.NewHandler(modules.Apdex),
|
||||
Dashboard: impldashboard.NewHandler(modules.Dashboard),
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/SigNoz/signoz/pkg/emailing/emailingtest"
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@ -22,11 +21,9 @@ func TestNewHandlers(t *testing.T) {
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
providerSettings := factorytest.NewSettings()
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlstore), jwt, emailing, providerSettings)
|
||||
userHandler := impluser.NewHandler(userModule)
|
||||
|
||||
modules := NewModules(sqlstore, userModule)
|
||||
handlers := NewHandlers(modules, userHandler)
|
||||
modules := NewModules(sqlstore, jwt, emailing, providerSettings)
|
||||
handlers := NewHandlers(modules)
|
||||
|
||||
reflectVal := reflect.ValueOf(handlers)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
@ -1,6 +1,8 @@
|
||||
package signoz
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/emailing"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/apdex/implapdex"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
@ -14,7 +16,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/savedview/implsavedview"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
|
||||
)
|
||||
|
||||
@ -28,14 +32,14 @@ type Modules struct {
|
||||
QuickFilter quickfilter.Module
|
||||
}
|
||||
|
||||
func NewModules(sqlstore sqlstore.SQLStore, user user.Module) Modules {
|
||||
func NewModules(sqlstore sqlstore.SQLStore, jwt *authtypes.JWT, emailing emailing.Emailing, providerSettings factory.ProviderSettings) Modules {
|
||||
return Modules{
|
||||
Organization: implorganization.NewModule(implorganization.NewStore(sqlstore)),
|
||||
Preference: implpreference.NewModule(implpreference.NewStore(sqlstore), preferencetypes.NewDefaultPreferenceMap()),
|
||||
User: user,
|
||||
SavedView: implsavedview.NewModule(sqlstore),
|
||||
Apdex: implapdex.NewModule(sqlstore),
|
||||
Dashboard: impldashboard.NewModule(sqlstore),
|
||||
User: impluser.NewModule(impluser.NewStore(sqlstore, providerSettings), jwt, emailing, providerSettings),
|
||||
QuickFilter: implquickfilter.NewModule(implquickfilter.NewStore(sqlstore)),
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/SigNoz/signoz/pkg/emailing/emailingtest"
|
||||
"github.com/SigNoz/signoz/pkg/factory/factorytest"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstoretest"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
@ -22,9 +21,7 @@ func TestNewModules(t *testing.T) {
|
||||
jwt := authtypes.NewJWT("", 1*time.Hour, 1*time.Hour)
|
||||
emailing := emailingtest.New()
|
||||
providerSettings := factorytest.NewSettings()
|
||||
userModule := impluser.NewModule(impluser.NewStore(sqlstore), jwt, emailing, providerSettings)
|
||||
|
||||
modules := NewModules(sqlstore, userModule)
|
||||
modules := NewModules(sqlstore, jwt, emailing, providerSettings)
|
||||
|
||||
reflectVal := reflect.ValueOf(modules)
|
||||
for i := 0; i < reflectVal.NumField(); i++ {
|
||||
|
@ -9,12 +9,12 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
||||
"github.com/SigNoz/signoz/pkg/sqlmigrator"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
|
||||
@ -40,6 +40,7 @@ type SigNoz struct {
|
||||
func New(
|
||||
ctx context.Context,
|
||||
config Config,
|
||||
jwt *authtypes.JWT,
|
||||
zeusConfig zeus.Config,
|
||||
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
|
||||
licenseConfig licensing.Config,
|
||||
@ -49,8 +50,6 @@ func New(
|
||||
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
|
||||
sqlstoreProviderFactories factory.NamedMap[factory.ProviderFactory[sqlstore.SQLStore, sqlstore.Config]],
|
||||
telemetrystoreProviderFactories factory.NamedMap[factory.ProviderFactory[telemetrystore.TelemetryStore, telemetrystore.Config]],
|
||||
userModuleFactory func(sqlstore sqlstore.SQLStore, emailing emailing.Emailing, providerSettings factory.ProviderSettings) user.Module,
|
||||
userHandlerFactory func(user.Module) user.Handler,
|
||||
) (*SigNoz, error) {
|
||||
// Initialize instrumentation
|
||||
instrumentation, err := instrumentation.New(ctx, config.Instrumentation, version.Info, "signoz")
|
||||
@ -185,14 +184,11 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userModule := userModuleFactory(sqlstore, emailing, providerSettings)
|
||||
userHandler := userHandlerFactory(userModule)
|
||||
|
||||
// Initialize all modules
|
||||
modules := NewModules(sqlstore, userModule)
|
||||
modules := NewModules(sqlstore, jwt, emailing, providerSettings)
|
||||
|
||||
// Initialize all handlers for the modules
|
||||
handlers := NewHandlers(modules, userHandler)
|
||||
handlers := NewHandlers(modules)
|
||||
|
||||
registry, err := factory.NewRegistry(
|
||||
instrumentation.Logger(),
|
||||
|
@ -2,19 +2,17 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const (
|
||||
SSOAvailable = "sso_available"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserAlreadyExists = errors.MustNewCode("user_already_exists")
|
||||
ErrPasswordAlreadyExists = errors.MustNewCode("password_already_exists")
|
||||
@ -57,6 +55,13 @@ type UserStore interface {
|
||||
|
||||
// Auth Domain
|
||||
GetDomainByName(ctx context.Context, name string) (*StorableOrgDomain, error)
|
||||
// org domain (auth domains) CRUD ops
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*GettableOrgDomain, error)
|
||||
ListDomains(ctx context.Context, orgId valuer.UUID) ([]*GettableOrgDomain, error)
|
||||
GetDomain(ctx context.Context, id uuid.UUID) (*GettableOrgDomain, error)
|
||||
CreateDomain(ctx context.Context, d *GettableOrgDomain) error
|
||||
UpdateDomain(ctx context.Context, domain *GettableOrgDomain) error
|
||||
DeleteDomain(ctx context.Context, id uuid.UUID) error
|
||||
|
||||
// Temporary func for SSO
|
||||
GetDefaultOrgID(ctx context.Context) (string, error)
|
||||
|
Loading…
x
Reference in New Issue
Block a user