201 lines
5.9 KiB
Go
201 lines
5.9 KiB
Go
|
package sqlite
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"net/url"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/google/uuid"
|
||
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||
|
"go.signoz.io/signoz/ee/query-service/model"
|
||
|
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||
|
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||
|
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||
|
"go.signoz.io/signoz/pkg/query-service/utils"
|
||
|
"go.uber.org/zap"
|
||
|
)
|
||
|
|
||
|
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
|
||
|
// get auth domain from email domain
|
||
|
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||
|
if apierr != nil {
|
||
|
zap.L().Error("failed to get domain from email", zap.Error(apierr))
|
||
|
return nil, model.InternalErrorStr("failed to get domain from email")
|
||
|
}
|
||
|
if domain == nil {
|
||
|
zap.L().Error("email domain does not match any authenticated domain", zap.String("email", email))
|
||
|
return nil, model.InternalErrorStr("email domain does not match any authenticated domain")
|
||
|
}
|
||
|
|
||
|
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
|
||
|
if err != nil {
|
||
|
zap.L().Error("failed to generate password hash when registering a user via SSO redirect", zap.Error(err))
|
||
|
return nil, model.InternalErrorStr("failed to generate password hash")
|
||
|
}
|
||
|
|
||
|
group, apiErr := m.GetGroupByName(ctx, baseconst.ViewerGroup)
|
||
|
if apiErr != nil {
|
||
|
zap.L().Error("GetGroupByName failed", zap.Error(apiErr))
|
||
|
return nil, apiErr
|
||
|
}
|
||
|
|
||
|
user := &basemodel.User{
|
||
|
Id: uuid.NewString(),
|
||
|
Name: "",
|
||
|
Email: email,
|
||
|
Password: hash,
|
||
|
CreatedAt: time.Now().Unix(),
|
||
|
ProfilePictureURL: "", // Currently unused
|
||
|
GroupId: group.Id,
|
||
|
OrgId: domain.OrgId,
|
||
|
}
|
||
|
|
||
|
user, apiErr = m.CreateUser(ctx, user, false)
|
||
|
if apiErr != nil {
|
||
|
zap.L().Error("CreateUser failed", zap.Error(apiErr))
|
||
|
return nil, apiErr
|
||
|
}
|
||
|
|
||
|
return user, nil
|
||
|
|
||
|
}
|
||
|
|
||
|
// PrepareSsoRedirect prepares redirect page link after SSO response
|
||
|
// is successfully parsed (i.e. valid email is available)
|
||
|
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) {
|
||
|
|
||
|
userPayload, apierr := m.GetUserByEmail(ctx, email)
|
||
|
if !apierr.IsNil() {
|
||
|
zap.L().Error("failed to get user with email received from auth provider", zap.String("error", apierr.Error()))
|
||
|
return "", model.BadRequestStr("invalid user email received from the auth provider")
|
||
|
}
|
||
|
|
||
|
user := &basemodel.User{}
|
||
|
|
||
|
if userPayload == nil {
|
||
|
newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
|
||
|
user = newUser
|
||
|
if apiErr != nil {
|
||
|
zap.L().Error("failed to create user with email received from auth provider", zap.Error(apiErr))
|
||
|
return "", apiErr
|
||
|
}
|
||
|
} else {
|
||
|
user = &userPayload.User
|
||
|
}
|
||
|
|
||
|
tokenStore, err := baseauth.GenerateJWTForUser(user)
|
||
|
if err != nil {
|
||
|
zap.L().Error("failed to generate token for SSO login user", zap.Error(err))
|
||
|
return "", model.InternalErrorStr("failed to generate token for the user")
|
||
|
}
|
||
|
|
||
|
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||
|
redirectUri,
|
||
|
tokenStore.AccessJwt,
|
||
|
user.Id,
|
||
|
tokenStore.RefreshJwt), nil
|
||
|
}
|
||
|
|
||
|
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError) {
|
||
|
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||
|
if apierr != nil {
|
||
|
return false, apierr
|
||
|
}
|
||
|
|
||
|
if domain != nil && domain.SsoEnabled {
|
||
|
// sso is enabled, check if the user has admin role
|
||
|
userPayload, baseapierr := m.GetUserByEmail(ctx, email)
|
||
|
|
||
|
if baseapierr != nil || userPayload == nil {
|
||
|
return false, baseapierr
|
||
|
}
|
||
|
|
||
|
if userPayload.Role != baseconst.AdminGroup {
|
||
|
return false, model.BadRequest(fmt.Errorf("auth method not supported"))
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
return true, nil
|
||
|
}
|
||
|
|
||
|
// PrecheckLogin is called when the login or signup page is loaded
|
||
|
// to check sso login is to be prompted
|
||
|
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) {
|
||
|
|
||
|
// assume user is valid unless proven otherwise
|
||
|
resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||
|
|
||
|
// check if email is a valid user
|
||
|
userPayload, baseApiErr := m.GetUserByEmail(ctx, email)
|
||
|
if baseApiErr != nil {
|
||
|
return resp, baseApiErr
|
||
|
}
|
||
|
|
||
|
if userPayload == nil {
|
||
|
resp.IsUser = false
|
||
|
}
|
||
|
|
||
|
ssoAvailable := true
|
||
|
err := m.checkFeature(model.SSO)
|
||
|
if err != nil {
|
||
|
switch err.(type) {
|
||
|
case basemodel.ErrFeatureUnavailable:
|
||
|
// do nothing, just skip sso
|
||
|
ssoAvailable = false
|
||
|
default:
|
||
|
zap.L().Error("feature check failed", zap.String("featureKey", model.SSO), zap.Error(err))
|
||
|
return resp, model.BadRequestStr(err.Error())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ssoAvailable {
|
||
|
|
||
|
resp.IsUser = true
|
||
|
|
||
|
// find domain from email
|
||
|
orgDomain, apierr := m.GetDomainByEmail(ctx, email)
|
||
|
if apierr != nil {
|
||
|
var emailDomain string
|
||
|
emailComponents := strings.Split(email, "@")
|
||
|
if len(emailComponents) > 0 {
|
||
|
emailDomain = emailComponents[1]
|
||
|
}
|
||
|
zap.L().Error("failed to get org domain from email", zap.String("emailDomain", emailDomain), zap.Error(apierr.ToError()))
|
||
|
return resp, apierr
|
||
|
}
|
||
|
|
||
|
if orgDomain != nil && orgDomain.SsoEnabled {
|
||
|
// 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 {
|
||
|
zap.L().Error("failed to parse referer", zap.Error(err))
|
||
|
return resp, model.InternalError(fmt.Errorf("failed to generate login request"))
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
zap.L().Error("failed to prepare saml request for domain", zap.String("domain", orgDomain.Name), zap.Error(err))
|
||
|
return resp, model.InternalError(err)
|
||
|
}
|
||
|
|
||
|
// set SSO to true, as the url is generated correctly
|
||
|
resp.SSO = true
|
||
|
}
|
||
|
}
|
||
|
return resp, nil
|
||
|
}
|