fix: changed ask admin message (#2215)

This commit is contained in:
Amol Umbark 2023-02-24 14:57:07 +05:30 committed by GitHub
parent 1f44f089e0
commit ab514cc0f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 109 additions and 32 deletions

View File

@ -9,6 +9,7 @@ import (
"go.signoz.io/signoz/ee/query-service/license"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
rules "go.signoz.io/signoz/pkg/query-service/rules"
"go.signoz.io/signoz/pkg/query-service/version"
)
@ -127,5 +128,11 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router) {
func (ah *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion()
ah.WriteJSON(w, r, map[string]string{"version": version, "ee": "Y"})
versionResponse := basemodel.GetVersionResponse{
Version: version,
EE: "Y",
SetupCompleted: ah.SetupCompleted,
}
ah.WriteJSON(w, r, versionResponse)
}

View File

@ -8,6 +8,7 @@ import (
"io/ioutil"
"net/http"
"net/url"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
@ -87,9 +88,16 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
// get invite object
invite, err := baseauth.ValidateInvite(ctx, req)
if err != nil || invite == nil {
if err != nil {
zap.S().Errorf("failed to validate invite token", err)
RespondError(w, model.BadRequest(err), nil)
return
}
if invite == nil {
zap.S().Errorf("failed to validate invite token: it is either empty or invalid", err)
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
return
}
// get auth domain from email domain
@ -250,15 +258,12 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
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) {
// this is the source url that initiated the login request
redirectUri := constants.GetDefaultSiteURL()
ctx := context.Background()
if !ah.CheckFeature(model.SSO) {
zap.S().Errorf("[receiveSAML] sso requested but feature unavailable %s in org domain %s", model.SSO)
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)

View File

@ -153,6 +153,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
type: UPDATE_CURRENT_VERSION,
payload: {
currentVersion: getUserVersionResponse.data.payload.version,
ee: getUserVersionResponse.data.payload.ee,
setupCompleted: getUserVersionResponse.data.payload.setupCompleted,
},
});
}

View File

@ -1,4 +1,5 @@
import { Button, Input, Space, Tooltip, Typography } from 'antd';
import getUserVersion from 'api/user/getVersion';
import loginApi from 'api/user/login';
import loginPrecheckApi from 'api/user/loginPrecheck';
import afterLogin from 'AppRoutes/utils';
@ -7,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { PayloadProps as PrecheckResultType } from 'types/api/user/loginPrecheck';
import { FormContainer, FormWrapper, Label, ParentContainer } from './styles';
@ -45,6 +47,26 @@ function Login({
const { notifications } = useNotifications();
const getUserVersionResponse = useQuery({
queryFn: getUserVersion,
queryKey: 'getUserVersion',
enabled: true,
});
useEffect(() => {
if (
getUserVersionResponse.isFetched &&
getUserVersionResponse.data &&
getUserVersionResponse.data.payload
) {
const { setupCompleted } = getUserVersionResponse.data.payload;
if (!setupCompleted) {
// no org account registered yet, re-route user to sign up first
history.push(ROUTES.SIGN_UP);
}
}
}, [getUserVersionResponse]);
useEffect(() => {
if (withPassword === 'Y') {
setPrecheckComplete(true);
@ -255,20 +277,6 @@ function Login({
</Typography.Paragraph>
)}
{!canSelfRegister && (
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
{t('prompt_create_account')}{' '}
<Typography.Link
onClick={(): void => {
history.push(ROUTES.SIGN_UP);
}}
style={{ fontWeight: 700 }}
>
{t('create_an_account')}
</Typography.Link>
</Typography.Paragraph>
)}
{canSelfRegister && (
<Typography.Paragraph italic style={{ color: '#ACACAC' }}>
{t('prompt_if_admin')}{' '}

View File

@ -57,6 +57,8 @@ const InitialValue: InitialValueTypes = {
role: null,
configs: {},
userFlags: {},
ee: 'Y',
setupCompleted: true,
};
const appReducer = (
@ -89,6 +91,8 @@ const appReducer = (
return {
...state,
currentVersion: action.payload.currentVersion,
ee: action.payload.ee,
setupCompleted: action.payload.setupCompleted,
};
}

View File

@ -46,6 +46,8 @@ export interface UpdateAppVersion {
type: typeof UPDATE_CURRENT_VERSION;
payload: {
currentVersion: AppReducer['currentVersion'];
ee: AppReducer['ee'];
setupCompleted: AppReducer['setupCompleted'];
};
}

View File

@ -1,4 +1,5 @@
export interface PayloadProps {
version: string;
ee: string;
ee: 'Y' | 'N';
setupCompleted: boolean;
}

View File

@ -29,4 +29,6 @@ export default interface AppReducer {
featureFlags: null | FeatureFlagPayload;
configs: ConfigPayload;
userFlags: null | UserFlags;
ee: 'Y' | 'N';
setupCompleted: boolean;
}

View File

@ -61,6 +61,11 @@ type APIHandler struct {
ruleManager *rules.Manager
featureFlags interfaces.FeatureLookup
ready func(http.HandlerFunc) http.HandlerFunc
// SetupCompleted indicates if SigNoz is ready for general use.
// at the moment, we mark the app ready when the first user
// is registers.
SetupCompleted bool
}
type APIHandlerOpts struct {
@ -100,6 +105,19 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
// if errReadingDashboards != nil {
// return nil, errReadingDashboards
// }
// check if at least one user is created
hasUsers, err := aH.appDao.GetUsersWithOpts(context.Background(), 1)
if err.Error() != "" {
// raise warning but no panic as this is a recoverable condition
zap.S().Warnf("unexpected error while fetch user count while initializing base api handler", err.Error())
}
if len(hasUsers) != 0 {
// first user is already created, we can mark the app ready for general use.
// this means, we disable self-registration and expect new users
// to signup signoz through invite link only.
aH.SetupCompleted = true
}
return aH, nil
}
@ -1645,7 +1663,13 @@ func (aH *APIHandler) getDisks(w http.ResponseWriter, r *http.Request) {
func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
version := version.GetVersion()
aH.WriteJSON(w, r, map[string]string{"version": version, "ee": "N"})
versionResponse := model.GetVersionResponse{
Version: version,
EE: "Y",
SetupCompleted: aH.SetupCompleted,
}
aH.WriteJSON(w, r, versionResponse)
}
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
@ -1777,6 +1801,12 @@ func (aH *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
if !aH.SetupCompleted {
// since the first user is now created, we can disable self-registration as
// from here onwards, we expect admin (owner) to invite other users.
aH.SetupCompleted = true
}
aH.Respond(w, nil)
}

View File

@ -267,7 +267,7 @@ func RegisterFirstUser(ctx context.Context, req *RegisterRequest) (*model.User,
func RegisterInvitedUser(ctx context.Context, req *RegisterRequest, nopassword bool) (*model.User, *model.ApiError) {
if req.InviteToken == "" {
return nil, model.BadRequest(fmt.Errorf("invite token is required"))
return nil, model.BadRequest(ErrorAskAdmin)
}
if !nopassword && req.Password == "" {

View File

@ -15,7 +15,7 @@ var (
ErrorInvalidRole = errors.New("Invalid role")
ErrorInvalidInviteToken = errors.New("Invalid invite token")
ErrorAskAdmin = errors.New("You are not allowed to create an account. Please ask your admin to send an invite link")
ErrorAskAdmin = errors.New("An invitation is needed to create an account. Please ask your admin (the person who has first installed SIgNoz) to send an invite.")
)
func randomHex(sz int) (string, error) {

View File

@ -19,6 +19,7 @@ type Queries interface {
GetUser(ctx context.Context, id string) (*model.UserPayload, *model.ApiError)
GetUserByEmail(ctx context.Context, email string) (*model.UserPayload, *model.ApiError)
GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError)
GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError)
GetGroup(ctx context.Context, id string) (*model.Group, *model.ApiError)
GetGroupByName(ctx context.Context, name string) (*model.Group, *model.ApiError)

View File

@ -345,7 +345,13 @@ func (mds *ModelDaoSqlite) GetUserByEmail(ctx context.Context,
return &users[0], nil
}
// GetUsers fetches total user count
func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *model.ApiError) {
return mds.GetUsersWithOpts(ctx, 0)
}
// GetUsersWithOpts fetches users and supports additional search options
func (mds *ModelDaoSqlite) GetUsersWithOpts(ctx context.Context, limit int) ([]model.UserPayload, *model.ApiError) {
users := []model.UserPayload{}
query := `select
@ -364,6 +370,9 @@ func (mds *ModelDaoSqlite) GetUsers(ctx context.Context) ([]model.UserPayload, *
g.id = u.group_id and
o.id = u.org_id`
if limit > 0 {
query = fmt.Sprintf("%s LIMIT %d", query, limit)
}
err := mds.db.Select(&users, query)
if err != nil {

View File

@ -585,3 +585,9 @@ func (ci *ClusterInfo) GetMapFromStruct() map[string]interface{} {
json.Unmarshal(data, &clusterInfoMap)
return clusterInfoMap
}
type GetVersionResponse struct {
Version string `json:"version"`
EE string `json:"ee"`
SetupCompleted bool `json:"setupCompleted"`
}